Validations in Ruby

Active Record allows you to check the state of a model before it is written to the database. There are several methods that can be used to check your models and validate that an attribute’s value is non-empty, unique (not existing in the database), conforms to a certain format, and many more.

Validation is a very important issue to consider when saving to a database, so the save and update methods take this into account when they run: they return false when validation fails, and they don’t actually perform any database operations. Each of these methods has an exclamation mark pair (save! and update!), which are stricter in that they throw an ActiveRecord::RecordInvalid exception if the validation fails. Short example:

class User<ApplicationRecord

  validates :name, presence: true

end

user = User.new

user.save # => false

user.save! # => ActiveRecord::RecordInvalid: Validation failed: Name can’t be blank

You can read more about validations in the Active Record Validations guide.

callbacks

Active Record callbacks allow you to attach code to specific events in the life cycle of your models. This allows you to add behavior to the model by transparently running code when these events occur, such as when you create a new record, update it, delete it, and so on. You can read more about callbacks in the Active Record callbacks guide.

Migrations

Rails introduces a DSL for database schema management called migrations. Migrations are stored in files that run against any database supported by Active Record using rake. Here is the migration that creates the table:

class CreatePublications < ActiveRecord::Migration[5.0]

  def change

    create_table :publications do |t|

      t.string :title

      t.text :description

      t.references :publication_type

      t.integer :publisher_id

      t.string :publisher_type

      t.boolean :single_issue

      t.timestamps

    end

    add_index :publications, :publication_type_id

  end

end

Rails keeps track of which files are committed to the database and provides an option to rollback. To actually create the table, you need to run rails db:migrate, and to roll it back, rails db:rollback.

Note that the code above is database independent: it will run in MySQL, PostgreSQL, Oracle, and others. You can read more about migrations in the Active Record migrations guide.

Active Record Migrations

Migrations are a feature of Active Record that allow you to change your database schema from time to time. Instead of writing schema changes in pure SQL, migrations allow you to use a simple Ruby DSL to describe changes to your tables.

After reading this guide, you will learn about:

The generators used to create them

Active Record methods to interact with your database

bin/rails tasks affecting migrations and your schema

How migrations are related to schema.rb

Overview of migrations

Migrations are a convenient way to change your database schema all the time in a consistent and simple way. They use Ruby DSL. So you don’t have to write SQL by hand, allowing your schema to be database independent.

Each migration can be thought of as a new ‘version’ of the database. The schema initially contains nothing, and each migration changes it by adding or removing tables, columns, or records. Active Record knows how to update your schema over time, moving it from a certain point in the past to the latest version. Active Record also updates your db/schema.rb file to match the current structure of your database.

Here is an example migration:

class CreateProducts < ActiveRecord::Migration[5.0]

  def change

    create_table :products do |t|

      t.string :name

      t.text :description

      t.timestamps

    end

  end

end

This migration adds a products table with a name string column and a description text column. A primary key named id will also be implicitly added by default, as this is the default primary key for all Active Record models. The timestamps macro adds two columns, created_at and updated_at. These special columns are automatically managed by Active Record if they exist.

Note that we have defined the change we want to happen as we move forward in time. Before running this migration, there is no table. After – the table will exist. Active Record also knows how to reverse this migration: if we rollback this migration, it will drop the table.

In databases that support transactions with schema-changing expressions, migrations are wrapped in a transaction. If the database does not support this and the migration fails, the parts that succeed will not be rolled back. You need to manually rollback.

NOTE: Some queries cannot be run in a transaction. If your adapter supports DDL transactions, disable_ddl_transaction can be used! to disable them for a separate migration.

If you want a migration for something that Active Record doesn’t know how to reverse, you can use reversible:

class ChangeProductsPrice < ActiveRecord::Migration[5.0]

  def change

    do |dir|

      change_table :products do |t|

        dir.up { t.change :price, :string }

        dir.down { t.change :price, :integer }

      end

    end

  end

end

On the other hand, you can use up and down instead of change:

class ChangeProductsPrice < ActiveRecord::Migration[5.0]

  def up

    change_table :products do |t|

      t.change :price, :string

    end

  end

  def down

    change_table :products do |t|

      t.change :price, :integer

    end

  end

end

Create a migration

Create an offline migration

Migrations are stored as files in the db/migrate directory, one file per class. The filename is YYYYMMDDHHMMSS_create_products.rb, which means that the UTC timestamp identifies the migration, followed by an underscore, followed by the migration name, where the words are separated by underscores. The name of the migration class contains the literal part of the file name, but in the CamelCase format (i.e. words are written together, each word begins with a capital letter). For example, 20080906120000_create_products.rb should define the CreateProducts class, and 20080906120001_add_details_to_products.rb should define AddDetailsToProducts. Rails uses this flag to determine which migrations should be run and in what order, so if you’re copying migrations from another application or generating the file yourself, be more vigilant.

Of course, calculating timestamps isn’t fun, so Active Record provides a generator to handle this:

$ bin/rails generate migration AddPartNumberToProducts

This will create an empty but properly named migration:

class AddPartNumberToProducts < ActiveRecord::Migration[5.0]

  def change

  end

end

If the migration name is of the form “AddXXXToYYY” or “RemoveXXXFromYYY” and is followed by a list of column names and their types, then the migration will generate the appropriate add_column and remove_column expressions.

$ bin/rails generate migration AddPartNumberToProducts part_number:string

will generate

class AddPartNumberToProducts < ActiveRecord::Migration[5.0]

  def change

    add_column :products, :part_number, :string

  end

end

Leave a Reply

Your email address will not be published.