Rails Active Records

Rails Active Records

·

14 min read

Introduction to Active Record

Active Record is an Object-Relational Mapping (ORM) framework in the Ruby on Rails web development framework. Active Record provides an easy-to-use interface to work with databases in a Rails application by mapping database tables to Ruby classes, and records to objects. With Active Record, developers can interact with databases using Ruby code and methods, such as .find and .create, rather than writing SQL queries directly. ORM frameworks like Active Record provide a level of abstraction over databases, making it easier for developers to work with databases in object-oriented programming languages like Ruby. By using Active Record, developers can focus on their application's domain model, rather than worrying about database schema and querying.

Database Tables and Migrations

To create a new database table along with a corresponding model in Rails, developers can use the rails generate model command followed by a name for the model and its attributes. For example, rails generate model User name:string would create both the migration and the model file for a User object with a name attribute of type string. We will cover models in more detail later in the article.

Every migration represents a change in the schema. For example, if you create a table and after 2 days you decide to add a column, the creation is migration and adding a column is another migration. Migrations play a crucial role in maintaining backward compatibility in a Rails application. They allow developers to make changes to the database schema over time while preserving existing data and keeping the application's code in sync with the database structure. Rails migrations make it easy to update and version control the database schema, and they also provide a way to roll back changes in case something goes wrong. The command rails db:rollback can be used to undo the most recent migration, and the VERSION option can be used to roll back to a specific migration version. For example, rails db:rollback VERSION=20191003104537 would roll back the migration with the timestamp 20191003104537. By using migrations, developers can easily manage and update the database schema over time, while ensuring that the application remains compatible with older versions of the database. This is important for maintaining the integrity of the data and the stability of the application.

Using 'Change' in Rails Migrations

Migrations in Rails are used to modify the database schema, typically by creating or modifying tables and columns. A basic migration in Rails consists of a Ruby class that inherits from ActiveRecord::Migration and defines a change method. The change method is a convenient way to specify both the "up" and "down" directions for the migration. For example, to add a new table called users with name and email columns, you could create a migration file called create_users.rb with the following code:

class CreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end

In this example, the create_table method is used to define the users table with the name and email columns, as well as a created_at and updated_at timestamp. To run this migration, you would use the rails db:migrate command.

Similarly, to add a column to an existing table, you could create a migration file called add_age_to_users.rb with the following code:

class AddAgeToUsers < ActiveRecord::Migration[6.1]
  def change
    add_column :users, :age, :integer
  end
end

In this example, the add_column method is used to add a new age column to the users table. To remove the age column from the users table, you could create a migration file called remove_age_from_users.rb with the following code:

class RemoveAgeFromUsers < ActiveRecord::Migration[6.1]
  def change
    remove_column :users, :age, :integer
  end
end

Overall, the change method is a very convenient way to define reversible migrations, as it allows you to specify both the "up" and "down" directions in a single method.

Modifying Schema in Migrations

In Rails migrations, there are several methods available for modifying the database schema. One common method is change_column, which allows you to modify the definition of an existing column. Another method is remove_column, which removes a column from a table. The add_index method is used to add an index to a table, which can help speed up queries. Conversely, the remove_index method is used to remove an index from a table. You can also use the rename_table method to rename an existing table. Finally, the execute method allows you to execute arbitrary SQL statements in the context of the migration. Here are some examples of using these methods:

class ModifyUsersTable < ActiveRecord::Migration[6.1]
  def change
    change_column :users, :email, :string, null: false
    remove_column :users, :age
    add_index :users, :email, unique: true
    remove_index :users, :name
    rename_table :users, :customers
    execute "ALTER TABLE customers ADD CONSTRAINT unique_name UNIQUE (name)"
  end
end

Managing Migrations with CLI

To manage migrations and to migrate to a certain migration or rollback, CLI instructions are provided for this purpose. Rails provides a set of command line instructions to manage migrations. The most commonly used instructions are db:migrate, which applies any pending migrations to the database, and db:rollback, which rolls back the most recent migration. For example, to run all pending migrations, you can use the command rails db:migrate. To rollback the last migration, you can use rails db:rollback.

There are several other useful CLI instructions available, including db:migrate:up and db:migrate:down, which allow you to migrate to a specific version of the database schema. You can also use db:migrate:redo to rollback the most recent migration and then immediately reapply it. Additionally, db:migrate:status provides information on the current status of your migrations. These commands can help you manage your database schema and keep it up to date with your Rails application.

Models

As mentioned earlier, to create a model developers can create models using this command: rails generate model command followed by a name for the model and its attributes. For example, rails generate model User name:string. Models represent the objects in your application and their interactions with the database. They define the data structure and logic of your application, including validations, associations, and behaviors. While the database holds the actual data, models provide an abstraction layer that allows you to interact with the data in a more convenient way, without worrying about low-level details such as SQL queries or database schema.

