rails关联方法如何工作?

rails关联方法如何工作? 让我们考虑这个例子

class User < ActiveRecord::Base has_many :articles end class Article < ActiveRecord::Base belongs_to :user end 

现在我可以做点什么

 @user = User.find(:first) @user.articles 

这会抓取属于该用户的文章。 到现在为止还挺好。

现在我可以继续在某些条件下对这些文章进行查找。

 @user.articles.find(:all, :conditions => {:sector_id => 3}) 

或者简单地声明和关联方法

 class User  {:sector_id => sector_id}) end end end 

并做

 @user.articles.of_sector(3) 

现在我的问题是,这是如何find使用关联方法获取的ActiveRecord对象数组? 因为如果我们实现我们自己的名为articlesUser实例方法并编写我们自己的实现,它给出了与关联方法完全相同的结果,那么ActiveRecord对象的fetch数组上的find将无法工作。

我的猜测是关联方法将某些属性附加到获取对象数组,这使得能够使用find和其他ActiveRecord方法进一步查询。 在这种情况下,代码执行的顺序是什么? 我怎么能validation这个?

它实际上是如何工作的,关联对象是一个“代理对象”。 关联类是AssociationProxy 。 如果你看一下该文件的第52行,你会看到:

 instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ } 

通过这样做, class对象不再存在于此对象上。 因此,如果您在此对象上调用class ,则会丢失方法。 因此,为代理对象实现了method_missing ,将方法调用转发给“target”:

 def method_missing(method, *args) if load_target unless @target.respond_to?(method) message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}" raise NoMethodError, message end if block_given? @target.send(method, *args) { |*block_args| yield(*block_args) } else @target.send(method, *args) end end end 

目标是一个数组,所以当你在这个对象上调用class时,它会说它是一个数组,但这只是因为目标是一个数组,实际的类是一个AssociationProxy,但你再也看不到了。

因此,您添加的所有方法(例如of_sector )都会添加到关联代理中,因此可以直接调用它们。 像[]class这样的方法没有在关联代理上定义,所以它们被发送到目标,这是一个数组。

为了帮助您了解这是如何发生的,请将其添加到association_proxy.rb的本地副本中该文件的第217行:

 Rails.logger.info "AssociationProxy forwarding call to `#{method.to_s}' method to \"#{@target}\":#{@target.class.to_s}" 

如果您不知道该文件的位置,那么gem which 'active_record/associations/association_proxy'将告诉您命令gem which 'active_record/associations/association_proxy' 。 现在,当您在AssociationProxy上调用class时,您将看到一条日志消息,告诉您它正在向目标发送该消息,这样可以更清楚地了解发生的情况。 这完全适用于Rails 2.3.2,并且可能会在其他版本中发生变化。

如前所述,活动记录关联创建了一个方便性的方便性方法。 当然,你可以编写自己的方法来获取所有内容。 但这不是Rails方式。

Rails Way是两个格言的顶点。 干(不要重复自己)和“约定优于配置”。 本质上,通过以有意义的方式命名事物,框架提供的一些强大的方法可以抽象出所有公共代码。 您在问题中放置的代码是可以通过单个方法调用替换的完美示例。

这些便利方法真正发挥作用的地方是更复杂的情况。 涉及联接模型,条件,validation等的事情。

要在执行@user.articles.find(:all, :conditions => ["created_at > ? ", tuesday])等操作时回答您的问题,Rails会准备两个SQL查询,然后将它们合并为一个。 您的版本只返回对象列表。 命名范围执行相同的操作,但通常不跨越模型边界。

您可以通过在控制台中调用这些内容时检查development.log中的SQL查询来validation它。

因此,我们暂时谈谈命名范围 ,因为它们给出了rails如何处理SQL的一个很好的例子,我认为它们是一种更简单的方式来演示幕后发生的事情,因为它们不需要任何模型关联。炫耀。

