According to the previous exploration of us, we already know that has_many
the method itself is a class method, defined in ActiveRecord::Associations
this class, the method can be seen from the definition:
1 |
def (name, scope = nil, options = {}, &extension) |
The first action of this method is to call the ActiveRecord::Associations::Builder::HasMany
build process. According to our previous exploration, build
it is defined in the ActiveRecord::Associations::Builder::Association
class methods of this class. And because inheritance as follows:
1 |
class ActiveRecord::Associations::Builder::HasMany < ActiveRecord::Associations::Builder::CollectionAssociation; end |
ActiveRecord::Associations
Under this Module1, you can directly Builder::HasMany
call build method and build the return value is an ActiveRecord::Reflection::HasManyReflection
object.
Our next goal is to explore this new reflection object after being out, after which treatment in the build process.
First look at the code build process:
1 |
def self.build(model, name, scope, options, &block) |
We already know that in previous exploration, local variables reflection of this approach is already an ActiveRecord::Reflection::HasManyReflection
object. Next, the object will be transferred into define_accessors
other methods.
Our goal today is to sort out small this define_accessors
method on reflection
the object to do something.
The Rails source code comments:
1 |
|
This method will get getter and setter methods, such as with our previous example is mentioned Product.first.users
and Product.first.users =
. Specifically how to achieve, we take a closer look at the code.
First look at the first line of this method, model this parameter, which is calling has_many
the class method call will go generated_association_methods
method. This method is this:
1 |
# activerecord-5.1.4/lib/active_record/core.rb |
Our assumption is that Product
this model to call this method, the implicit variables of this method to perform self is Product
the class, so the mod will be a local variable named Product::GeneratedAssociationMethods
module of. And this module will be include, a method which will be Product
the example method. For
1 |
@generated_association_methods ||= begin |
With this configuration, based on experience, to prevent most if #generated_association_methods
when the method is called multiple times, which will not be covered by the instance variable. Moreover, instance variables and method names in this method is the same, in line with the convention on the definition of ruby memoization method .
What is the big column Rails source code is 100 days (8) S: //en.wikipedia.org/wiki/Memoization "target =" _ blank "rel =" noopener noreferrer "> memoization method described in Wikipedia, which is a? when the seed part of the calculation result is stored up, next time call the same calculation, you can directly deposit out good results, without re-calculation. this helps performance commission program, can also be used when multiple calls to guarantee results consistency.
Back define_accessors
method, we already know, mixin is a name GeneratedAssociationMethods
of the empty module.
Then, we have to remove the name attribute of reflection. However, creating an object of reflection time, after a lot of layers of abstraction, I did not know you still remember what the name attribute in the end yes. Looking from the source code, in HasManyReflection
an ancestor ( MacroReflection
), there is a constructor creates the getter method name, the name is something that has_many :users
this call incoming symbol, therefore, is the name :users
.
另外,我们也可以从 Product.first.users
得到 User::ActiveRecord_Associations_CollectionProxy
对象了中取出这个 reflection 对象 —— Product.first.users.instance_variable_get(:@association).reflection
,这个对象了中的实例变量如下:
1 |
#<ActiveRecord::Reflection::HasManyReflection:0x007f87472e3698> |
对于这么多的实例变量,我们目前用到的只有@name
,剩下的我们以后会用到。
接下来,在define_accessors
方法中,rails 又分别调用了define_readers
和define_writers
方法,这两个方法是这样的:
1 |
|
其中参数 mixin 就是我们刚刚新建的 module —— Product::GeneratedAssociationMethods
。现在,利用 classeval 方法向这个 module 中动态插入两个方法。根据 name 的值是 :users
,插入的来个方法就成了 users
和 users=
,这是使用非常常用的来个方法。在插入这两个方法的时候,rails 还直到了代码在文件中的位置: `mixin.classeval <<-CODE, FILE, __LINE + 1,这表示代码动态插入的位置在当前文件(
activerecord-5.1.4/lib/active_record/associations/builder/association.rb`)的此行 + 1。我们可以验证一下:
1 |
Product.first.method(:users).source_location |
到这里,我们已经探索完了 define_accessors
方法在构建 reflection 对象时的作用。总结一些就是:
- 为调用 has_many 的 model 生成 model::GeneratedAssociationMethods 这个 module
- 在这个 module 中动态插入两个 associations 的方法,这样所有 model 的实例对象,就都可以访问 associations 方法了。
不过,我们还有一些问题没有探索,就是在动态生成的 associations 方法,调用了 association
这个方法,这个方法是做什么的。我们将在接下来探索。