🏠 HOME
OUR STACK
Model classes inherits from the rails ApplicationRecord
class.
Here is an example of the ApplicationRecord class for one of our application:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: {
writing: :database_name,
reading: :database_name
}
end
Model classes are the interface between our application and our database.
As always, Rails favour conventions over configurations. By default, ActiveRecord uses some naming conventions to find out how the mapping between model classes and database tables should be done.
Model classes are singular and capitalized, and they follow the CamelCase convention. In the other hand, database tables are plural with underscores separating words (following the snake_case convention).
Here are a few examples of model classes and their database tables:
To create a new model, you can simply add a new file under the models folder. Just be careful, there has to be a corresponding table in the database in order to use it. This means that you have to run a migration in order to create that table in the database.
If you’re lazy, you can simply run:
rails g model User name:string email:string company:references
This command will create a migration file that can be run to create the table in the database, and it will also create the model class with the appropriate attributes. Just don’t forget to run the migration using rails db:migrate
to create the table and to make the model usable.
In this example, we can see that it will create a table with two string attributes (name & email), but it will also add another attribute: company. This one is a bit different from the previous ones, because we don’t directly assign a type to it. Instead, we say that the company attribute is a reference to a record in another table. This means that we already have a table called companies
and the equivalent model class. With the naming of the attribute, which correspond to an existing model, Rails will be able to find the right table it refers to. Convention over configuration they say.
In case you want to see more of rails generate
command lines, check out this cool cheat sheet.
Here is an example of a model class:
class User < ApplicationRecord
belongs_to :company
scope :for_company, ->(company) { where(company: company) }
delegate :name, to: :company, prefix: true
def name_or_email
[first_name, last_name].compact.join(' ').blank? ? email : [first_name, last_name].compact.join(' ')
end
end
We can see that its attribute are not defined here, but you can still simply access them by calling them on your record. For example, if you’ve retrieved a user record, you can call user.first_name
. So keep in mind that every table column is accessible, even if they are not explicitly defined on the model class.
On the other hand, the relations must be explicit in your model class. In this example, it’s on the second line: belongs_to :company
. This means that we have an attribute called company
which references another record in our database.
<aside>
<img src="/icons/info-alternate_blue.svg" alt="/icons/info-alternate_blue.svg" width="40px" /> If you used rails g model
to generate your model , your relations should already be present in your model class.
</aside>
You can also find a scope on line 4**. This scope will allow you to reduce your queries by scoping them the way you want.** In this case, calling User.for_company(4)
will retrieve every users related to the company whose ID is 4**.** They are method aliases for where clauses.
On the 6th line, you can see that we added an attribute by delegation. This means that we will delegate this attribute to the company (an instance of another class), and give an access to it on the user record with the company
prefix. In other words, we can directly do user.company_name
instead of doing user.company.name
.
Finally, you can see on lines 8 to 10 that we defined a custom method for this model class. This method will return the first_name
and the last_name
if they are present, or return the email
otherwise. We can directly use it on our record like this: user.name_or_email
.
ActiveRecord follows the CRUD implementation, which is an acronym for Create, Read, Update & Delete. By default, ActiveRecord defines multiple methods that you can directly call on your model classes.
To create a new instance of a model class, we can think of two methods: new
and create
. The first one will return a new instance of your model class, while the second one will do the same but will also persist the newly created instance to the database.
Let’s take a deeper look with our User
model class:
# Returns the new user
user = User.new(first_name: 'Quentin', last_name: 'Tarantino')
# Returns the new user after having try to save it in the database
user = User.create(first_name: 'Quentin', last_name: 'Tarantino')
Another set of methods we can frequently find in our applications are find_or_initialize_by
and find_or_create_by
. Their names are quite explicit: they will either find or create a new record. The difference between these two is the same than the one for new and create. The first one will return a record, the second one will return a record and save it to the database.
# This should find the existing record in the DB and return it
user = User.find_or_initialize_by(last_name: 'Tarantino')
# This should initialize the new record in the DB and return it
user = User.find_or_initialize_by(last_name: 'Kubrick')
Reading data from the database is super easy thanks to ActiveRecord. It provides a rich set of methods to access data within a database. Below are a few examples of different data access, and if you want more information you can find it on this guide.
# Retrieve the user with the ID 18
user = User.find(18)
# Retrieve the first user
user = User.first
# Retrieve the first user whose name is Tarantino
user = User.find_by(last_name: 'Tarantino')
# Retrieve an array of users who were invited
users = User.where(invited: true)
Also, you can now use your awesome scope:
# Retrive an of array of users whose company has the ID 4
users = User.for_company(4)
Once a model instance has been retrieved, its attributes can be directly modified and then saved to the database.
You can do user.name = 'Steven Spielberg'
and then user.save
to save the modifications into the database. You can also directly do user.update(name: 'Steven Spielberg')
which will take care of both the modification and the saving.
You can also update multiple attributes at once using update_attributes
or assign_attributes
. The only difference between those two is that the first one will update and save, while the second one will just update without saving (you’ll still have to do it if you want your modifications to be persisted). Both of these methods need a hash as parameter which contains the attributes you want to edit. Check out the example below:
user.update_attributes({
first_name: 'Steven',
last_name: 'Spielberg',
email: '[email protected]'
})
As for the update, any record can be deleted once it has been retrieved. To do this, you simply have to call the method destroy
on the record you want to delete.
You can also destroy every record of an array by call the destroy_all
method on the collection representing your records. You have to be very careful with that though, as it is quite definitive.
# Destroy a specific record
user.destroy
# Destroy every users whose company ID is 4
users = User.for_company(4)
users.detroy_all
Rails allows you to save nested attributes through the parent record.
By default, this option is turned off, but you can enable it by using the accepts_nested_attributes_for
method.
Let’s analyze this with an example. Here is our (simplified) company
model:
class Company < ApplicationRecord
has_many :users, dependent: :destroy
accepts_nested_attributes_for :users
end
If you have retrieved a company
object, and you want to update all of its users, you can simply do company.users = all_users
(considering all_users to be an array of user records).
<aside>
<img src="/icons/info-alternate_blue.svg" alt="/icons/info-alternate_blue.svg" width="40px" /> In our Prospect.io application, we use a monkey patch that supplement the rails NestedAttributes
module to support more options. You can find this file called nested_attributes.rb
in our codebase.
</aside>
On the second line, you can see that we declared the users relation to the company (as we did in the user model for the company). What is interesting here is the dependent: destroy
. This means that if we delete a company, all of its users will be destroyed as well. To be more precise, the method destroy will be called on every user records of this company. This is how to delete nested records when the parent record is being deleted.