命名范围可用于执行模型的自定义搜索。 它们可以链接在一起,甚至可以通过关联来调用。 您可以轻松创建返回相同列表的自定义查找程序,但之后会遇到问题中提到的相同问题。

 class Article < ActiveRecord::Base belongs_to :user has_many :comments has_many :commentators, :through :comments, :class_name => "user" named_scope :edited_scope, :conditions => {:edited => true} named_scope :recent_scope, lambda do { :conditions => ["updated_at > ? ", DateTime.now - 7.days]} def self.edited_method self.find(:all, :conditions => {:edited => true}) end def self.recent_method self.find(:all, :conditions => ["updated_at > ?", DateTime.now - 7 days]) end end Article.edited_scope => # Array of articles that have been flagged as edited. 1 SQL query. Article.edited_method => # Array of Articles that have been flagged as edited. 1 SQL query. Array.edited_scope == Array.edited_method => true # return identical lists. Article.recent_scope => # Array of articles that have been updated in the past 7 days. 1 SQL query. Article.recent_method => # Array of Articles that have been updated in the past 7 days. 1 SQL query. Array.recent_scope == Array.recent_method => true # return identical lists. 

事情发生了变化:

 Article.edited_scope.recent_scope => # Array of articles that have both been edited and updated in the past 7 days. 1 SQL query. Article.edited_method.recent_method => # no method error recent_scope on Array # Can't even mix and match. Article.edited_scope.recent_method => # no method error Article.recent_method.edited_scope => # no method error # works even across associations. @user.articles.edited.comments => # Array of comments belonging to Articles that are flagged as edited and belong to @user. 1 SQL query. 

实质上,每个命名范围都会创建一个SQL片段。 Rails将巧妙地与链中的每个其他SQL片段合并,以生成单个查询,完全按照您的需要进行返回。 关联方法添加的方法以相同的方式工作。 这就是他们与named_scopes无缝集成的原因。

混合和匹配不起作用的原因与问题中定义的of_sector方法不起作用的原因相同。 edited_methods返回一个数组,其中edited_scope(以及查找和所有其他AR便利方法称为链的一部分)将其SQL片段向前传递给链中的下一个事物。 如果它是链中的最后一个,则执行查询。 同样,这也行不通。

 @edited = Article.edited_scope @edited.recent_scope 

您试图使用此代码。 这是正确的方法:

 class User < ActiveRecord::Base has_many :articles do def of_sector(sector_id) find(:all, :conditions => {:sector_id => sector_id}) end end end 

要实现此function,您需要执行此操作:

 class Articles < ActiveRecord::Base belongs_to :user named_scope :of_sector, lambda do |*sectors| { :conditions => {:sector_id => sectors} } end end class User < ActiveRecord::Base has_many :articles end 

然后你可以做这样的事情:

 @user.articles.of_sector(4) => # articles belonging to @user and sector of 4 @user.articles.of_sector(5,6) => # articles belonging to @user and either sector 4 or 5 @user.articles.of_sector([1,2,3,]) => # articles belonging to @user and either sector 1,2, or 3 

如前所述,在做的时候

 @user.articles.class => Array 

你实际得到的是Array。 那是因为#class方法未定义,如前所述。

但是如何获得@ user.articles的实际类(应该是代理)?

 Object.instance_method(:class).bind(@user.articles).call => ActiveRecord::Associations::CollectionProxy 

为什么你首先得到Array? 因为#class方法通过方法missin委托给CollectionProxy @target实例,这实际上是一个数组。 你可以通过做这样的事情来窥视场景:

 @user.articles.proxy_association 

当你进行关联( has_onehas_many等)时,它会告诉模型ActiveRecord自动包含一些方法。 但是,当您决定创建一个自己返回关联的实例方法时,您将无法使用这些方法。

序列是这样的

  1. User模型中设置articles ,即执行has_many :articles
  2. ActiveRecord自动包含进入模型的方便方法(例如sizeempty?findallfirst等)
  3. Article设置user ,即执行belongs_to :user
  4. ActiveRecord自动将方便的方法包含到模型中(例如user=等)

因此,很明显,当你声明一个关联时,这些方法是由ActiveRecord自动添加的,这是处理大量工作的美妙之处,需要手动完成,否则=)

你可以在这里阅读更多相关信息: http : //guides.rubyonrails.org/association_basics.html#detailed-association-reference

希望这有助于=)