The problem: Active Record objects have built in serialization (to_json
), but I want to use vanilla objects wherever possible.
My solution:
class Report
attr_reader :data, :group_reports
def initialize(data)
@data = data
@group_reports = (data.weekly_group_reports + [data.weekly_used_report]).map { |group_report|
GroupReport.build(group_report, self)
}
end
#Not shown: a bunch of public methods
def to_hash
attributes.each_with_object({}) { |e,h|
h[e] = send(e)
}.merge({ group_reports: group_reports.map(&:to_hash) })
end
# When public_methods is used with the false flag, an
# array of the instance's methods WITHOUT the ancestors'
# are returned.
# Note that we're subtracting methods that we don't want
# to serialize. We don't need to blacklist :attributes since we're
# able to leave that as a private method.
private
def attributes
public_methods(false) - [:data, :to_hash, :group_reports]
end
end
.
class GroupReport
attr_reader :data, :back
def initialize(data, report)
@data = data
@back = report.per_retail_unit_new
end
def to_hash
attributes.each_with_object({}) { |e,h|
h[e] = send(e)
}
end
# public_methods(false) doesn't work here since we're using a strategy pattern.
# If an instance of GroupReportUsed was sent public_methods(false), it would
# only return [:franchise_name]. Luckily, we're able to manually blacklist the
# next highest known ancestor.
def attributes
public_methods - Object.methods - [:attributes, :data, :to_hash]
end
def franchise_name
data.franchise_name
end
def front
data.average_gross
end
end
class GroupReportUsed < GroupReport
def initialize(data, report)
@data = data
@back = report.per_retail_unit_used
end
def franchise_name
"Used"
end
end
I chose to expose this functionality via creating a hash rather than implementing the interface that Active Model likes. The end result looks like: render json: report.to_hash
That might be inefficient on larger objects since I'm creating an intermediary hash, but I find this to be significantly faster than relying on the built in Active Record serialization. That's not the point of the exercise, but it was a nice bonus.
Not sure if this is useful for anybody else, but I thought I'd share.