rails内的class关于添加了has_many或者belongs_to后如何动态添加一些额外的方法 ?
rails版本5.0.0.1
比如有如下的两个类
class User < ActiveRecord::Base has_many :posts end class Post < ActiveRecord::Base belongs_to :user end
这个时候有一个user
user = User.take
然后可以使用user.posts来找到属于user的所有的posts,同理如果有一个post也可以通过post.user来找到属于它自己的user。
这个时候不禁想问,这个user的.post方法是怎么来的呢?
在ActiveRecord::Core里有个generated_association_methods方法
def generated_association_methods @generated_association_methods ||= begin mod = const_set(:GeneratedAssociationMethods, Module.new) include mod mod end end
当你的rails加载你的class就会执行这个方法(比如加载了User class),并新建一个xxx::GeneratedAssociationMethods的module(加载User class就创建了属于user自己的module:User::GeneratedAssociationMethods),然后通过include方式将xxx::GeneratedAssociationMethods module加载进入到你的class里。而这个module就会用来存放user的.posts类似这样的方法。
在User class中添加的has_many :posts就在加载class时执行一系列方法到2个方法:self.define_readers(mixin, name)、self.define_writers(mixin, name)
这里的两个参数对应的分别为mixin: User, name: posts
具体代码如下:
def self.define_readers(mixin, name) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}(*args) association(:#{name}).reader(*args) end CODE end def self.define_writers(mixin, name) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}=(value) association(:#{name}).writer(value) end CODE end
这两个方法就是动态地添加.posts(*args)和.posts=(value) 2个user的实例方法。
class_eval 这个方法会在一个已存在的类的上下文中执行一个块,并且会修改这个类的内容(《ruby元编程》的解释)。
如下代码会添加.posts(*args)到User::GeneratedAssociationMethods模块里
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}(*args) association(:#{name}).reader(*args) end CODE
如果按照《ruby元编程》的知识的话,可以这样改写
mixin.class_eval do define_method(name) do |*args| association(name.to_sym).reader(*args) end end
又或者可以一行改写
mixin.class_eval("def #{name}(*args); association(:#{name}).reader(*args); end")