23
loading...
This website collects cookies to deliver better user experience
class Teacher < ApplicationRecord
has_many :books
end
class Book < ApplicationRecord
belongs_to :teacher
end
define_accessors model, reflection
#define_accessors
method. It seems pretty likely we're going to get to see how our #books
and our #books=
are finally created here. def self.define_accessors(model, reflection)
mixin = model.generated_association_methods
name = reflection.name
define_readers(mixin, name)
define_writers(mixin, name)
end
def generated_association_methods # :nodoc:
@generated_association_methods ||= begin
mod = const_set(:GeneratedAssociationMethods, Module.new)
private_constant :GeneratedAssociationMethods
include mod
mod
end
end
||=
portion which points to either something that exists or something that needs to be set to point to the following line of code. I've spent sometime looking at this method and I still find it a little strange. My assumption with this overall method is that it is a method that is used in multiple places in the code base, and in order to make it more flexible and dynamic it can either create a new version of generated methods or leave it alone if it already exists. Ensuring that is the intended logic would likely take a much deeper dive into the code base. However, as we spent a little time broadly exploring the HasManyAssocation class when it would have been passed in earlier, it seems likely that our necessary methods are already set rather than needing to be created again and simply need to be attached to our class. generated_assocation_methods
is being called on (our Teacher model), we can see that whether we needed to generate more methods or if our methods already existed, the new module is being added to the model class as an instance variable called @generated_assocation_methods
. This seems to line up quite nicely with our definition of a mixin in which multiple class inheritances are able to be combined to create what will ultimately allow our Teacher class to make use of our specific instance of those private HasManyAssocation instance methods. It seems like this is starting to come together name = reflection.name
define_readers(mixin, name)
define_writers(mixin, name)
define_readers
and define_writers
that we will need to - once again - scroll down the file to locate.def self.define_readers(mixin, name)
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}
association(:#{name}).reader
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
class_eval
method is part of the Ruby language itself, Ruby Docs, and is a method that, "Evaluates the string or block in the context of mod, except that when a block is given, constant/class variable lookup is not affected. This can be used to add methods to a class." Which is exactly what it is doing in our case, the heredoc will ultimately resolve as a string and opens with <<-CODE
and closes with CODE
. From googling, the __FILE__
portion seems to be a way in Ruby to reference the current file that the code is running in, and unsurprisingly, the __LINE__
refers similarly to the current line. My assumption here in terms of the problem it might be solving is that when the methods are generated, they are being created in a way that if the program does not move to the next available line before generating the code, each time a method is created (after the first method) it would cause the program to run into an error as an end
would proceed a def
on the same line. These two methods are where our getter and setter methods are actually defined. However, the way they're written is so dynamic that (assuming we've missed all those possible errors along the way) we can pass so many different names and associations in to Rails and always get our specific named variation back. The magic is advanced and clever usage of interpolation to create whatever is needed! These particular methods should help give us the #books
and `#books=, but this style of generating method writers appears elsewhere in the ActiveRecord code base.
`def self.define_writers(mixin, name)
super
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name.to_s.singularize}_ids=(ids)
association(:#{name}).ids_writer(ids)
end
CODE
end
#camelize
method elsewhere. These are the kinds of methods and writing that generate the convention that makes using Rails relatively easy.has_many
association, we'll get the following commands back from similar reader/writer pairs throughout the process of generating this specific macro:
Teacher#books.empty?, Teacher#books.size, Teacher#books, Project#books<<(book), Teacher#books.delete(book), Teacher#books.destroy(book), Teacher#books.find(book_id), Teacher#books.build, Teacher#books.create
define_callbacks model, reflection
define_validations model, reflection
reflection
end
`
validates :books, presence: true
has_many
with these posts - and assuredly some explanations are inadequate - I hope that the process of working with Rails continues to look less like magic and more like: