ActiveRecord Internals : You are not ready for this
Introduction to sorcery
I like this quote: Magic is the technology we still need to understand. Rails is an excellent framework; it hides complexity from developers, but because of that, many people qualify it as magical.
This article will demystify Rails by looking deeper into the Rails active record association internals. In the meantime, we will also try to understand pretty cool programming concepts.
I hope you are ready ( you are not ), it's going to be fun, let's go.
Oh yes, and if you like to learn or read about Rails, Ruby, databases, and a lot of tech-related stuff :
Keep in Touch
On Twitter/X : [@yet_anotherDev] (https://twitter.com/yet_anotherDev)
On Linkedin : Lucas Barret
Did you say association?
Let's begin with defining active record associations and why they are helpful.
If you are a Rails developer, you should know, and I am sure you know it, this fantastic website : Rails Guides.
Going to this link and you can read that : association is a connection between two active record models, and that's all. But Why do they exist? Because it makes everyday operations straightforward and more accessible.
Nice, keep reading and we see that Associations are implemented using macro-style programming ?
Macro style Ruby Fu
A lot of definition in this article but yet this is useful to be sure we all understand the same thing. Now going to Wikipedia we can see that : a macro is a rule or a pattern that specifies how a specific input should be mapped to a replacement output.
This makes everyday tasks or definitions available for programmers as a single statement. ( Like your associations statements ).
This kind of programming style enables define our association in a declarative way. If you define an Athlete class that has_many
Medal(s), you will write it as it :
class Medal < ActiveRecord::Base
belongs_to :athlete
end
class Athlete < ActiveRecord::Base
has_many :medals
end
You declare that your athlete has_many
medals and that medal belongs_to
athlete.
In ruby, macros and this kind of declarative style of programming are possible thanks to the class-level method but also thanks to how ruby classes work.
Go back to class
Ruby Classes are no unique code; as with everything else in Ruby, they are executable.
If I define a Ruby class called: Athlete, with a workout class method. I could call it inside the class, and when my ruby interpreter reads my class code, it will execute my workout method.
#./athlete.rb
class Athlete
def self.workout
p '1 push up'
end
workout
end
> ruby athlete.rb
> 1 push up
As you can see, your class definition code is executed!
It is precisely what is happening with your has_many declaration. It is a method call with an argument which is the name of your associate model in a simple case we illustrated earlier.
Dancing in the dark
It is time to dive into rails code; let's see what we can find. As we want to find out how has_many associations work, the first step is to look for a definition of a method called has_many.
If you look in the ActiveRecord code, you will find :
#rails/activerecord/associations.rb
def has_many(name, scope = nil, **options, &extension)
reflection = Builder::HasMany.build(self, name, scope, options, &extension)
Reflection.add_reflection self, name, reflection
end
But this is weird, and you could ask why this is not defined as a class method.
Looking around, this is a class method defined in the module ClassMethods, with another pattern we won't cover here.
Now in the internal of this function, little is done. We build a reflection and add it to the Reflection class with the association name.
If we go to the rails/activerecord/associations/builder/has_many.rb
file, what we see is not helpful for what we want to understand at first sight.
We see the options available for our association and the dependent options. But there has yet to be a build class methods function.
module ActiveRecord::Associations::Builder # :nodoc:
class HasMany < CollectionAssociation # :nodoc:
def self.macro
:has_many
end
def self.valid_options(options)
...
end
def self.valid_dependent_options
...
end
private_class_method :macro, :valid_options, :valid_dependent_options
end
end
Nevertheless, this class inherits from CollectionAssociation let's see what we got in rails/activerecord/associations/builder/collection_association.rb
; there are many cool things here, but still no build methods.
module ActiveRecord::Associations::Builder
class CollectionAssociation < Association
But as before, this class inherits from another class, Association. Let's see this one.
And this is what we are looking for. We have the build methods, which all the associations share.
And in this method, the famous reflection is built and returned to the has_many class methods.
def self.build(model, name, scope, options, &block)
if model.dangerous_attribute_method?(name)
raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
"This will conflict with a method #{name} already defined by Active Record. " \
"Please choose a different association name."
end
reflection = create_reflection(model, name, scope, options, &block)
define_accessors model, reflection
define_callbacks model, reflection
define_validations model, reflection
define_change_tracking_methods model, reflection
reflection
end
But why is reflection needed? What is a reflection?
This will be the subject of my next article...
Conclusion
We have begin our journey in the dark magic Rails. If you want to feel powerful, wait for the second one next week.
If you liked this article and would like to know more or hear from it differently: check this keynote from Daniel Colson.
I dove into the code and began this article before seeing the keynote. Then I was like, should I not publish it? Eventually, I got more knowledge from this conference and tried to improve the article the best I could. I hope this article will help my fellow rubyists.
See you for the second part next week.