Rails Glossary > Models

Models

The fundamental block of the MVC pattern. Let's learn what they are and best practices

In general, the term “model” refers to a representation of an object, a person, or a system.

In Rails, it's the root of the Model-View-Controller, or MVC, the architectural pattern generally used to develop user interfaces that's also used by the framework.

Their elemental function is to represent the data structure and business logic of an application while being able to access data stored in the database.

But that's not the only role they play in Rails. Let's see what are their main responsibilities:

Rails models responsibilities

Even if the function of models can be supplemented by other objects in some design patterns, in Rails, models have a couple of distinct responsibilities:

Data representation and persistence

Typically, models correspond to tables in the database following Rails conventions.

Each instance of the model represents a row in the table that, by convention, receives the name of the model but pluralized: The Usermodel corresponds to the users table.

Mainly, Rails models inherit behavior from ApplicationRecord which implements the Active Record ORM (Object-Relational Mapping) to facilitate the relation with the data stored in the database.

Active Record provides an easy-to-use interface for:

  • Object mapping: in charge of mapping database tables to Ruby objects: database records become Ruby objects that have attributes that map to the db tables.
  • Performing CRUD operations: creating, reading, updating and deleting records.
  • Entity relationships: AR provides an easy way to define how data relates to each other.
  • Validations: ensuring data is valid before it's saved to the database. This can be performed at the database level, but Rails offers an extra way of achieving the same outcome.
  • Callbacks: lifecycle hooks that we can use to execute code at specific points of the object's life: before or after validations are performed or the object is saved to the database.
  • Scopes: provide a way to retrieve collections of records with specific requirements using the ORM, Arel or plain SQL. ### Business Logic Besides object mapping and accessing the database, Rails models are used to encapsulate our application's business logic, which is the code that's specific to our application or business domain.

If we're building a SaaS app, we might need logic to see if a User is subscribed or on a trial using our specific requirements.

It represents the rules, processes, and constraints that define how our application operates based on the requirements or our user's needs.

When using models, the logic is usually implemented using tools like custom methods, validations, associations, callbacks, etc.

We're also encouraged to use Ruby classes, also known as Plain-Ol'-Ruby-Objects, to represent entities in our application's domain that are not necessarily backed by a database.

Data relationships

Models are also in charge of the way data is connected by using the relational nature of SQL, which enforces those relationships using primary and foreign keys.

In Rails, we can define associations using the following methods:

  • has_many: indicates a one-to-many relationship. For example, an Author can have many books represented by the Book model.
  • belongs_to: is the inverse relationship. A book belongs to an author via a foreign key which, by convention, should be an author_id defined in the Book model.
  • has_one: indicates a one-to-one relationship. Like a user who has a Profile.
  • has_and_belongs_to_many: indicates a many-to-many relationship where we have an intermediate table, but we don't define a model. For example, a Post has many tags and, at the same time, a Tag can belong to many posts. To achieve this, we need a table called post_tags by convention and define the has_and_belongs_to_many in both models.
  • has_many :through: indicates a many-to-many relationship, but we have a model to represent it. For example, we could have a PostTag or PostTagging model, where we would add a belongs_to association to both posts and tags. Meanwhile, we would declare the has_many :through in both models, providing us with access to the list of tags for a given post or a list of posts where a given tag was used. ## Model feature implementation The following are common ways of implementing the Rails model features explained above: ### Validations They allow us to ensure data integrity in the application itself, and they should be accompanied by database constraints to avoid data corruption:
class User < ApplicationRecord
  validates :email, presence: true, uniqueness: true
  validates :username, presence: true, length: {minimum: 6, maximum: 50}
end

The validations above require an email and username to be present to persist a user to the database, but they also require the username to be unique and the length of the username to be at least 6 and at most 50 characters.

Rails includes many validations by default, but we can also define a custom validation using the validate method:

class User < ApplicationRecord
  validate :minimum_age

  private
  def minimum_age
    return if date_of_birth.blank?

    if date_of_birth > 18.years.ago.to_date
      errors.add(:date_of_birth, "You must be at least 18 years old")
    end
  end  
end

Callbacks

They allow us to execute code between the object's lifecycle:

class Article < ApplicationRecord
  before_save :normalize_title

  private

  def normalize_title
    self.title = title.downcase.titleize
  end
end

The code above makes sure our article's title is normalized before being saved to the database.

Rails provides us with many callbacks that allow us to execute code when an object is being created, updated, destroyed or associated with other objects via associations.

Scopes

They are a way to retrieve a subset of records or sort them using a given criterion.

We can use Active Record, SQL, or even Arel to achieve our goal:

class Product < ApplicationRecord
  scope :recent, -> { order(created_at: :desc) }
  scope :by_price_ascending, -> { order(price_cents: :asc) }
  scope :active, -> { where(active: true) }
end

We use the order method to sort records and where to query our database.

We can also use raw SQL if it suits us:

class User < ApplicationRecord
  scope :active_last_week, -> { where("last_login_at >= ?", 1.week.ago) }
end

Or Arel which is a helper library that helps Rails build SQL queries that we can also use:

class User < ApplicationRecord
  scope :active_last_week, -> {
    where(Arel::Table.new(:users)[:last_login_at].gteq(1.week.ago))
  }
end

Rails models best practices

Even though models are a special type of object that inherits from ApplicationRecord, most of the OOP design advice applies to them.

The following is advice that's usually given when talking about models:

  • Single responsibility principle: models should focus on a single entity and the aspects that are related to it. For example: a User should not be too worried about subscription or payment logic. Otherwise, it usually means we're missing an object to model those other entities.
  • Fat models and skinny controllers: this is a common motto that was frequently used when Rails came out. It means that, given the choice, we should prefer to put business logic in models and not in controllers. There are many other design choices that can improve this simplified vision of application design, but it's a great starting point, especially when compared to alternatives like doing most work in controllers.
  • DRY or Don't Repeat Yourself: reusing code is encouraged. The DRY principle centers around the fact that duplicate code can become an issue in the future, so it promotes the use of reusable code. In Rails, we can use concerns or—more rarely—inheritance to achieve this goal.
  • Unit and integration testing: adding tests to make sure our models work like intended is desirable. Automated tests might seem like a drag at first but they pay for themselves almost invariably in the long term.
Try Avo for free