How do Rails ActiveRecord relationship helpers all

2019-03-02 07:15发布

问题:

In Rails, I have created a Model that retrieves users from an LDAP database rather than from ActiveRecord. Now I am attempting to integrate my ActiveRecord models with the LDAP-based models, so I am writing methods in my models that emulate some common ActiveRecord methods.

One of the methods I am trying to emulate is one that is normally created by the has_many through relationship on ActiveRecord. In ActiveRecord, this relationship would allow the following:

user = User.first
groups = user.groups # == Array of Groups
groups << Group.create(name: "Test") # How does Rails allow this?

How exactly does Rails allow this? I've tried dynamically assigning methods to the array instance returned by user.groups, but there doesn't seem to be any way to make those methods aware of which user record the array was created from. (So they can assign user_id on the new relationship record.) What am I missing?

回答1:

Though user.groups appears to be an array of groups, it's actually an entirely separate class -- a Rails internal class that you usually don't know much about called an association proxy. The proxy responds to methods like <<, create, new and so on by proxying requests to the target class and then setting the association appropriately.

If you want similar functionality you'll have to implement your own kind of proxy associations. Doing so will be pretty complicated, but this might get you started.

module LDAP
  class Association
    attr_accessor :source, :target

    def initialize(source, target)
      @source = source
      @target = target
    end

    def <<(obj)
      @source.group_ids = [group_ids + obj].flatten.uniq
      @source.save
    end

  end
end

class User
  def groups
    LDAP::Association.new(self, Group)
  end
end

This is not even particularly close to how ActiveRecord implements association proxies. However, this is quite a bit simpler than ActiveRecord's solution and should be enough to duplicate some basic ActiveRecord functionality.



回答2:

I would go about doing this by peeking into the Rails Source Code, e.g. the code for the Group.create example above can be found in

http://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html

  def create(attributes = nil, options = {}, &block)
    if attributes.is_a?(Array)
      attributes.collect { |attr| create(attr, options, &block) }
    else
      object = new(attributes, options, &block)
      object.save
      object
    end
  end
end