Table of Contents

How does Ruby-on-Rails ActiveRecord Work Behind the Scenes?

In the past, to build a web application you required the skills to code in your business logic.

Rails is a Model-View-Controller web framework that uses an ORM in the form of ActiveRecord for the Model layer.

For me, like many others, Rails has a very “magical” feel to it. I decided to stop believing blindly in magic and find what happens behind the scenes and I started with ActiveRecord.

In this post I will explain what I have found.

 

What is ActiveRecord?

ActiveRecord is an ORM. It's a layer of Ruby code that runs between your database and your logic code.

When you need to make changes to the database, you'll write Ruby code, and then run "migrations" which makes the actual changes to the database. The cool part is that it doesn't matter what database you're using: Rails can handle pretty much all of 'em, and the method format will always be the same.

It’s possible to use ActiveRecord without Rails but in this post it's all about how they're used in RoR.

Working with ActiveRecord

Now the first thing we are going to discuss is creating a model. It’s easy to create a model inside a Rails app by using rails generate command. If you want to create a model for a Person for example, any of the following commands will work,

rails generate model Person
rails g model Person
rails g model Person first_name last_name age:integer

The first two lines are the same thing, rails g is just shorthand for generate. The third one gives Rails a little more information, so it can do a little more work for you.

We're saying we want this model to have three fields: first_name, last_name, and age. For first_name and last_name, we don't specify a type, so it defaults to a string. For age, we specifically tell it that it should be an integer.

Among other output, you should see something like this,

create	db/migrate/20130213204626_create_people.rb
create app/models/person.rb

The first file is your migration file (of course, the timestamp will be different). The second is a Ruby class that represents your Model. And here is where the magic begins.

Let’s take a look to the migration file first.

Assuming that you run the last command of the example above you would have something like this,

class CreatePeople < ActiveRecord::Migration
def change
create_table :people do |t|
t.string :first_name
t.string :last_name
t.integer :age

t.timestamps
end
end
end

Where this file come from?

Well when we run the rails generate model command we call the model_generator.rb file in rails specifically to this two methods,


# creates the migration file for the model.
def create_migration_file
return unless options[:migration] && options[:parent].nil?
attributes.each { |a| a.attr_options.delete(:index) if a.reference?
&& !a.has_index? } if options[:indexes] == false
migration_template "../../migration/templates/create_table_migration.rb",
File.join(db_migrate_path, "create_#{table_name}.rb")
end

def create_model_file
template "model.rb", File.join("app/models", class_path, "#{file_name}.rb")
end

The first method creates the migration file that we saw before and the second method creates the Model file.

Let’s take a better look to the first method, the last line in particular,

Migration_template "../../migration/templates/create_table_migration.rb", File.join(db_migrate_path, "create_#{table_name}.rb")

The …/create_table_migration.rb is a template file, so let’s take a look at it,

class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
def change
create_table :<%= table_name %><%= primary_key_type %> do |t|
<% attributes.each do |attribute| -%>
<% if attribute.password_digest? -%>
t.string :password_digest<%= attribute.inject_options %>
<% elsif attribute.token? -%>
t.string :<%= attribute.name %><%= attribute.inject_options %>
<% else -%>
t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
<% end -%>
<% end -%>
<% if options[:timestamps] %>
t.timestamps
<% end -%>
end
<% attributes.select(&:token?).each do |attribute| -%>
add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true
<% end -%>
<% attributes_with_index.each do |attribute| -%>
add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
<% end -%>
end
end

This has the same format as the migration file that we saw before. We can see here that the attributes that we pass to the Rails generate model command are being used to generate this file.

Now that we have our Model and our migration file we have to do one more thing: we have to run rake db:migrate. This will run our migration file and a table will be created in the database.

We are ready for the next step to use this model we just created,

p = Person.new first_name: "John", last_name: "Doe", age: 30

This will create a new Person instance. But this information it’s not yet saved in the database. To do so we need to also run the save method.

p.save

Now the instance is saved in the database. The save method runs an SQL query command like this one,

INSERT INTO "people" ("age", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?)  [["age", 30], ["created_at", Fri, 15 Feb 2013 16:02:18 UTC +00:00], ["first_name", "John"], ["last_name", "Doe"], ["updated_at", Fri, 15 Feb 2013 16:02:18 UTC +00:00]]

Inside the abstract adapters we have this method,

# Executes an INSERT query and returns the new record's ID
#
# +id_value+ will be returned unless the value is +nil+, in
# which case the database will attempt to calculate the last inserted
# id and return that value.
#
# If the next id was calculated in advance (as in Oracle), it should be
# passed in as +id_value+.
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
sql, binds = to_sql_and_binds(arel, binds)
value = exec_insert(sql, name, binds, pk, sequence_name)
id_value || last_inserted_id(value)
end

Conclusion

Rails makes heavy use of metaprogramming techniques to provide a huge amount of functionality out of the box. This may feel like magic sometimes because we are not used to this kind of logic or because we don’t fully understand it.

One of the benefits of using Rails and ActiveRecord specifically is that ActiveRecord creates an array of attribute accessors and dynamic finder methods that match the columns in the database tables.

There are methods for all kind of transactions, insert, update, find, delete. All of them works in a similar way: the parameters are received, then a SQL query is generated, and then it is executed so the information is updated in the database.

Learn More about Encora

We are the software development company fiercely committed and uniquely equipped to enable companies to do what they can’t do now.

Learn More

Global Delivery

READ MORE

Careers

READ MORE

Industries

READ MORE

Related Insights

Enabling Transformation in Hospitality through Technology-Led Innovation

As the exclusive sponsor of the 2024 Hotel Visionary Awards, we support organizations leading ...

Read More

Key Insights from HLTH 2024: The Future of Patient-Centered Healthcare

Discover key insights from HLTH 2024 on digital health, AI in diagnostics, data interoperability, ...

Read More

Data-Driven Engineering: Transforming Operations and Products from Insight to Impact

Discover how data-driven engineering transforms operations and product development, enhancing team ...

Read More
Previous Previous
Next

Accelerate Your Path
to Market Leadership 

Encora logo

Santa Clara, CA

+1 669-236-2674

letstalk@encora.com

Innovation Acceleration

Speak With an Expert

Encora logo

Santa Clara, CA

+1 (480) 991 3635

letstalk@encora.com

Innovation Acceleration