For example, suppose you have a blog application with articles table that has columns: title, body, and author. You would define a model for the articles, specifying its attributes (such as title, body, and author).

Here's an example of a simple Article model in Rails:

class Article < ApplicationRecord
end

Querying the database

To create a new article, you would call the create method on the Article class:

article = Article.create(title: "Hello, world!", body: "This is my first post.", author: "John Doe")

To retrieve all articles, you would call the all method:

articles = Article.all

To find a specific article by ID, you would call the find method:

article = Article.find(params[:id])

To update an article, you would call the update method:

article.update(title: "New title")

And to delete an article, you would call the destroy method:

article.destroy

These are the basic CRUD operations (Create, Read, Update, Delete) for querying and manipulating the data in a model. They provide a simple and consistent interface for interacting with your application's data, regardless of the underlying database.

Associations

In Ruby on Rails, associations define the relationships between different models in your application. They allow you to specify how models are related to each other and provide convenient methods for accessing and manipulating associated data. There are several types of associations in Rails, including belongs_to, has_one, has_many, has_one, through:, and has_many, through:.

Let's consider an example schema with five models: Author, Biography, Profile, Book, Review, and BookAuthor. Here are the associations:

  • belongs_to: The belongs_to association specifies that one record in a table belongs to another record in a different table. For example, an author can have one biography, so we would define the association in the Biography model:
class Biography < ApplicationRecord
  belongs_to :author
end
  • has_one: The has_one association specifies that one record in a table has one associated record in another table. For example, a biography can have one profile, so we would define the association in the Biography model:
class Biography < ApplicationRecord
  belongs_to :author
  has_one :profile
end
  • has_many: The has_many association specifies that one record in a table has many associated records in another table. For example, a book can have many reviews, so we would define the association in the Book model:
class Book < ApplicationRecord
  has_many :reviews
end
  • has_one, through:: The has_one, through: association allows you to define a has_one association through a has_many association. For example, an author can have one profile through their biography, so we would define the association in the Author model:
class Author < ApplicationRecord
  has_one :biography
  has_one :profile, through: :biography
end
  • has_many, through:: The has_many, through: association allows you to define a has_many association through another has_many association. For example, an author can have many books through BookAuthor, so we would define the association in the Author model:
class Author < ApplicationRecord
  has_many :book_authors
  has_many :books, through: :book_authors
end

Each association provides different functionalities for querying and manipulating the associated data. For example, with the associations defined above, you could do:

  • Author.create: Creates a new author record.

  • book.reviews: Returns all reviews associated with a book.

  • author.profile: Returns the profile associated with an author through their biography.

This is a list of many of the most useful generated methods for each association:

  • belongs_to: The belongs_to association generates the following methods:

    • association: Returns the associated object (in this case, the Author object).

    • association=: Sets the associated object.

    • build_association: Builds a new associated object.

    • create_association: Builds and saves a new associated object.

For example, if we have a Biography record and we want to get the associated Author, we can use the author method like this:

    biography = Biography.find(1)
    author = biography.author
  • has_one: The has_one association generates the following methods:

    • association: Returns the associated object (in this case, the Profile object).

    • association=: Sets the associated object.

    • build_association: Builds a new associated object.

    • create_association: Builds and saves a new associated object.

For example, if we have a Biography record and we want to get the associated Profile, we can use the profile method like this:

    biography = Biography.find(1)
    profile = biography.profile
  • has_many: The has_many association generates the following methods:

    • collection: Returns a collection of associated objects (in this case, an array of Review objects).

    • collection=: Sets the collection of associated objects.

    • build_association: Builds a new associated object.

    • create_association: Builds and saves a new associated object.

For example, if we have a Book record and we want to get all the associated Review records, we can use the reviews method like this:

    book = Book.find(1)
    reviews = book.reviews
  • has_one, through:: The has_one, through: association generates the following methods:

    • association: Returns the associated object (in this case, the Profile object).

    • association=: Sets the associated object.

    • build_association: Builds a new associated object.

    • create_association: Builds and saves a new associated object.

For example, if we have an Author record and we want to get the associated Profile through their Biography, we can use the profile method like this:

    author = Author.find(1)
    profile = author.profile
  • has_many, through:: The has_many, through: association generates the following methods:

    • collection: Returns a collection of associated objects (in this case, an array of Book objects).

    • collection=: Sets the collection of associated objects.

    • build_association: Builds a new associated object.

    • create_association: Builds and saves a new associated object.

For example, if we have an Author record and we want to get all the associated Book records through their BookAuthor records, we can use the books method like this:

    author = Author.find(1)
    books = author.books

Validations

In Ruby on Rails, validations are used to ensure that the data entered by users is valid and meets certain criteria before it is saved to the database. Validations can check that a value is present, unique, has a specific format, and more. For example, the following code defines a Book model with a validation that checks that the title attribute is present:

