问题
I have two models where A has_many B. If I load A including associated B as such:
a = A.find(:first, include: :bs)
a.inspect
only shows the attributes of a:
=> "#<A id: 1, name: \"Test\", created_at: \"2012-07-02 21:50:32\", updated_at: \"2012-07-02 21:50:32\">"
How can I do a.inspect
such that it displays all associated a.bs
?
回答1:
You can't do that by default. It might create too many problems and side effects with inspecting objects. However you could extend inspect
yourself with something like this:
class A < ActiveRecord::Base
...
def inspect
[super, bs.inspect].join("\n")
end
end
Note though that that's not very clever, since it will force the loading of bs
every time you inspect an A
instance. So maybe you want to be smarter and do something like this:
def inspect
[super, bs.loaded? ? bs.inspect : nil].compact.join("\n")
end
This will only inspect bs
if it's already preloaded (with :include
for example).
Or maybe you want to create a super_inspect
instead that does everything automatically. You could extend ActiveRecord::Base
with something like:
class ActiveRecord::Base
def deep_inspect
([inspect] + self.class.reflect_on_all_associations.map { |a|
self.send(a.name).inspect
}).compact.join("\n ")
end
end
This will automatically look up all the associations with reflect_on_all_associations, and if the association is loaded it will call inspect
on that.
Now you can modify the above code however you want to create your own customized inspect, or just extend the current inspect if you like. Anything is possible with a little bit of code.
Here is an example of an updated version that is a bit smarter:
class ActiveRecord::Base
def deep_inspect
([inspect] + self.class.reflect_on_all_associations.map { |a|
out = ""
assoc = self.send(a.name)
# Check for collection
if assoc.is_a?(ActiveRecord::Associations::CollectionProxy)
# Include name of collection in output
out += "\n#{assoc.name.pluralize}:\n"
out += self.send(a.name).to_a.inspect
else
out += self.send(a.name).inspect
end
out
}).compact.join("\n ")
end
end
回答2:
Along the same line as the answer from @Casper, here is a helper method that marshals all associations down the dependency chain:
# app/models/application_record.rb
#
# placing the helper in the ApplicationRecord superclass
# allows all application models to inherit the helper
class ApplicationRecord < ActiveRecord::Base
def self.marshal
# collect the names of the objects associations
single_associations = self.class.reflect_on_all_associations(:has_one ).map {|x| x.name}
plural_associations = self.class.reflect_on_all_associations(:has_many).map {|x| x.name}
# serialize the object as a JSON-compatible hash
self.as_json.merge(
# merge in a hash containing each `has_one` association via recursive marshalling
# the resulting set of associated objects are merged into
# the original object's serialized hash, each keyed by the name of the association
single_associations.reduce({}) { |memo, assoc| memo.merge({ assoc => self.send(assoc).marshal }) }.as_json
).merge(
# merge in the `has_many` associations
# the resulting set of associated collections must then be processed
# via mapping each collection into an array of singular serialized objects
plural_associations.reduce({}) { |memo, assoc| memo.merge({ assoc => self.send(assoc).map {|item| item.marshal } }) }.as_json
)
end
end
You would then be able to call this helper method by calling:
Marshal.serialize a
This is not quite the same as an inspection, since it is actually serializing the object into a hash structure, but it will give you similar information.
Note that the possible associations are separated are separated into two groups: singular associations (which reference a single target object), and plural associations (which are ActiveRecord CollectionProxy objects, i.e. they are Enumerable). Because we are serializing associated objects as hashes, each has_many
association must be parsed as a collection of individually serialized objects (e.g. we map each association within the collection as its serialized form).
The belongs_to
association should be ignored, as mapping associations in both directions would immediately create a circular dependency graph. If you wish to marshal along the "chain of belonging" instead, you could do something like
def self.trace
parent_associations = obj.class.reflect_on_all_associations(:belongs_to).map {|x| x.name}
obj.as_json.merge single_associations.reduce({}) { |memo, assoc| memo.merge({ assoc => obj.send(assoc).trace }) }.as_json
end
来源:https://stackoverflow.com/questions/11318353/inspect-object-with-associations