Inspect object with associations

醉酒当歌 提交于 2020-02-05 10:33:11

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!