class Book < ApplicationRecord
  validates :title, presence: true
end

If a new Book object is created and saved without a title attribute, the validation will fail and the record won't be saved to the database.

Validations are run automatically before data is saved to the database. If any validation fails, the save or update method will return false. The save! and update! methods are also available, and will raise an ActiveRecord::RecordInvalid error if the validation fails. This allows for more fine-grained error handling and can make it easier to provide helpful feedback to users when their input is invalid.

Callbacks

In Ruby on Rails, callbacks are methods that are called at specific points during the lifecycle of an Active Record object. Callbacks allow developers to trigger custom logic at specific points, such as before or after an object is created, updated, or destroyed. The most commonly used callbacks are before_save, after_save, before_create, after_create, before_update, after_update, before_destroy, and after_destroy.

Developers can limit when a callback is fired by specifying on option, which can take the values :create, :update, and :destroy. For example, the following code defines a User model with a before_create callback that sets the user's email to lowercase before it is saved to the database:

class User < ApplicationRecord
  before_create :downcase_email

  private
  def downcase_email
    self.email = email.downcase
  end
end

In this example, the downcase_email method is called only before a new record is created, because the callback is defined with before_create.

Callbacks can be triggered by several methods, including save, create, update, and destroy. It's important to note that some methods, such as update_columns and update_all, bypass callbacks altogether. A full list of these methods can be found in rails active record documentation.

Scopes

In Ruby on Rails, scopes are a way to define pre-defined queries on a model that can be reused throughout your application. Scopes provide a mechanism for encapsulating common query logic into a single location, making your code more maintainable and easier to read. By defining scopes on your model, you can avoid repeating the same SQL queries over and over again and build more complex queries by chaining scopes together.

You can define regular scopes using the scope method, like this:

class Post < ApplicationRecord
  scope :published, -> { where(published: true) }
  default_scope { order(created_at: :desc) }
end

In this example, we define a regular scope called published that returns all published posts. We also define a default scope that orders posts by their creation date in descending order. Now, whenever we query the Post model, the default scope will automatically be applied, but we can still use the published scope:

# This will return all published posts ordered by their creation date in descending order
Post.all

# This will return all published posts ordered by their creation date in descending order
Post.published.all

Regular scopes can also be combined with default scopes to create more complex queries:

class Post < ApplicationRecord
  scope :published, -> { where(published: true) }
  default_scope { order(created_at: :desc) }
  scope :by_category, ->(category) { where(category: category) }
end

# This will return all published posts in the 'Ruby' category ordered by their creation date in descending order
Post.published.by_category('Ruby').all

Overall, scopes and default scopes are powerful tools in Ruby on Rails that can help you write more maintainable and readable code in your models.

Advanced Techniques

Polymorphic Associations

Polymorphic associations and single-table inheritance are advanced techniques in Rails that allow for more flexible and reusable database relationships. Polymorphic associations allow a model to belong to multiple other models on a single association, which can be useful when you have multiple models that share common attributes. For example, suppose you have a Comment model that can be associated with either a Post or a Video model. You can define the polymorphic association like this:

class Comment < ApplicationRecord
  belongs_to :commentable, polymorphic: true
end

class Post < ApplicationRecord
  has_many :comments, as: :commentable
end

class Video < ApplicationRecord
  has_many :comments, as: :commentable
end

In this example, the Comment model has a belongs_to association called commentable, which is polymorphic. The Post and Video models each have a has_many association called comments, with the as: :commentable option to indicate that the comments association is polymorphic and can be associated with either a Post or a Video.

Single-talbe inheritance

Single-table inheritance, on the other hand, allows you to store multiple types of objects in a single database table, which can simplify your database schema and reduce redundancy. For example, suppose you have a Vehicle model that represents different types of vehicles, such as cars, trucks, and motorcycles. You can use single-table inheritance to store all types of vehicles in a single vehicles table:

class Vehicle < ApplicationRecord
end

class Car < Vehicle
end

class Truck < Vehicle
end

class Motorcycle < Vehicle
end

In this example, the Car, Truck, and Motorcycle models all inherit from the Vehicle model, which has a corresponding vehicles table in the database. Each subclass has its own set of attributes and methods, but they all share the same table structure. This can simplify your code and make it easier to maintain, especially if you have a large number of models that share common attributes. However, single-table inheritance can also lead to performance issues if the table becomes too large, so it should be used with care.

Conclusion

Active record is a crucial tool for modern web development, providing a flexible and efficient means of managing relational databases. Through object-relational mapping, developers can create models that interact with database tables, while features like validations, callbacks, and scopes streamline workflow. Active record's ability to handle complex associations like polymorphic associations and single-table inheritance further enhances its versatility. Its widespread adoption in frameworks like Ruby on Rails underscores its importance, and mastering active record is a key step for any web developer looking to create powerful, efficient applications.

References