Chi*_*tan 20 activerecord ruby-on-rails model-associations
rails关联方法如何工作?让我们考虑这个例子
class User < ActiveRecord::Base
has_many :articles
end
class Article < ActiveRecord::Base
belongs_to :user
end
Run Code Online (Sandbox Code Playgroud)
现在我可以做点什么了
@user = User.find(:first)
@user.articles
Run Code Online (Sandbox Code Playgroud)
这会抓取属于该用户的文章.到现在为止还挺好.
现在我可以继续在某些条件下对这些文章进行查找.
@user.articles.find(:all, :conditions => {:sector_id => 3})
Run Code Online (Sandbox Code Playgroud)
或者简单地声明和关联方法
class User < ActiveRecord::Base
has_many :articles do
def of_sector(sector_id)
find(:all, :conditions => {:sector_id => sector_id})
end
end
end
Run Code Online (Sandbox Code Playgroud)
并做
@user.articles.of_sector(3)
Run Code Online (Sandbox Code Playgroud)
现在我的问题是,这find对ActiveRecord使用关联方法获取的对象数组有何影响?因为如果我们实现我们自己的User实例方法调用articles并编写我们自己的实现,它给出了与关联方法完全相同的结果,那么ActiveRecord对象的获取数组上的查找将不起作用.
我的猜测是,关联方法将某些属性附加到获取对象数组,从而可以使用find其他ActiveRecord方法进一步查询.在这种情况下,代码执行的顺序是什么?我怎么能验证这个?
pjb*_*jb3 21
它的实际工作原理是关联对象是一个"代理对象".关联类是AssociationProxy.如果你看一下该文件的第52行,你会看到:
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
Run Code Online (Sandbox Code Playgroud)
通过这样做,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
Run Code Online (Sandbox Code Playgroud)
目标是一个数组,所以当你调用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}"
Run Code Online (Sandbox Code Playgroud)
如果您不知道该文件的位置,该命令gem which 'active_record/associations/association_proxy'将告诉您.现在,当您调用classAssociationProxy时,您将看到一条日志消息,告诉您它正在向目标发送该消息,这样可以更清楚地了解正在发生的事情.这完全适用于Rails 2.3.2,并且可能会在其他版本中发生变化.
如前所述,活动记录关联创建了一个方便的方便性方法.当然,你可以编写自己的方法来获取所有内容.但这不是Rails方式.
Rails Way是两个格言的顶点.干(不要重复自己)和"约定优于配置".本质上,通过以有意义的方式命名事物,框架提供的一些强大方法可以抽象出所有常见代码.您在问题中放置的代码是可以通过单个方法调用替换的完美示例.
这些便利方法真正发挥作用的地方是更复杂的情况.涉及联接模型,条件,验证等的事情.
要在执行类似操作时回答您的问题@user.articles.find(:all, :conditions => ["created_at > ? ", tuesday]),Rails会准备两个SQL查询,然后将它们合并为一个.您的版本只返回对象列表.命名范围执行相同的操作,但通常不跨越模型边界.
您可以通过在控制台中调用这些内容时检查development.log中的SQL查询来验证它.
因此,我们暂时谈谈命名范围,因为它们提供了一个关于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.
Run Code Online (Sandbox Code Playgroud)
事情发生了变化:
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.
Run Code Online (Sandbox Code Playgroud)
实质上,每个命名范围都会创建一个SQL片段.Rails将巧妙地与链中的每个其他SQL片段合并,以生成单个查询,完全按照您的需要进行返回.关联方法添加的方法以相同的方式工作.这就是他们与named_scopes无缝集成的原因.
混合和匹配不起作用的原因与问题中定义的of_sector方法不起作用的原因相同.edited_methods返回一个数组,其中,edit_scope(以及查找和所有其他AR便捷方法称为链的一部分)将其SQL片段向前传递给链中的下一个事物.如果它是链中的最后一个,则执行查询.同样,这也行不通.
@edited = Article.edited_scope
@edited.recent_scope
Run Code Online (Sandbox Code Playgroud)
您试图使用此代码.这是正确的方法:
class User < ActiveRecord::Base
has_many :articles do
def of_sector(sector_id)
find(:all, :conditions => {:sector_id => sector_id})
end
end
end
Run Code Online (Sandbox Code Playgroud)
要实现此功能,您需要执行此操作:
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
Run Code Online (Sandbox Code Playgroud)
然后你可以做这样的事情:
@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
Run Code Online (Sandbox Code Playgroud)