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
: Thebelongs_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 theBiography
model:
class Biography < ApplicationRecord
belongs_to :author
end
has_one
: Thehas_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 theBiography
model:
class Biography < ApplicationRecord
belongs_to :author
has_one :profile
end
has_many
: Thehas_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 theBook
model:
class Book < ApplicationRecord
has_many :reviews
end
has_one, through:
: Thehas_one, through:
association allows you to define ahas_one
association through ahas_many
association. For example, an author can have one profile through their biography, so we would define the association in theAuthor
model:
class Author < ApplicationRecord
has_one :biography
has_one :profile, through: :biography
end
has_many, through:
: Thehas_many, through:
association allows you to define ahas_many
association through anotherhas_many
association. For example, an author can have many books throughBookAuthor
, so we would define the association in theAuthor
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
: Thebelongs_to
association generates the following methods:association
: Returns the associated object (in this case, theAuthor
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
: Thehas_one
association generates the following methods:association
: Returns the associated object (in this case, theProfile
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
: Thehas_many
association generates the following methods:collection
: Returns a collection of associated objects (in this case, an array ofReview
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:
: Thehas_one, through:
association generates the following methods:association
: Returns the associated object (in this case, theProfile
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:
: Thehas_many, through:
association generates the following methods:collection
: Returns a collection of associated objects (in this case, an array ofBook
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.