Skip to content

Latest commit

 

History

History
4620 lines (3833 loc) · 135 KB

activerecord.adoc

File metadata and controls

4620 lines (3833 loc) · 135 KB

ActiveRecord

ActiveRecord is a level of abstraction that offers access to a SQL database. ActiveRecord implements the architectural pattern Active Record.

Note
This is referred to as object-relational-mapping or ORM. I find it rather dry and boring, but in case you have trouble going to sleep tonight, have a look at http://en.wikipedia.org/wiki/Object_relational_mapping.

One of the recipes for the success of Rails is surely the fact that is uses ActiveRecord. The programming and use "feels Ruby like" and it is much less susceptible to errors than pure SQL. When working with this chapter, it helps if you have some knowledge of SQL, but this is not required and also not essential for working with ActiveRecord.

Note
This chapter is only about ActiveRecord. So I am not going to integrate any tests to keep the examples as simple as possible.

Creating Database/Model

Model in this context refers to the data model of Model-View-Controller (MVC).

As a first example, let’s take a list of countries in Europe. First, we create a new Rails project:

$ rails new europe
  [...]
$ cd europe

Next, let’s have a look at the help page for rails generate model:

$ rails generate model
Running via Spring preloader in process 21883
Usage:
  rails generate model NAME [field[:type][:index] field[:type][:index]] [options]

[...]

Description:
    Stubs out a new model. Pass the model name, either CamelCased or
    under_scored, and an optional list of attribute pairs as arguments.

[...]

Available field types:

    Just after the field name you can specify a type like text or boolean.
    It will generate the column with the associated SQL type. For instance:

        `rails generate model post title:string body:text`

    will generate a title column with a varchar type and a body column with a text
    type. If no type is specified the string type will be used by default.
    You can use the following types:

        integer
        primary_key
        decimal
        float
        boolean
        binary
        string
        text
        date
        time
        datetime
[...]

The usage description rails generate model NAME [field[:type][:index] field[:type][:index]] [options] tells us that after rails generate model comes the name of the model and then the table fields. If you do not put :type after a table field name, it is assumed to be a string.

Let’s create the model country:

$ rails generate model Country name population:integer
Running via Spring preloader in process 22053
      invoke  active_record
      create    db/migrate/20170322165321_create_countries.rb
      create    app/models/country.rb
      invoke    test_unit
      create      test/models/country_test.rb
      create      test/fixtures/countries.yml

The generator has created a database migration file with the name db/migrate/20170322165321_create_countries.rb. It provides the following code:

db/migrate/20170322165321_create_countries.rb
class CreateCountries < ActiveRecord::Migration[5.1]
  def change
    create_table :countries do |t|
      t.string :name
      t.integer :population

      t.timestamps
    end
  end
end

A migration contains database changes. In this migration, a class CreateCountries is defined as a child of ActiveRecord::Migration. The method change is used to define a migration and the associated roll-back.

With the command rails db:migrate we can apply the migrations, in other words, create the corresponding database table:

$ rails db:migrate
== 20170322165321 CreateCountries: migrating ==================================
-- create_table(:countries)
   -> 0.0010s
== 20170322165321 CreateCountries: migrated (0.0011s) =========================
Note
You will find more details on migrations in the section "Migrations".

Let’s have a look at the file app/models/country.rb:

app/models/country.rb
class Country < ApplicationRecord
end

Hmmm …​ the class Country is a child of ApplicationRecord which inherits from ApplicationRecord. In ApplicationRecord you’ll find all the ActiveRecord magic.

The Attributes id, created_at and updated_at

Even if you cannot see it in the migration, we also get the attributes id, created_at und updated_at by default for each ActiveRecord model. In the Rails console, we can output the attributes of the class Country by using the class method column_names:

$ rails console
Running via Spring preloader in process 22303
Loading development environment (Rails 5.2.0)
>> Country.column_names
=> ["id", "name", "population", "created_at", "updated_at"]
>> exit

The attribute created_at stores the time when the record was initially created. updated_at stores the time of the last update for this record.

id is used a central identification of the record (primary key). The id is automatically incremented by 1 for each new record.

Getters and Setters

To read and write values of a SQL table row you can use getters and setters by ActiveRecord provided getters and setters. These attr_accessors are automatically created. The getter of the field updated_at for a given Country with the name germany would be germany.updated_at.

Possible Data Types in ActiveRecord

ActiveRecord is a layer between Ruby and various relational databases. Unfortunately, many SQL databases have different perspectives regarding the definition of columns and their content. But you do not need to worry about this, because ActiveRecord solves this problem transparently for you.

To generate a model, you can use the following field types:

Table 1. Field Types
Name Description

binary

This is a BLOB (Binary Large Object) in the classical sense. Never heard of it? Then you probably won’t need it. See also http://en.wikipedia.org/wiki/Binary_large_object

boolean

true, false or nil

date

You can store a date here.

datetime

Here you can store a date including a time.

integer

For storing an integer. See also http://en.wikipedia.org/wiki/Integer_(computer_science)

decimal

For storing a decimal number.

primary_key

This is an integer that is automatically incremented by 1 by the database for each new entry. This field type is often used as key for linking different database tables or models. See also http://en.wikipedia.org/wiki/Unique_key

string

A string, in other words a sequence of any characters, up to a maximum of 2^8 -1 (= 255) characters. See also http://en.wikipedia.org/wiki/String_(computer_science)

text

Also a string - but considerably bigger. By default, up to 2^16 -1 (= 65535) characters can be saved here.

time

A time.

timestamp

A time with date, filled in automatically by the database.

Decimal

You can also define a decimal with the model generator. But you need to observe the special syntax (you have to use ' if you are using the Bash shell).

Example for creating a price with a decimal:

$ rails generate model product name 'price:decimal{7,2}'
  [...]
$

That would generate this migration:

db/migrate/20170322170623_create_products.rb
class CreateProducts < ActiveRecord::Migration[5.1]
  def change
    create_table :products do |t|
      t.string :name
      t.decimal :price, precision: 7, scale: 2

      t.timestamps
    end
  end
end

In "Migrations" we will provide more information on the individual data types and discuss available options.

Naming Conventions (Country vs. country vs. countries)

ActiveRecord automatically uses the English plural forms. So for the class Country, it’s countries. If you are not sure about a term, you can also work with the class and method name.

$ rails console
Running via Spring preloader in process 23132
Loading development environment (Rails 5.2.0)
>> Country.name.tableize
=> "countries"
>> Country.name.foreign_key
=> "country_id"
>> exit

Database Configuration

Which database is used by default? Let’s have a quick look at the configuration file for the database (config/database.yml):

config/database.yml
# SQLite version 3.x
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
#
default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: db/test.sqlite3

production:
  <<: *default
  database: db/production.sqlite3

As we are working in development mode, Rails has created a new SQLite3 database in the file db/development.sqlite3 as a result of rails db:migrate and will save all data there.

Fans of command line clients can use sqlite3 for viewing this database:

$ sqlite3 db/development.sqlite3
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> .tables
ar_internal_metadata  countries             schema_migrations
sqlite> .schema countries
CREATE TABLE "countries" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"name" varchar, "population" integer, "created_at" datetime NOT NULL,
"updated_at" datetime NOT NULL);
sqlite> .exit

Adding Records

Actually, I would like to show you first how to view records, but to show records you have to create them first. So first, here is how you can create a new record with ActiveRecord.

create

The most frequently used method for creating a new record is create.

Let’s try creating a country in the console with the command Country.create(name: 'Germany', population: 81831000)

$ rails console
Running via Spring preloader in process 23285
Loading development environment (Rails 5.2.0)
>> Country.create(name: 'Germany', population: 81831000)
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "countries" ("name", "population", "created_at",
  "updated_at") VALUES (?, ?, ?, ?)  [["name", "Germany"],
  ["population", 81831000], ["created_at", "2017-03-22 17:10:30.859482"],
  ["updated_at", "2017-03-22 17:10:30.859482"]]
   (2.2ms)  commit transaction
=> #<Country id: 1, name: "Germany", population: 81831000,
created_at: "2017-03-22 17:10:30", updated_at: "2017-03-22 17:10:30">
>> exit

ActiveRecord saves the new record and outputs the executed SQL command in the development environment. But to make absolutely sure it works, let’s have a last look with the command line client sqlite3:

$ sqlite3 db/development.sqlite3
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> SELECT * FROM countries;
1|Germany|81831000|2017-03-23 17:10:03.141592|2017-03-22 17:10:03.141592
sqlite> .exit

Syntax

The method create can handle a number of different syntax constructs. If you want to create a single record, you can do this with or without {}-brackets within the the ()-brackets:

  • Country.create(name: 'Germany', population: 81831000)

  • Country.create({name: 'Germany', population: 81831000})

Similarly, you can describe the attributes differently:

  • Country.create(:name ⇒ 'Germany', :population ⇒ 81831000)

  • Country.create('name' ⇒ 'Germany', 'population' ⇒ 81831000)

  • Country.create( name: 'Germany', population: 81831000)

You can also pass an array of hashes to create and use this approach to create several records at once:

Country.create([{name: 'Germany'}, {name: 'France'}])

new

In addition to create there is also new. But you have to use the save method to save an object created with new (which has both advantages and disadvantages):

$ rails console
Running via Spring preloader in process 23679
Loading development environment (Rails 5.2.0)
>> france = Country.new
=> #<Country id: nil, name: nil, population: nil, created_at: nil,
updated_at: nil>
>> france.name = 'France'
=> "France"
>> france.population = 65447374
=> 65447374
>> france.save
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "countries" ("name", "population", "created_at",
  "updated_at") VALUES (?, ?, ?, ?)  [["name", "France"],
  ["population", 65447374], ["created_at", "2017-03-22 17:15:30.001686"],
  ["updated_at", "2017-03-22 17:15:30.001686"]]
   (2.1ms)  commit transaction
=> true
>> france
=> #<Country id: 2, name: "France", population: 65447374,
created_at: "2017-03-22 17:15:30", updated_at: "2017-03-22 17:15:30">

You can also pass parameters for the new record directly to the method new, just as with create:

>> belgium = Country.new(name: 'Belgium', population: 10839905)
=> #<Country id: nil, name: "Belgium", population: 10839905,
created_at: nil, updated_at: nil>
>> belgium.save
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "countries" ("name", "population",
  "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "Belgium"],
  ["population", 10839905], ["created_at", "2017-03-22 17:16:31.091853"],
  ["updated_at", "2017-03-22 17:16:31.091853"]]
   (2.5ms)  commit transaction
=> true
>> exit

new_record?

With the method new_record? you can find out if a record has already been saved or not. If a new object has been created with new and not yet been saved, then the result of new_record? is true. After a save it’s false.

Example:

$ rails console
Running via Spring preloader in process 23823
Loading development environment (Rails 5.2.0)
>> netherlands = Country.new(name: 'Netherlands')
=> #<Country id: nil, name: "Netherlands", population: nil,
created_at: nil, updated_at: nil>
>> netherlands.new_record?
=> true
>> netherlands.save
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "countries" ("name", "created_at",
  "updated_at") VALUES (?, ?, ?)  [["name", "Netherlands"],
  ["created_at", "2017-03-22 17:17:34.694389"],
  ["updated_at", "2017-03-22 17:17:34.694389"]]
   (2.1ms)  commit transaction
=> true
>> netherlands.new_record?
=> false
>> exit
Tip
For already existing records, you can also check for changes with the method changed? (see "changed?"). You can even use netherland.population_changed? to check if just the attribute popluation was changed.

first, last and all

In certain cases, you may need the first record, or the last one, or perhaps even all records. Conveniently, there is a ready-made method for each case. Let’s start with the easiest ones: first and last.

$ rails console
Running via Spring preloader in process 24090
Loading development environment (Rails 5.2.0)
>> Country.first
  Country Load (0.2ms)  SELECT  "countries".* FROM "countries" ORDER BY
  "countries"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Country id: 1, name: "Germany", population: 81831000, created_at:
"2017-03-22 17:10:30", updated_at: "2017-03-22 17:10:30">
>> Country.last
  Country Load (0.3ms)  SELECT  "countries".* FROM "countries" ORDER BY
  "countries"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<Country id: 4, name: "Netherlands", population: nil, created_at:
"2017-03-22 17:17:34", updated_at: "2017-03-22 17:17:34">

And now all at once with all:

>> Country.all
  Country Load (0.2ms)  SELECT "countries".* FROM "countries"
=> #<ActiveRecord::Relation [#<Country id: 1, name: "Germany",
population: 81831000, created_at: "2017-03-22 17:10:30",
updated_at: "2017-03-22 17:10:30">, #<Country id: 2, name: "France",
population: 65447374, created_at: "2017-03-22 17:15:30",
updated_at: "2017-03-22 17:15:30">, #<Country id: 3, name: "Belgium",
population: 10839905, created_at: "2017-03-22 17:16:31",
updated_at: "2017-03-22 17:16:31">, #<Country id: 4, name: "Netherlands",
population: nil, created_at: "2017-03-22 17:17:34",
updated_at: "2017-03-22 17:17:34">]>

But the objects created by first, last and all are different.

>> Country.first.class
  Country Load (0.3ms)  SELECT  "countries".* FROM "countries"
  ORDER BY "countries"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> Country(id: integer, name: string, population: integer,
created_at: datetime, updated_at: datetime)
>> Country.all.class
=> Country::ActiveRecord_Relation

So Country.first is a Country which makes sense. But Country.all is something we haven’t had yet. Let’s use the console to get a better idea of it:

>> puts Country.all.to_yaml
  Country Load (0.4ms)  SELECT "countries".* FROM "countries"
---
- !ruby/object:Country
  concise_attributes:
  - !ruby/object:ActiveRecord::Attribute::FromDatabase
    name: id
    value_before_type_cast: 1
  - !ruby/object:ActiveRecord::Attribute::FromDatabase
    name: name
    value_before_type_cast: Germany
  - !ruby/object:ActiveRecord::Attribute::FromDatabase
    name: population
    value_before_type_cast: 81831000
  - !ruby/object:ActiveRecord::Attribute::FromDatabase
    name: created_at
    value_before_type_cast: '2017-03-22 17:10:30.859482'
  - !ruby/object:ActiveRecord::Attribute::FromDatabase
    name: updated_at
    value_before_type_cast: '2017-03-22 17:10:30.859482'
  new_record: false
  active_record_yaml_version: 2
[...]
=> nil

hmmm…​ by using the to_yaml method suddenly the database has work to do. The reason for this behavior is optimization. Let’s assume that you want to chain a couple of methods. Than it might be better for ActiveRecord to wait till the very last second which it does. It only requests the data from the SQL database when it has to do it (it’s called Lazy Loading). Until than it stores the request in a ActiveRecord::Relation.

The result of Country.all is actually an Array of Country.

If Country.all returns an array, then we should also be able to use iterators and each, right? Yes, of course! That is the beauty of it. Here is a little experiment with each:

>> Country.all.each do |country|
?> puts country.name
>> end
  Country Load (0.1ms)  SELECT "countries".* FROM "countries"
Germany
France
Belgium
Netherlands
=> [#<Country id: 1, name: "Germany", [...]]

So can we also use .all.first as an alternative for .first? Yes, but it does not make much sense. Have a look for yourself:

>> Country.first
  Country Load (0.2ms)  SELECT  "countries".* FROM "countries"
  ORDER BY "countries"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Country id: 1, name: "Germany", population: 81831000,
created_at: "2017-03-22 17:10:30", updated_at: "2017-03-22 17:10:30">
>> Country.all.first
  Country Load (0.2ms)  SELECT  "countries".* FROM "countries"
  ORDER BY "countries"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Country id: 1, name: "Germany", population: 81831000,
created_at: "2017-03-22 17:10:30", updated_at: "2017-03-22 17:10:30">
>> exit

Country.first and Country.all.first result in exact the same SQL query because ActiveRecord optimizes it.

Note
ActiveRecord does not only provide the first method but also second, third, fourth and fifth. It’s obvious what they do.

Populating the Database with seeds.rb

With the file db/seeds.rb, the Rails gods have given us a way of feeding default values easily and quickly to a fresh installation. This is a normal Ruby program within the Rails environment. You have full access to all classes and methods of your application.

With that you don’t need to enter everything manually with rails console to create all initial records in a new Rails application. You can use the file db/seeds.rb:

db/seeds.rb
Country.create(name: 'Germany', population: 81831000)
Country.create(name: 'France', population: 65447374)
Country.create(name: 'Belgium', population: 10839905)
Country.create(name: 'Netherlands', population: 16680000)

You then populate it with data via rails db:seed.

If you want to delete the existing database, recreate it and than populate it with the seeds you can use rails db:reset. That’s what we do here:

$ rails db:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
-- create_table("countries", {:force=>:cascade})
   -> 0.0050s
-- create_table("countries", {:force=>:cascade})
   -> 0.0032s

I use the file db/seeds.rb at this point because it offers a simple mechanism for filling an empty database with useful values. In the course of this book, this will make it easier for us to set up quick example scenarios.

It’s all just Ruby code

The db/seeds.rb is a Ruby program. Correspondingly, we can also use the following approach as an alternative:

db/seeds.rb
country_list = [
  [ "Germany", 81831000 ],
  [ "France", 65447374 ],
  [ "Belgium", 10839905 ],
  [ "Netherlands", 16680000 ]
]

country_list.each do |name, population|
  Country.create( name: name, population: population )
end

The result is the same. I am showing you this example to make it clear that you can program normally within db/seeds.rb.

Generating seeds.rb From Existing Data

Sometimes it can be useful to export the current data pool of a Rails application into a db/seeds.rb. While writing this book, I encountered this problem in almost every chapter. Unfortunately, there is no standard approach for this. I am showing you what you can do in this case. There are other, more complex scenarios that can be derived from my approach.

We create our own little rake task for that. A rake task is a Ruby programm which is stored in the lib/tasks/ directory and which has full access to the Rails environment.

lib/tasks/export.rake
namespace :export do
  desc "Prints Country.all in a seeds.rb way."
  task :seeds_format => :environment do
    Country.order(:id).all.each do |country|
      bad_keys = ['created_at', 'updated_at', 'id']
      serialized = country.serializable_hash.
                   delete_if{|key,value| bad_keys.include?(key)}
      puts "Country.create(#{serialized})"
    end
  end
end

Then you can call the corresponding rake task with the command rails export:seeds_format:

$ rails export:seeds_format
Country.create({"name"=>"Germany", "population"=>81831000})
Country.create({"name"=>"France", "population"=>65447374})
Country.create({"name"=>"Belgium", "population"=>10839905})
Country.create({"name"=>"Netherlands", "population"=>16680000})

You can either expand this program so that the output is written directly into the db/seeds.rb or you can simply use the shell:

$ rails export:seeds_format > db/seeds.rb

Searching and Finding with Queries

The methods first and all are already quite nice, but usually you want to search for something more specific with a query.

For describing queries, we create a new Rails project:

$ rails new jukebox
  [...]
$ cd jukebox
$ rails generate model Album name release_year:integer
  [...]
$ rails db:migrate
  [...]

For the examples uses here, use a db/seeds.rb with the following content:

db/seeds.rb
Album.create(name: "Sgt. Pepper's Lonely Hearts Club Band", release_year: 1967)
Album.create(name: "Pet Sounds", release_year: 1966)
Album.create(name: "Revolver", release_year: 1966)
Album.create(name: "Highway 61 Revisited", release_year: 1965)
Album.create(name: "Rubber Soul", release_year: 1965)
Album.create(name: "What's Going On", release_year: 1971)
Album.create(name: "Exile on Main St.", release_year: 1972)
Album.create(name: "London Calling", release_year: 1979)
Album.create(name: "Blonde on Blonde", release_year: 1966)
Album.create(name: "The Beatles", release_year: 1968)

Then, set up the new database with rails db:reset:

$ rails db:reset
Dropped database 'db/development.sqlite3'
Database 'db/test.sqlite3' does not exist
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
-- create_table("active_storage_attachments", {:force=>:cascade})
   -> 0.0074s
-- create_table("active_storage_blobs", {:force=>:cascade})
   -> 0.0033s
-- create_table("albums", {:force=>:cascade})
   -> 0.0020s
-- create_table("active_storage_attachments", {:force=>:cascade})
   -> 0.0077s
-- create_table("active_storage_blobs", {:force=>:cascade})
   -> 0.0040s
-- create_table("albums", {:force=>:cascade})
   -> 0.0021s

find

The simplest case is searching for a record via a primary key (by default, the id field in the database table). If I know the ID of an object, then I can search for the individual object or several objects at once via the ID:

$ rails console
Running via Spring preloader in process 26956
Loading development environment (Rails 5.2.0)
>> Album.find(2)
  Album Load (0.2ms)  SELECT  "albums".* FROM "albums"
  WHERE "albums"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
=> #<Album id: 2, name: "Pet Sounds", release_year: 1966,
created_at: "2017-03-22 18:19:06", updated_at: "2017-03-22 18:19:06">
>> Album.find([1,3,7])
  Album Load (0.4ms)  SELECT "albums".* FROM "albums"
  WHERE "albums"."id" IN (1, 3, 7)
=> [#<Album id: 1, name: "Sgt. Pepper's Lonely Hearts Club Band",
release_year: 1967, created_at: "2017-03-22 18:19:06",
updated_at: "2017-03-22 18:19:06">, #<Album id: 3, name: "Revolver",
release_year: 1966, created_at: "2017-03-22 18:19:06",
updated_at: "2017-03-22 18:19:06">, #<Album id: 7,
name: "Exile on Main St.", release_year: 1972,
created_at: "2017-03-22 18:19:06", updated_at: "2017-03-22 18:19:06">]

If you always want to have an array as result, you also always have to pass an array as parameter:

>> Album.find(5).class
  Album Load (0.2ms)  SELECT  "albums".* FROM "albums"
  WHERE "albums"."id" = ? LIMIT ?  [["id", 5], ["LIMIT", 1]]
=> Album(id: integer, name: string, release_year: integer,
created_at: datetime, updated_at: datetime)
>> Album.find([5]).class
  Album Load (0.1ms)  SELECT  "albums".* FROM "albums"
  WHERE "albums"."id" = ? LIMIT ?  [["id", 5], ["LIMIT", 1]]
=> Array
>> exit
Warning
The method find generates an exception if the ID you are searching for does not have a record in the database.

where

With the method where, you can search for specific values in the database. Let’s search for all albums from the year 1966:

$ rails console
Running via Spring preloader in process 27119
Loading development environment (Rails 5.2.0)
>> Album.where(release_year: 1966)
  Album Load (0.2ms)  SELECT "albums".* FROM "albums"
  WHERE "albums"."release_year" = ?  [["release_year", 1966]]
=> #<ActiveRecord::Relation [#<Album id: 2, name: "Pet Sounds",
release_year: 1966, created_at: "2017-03-22 18:19:06",
updated_at: "2017-03-22 18:19:06">, #<Album id: 3,
name: "Revolver", release_year: 1966,
created_at: "2017-03-22 18:19:06", updated_at: "2017-03-22 18:19:06">,
#<Album id: 9, name: "Blonde on Blonde", release_year: 1966,
created_at: "2017-03-22 18:19:06", updated_at: "2017-03-22 18:19:06">]>
>> Album.where(release_year: 1966).count
   (0.3ms)  SELECT COUNT(*) FROM "albums"
   WHERE "albums"."release_year" = ?  [["release_year", 1966]]
=> 3

You can also use where to search for ranges:

>> Album.where(release_year: 1960..1966).count
   (0.3ms)  SELECT COUNT(*) FROM "albums"
   WHERE ("albums"."release_year" BETWEEN ? AND ?)
   [["release_year", 1960], ["release_year", 1966]]
=> 5

And you can also specify several search factors simultaneously, separated by commas:

>> Album.where(release_year: 1960..1966, id: 1..5).count
   (0.4ms)  SELECT COUNT(*) FROM "albums"
   WHERE ("albums"."release_year" BETWEEN ? AND ?)
   AND ("albums"."id" BETWEEN ? AND ?)  [["release_year", 1960],
   ["release_year", 1966], ["id", 1], ["id", 5]]
=> 4

Or an array of parameters:

>> Album.where(release_year: [1966, 1968]).count
   (0.2ms)  SELECT COUNT(*) FROM "albums"
   WHERE "albums"."release_year" IN (1966, 1968)
=> 4

The result of where is always an array. Even if it only contains one hit or if no hit is returned (which will result in an empty array). If you are looking for the first hit, you need to combine the method where with the method first:

>> Album.where(release_year: [1966, 1968]).first
  Album Load (0.4ms)  SELECT  "albums".* FROM "albums"
  WHERE "albums"."release_year" IN (1966, 1968)
  ORDER BY "albums"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Album id: 2, name: "Pet Sounds", release_year: 1966,
created_at: "2017-03-22 18:19:06", updated_at: "2017-03-22 18:19:06">
>> exit

not

The method not provides a way to search for the exact oposite of a where query. Example:

$ rails console
Running via Spring preloader in process 27349
Loading development environment (Rails 5.2.0)
>> Album.where.not(release_year: 1968).count
   (0.2ms)  SELECT COUNT(*) FROM "albums"
   WHERE ("albums"."release_year" != ?)  [["release_year", 1968]]
=> 9
>> exit

or

The method or provides a way to combine queries with a logical or. Example:

$ rails console
Running via Spring preloader in process 27449
Loading development environment (Rails 5.2.0)
>> Album.where(release_year: 1967).or(Album.where(name: 'The Beatles')).count
   (0.2ms)  SELECT COUNT(*) FROM "albums"
   WHERE ("albums"."release_year" = ? OR "albums"."name" = ?)
   [["release_year", 1967], ["name", "The Beatles"]]
=> 2
>> exit

SQL Queries with where

Sometimes there is no other way and you just have to define and execute your own SQL query. In ActiveRecord, there are two different ways of doing this. One sanitizes each query before executing it and the other passes the query on to the SQL database 1 to 1 as it is. Normally, you should always use the sanitized version because otherwise you can easily fall victim to an SQL injection attack (see http://en.wikipedia.org/wiki/Sql_injection).

Note
If you do not know much about SQL, you can safely skip this section. The SQL commands used here are not explained further.
Sanitized Queries

In this variant, all dynamic search parts are replaced by a question mark as placeholder and only listed as parameters after the SQL string.

In this example, we are searching for all albums whose name contains the string “on”:

$ rails console
Running via Spring preloader in process 27553
Loading development environment (Rails 5.2.0)
>> Album.where( 'name like ?', '%on%').count
   (0.1ms)  SELECT COUNT(*) FROM "albums" WHERE (name like '%on%')
=> 5

Now the number of albums that were published from 1965 onwards:

>> Album.where( 'release_year > ?', 1964 ).count
   (0.2ms)  SELECT COUNT(*) FROM "albums" WHERE (release_year > 1964)
=> 10

The number of albums that are more recent than 1970 and whose name contains the string “on”:

>> Album.where( 'name like ? AND release_year > ?', '%on%', 1970 ).count
   (0.4ms)  SELECT COUNT(*) FROM "albums" WHERE (name like '%on%'
   AND release_year > 1970)
=> 3

If the variable search_string contains the desired string, you can search for it as follows:

>> search_string = 'ing'
=> "ing"
>> Album.where( 'name like ?', "%#{search_string}%").count
   (0.2ms)  SELECT COUNT(*) FROM "albums" WHERE (name like '%ing%')
=> 2
>> exit

Dangerous SQL Queries

If you really know what you are doing, you can of course also define the SQL query completely and forego the sanitizing of the query.

Let’s count all albums whose name contain the string “on”:

$ rails console
Running via Spring preloader in process 27699
Loading development environment (Rails 5.2.0)
>> Album.where( "name like '%on%'" ).count
   (0.2ms)  SELECT COUNT(*) FROM "albums" WHERE (name like '%on%')
=> 5
>> exit

Please only use this variation if you know exactly what you are doing and once you have familiarized yourself with the topic SQL injections (see http://en.wikipedia.org/wiki/Sql_injection).

Lazy Loading

Lazy Loading is a mechanism that only carries out a database query if the program flow cannot be realised without the result of this query. Until then, the query is saved as ActiveRecord::Relation.

Note
Incidentally, the opposite of lazy loading is referred to as eagerloading.

Does it make sense in principle, but you aren’t sure what the point of it all is? Then let’s cobble together a query where we nest several methods. In the following example, a is defined more and more closely and only at the end (when calling the method all) the database query would really be executed in a production system. With the method ActiveRecord methods to_sql you can display the current SQL query.

$ rails console
Running via Spring preloader in process 27764
Loading development environment (Rails 5.2.0)
>> a = Album.where(release_year: 1965..1968)
  Album Load (0.2ms)  SELECT "albums".* FROM "albums" WHERE
  ("albums"."release_year" BETWEEN 1965 AND 1968)
=> #<ActiveRecord::Relation [#<Album id: 1, [...]]>
>> a.class
=> Album::ActiveRecord_Relation
>> a = a.order(:release_year)
  Album Load (0.3ms)  SELECT "albums".* FROM "albums" WHERE
  ("albums"."release_year" BETWEEN 1965 AND 1968)  ORDER BY
  "albums"."release_year" ASC
=> #<ActiveRecord::Relation [#<Album id: 4, [...]]>
>> a = a.limit(3)
  Album Load (0.4ms)  SELECT  "albums".* FROM "albums" WHERE
  ("albums"."release_year" BETWEEN 1965 AND 1968)  ORDER BY
  "albums"."release_year" ASC LIMIT 3
=> #<ActiveRecord::Relation [#<Album id: 4, [...]]>
>> exit

The console can be a bit tricky about this. It tries to help the developer by actually showing the result but in a non-console environment this would would only happen at the very last time.

Automatic Optimization

One of the great advantages of lazy loading is the automatic optimization of the SQL query through ActiveRecord.

Let’s take the sum of all release years of the albums that came out in the 70s. Then we sort the albums alphabetically and then calculate the sum.

$ rails console
Running via Spring preloader in process 27764
Loading development environment (Rails 5.2.0)
>> Album.where(release_year: 1970..1979).sum(:release_year)
   (1.5ms)  SELECT SUM("albums"."release_year") FROM "albums" WHERE
   ("albums"."release_year" BETWEEN 1970 AND 1979)
=> 5922
>> Album.where(release_year: 1970..1979).order(:name).sum(:release_year)
   (0.3ms)  SELECT SUM("albums"."release_year") FROM "albums" WHERE
   ("albums"."release_year" BETWEEN 1970 AND 1979)
=> 5922
>> exit

Logically, the result is the same for both queries. But the interesting thing is that ActiveRecord uses the same SQL code for both queries. It has detected that order is completely irrelevant for sum and therefore took it out altogether.

Note
In case you are asking yourself why the first query took 1.5ms and the second 0.3ms: ActiveRecord cached the results of the first SQL request.

order and reverse_order

To sort a database query, you can use the method order.

Example: all albums from the 60s, sorted by name:

$ rails console
Running via Spring preloader in process 27764
Loading development environment (Rails 5.2.0)
>> Album.where(release_year: 1960..1969).order(:name)
  Album Load (0.2ms)  SELECT "albums".* FROM "albums" WHERE
  ("albums"."release_year" BETWEEN 1960 AND 1969)  ORDER BY "albums"."name"
  ASC
=> #<ActiveRecord::Relation [#<Album id: 9, name: "Blonde on Blonde" [...]]>

With the method reverse_order you can reverse an order previously defined via order:

>> Album.where(release_year: 1960..1969).order(:name).reverse_order
  Album Load (0.3ms)  SELECT "albums".* FROM "albums" WHERE
  ("albums"."release_year" BETWEEN 1960 AND 1969)  ORDER BY "albums"."name"
  DESC
=> #<ActiveRecord::Relation [#<Album id: 10, name: "The Beatles" [...]]>

limit

The result of any search can be limited to a certain range via the method limit.

The first 5 albums from the 60s:

>> Album.where(release_year: 1960..1969).limit(5)
  Album Load (0.3ms)  SELECT  "albums".* FROM "albums" WHERE
  ("albums"."release_year" BETWEEN 1960 AND 1969) LIMIT 5
=> #<ActiveRecord::Relation [#<Album id: 1, [...]]>

All albums sorted by name, then the first 5 of those:

>> Album.order(:name).limit(5)
  Album Load (0.4ms)  SELECT  "albums".* FROM "albums"  ORDER BY
  "albums"."name" ASC LIMIT 5
=> #<ActiveRecord::Relation [#<Album id: 9, name: "Blonde [...]]>

offset

With the method offset, you can define the starting position of the method limit.

First, we return the first two records and then the first two records with an offset of 5:

>> Album.limit(2)
  Album Load (1.0ms)  SELECT  "albums".* FROM "albums" LIMIT 2
=> #<ActiveRecord::Relation [#<Album id: 1, [...]>, #<Album id: 2, [...]]>
>> Album.limit(2).offset(5)
  Album Load (0.3ms)  SELECT  "albums".* FROM "albums" LIMIT 2 OFFSET 5
=> #<ActiveRecord::Relation [#<Album id: 6, [...]>, #<Album id: 7, [...]>]>

group

With the method group, you can return the result of a query in grouped form.

Let’s return all albums, grouped by their release_year:

$ rails console
Running via Spring preloader in process 27764
Loading development environment (Rails 5.2.0)
>> Album.group(:release_year)
  Album Load (0.3ms)  SELECT "albums".* FROM "albums" GROUP BY "albums"."release_year"
=> #<ActiveRecord::Relation [#<Album id: 5, name: "Rubber Soul", release_year:
1965, created_at: "2015-12-16 17:45:34", updated_at: "2015-12-16 17:45:34">,
#<Album id: 9, name: "Blonde on Blonde", release_year: 1966, created_at:
"2015-12-16 17:45:34", updated_at: "2015-12-16 17:45:34">, #<Album id: 1,
name: "Sgt. Pepper's Lonely Hearts Club Band", release_year: 1967, created_at:
"2015-12-16 17:45:34", updated_at: "2015-12-16 17:45:34">, #<Album id: 10,
name: "The Beatles", release_year: 1968, created_at: "2015-12-16 17:45:34",
updated_at: "2015-12-16 17:45:34">, #<Album id: 6, name: "What's Going On",
release_year: 1971, created_at: "2015-12-16 17:45:34", updated_at: "2015-12-16
17:45:34">, #<Album id: 7, name: "Exile on Main St.", release_year: 1972,
created_at: "2015-12-16 17:45:34", updated_at: "2015-12-16 17:45:34">, #<Album
id: 8, name: "London Calling", release_year: 1979, created_at: "2015-12-16
17:45:34", updated_at: "2015-12-16 17:45:34">]>
>> exit

pluck

Normally, ActiveRecord pulls all table columns from the database and leaves it up to the programmer to later pick out the components he is interested in. But in case of large amounts of data, it can be useful and above all much quicker to define a specific database field directly for the query. You can do this via the method pluck.

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> Album.where(release_year: 1960..1969).pluck(:name)
   (0.2ms)  SELECT "albums"."name" FROM "albums"
   WHERE ("albums"."release_year" BETWEEN ? AND ?)
   [["release_year", 1960], ["release_year", 1969]]
=> ["Sgt. Pepper's Lonely Hearts Club Band", "Pet Sounds", "Revolver",
"Highway 61 Revisited", "Rubber Soul", "Blonde on Blonde", "The Beatles"]

As a result, pluck returns an array. You can pluck more than one fields too:

>> Album.where(release_year: 1960..1969).pluck(:name, :release_year)
   (0.3ms)  SELECT "albums"."name", "albums"."release_year"
   FROM "albums" WHERE ("albums"."release_year" BETWEEN ? AND ?)
   [["release_year", 1960], ["release_year", 1969]]
=> [["Sgt. Pepper's Lonely Hearts Club Band", 1967],
["Pet Sounds", 1966], ["Revolver", 1966], ["Highway 61 Revisited", 1965],
["Rubber Soul", 1965], ["Blonde on Blonde", 1966], ["The Beatles", 1968]]

select

select works like pluck but returns an ActiveRecord::Relation.

>> Album.where(release_year: 1960..1969).select(:name)
  Album Load (0.2ms)  SELECT "albums"."name" FROM "albums"
  WHERE ("albums"."release_year" BETWEEN 1960 AND 1969)
=> #<ActiveRecord::Relation [#<Album id: nil,
name: "Sgt. Pepper's Lonely Hearts Club Band">,
#<Album id: nil, name: "Pet Sounds">,
#<Album id: nil, name: "Revolver">,
#<Album id: nil, name: "Highway 61 Revisited">,
#<Album id: nil, name: "Rubber Soul">,
#<Album id: nil, name: "Blonde on Blonde">,
#<Album id: nil, name: "The Beatles">]>

first_or_create and first_or_initialize

The methods first_or_create and first_or_initialize are ways to search for a specific entry in your database or create one if the entry doesn’t exist already. Both have to be chained to a where search.

>> Album.where(name: 'Test')
  Album Load (0.2ms)  SELECT "albums".* FROM "albums"
  WHERE "albums"."name" = ?  [["name", "Test"]]
=> #<ActiveRecord::Relation []>
>> test = Album.where(name: 'Test').first_or_create
  Album Load (0.3ms)  SELECT  "albums".* FROM "albums"
  WHERE "albums"."name" = ?  ORDER BY "albums"."id" ASC LIMIT 1
  [["name", "Test"]]
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "albums" ("name", "created_at", "updated_at")
  VALUES (?, ?, ?)  [["name", "Test"],
  ["created_at", "2015-12-16 18:34:35.775645"],
  ["updated_at", "2015-12-16 18:34:35.775645"]]
   (9.2ms)  commit transaction
=> #<Album id: 11, name: "Test", release_year: nil,
created_at: "2015-12-16 18:34:35", updated_at: "2015-12-16 18:34:35">

Calculations

average

With the method average, you can calculate the average of the values in a particular column of the table. Our data material is of course not really suited to this. But as an example, let’s calculate the average release year of all albums and then the same for albums from the 60s:

>> Album.average(:release_year)
   (0.3ms)  SELECT AVG("albums"."release_year") FROM "albums"
=> #<BigDecimal:7fd76fd027a0,'0.19685E4',18(36)>
>> Album.average(:release_year).to_s
   (0.2ms)  SELECT AVG("albums"."release_year") FROM "albums"
=> "1968.5"
>> Album.where( :release_year => 1960..1969 ).average(:release_year)
   (0.1ms)  SELECT AVG("albums"."release_year") FROM "albums" WHERE
   ("albums"."release_year" BETWEEN 1960 AND 1969)
=> #<BigDecimal:7fd76fc908d0,'0.1966142857 14286E4',27(36)>
>> Album.where( :release_year => 1960..1969 ).average(:release_year).to_s
   (0.3ms)  SELECT AVG("albums"."release_year") FROM "albums" WHERE
   ("albums"."release_year" BETWEEN 1960 AND 1969)
=> "1966.14285714286"

count

The name says it all: the method count counts the number of records.

First, we return the number of all albums in the database and then the number of albums from the 60s:

>> Album.count
   (0.1ms)  SELECT COUNT(*) FROM "albums"
=> 11

maximum

With the method maximum, you can output the item with the highest value within a query.

Let’s look for the highest release year:

>> Album.maximum(:release_year)
   (0.2ms)  SELECT MAX("albums"."release_year") FROM "albums"
=> 1979

minimum

With the method minimum, you can output the item with the lowest value within a query.

Let’s find the lowest release year:

>> Album.minimum(:release_year)
   (0.2ms)  SELECT MIN("albums"."release_year") FROM "albums"
=> 1965

sum

With the method sum, you can calculate the sum of all items in a specific column of the database query.

Let’s find the sum of all release years:

>> Album.sum(:release_year)
   (0.2ms)  SELECT SUM("albums"."release_year") FROM "albums"
=> 19685

SQL EXPLAIN

Most SQL databases can provide detailed information on a SQL query with the command EXPLAIN. This does not make much sense for our mini application, but if you are working with a large database one day, then EXPLAIN is a good debugging method, for example to find out where to place an index. SQL EXPLAIN can be called with the method explain (it will be displayed in prettier form if you add a puts):

>> Album.where(release_year: 1960..1969)
  Album Load (0.2ms)  SELECT "albums".* FROM "albums" WHERE
  ("albums"."release_year" BETWEEN 1960 AND 1969)
=> #<ActiveRecord::Relation [#<Album id: 1, [...]>]>
>> Album.where(release_year: 1960..1969).explain
  Album Load (0.3ms)  SELECT "albums".* FROM "albums" WHERE
  ("albums"."release_year" BETWEEN 1960 AND 1969)
=> EXPLAIN for: SELECT "albums".* FROM "albums" WHERE ("albums"."release_year"
BETWEEN 1960 AND 1969)
0|0|0|SCAN TABLE albums

Batches

ActiveRecord stores the results of a query in Memory. With very large tables and results that can become a performance issue. To address this you can use the find_each method which splits up the query into batches with the default size of 1,000 (can be configured with the :batch_size option). Our example Album table is too small to show the effect but the method would be used like this:

>> Album.where(release_year: 1960..1969).find_each do |album|
?>   puts album.name.upcase
>> end
  Album Load (0.2ms)  SELECT  "albums".* FROM "albums" WHERE
  ("albums"."release_year" BETWEEN 1960 AND 1969)  ORDER BY "albums"."id" ASC
  LIMIT 1000
SGT. PEPPER'S LONELY HEARTS CLUB BAND
PET SOUNDS
REVOLVER
HIGHWAY 61 REVISITED
RUBBER SOUL
BLONDE ON BLONDE
THE BEATLES
=> nil

Editing a Record

Adding and searching data is quite nice, but often you want to edit a record. To show how that’s done I use the album database from the section "Searching and Finding with Queries".

Simple Editing

Simple editing of a record takes place with the following steps:

  • Finding the record and creating a corresponding instance

  • Changing the attribute

  • Saving the record via the method ActiveRecord methods save

We are now searching for the album “The Beatles” and changing it’s name to “A Test”:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> beatles_album = Album.where(name: 'The Beatles').first
  Album Load (0.2ms)  SELECT  "albums".* FROM "albums"
  WHERE "albums"."name" = ?  ORDER BY "albums"."id" ASC LIMIT 1
  [["name", "The Beatles"]]
=> #<Album id: 10, name: "The Beatles", release_year: 1968,
created_at: "2015-12-16 17:45:34", updated_at: "2015-12-16 17:45:34">
>> beatles_album.name
=> "The Beatles"
>> beatles_album.name = 'A Test'
=> "A Test"
>> beatles_album.save
   (0.1ms)  begin transaction
  SQL (0.6ms)  UPDATE "albums" SET "name" = ?, "updated_at" = ?
  WHERE "albums"."id" = ?  [["name", "A Test"],
  ["updated_at", "2015-12-16 18:46:03.851575"], ["id", 10]]
   (9.2ms)  commit transaction
=> true
>> exit

Active Model Dirty

ActiveModel::Dirty provides simple mechanisms to track changes of an ActiveRecord Model.

changed?

If you are not sure if a record has been changed and not yet saved, you can check via the method changed?:

>> beatles_album = Album.where(id: 10).first
  Album Load (0.4ms)  SELECT  "albums".* FROM "albums" WHERE "albums"."id" = ?
  ORDER BY "albums"."id" ASC LIMIT 1  [["id", 10]]
=> #<Album id: 10, name: "A Test", release_year: 1968, created_at: "2015-12-16
17:45:34", updated_at: "2015-12-16 18:46:03">
>> beatles_album.changed?
=> false
>> beatles_album.name = 'The Beatles'
=> "The Beatles"
>> beatles_album.changed?
=> true
>> beatles_album.save
   (0.1ms)  begin transaction SQL (0.6ms)  UPDATE "albums" SET "name" = ?,
   "updated_at" = ? WHERE "albums"."id" = ?  [["name", "The Beatles"],
   ["updated_at", "2015-12-16 18:47:26.794527"], ["id", 10]] (9.2ms)  commit
   transaction
=> true
>> beatles_album.changed?
=> false

_changed?

An attribute name followed by _changed? tracks changes to a specific attribute.

>> beatles_album = Album.where(id: 10).first
  Album Load (0.5ms)  SELECT  "albums".* FROM "albums" WHERE "albums"."id" = ? ORDER BY "albums"."id" ASC LIMIT ?  [["id", 10], ["LIMIT", 1]]
=> #<Album id: 10, name: "The Beatles", release_year: 1968, created_at: "2016-01-21 10:15:51", updated_at: "2016-01-21 10:15:51">
>> beatles_album.release_year_changed?
=> false
>> beatles_album.release_year = 1900
=> 1900
>> beatles_album.release_year_changed?
=> true

update

With the method update you can change several attributes of an object in one go and then immediately save them automatically.

Let’s use this method within the example used in the section "Simple Editing":

>> first_album = Album.first
  Album Load (0.1ms)  SELECT  "albums".* FROM "albums" ORDER BY "albums"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Album id: 1, name: "Sgt. Pepper's Lonely Hearts Club Band", release_year: 1967, created_at: "2016-01-21 10:15:51", updated_at: "2016-01-21 10:15:51">
>> first_album.changed?
=> false
>> first_album.update(name: 'Another Test')
   (0.1ms)  begin transaction
  SQL (0.4ms)  UPDATE "albums" SET "name" = ?, "updated_at" = ? WHERE "albums"."id" = ?  [["name", "Another Test"], ["updated_at", 2016-01-21 12:11:27 UTC], ["id", 1]]
   (0.9ms)  commit transaction
=> true
>> first_album.changed?
=> false
>> first_album
=> #<Album id: 1, name: "Another Test", release_year: 1967, created_at: "2016-01-21 10:15:51", updated_at: "2016-01-21 12:11:27">

Locking

There are many ways of locking a database. By default, Rails uses “optimistic locking” of records. To activate locking a model needs to have an attribute with the name lock_version which has to be an integer. To show how it works I’ll create a new Rails project with a Product model. Then I’ll try to change the price of the first Product on two different instances. The second change will raise an ActiveRecord::StaleObjectError.

Example setup:

$ rails new shop
  [...]
$ cd shop
$ rails generate model Product name 'price:decimal{8,2}'
  lock_version:integer
  [...]
$ rails db:migrate
  [...]
$

Raising an ActiveRecord::StaleObjectError:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> Product.create(name: 'Orange', price: 0.5)
   (0.1ms)  begin transaction SQL (0.7ms)  INSERT INTO "products" ("name",
   "price", "created_at", "updated_at", "lock_version")
   VALUES (?, ?, ?, ?, ?) [["name", "Orange"], ["price", 0.5],
   ["created_at", "2015-12-16 19:02:17.338531"],
   ["updated_at", "2015-12-16 19:02:17.338531"],
   ["lock_version", 0]]
   (1.0ms)  commit transaction
=> #<Product id: 1, name: "Orange", price:
#<BigDecimal:7feb59231198,'0.5E0',9(27)>, lock_version: 0, created_at:
"2015-12-16 19:02:17", updated_at: "2015-12-16 19:02:17">
>> a = Product.first
  Product Load (0.4ms)  SELECT  "products".* FROM "products"  ORDER BY
  "products"."id" ASC LIMIT 1
=> #<Product id: 1, name: "Orange", price:
#<BigDecimal:7feb5918a870,'0.5E0',9(27)>, lock_version: 0, created_at:
"2015-12-16 19:02:17", updated_at: "2015-12-16 19:02:17">
>> b = Product.first
  Product Load (0.3ms)  SELECT  "products".* FROM "products"  ORDER BY
  "products"."id" ASC LIMIT 1 => #<Product id: 1, name: "Orange", price:
  #<BigDecimal:7feb59172d60,'0.5E0',9(27)>, lock_version: 0, created_at:
  "2015-12-16 19:02:17", updated_at: "2015-12-16 19:02:17">
>> a.price = 0.6
=> 0.6
>> a.save
   (0.1ms)  begin transaction
  SQL (0.4ms)  UPDATE "products" SET "price" = 0.6, "updated_at" = '2015-12-16
  19:02:59.514736', "lock_version" = 1 WHERE "products"."id" = ? AND
  "products"."lock_version" = ?  [["id", 1], ["lock_version", 0]]
   (9.1ms)  commit transaction
=> true
>> b.price = 0.7
=> 0.7
>> b.save
   (0.1ms)  begin transaction
  SQL (0.3ms)  UPDATE "products" SET "price" = 0.7, "updated_at" = '2015-12-16
  19:03:08.408511', "lock_version" = 1 WHERE "products"."id" = ? AND
  "products"."lock_version" = ?  [["id", 1], ["lock_version", 0]]
   (0.1ms)  rollback transaction
ActiveRecord::StaleObjectError: Attempted to update a stale object: Product
[...]
>> exit

You have to deal with the conflict by rescuing the exception and fix the conflict depending on your business logic.

Important
Please make sure to add a lock_version hidden field in your forms when using this mechanism with a WebGUI.

has_many – 1:n Association

In order to explain has_many, let’s create a food store application. We create a Category and a Product model. A Product belongs to a Category. It’s a 1:n association (one-to-many association).

Note
Associations are also sometimes referred to as relations or relationships.

First, we create a Rails application:

$ rails new food_store
  [...]
$ cd food_store

Now we create the model for the categories:

$ rails generate model Category name
  [...]
$

And finally, we create the database table for the Product. In this, we need an assignment field to the category table. This foreign key is always set by default as name of the referenced object (here: category) with an attached _id. We could run the command rails generate model product name price:integer category_id:integer but there is a better way of doing it:

$ rails generate model product name price:integer category:references
Running via Spring preloader in process 35988
      invoke  active_record
      create    db/migrate/20170323074157_create_products.rb
      create    app/models/product.rb
      invoke    test_unit
      create      test/models/product_test.rb
      create      test/fixtures/products.yml

Why is it better? Because it creates a different kind of migration which includes a foreign key optimization:

db/migrate/20170323074157_create_products.rb
class CreateProducts < ActiveRecord::Migration[5.1]
  def change
    create_table :products do |t|
      t.string :name
      t.integer :price
      t.references :category, foreign_key: true

      t.timestamps
    end
  end
end

Then execute a rails db:migrate so that the database tables are actually created:

$ rails db:migrate

Let’s have a look at this on the console:

$ rails console
Running via Spring preloader in process 36245
Loading development environment (Rails 5.2.0)
>> Category.column_names
=> ["id", "name", "created_at", "updated_at"]
>> Product.column_names
=> ["id", "name", "price", "category_id", "created_at", "updated_at"]
>> exit

The two database tables are set up and can be used with ActiveRecord. And because we used category:references it automatically inserted the belongs to relationship into the Product model:

app/models/product.rb
class Product < ApplicationRecord
  belongs_to :category
end

But we have to add the "has many" part manually in the Category model:

app/models/category.rb
class Category < ApplicationRecord
  has_many :products
end

That’s all we need to do to tell ActiveRecord about the 1:n relation. These two simple definitions form the basis for a good deal of ActiveRecord magic. It will generate a bunch of cool new methods for us to link both models.

Creating Records

In this example, we want to save a record for the product "Apple" which belongs to the category "Fruits". Fire up your console and follow my lead.

create

First create a new category for the fruits:

$ rails console
Running via Spring preloader in process 37142
Loading development environment (Rails 5.2.0)
>> fruits = Category.create(name: "Fruits")
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "categories"
  ("name", "created_at", "updated_at") VALUES (?, ?, ?)
  [["name", "Fruits"], ["created_at", "2017-03-23 07:55:13.482884"],
  ["updated_at", "2017-03-23 07:55:13.482884"]]
   (2.3ms)  commit transaction
=> #<Category id: 1, name: "Fruits",
created_at: "2017-03-23 07:55:13",
updated_at: "2017-03-23 07:55:13">

Because the Category model has a has_many :products definition is provides a products method which we can use to get all products of a given category:

>> fruits.products
  Product Load (0.2ms)  SELECT "products".* FROM "products" WHERE "products"."category_id" = ?  [["category_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy []>

But it gets even better. We can chain the create method after the fruits.products to actually create a new product which has the correct category_id:

>> apple = fruits.products.create(name: "Apple", price: 1)
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "products"
  ("name", "price", "category_id", "created_at", "updated_at")
  VALUES (?, ?, ?, ?, ?)  [["name", "Apple"], ["price", 1],
  ["category_id", 1], ["created_at", "2017-03-23 08:00:39.595699"],
  ["updated_at", "2017-03-23 08:00:39.595699"]]
   (3.4ms)  commit transaction
=> #<Product id: 1, name: "Apple", price: 1, category_id: 1,
created_at: "2017-03-23 08:00:39", updated_at: "2017-03-23 08:00:39">

Of course this can be done manually too:

>> pineapple = Product.create(name: "Pineapple", price: 2, category_id: 1)
   (0.1ms)  begin transaction
  Category Load (0.3ms)  SELECT  "categories".* FROM "categories"
  WHERE "categories"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  SQL (0.4ms)  INSERT INTO "products"
  ("name", "price", "category_id", "created_at", "updated_at")
  VALUES (?, ?, ?, ?, ?)  [["name", "Pineapple"], ["price", 2],
  ["category_id", 1], ["created_at", "2017-03-23 08:04:16.382548"],
  ["updated_at", "2017-03-23 08:04:16.382548"]]
   (2.5ms)  commit transaction
=> #<Product id: 2, name: "Pineapple", price: 2, category_id: 1,
created_at: "2017-03-23 08:04:16", updated_at: "2017-03-23 08:04:16">

If you don’t want to chain create after fruits.products you can also create a new Product and fill in the category_id like this:

>> orange = Product.create(name: "Orange", price: 1, category: fruits)
   (0.1ms)  begin transaction
  SQL (1.3ms)  INSERT INTO "products"
  ("name", "price", "category_id", "created_at", "updated_at")
  VALUES (?, ?, ?, ?, ?)  [["name", "Orange"], ["price", 1],
  ["category_id", 1], ["created_at", "2017-03-23 08:15:37.575534"],
  ["updated_at", "2017-03-23 08:15:37.575534"]]
   (2.4ms)  commit transaction
=> #<Product id: 3, name: "Orange", price: 1, category_id: 1,
created_at: "2017-03-23 08:15:37", updated_at: "2017-03-23 08:15:37">

I think the chained version is the best but who am I to judge.

Now we have three products which belong to fruits:

>> fruits.products.count
   (0.2ms)  SELECT COUNT(*) FROM "products"
   WHERE "products"."category_id" = ?  [["category_id", 1]]
=> 3
>> exit

build

The method build resembles create. But the record is not saved. This only happens after a save:

$ rails console
Running via Spring preloader in process 40092
Loading development environment (Rails 5.2.0)
>> fruits = Category.where(name: "Fruits").first
  Category Load (0.1ms)  SELECT  "categories".* FROM "categories"
  WHERE "categories"."name" = ? ORDER BY "categories"."id" ASC LIMIT ?
  [["name", "Fruits"], ["LIMIT", 1]]
=> #<Category id: 1, name: "Fruits", created_at: "2017-03-23 07:55:13",
updated_at: "2017-03-23 07:55:13">
>> cherry = fruits.products.build(name: "Cherry", price: 1)
=> #<Product id: nil, name: "Cherry", price: 1, category_id: 1,
created_at: nil, updated_at: nil>
>> cherry.save
   (0.1ms)  begin transaction
  SQL (1.9ms)  INSERT INTO "products" ("name", "price", "category_id",
  "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["name", "Cherry"],
  ["price", 1], ["category_id", 1],
  ["created_at", "2017-03-23 08:22:48.044002"],
  ["updated_at", "2017-03-23 08:22:48.044002"]]
   (2.6ms)  commit transaction
=> true
>> exit
Warning

When using create and build, you of course have to observe logical dependencies, otherwise there will be an error. For example, you cannot chain two build methods. Example:

>> Category.build(name: "Vegetable").products.build(name: "Potato")
NoMethodError: undefined method `build' for #<Class:0x007f8d7c72c020>
	from (irb):3

Accessing Records

First we need example data. Please populate the file db/seeds.rb with the following content:

db/seeds.rb
fruits = Category.create(name: "Fruits")
vegetables = Category.create(name: "Vegetables")
jams = Category.create(name: "Jams")

fruits.products.create(name: "Apple", price: 1)
fruits.products.create(name: "Banana", price: 2)
fruits.products.create(name: "Pineapple", price: 3)
fruits.products.create(name: "Raspberry", price: 1)
fruits.products.create(name: "Strawberry", price: 1)

vegetables.products.create(name: "Potato", price: 2)
vegetables.products.create(name: "Carrot", price: 1)
vegetables.products.create(name: "Broccoli", price: 2)
vegetables.products.create(name: "Cauliflower", price: 1)

jams.products.create(name: "Strawberry", price: 1)
jams.products.create(name: "Raspberry", price: 1)

Now drop the database and refill it with the db/seeds.rb:

$ rails db:reset

We already know how to access the products of a given category:

$ rails console
Running via Spring preloader in process 45107
Loading development environment (Rails 5.2.0)
>> Category.first.products.count
  Category Load (0.1ms)  SELECT  "categories".* FROM "categories"
  ORDER BY "categories"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.1ms)  SELECT COUNT(*) FROM "products"
   WHERE "products"."category_id" = ?  [["category_id", 1]]
=> 5

You can access the records simply via the plural form of the n model. Hmmmm, maybe it also works the other way round? Let’s try the singluar of the 1 model:

>> Product.first.category
  Product Load (0.3ms)  SELECT  "products".* FROM "products"
  ORDER BY "products"."id" ASC LIMIT ?  [["LIMIT", 1]]
  Category Load (0.2ms)  SELECT  "categories".* FROM "categories"
  WHERE "categories"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<Category id: 1, name: "Fruits", created_at: "2017-03-23 14:23:16",
updated_at: "2017-03-23 14:23:16">
>> exit

Bingo! Accessing the associated Category class is also very easy. And as it’s only a single record (belongs_to), the singular form is used in this case.

Note
If there was no product for a category, the result would be an empty array. If no category is associated with an product, then ActiveRecord outputs the value nil as Category.

Searching For Records

And off we go. First we check how many products are in the database:

$ rails console
Running via Spring preloader in process 45328
Loading development environment (Rails 5.2.0)
>> Product.count
   (0.1ms)  SELECT COUNT(*) FROM "products"
=> 11

And how many categories?

>> Category.count
   (0.1ms)  SELECT COUNT(*) FROM "categories"
=> 3

joins

To find all categories that have at least one product with the name "Strawberry" we use a join.

>> Category.joins(:products).where(:products => {name: "Strawberry"})
  Category Load (0.2ms)  SELECT "categories".* FROM "categories"
  INNER JOIN "products" ON "products"."category_id" = "categories"."id"
  WHERE "products"."name" = ?  [["name", "Strawberry"]]
=> #<ActiveRecord::Relation [#<Category id: 1, name: "Fruits",
created_at: "2017-03-23 14:33:14", updated_at: "2017-03-23 14:33:14">,
#<Category id: 3, name: "Jams", created_at: "2017-03-23 14:33:14",
updated_at: "2017-03-23 14:33:14">]>
>>

The database contains two categories with a product 'Strawberry'. In the SQL, you can see that the method joins executes an INNER JOIN.

Of course, we can also do it the other way round. We could search for the products with the category "Jams":

>> Product.joins(:category).where(:categories => {name: "Jams"})
  Product Load (0.4ms)  SELECT "products".* FROM "products"
  INNER JOIN "categories" ON "categories"."id" = "products"."category_id"
  WHERE "categories"."name" = ?  [["name", "Jams"]]
=> #<ActiveRecord::Relation [#<Product id: 10, name: "Strawberry",
price: 1, category_id: 3, created_at: "2017-03-23 14:33:15",
updated_at: "2017-03-23 14:33:15">, #<Product id: 11, name: "Raspberry",
price: 1, category_id: 3, created_at: "2017-03-23 14:33:15",
updated_at: "2017-03-23 14:33:15">]>

includes

includes is very similar to the method joins (see joins). Again, you can use it to search within a 1:n association. Let’s repeat the searches we just did with includes instead of joins:

>> Category.includes(:products).where(:products => {name: "Strawberry"})
  SQL (0.4ms)  SELECT "categories"."id" AS t0_r0, "categories"."name"
  AS t0_r1, "categories"."created_at" AS t0_r2, "categories"."updated_at"
  AS t0_r3, "products"."id" AS t1_r0, "products"."name" AS t1_r1,
  "products"."price" AS t1_r2, "products"."category_id" AS t1_r3,
  "products"."created_at" AS t1_r4, "products"."updated_at" AS t1_r5
  FROM "categories" LEFT OUTER JOIN "products" ON
  "products"."category_id" = "categories"."id" WHERE
  "products"."name" = ?  [["name", "Strawberry"]]
=> #<ActiveRecord::Relation [#<Category id: 1, name: "Fruits",
created_at: "2017-03-23 14:33:14", updated_at: "2017-03-23 14:33:14">,
#<Category id: 3, name: "Jams", created_at: "2017-03-23 14:33:14",
updated_at: "2017-03-23 14:33:14">]>
>> exit

In the console output, you can see that the SQL code is different from the joins query.

joins only reads in the Category records and includes also reads the associated Product records.

joins vs. includes

Why would you want to use includes at all? Well, if you already know before the query that you will later need all products data, then it makes sense to use includes, because then you only need one database query. That is a lot faster than starting a seperate query for each n.

In that case, would it not be better to always work with includes? No, it depends on the specific case. When you are using includes, a lot more data is transported initially. This has to be cached and processed by ActiveRecord, which takes longer and requires more resources.

delete and destroy

With the methods destroy, destroy_all, delete and delete_all you can delete records, as described in "Delete/Destroy a Record". In the context of has_many, this means that you can delete the Product records associated with a Category in one go:

$ rails console
Running via Spring preloader in process 46835
Loading development environment (Rails 5.2.0)
>> Category.first.products.destroy_all
  Category Load (0.3ms)  SELECT  "categories".* FROM "categories"
  ORDER BY "categories"."id" ASC LIMIT ?  [["LIMIT", 1]]
  Product Load (0.2ms)  SELECT "products".* FROM "products"
  WHERE "products"."category_id" = ?  [["category_id", 1]]
   (0.1ms)  begin transaction
  SQL (0.4ms)  DELETE FROM "products" WHERE "products"."id" = ?  [["id", 1]]
  SQL (0.1ms)  DELETE FROM "products" WHERE "products"."id" = ?  [["id", 2]]
  SQL (0.1ms)  DELETE FROM "products" WHERE "products"."id" = ?  [["id", 3]]
  SQL (0.1ms)  DELETE FROM "products" WHERE "products"."id" = ?  [["id", 4]]
  SQL (0.1ms)  DELETE FROM "products" WHERE "products"."id" = ?  [["id", 5]]
   (2.8ms)  commit transaction
=> [#<Product id: 1, name: "Apple", price: 1, category_id: 1,
created_at: "2017-03-23 14:33:15", updated_at: "2017-03-23 14:33:15">,
#<Product id: 2, name: "Banana", price: 2, category_id: 1,
created_at: "2017-03-23 14:33:15", updated_at: "2017-03-23 14:33:15">,
#<Product id: 3, name: "Pineapple", price: 3, category_id: 1,
created_at: "2017-03-23 14:33:15", updated_at: "2017-03-23 14:33:15">,
#<Product id: 4, name: "Raspberry", price: 1, category_id: 1,
created_at: "2017-03-23 14:33:15", updated_at: "2017-03-23 14:33:15">,
#<Product id: 5, name: "Strawberry", price: 1, category_id: 1,
created_at: "2017-03-23 14:33:15", updated_at: "2017-03-23 14:33:15">]
>> Category.first.products.count
  Category Load (0.2ms)  SELECT  "categories".* FROM "categories"
  ORDER BY "categories"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.3ms)  SELECT COUNT(*) FROM "products"
   WHERE "products"."category_id" = ?  [["category_id", 1]]
=> 0
>> exit

Options

I can’t comment on all possible options at this point. But I’d like to show you the most often used ones. For all others, please refer to the Ruby on Rails documentation that you can find on the Internet at http://rails.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html.

belongs_to

The most important option for belongs_to is.

touch: true

It automatically sets the field updated_at of the entry in the table Category to the current time when an Product is edited. In the app/models/product.rb, it would look like this:

app/models/product.rb
class Product < ApplicationRecord
  belongs_to :category, touch: true
end

has_many

The most important options for has_many are:

dependent: :destroy

If a category is removed, then it usually makes sense to also automatically remove all products dependent on this category. This can be done via :dependent ⇒ :destroy in the app/models/category.rb:

app/models/category.rb
class Category < ApplicationRecord
  has_many :products, dependent: :destroy
end

In the following example, we destroy the last category in the database table. All products of this category are also automatically destroyed:

$ rails console
Running via Spring preloader in process 47105
Loading development environment (Rails 5.2.0)
>> Product.count
   (0.1ms)  SELECT COUNT(*) FROM "products"
=> 6
>> Category.last.destroy
  Category Load (0.1ms)  SELECT  "categories".* FROM "categories"
  ORDER BY "categories"."id" DESC LIMIT ?  [["LIMIT", 1]]
   (0.1ms)  begin transaction
  Product Load (0.2ms)  SELECT "products".* FROM "products"
  WHERE "products"."category_id" = ?  [["category_id", 3]]
  SQL (0.6ms)  DELETE FROM "products" WHERE "products"."id" = ?  [["id", 10]]
  SQL (0.1ms)  DELETE FROM "products" WHERE "products"."id" = ?  [["id", 11]]
  SQL (0.1ms)  DELETE FROM "categories" WHERE "categories"."id" = ?  [["id", 3]]
   (4.7ms)  commit transaction
=> #<Category id: 3, name: "Jams", created_at: "2017-03-23 14:33:14",
updated_at: "2017-03-23 15:02:08">
>> Product.count
   (0.2ms)  SELECT COUNT(*) FROM "products"
=> 4
>> exit
Important
Please always remember the difference between the methods destroy (see "destroy") and delete (see the "delete"). This association only works with the method destroy.

Many-to-Many – n:n Association

Up to now, we have always associated a database table directly with another table. For many-to-many, we will associate two tables via a third table. As example for this kind of relation, we use an order in an online shop. In this type of shop system, a Product can appear in several orders (Order) and at the same time an order can contain several products. This is referred to as many-to-many. Let’s recreate this scenario with code.

Preparation

Create the shop application:

$ rails new online_shop
  [...]
$ cd online_shop

A model for products:

$ rails generate model product name 'price:decimal{7,2}'

A model for an order:

$ rails generate model order delivery_address

And a model for individual items of an order:

$ rails generate model line_item order:references \
product:references quantity:integer

Then, create the database:

$ rails db:migrate

And setup some example data:

db/seeds.rb
Product.create(name: 'Milk', price: 0.45)
Product.create(name: 'Butter', price: 0.75)
Product.create(name: 'Flour', price: 0.45)
Product.create(name: 'Eggs', price: 1.45)
$ rails db:seed

The Association

An order (Order) consists of one or several items (LineItem). This LineItem consists of the order_id, a product_id and the number of items ordered (quantity). The individual product is defined in the product database (Product).

Associating the models happens as always in the directory app/models. First, in the file app/models/order.rb:

app/models/order.rb
class Order < ApplicationRecord
  has_many :line_items
  has_many :products, through: :line_items
end

Then in the counterpart in the file app/models/product.rb:

app/models/product.rb
class Product < ApplicationRecord
  has_many :line_items
  has_many :orders, through: :line_items
end

The file app/models/line_item.rb: has been filled by the generator:

app/models/line_item.rb
class LineItem < ApplicationRecord
  belongs_to :order
  belongs_to :product
end

The Association Works Transparent

As we implement the associations via has_many, most things will already be familiar to you from the section "has_many - 1:n Association". I am going to show a few examples.

We create a new Order object:

$ rails console
Running via Spring preloader in process 48290
Loading development environment (Rails 5.2.0)
>> order = Order.new(delivery_address: '123 Acme Street')
=> #<Order id: nil, delivery_address: "123 Acme Street",
created_at: nil, updated_at: nil>

Logically, this new order does not yet contain any products:

>> order.products.count
=> 0

As often, there are several ways of adding products to the order. The simplest way: as the products are integrated as array, you can simply insert them as elements of an array:

>> order.products << Product.first
  Product Load (0.5ms)  SELECT  "products".* FROM "products"
  ORDER BY "products"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<ActiveRecord::Associations::CollectionProxy
[#<Product id: 1, name: "Milk", price: 0.45e0,
created_at: "2017-03-23 15:14:22",
updated_at: "2017-03-23 15:14:22">]>

But if the customer wants to buy three times milk instead of one, we need to enter it in the LineItem (in the linking element) table. ActiveRecord already build an object for us:

>> order.line_items
=> #<ActiveRecord::Associations::CollectionProxy
[#<LineItem id: nil, order_id: nil, product_id: 1, quantity: nil,
created_at: nil, updated_at: nil>]>

And we have access to it. So we can change the quanitity:

>> order.line_items.first.quantity = 3
=> 3

But neither the order nor any other object is yet saved in the database. We have to call the save method to do this:

>> order.save
   (0.1ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "orders" ("delivery_address", "created_at",
  "updated_at") VALUES (?, ?, ?)  [["delivery_address", "123 Acme Street"],
  ["created_at", "2017-03-23 15:22:48.536239"],
  ["updated_at", "2017-03-23 15:22:48.536239"]]
  SQL (0.2ms)  INSERT INTO "line_items" ("order_id", "product_id",
  "quantity", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)
  [["order_id", 2], ["product_id", 1], ["quantity", 3],
  ["created_at", "2017-03-23 15:22:48.539047"],
  ["updated_at", "2017-03-23 15:22:48.539047"]]
   (2.1ms)  commit transaction
=> true

Alternatively, we can also buy butter twice directly by adding a LineItem:

>> order.line_items.create(product: Product.second, quantity: 2)
  Product Load (0.2ms)  SELECT  "products".* FROM "products"
  ORDER BY "products"."id" ASC LIMIT ? OFFSET ?  [["LIMIT", 1],
  ["OFFSET", 1]]
   (0.1ms)  begin transaction
  SQL (2.1ms)  INSERT INTO "line_items" ("order_id", "product_id",
  "quantity", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)
  [["order_id", 2], ["product_id", 2], ["quantity", 2],
  ["created_at", "2017-03-23 15:25:32.991756"],
  ["updated_at", "2017-03-23 15:25:32.991756"]]
   (2.2ms)  commit transaction
=> #<LineItem id: 3, order_id: 2, product_id: 2, quantity: 2,
created_at: "2017-03-23 15:25:32", updated_at: "2017-03-23 15:25:32">

All searches and queries (including joins and includes) work for you as a Rails programmer the same as without the has_many through. ActiveRecord takes care of the details.

Polymorphic Associations

Already the word "polymorphic" will probably make you tense up. What can it mean? Here is what the website http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html tells us: “Polymorphic associations on models are not restricted on what types of models they can be associated with.” Well, there you go - as clear as mud! ;-)

I am showing you an example in which we create a Car`model and a `Bike model.. To describe a car or bike, we use a Tag model. A car and a bike can have any number of tags.

The application:

$ rails new bike_car_example
  [...]
$ cd bike_car_example

Now the three required models:

$ rails generate model Car name
  [...]
$ rails generate model Bike name
  [...]
$ rails generate model Tag name taggable:references{polymorphic}
  [...]
$ rails db:migrate
  [...]

Car and Bike are clear. For Tag we use the migration shortcut taggable:references{polymorphic} to generate the fields taggable_type and taggable_id, to give ActiveRecord an opportunity to save the assignment for the polymorphic association. We have to enter it accordingly in the model.

The model generator already filed the app/models/tag.rb file with the configuration for the polymorphic association:

app/models/tag.rb
class Tag < ApplicationRecord
  belongs_to :taggable, polymorphic: true
end

For the other models we have to add the polymorphic association manually:

app/models/car.rb
class Car < ApplicationRecord
  has_many :tags, as: :taggable
end
app/models/bike.rb
class Bike < ApplicationRecord
  has_many :tags, as: :taggable
end

For Car and Bike we use an additional :as: :taggable when defining has_many. For Tag we use belongs_to :taggable, polymorphic: true to indicate the polymorphic association to ActiveRecord.

Tip
The suffix “able” in the name “taggable” is commonly used in Rails, but not obligatory. For creating the association we now not only need the ID of the entry, but also need to know which model it actually is. So the term “taggable_type” makes sense.

Let’s go into the console and create a car and a bike:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> beetle = Car.create(name: 'Beetle')
   (0.1ms)  begin transaction
  SQL (0.8ms)  INSERT INTO "cars" ("name", "created_at", "updated_at") VALUES
  (?, ?, ?)  [["name", "Beetle"], ["created_at", "2015-12-17
  13:39:54.793336"], ["updated_at", "2015-12-17 13:39:54.793336"]]
   (0.8ms)  commit transaction
=> #<Car id: 1, name: "Beetle", created_at: "2015-12-17 13:39:54", updated_at:
"2015-12-17 13:39:54">
>> mountainbike = Bike.create(name: 'Mountainbike')
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "bikes" ("name", "created_at", "updated_at") VALUES
  (?, ?, ?)  [["name", "Mountainbike"], ["created_at", "2015-12-17
  13:39:55.896512"], ["updated_at", "2015-12-17 13:39:55.896512"]]
   (9.0ms)  commit transaction
=> #<Bike id: 1, name: "Mountainbike", created_at: "2015-12-17 13:39:55",
updated_at: "2015-12-17 13:39:55">

Now we define for each a tag with the color of the corresponding object:

>> beetle.tags.create(name: 'blue')
   (0.1ms)  begin transaction
  SQL (1.0ms)  INSERT INTO "tags" ("name", "taggable_id", "taggable_type",
  "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["name", "blue"],
  ["taggable_id", 1], ["taggable_type", "Car"], ["created_at", "2015-12-17
  13:41:04.984444"], ["updated_at", "2015-12-17 13:41:04.984444"]]
   (0.9ms)  commit transaction
=> #<Tag id: 1, name: "blue", taggable_id: 1, taggable_type: "Car",
created_at: "2015-12-17 13:41:04", updated_at: "2015-12-17 13:41:04">
>> mountainbike.tags.create(name: 'black')
   (0.1ms)  begin transaction
  SQL (0.7ms)  INSERT INTO "tags" ("name", "taggable_id", "taggable_type",
  "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["name", "black"],
  ["taggable_id", 1], ["taggable_type", "Bike"], ["created_at", "2015-12-17
  13:41:17.315318"], ["updated_at", "2015-12-17 13:41:17.315318"]]
   (8.2ms)  commit transaction
=> #<Tag id: 2, name: "black", taggable_id: 1, taggable_type: "Bike",
created_at: "2015-12-17 13:41:17", updated_at: "2015-12-17 13:41:17">

For the beetle, we add another Tag:

>> beetle.tags.create(name: 'Automatic')
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "tags" ("name", "taggable_id", "taggable_type",
  "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["name", "Automatic"],
  ["taggable_id", 1], ["taggable_type", "Car"], ["created_at", "2015-12-17
  13:41:51.042746"], ["updated_at", "2015-12-17 13:41:51.042746"]]
   (9.2ms)  commit transaction
=> #<Tag id: 3, name: "Automatic", taggable_id: 1, taggable_type: "Car",
created_at: "2015-12-17 13:41:51", updated_at: "2015-12-17 13:41:51">

Let’s have a look at all Tag items:

>> Tag.all
  Tag Load (0.3ms)  SELECT "tags".* FROM "tags"
=> #<ActiveRecord::Relation [#<Tag id: 1, name: "blue", taggable_id: 1,
taggable_type: "Car", created_at: "2015-12-17 13:41:04", updated_at:
"2015-12-17 13:41:04">, #<Tag id: 2, name: "black", taggable_id: 1,
taggable_type: "Bike", created_at: "2015-12-17 13:41:17", updated_at:
"2015-12-17 13:41:17">, #<Tag id: 3, name: "Automatic", taggable_id: 1,
taggable_type: "Car", created_at: "2015-12-17 13:41:51", updated_at:
"2015-12-17 13:41:51">]>

And now all tags of the beetle:

>> beetle.tags
  Tag Load (0.3ms)  SELECT "tags".* FROM "tags" WHERE "tags"."taggable_id" = ?
  AND "tags"."taggable_type" = ?  [["taggable_id", 1], ["taggable_type",
  "Car"]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Tag id: 1, name: "blue",
taggable_id: 1, taggable_type: "Car", created_at: "2015-12-17 13:41:04",
updated_at: "2015-12-17 13:41:04">, #<Tag id: 3, name: "Automatic",
taggable_id: 1, taggable_type: "Car", created_at: "2015-12-17 13:41:51",
updated_at: "2015-12-17 13:41:51">]>

Of course you can also check which object the last Tag belongs to:

>> Tag.last.taggable
  Tag Load (0.3ms)  SELECT  "tags".* FROM "tags"  ORDER BY "tags"."id" DESC
  LIMIT 1
  Car Load (0.4ms)  SELECT  "cars".* FROM "cars" WHERE "cars"."id" = ? LIMIT 1
  [["id", 1]]
=> #<Car id: 1, name: "Beetle", created_at: "2015-12-17 13:39:54", updated_at:
"2015-12-17 13:39:54">
>> exit

Polymorphic associations are always useful if you want to normalize the database structure. In this example, we could also have defined a model CarTag and BikeTag, but as Tag is the same for both, a polymorphic association makes more sense in this case.

Options

Polymorphic associations can be configured with the same options as a normal has_many association.

Delete/Destroy a Record

To remove a database record, you can use the methods destroy and delete. It’s quite easy to confuse these two terms, but they are different and after a while you get used to it.

As an example, we use the following Rails application:

$ rails new bookshelf
  [...]
$ cd bookshelf
$ rails generate model book title
  [...]
$ rails generate model author book:references first_name last_name
  [...]
$ rails db:migrate
  [...]
$
app/models/book.rb
class Book < ApplicationRecord
  has_many :authors, dependent: :destroy
end
app/models/author.rb
class Author < ApplicationRecord
  belongs_to :book
end

destroy

With destroy you can remove a record and any existing dependencies are also taken into account (see for example :dependent ⇒ :destroy). Simply put: to be on the safe side, it’s better to use destroy because then the Rails system does more for you.

Let’s create a record and then destroy it again:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> book = Book.create(title: 'Homo faber')
   (0.1ms)  begin transaction
  SQL (0.7ms)  INSERT INTO "books" ("title", "created_at", "updated_at")
  VALUES (?, ?, ?)  [["title", "Homo faber"], ["created_at", "2015-12-17
  13:49:58.092997"], ["updated_at", "2015-12-17 13:49:58.092997"]]
   (9.0ms)  commit transaction
=> #<Book id: 1, title: "Homo faber", created_at: "2015-12-17 13:49:58",
updated_at: "2015-12-17 13:49:58">
>> Book.count
   (0.3ms)  SELECT COUNT(*) FROM "books"
=> 1
>> book.destroy
   (0.1ms)  begin transaction
  Author Load (0.1ms)  SELECT "authors".* FROM "authors" WHERE
  "authors"."book_id" = ?  [["book_id", 1]]
  SQL (0.3ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 1]]
   (9.0ms)  commit transaction
=> #<Book id: 1, title: "Homo faber", created_at: "2015-12-17 13:49:58",
updated_at: "2015-12-17 13:49:58">
>> Book.count
   (0.5ms)  SELECT COUNT(*) FROM "books"
=> 0

As we are using the option dependent: :destroy in the Book model, we can also automatically remove all authors:

>> Book.create(title: 'Homo faber').authors.create(first_name: 'Max',
   last_name: 'Frisch')
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "books" ("title", "created_at", "updated_at")
  VALUES (?, ?, ?)  [["title", "Homo faber"], ["created_at", "2015-12-17
  13:50:43.062148"], ["updated_at", "2015-12-17 13:50:43.062148"]]
   (9.1ms)  commit transaction
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "authors" ("first_name", "last_name", "book_id",
  "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["first_name", "Max"],
  ["last_name", "Frisch"], ["book_id", 2], ["created_at", "2015-12-17
  13:50:43.083211"], ["updated_at", "2015-12-17 13:50:43.083211"]]
   (0.9ms)  commit transaction
=> #<Author id: 1, book_id: 2, first_name: "Max", last_name: "Frisch",
created_at: "2015-12-17 13:50:43", updated_at: "2015-12-17 13:50:43">
>> Author.count
   (0.2ms)  SELECT COUNT(*) FROM "authors"
=> 1
>> Book.first.destroy
  Book Load (0.3ms)  SELECT  "books".* FROM "books"  ORDER BY "books"."id" ASC
  LIMIT 1
   (0.1ms)  begin transaction
  Author Load (0.1ms)  SELECT "authors".* FROM "authors" WHERE
  "authors"."book_id" = ?  [["book_id", 2]]
  SQL (0.3ms)  DELETE FROM "authors" WHERE "authors"."id" = ?  [["id", 1]]
  SQL (0.1ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 2]]
   (9.1ms)  commit transaction
=> #<Book id: 2, title: "Homo faber", created_at: "2015-12-17 13:50:43",
updated_at: "2015-12-17 13:50:43">
>> Author.count
   (0.2ms)  SELECT COUNT(*) FROM "authors"
=> 0

When removing records, please always consider the difference between the content of the database table and the value of the currently removed object. The instance is frozen after removing the database field. So it is no longer in the database, but still present in the program, yet it can no longer be modified there. It is read-only. To check, you can use the method frozen?:

>> book = Book.create(title: 'Homo faber')
   (0.2ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "books" ("title", "created_at", "updated_at")
  VALUES (?, ?, ?)  [["title", "Homo faber"], ["created_at", "2015-12-17
  13:51:41.460050"], ["updated_at", "2015-12-17 13:51:41.460050"]]
   (8.9ms)  commit transaction
=> #<Book id: 3, title: "Homo faber", created_at: "2015-12-17 13:51:41",
updated_at: "2015-12-17 13:51:41">
>> book.destroy
   (0.1ms)  begin transaction
  Author Load (0.2ms)  SELECT "authors".* FROM "authors" WHERE
  "authors"."book_id" = ?  [["book_id", 3]]
  SQL (0.5ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 3]]
   (9.2ms)  commit transaction
=> #<Book id: 3, title: "Homo faber", created_at: "2015-12-17 13:51:41",
updated_at: "2015-12-17 13:51:41">
>> Book.count
   (0.2ms)  SELECT COUNT(*) FROM "books"
=> 0
>> book
=> #<Book id: 3, title: "Homo faber", created_at: "2015-12-17 13:51:41",
updated_at: "2015-12-17 13:51:41">
>> book.frozen?
=> true

The record has been removed from the database, but the object with all its data is still present in the running Ruby program. So could we then revive the entire record? The answer is yes, but it will then be a new record:

>> Book.create(title: book.title)
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "books" ("title", "created_at", "updated_at")
  VALUES (?, ?, ?)  [["title", "Homo faber"], ["created_at", "2015-12-17
  13:52:51.438501"], ["updated_at", "2015-12-17 13:52:51.438501"]]
   (8.7ms)  commit transaction
=> #<Book id: 4, title: "Homo faber", created_at: "2015-12-17 13:52:51",
updated_at: "2015-12-17 13:52:51">
>> exit

delete

With delete you can remove a record directly from the database. Any dependencies to other records in the model are not taken into account. The method delete only deletes that one row in the database and nothing else.

Let’s create a book with one author and then remove the book with delete:

$ rails db:reset
  [...]
$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> Book.create(title: 'Homo faber').authors.create(first_name: 'Max',
   last_name: 'Frisch')
   (0.5ms)  begin transaction
   [...]
   (0.8ms)  commit transaction
=> #<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch",
created_at: "2015-12-17 13:54:46", updated_at: "2015-12-17 13:54:46">
>> Author.count
   (0.2ms)  SELECT COUNT(*) FROM "authors"
=> 1
>> Book.last.delete
  Book Load (0.2ms)  SELECT  "books".* FROM "books"  ORDER BY "books"."id"
  DESC LIMIT 1
  SQL (1.5ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 1]]
=> #<Book id: 1, title: "Homo faber", created_at: "2015-12-17 13:54:46",
updated_at: "2015-12-17 13:54:46">
>> Author.count
   (0.2ms)  SELECT COUNT(*) FROM "authors"
=> 1
>> Book.count
   (0.2ms)  SELECT COUNT(*) FROM "books"
=> 0
>> exit

The record of the book 'Homo faber' is deleted, but the author is still in the database.

As with destroy, an object also gets frozen when you use delete (see "destroy"). The record is already removed from the database, but the object itself is still there.

Transactions

In the world of databases, the term transaction refers to a block of SQL statements that must be executed together and without interruption. If an error should occur within the transaction, the database is reset to the state before the start of the transaction.

Now and again, there are areas of application where you need to carry out a database transaction. The classic example is transferring money from one account to another. That only makes sense if both actions (debiting one account and crediting the recipient’s account) are executed.

A transaction follows this pattern:

ApplicationRecord.transaction do
  Book.create(:title => 'A')
  Book.create(:title => 'B')
  Book.create(:title => 'C').authors.create(:last_name => 'Z')
end

Transactions are a complex topic. If you want to find out more, you can consult the ri help on the shell via ri ActiveRecord::Transactions::ClassMethods.

Important
The methods save and destroy are automatically executed within the transaction wrapper. That way, Rails ensures that no undefined state can arise for these two methods.
Warning
Transactions are not natively supported by all databases. In that case, the code will still work, but you no longer have the security of the transaction.

Scopes

When programming Rails applications, it is sometimes clearer and simpler to define frequent searches as separate methods. In Rails speak, these are referred to as NamedScope. These NamedScopes can be chained, just like other methods.

Preparation

We build a little online shop:

$ rails new shop
  [...]
$ cd shop
$ rails generate model product name 'price:decimal{7,2}' \
weight:integer in_stock:boolean expiration_date:date
  [...]
$ rails db:migrate
  [...]
$

Please populate the file db/seeds.rb with the following content:

db/seeds.rb
Product.create(name: 'Milk (1 liter)', weight: 1000, in_stock: true, price:
0.45, expiration_date: Date.today + 14.days)
Product.create(name: 'Butter (250 g)', weight: 250, in_stock: true, price:
0.75, expiration_date: Date.today + 14.days)
Product.create(name: 'Flour (1 kg)', weight: 1000, in_stock: false, price:
0.45, expiration_date: Date.today + 100.days)
Product.create(name: 'Jelly Babies (6 x 300 g)', weight: 1500, in_stock: true,
price: 4.96, expiration_date: Date.today + 1.year)
Product.create(name: 'Super-Duper Cake Mix', in_stock: true, price: 11.12,
expiration_date: Date.today + 1.year)
Product.create(name: 'Eggs (12)', in_stock: true, price: 2, expiration_date:
Date.today + 7.days)
Product.create(name: 'Peanuts (8 x 200 g bag)', in_stock: false, weight: 1600,
price: 17.49, expiration_date: Date.today + 1.year)

Now populate it with the db/seeds.rb:

$ rails db:seed
  [...]
$

Defining a Scope

If we want to count products that are in stock in our online shop, then we can use the following query each time:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> Product.where(in_stock: true).count
   (0.1ms)  SELECT COUNT(*) FROM "products" WHERE "products"."in_stock" = 't'
=> 5
>> exit

But we could also define a NamedScope available in the app/models/product.rb:

app/models/product.rb
class Product < ApplicationRecord
  scope :available, -> { where(in_stock: true) }
end

And then use it:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> Product.available.count
   (0.1ms)  SELECT COUNT(*) FROM "products" WHERE "products"."in_stock" = 't'
=> 5
>> exit

Let’s define a second NamedScope for this example in the app/models/product.rb:

app/models/product.rb
class Product < ApplicationRecord
  scope :available, -> { where(in_stock: true) }
  scope :cheap, -> { where(price: 0..1) }
end

Now we can chain both named scopes to output all cheap products that are in stock:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> Product.cheap.count
   (0.3ms)  SELECT COUNT(*) FROM "products" WHERE ("products"."price" BETWEEN
   0 AND 1)
=> 3
>> Product.cheap.available.count
   (0.3ms)  SELECT COUNT(*) FROM "products" WHERE ("products"."price" BETWEEN
   0 AND 1) AND "products"."in_stock" = 't'
=> 2
>> exit

Passing in Arguments

If you need a NamedScope that can also process parameters, then that is no problem either. The following example outputs products that are cheaper than the specified value. The app/models/product.rb looks like this:

app/models/product.rb
class Product < ApplicationRecord
  scope :cheaper_than, ->(price) { where("price < ?", price) }
end

Now we can count all products that cost less than 50 cent:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> Product.cheaper_than(0.5).count
   (0.2ms)  SELECT COUNT(*) FROM "products" WHERE (price < 0.5)
=> 2
>> exit

Creating New Records with Scopes

Let’s use the following app/models/product.rb:

app/models/product.rb
class Product < ApplicationRecord
  scope :available, -> { where(in_stock: true) }
end

With this NamedScope we can not only find all products that are in stock, but also create new products that contain the value true in the field in_stock:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> product = Product.available.build
=> #<Product id: nil, name: nil, price: nil, weight: nil, in_stock: true,
expiration_date: nil, created_at: nil, updated_at: nil>
>> product.in_stock
=> true
>> exit

This works with the method build (see "build") and create (see "create").

Validation

Non-valid records are frequently a source of errors in programs. With validates, Rails offers a quick and easy way of validating them. That way you can be sure that only meaningful records will find their way into your database.

Preparation

Let’s create a new application for this chapter:

$ rails new shop
  [...]
$ cd shop
$ rails generate model product name 'price:decimal{7,2}' \
weight:integer in_stock:boolean expiration_date:date
  [...]
$ rails db:migrate
  [...]
$

The Basic Idea

For each model, there is a matching model file in the directory app/models/. In this Ruby code, we can not only define database dependencies, but also implement all validations. The advantage: Every programmer knows where to find it.

Without any validation, we can create an empty record in a model without a problem:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> product = Product.create
[...]
=> #<Product id: 1, name: nil, price: nil, weight: nil,
in_stock: nil, expiration_date: nil, created_at: "2016-01-21 13:18:31",
updated_at: "2016-01-21 13:18:31">
>> exit

But in practice, this record with no content doesn’t make any sense. A Product needs to have a name and a price. That’s why we can define validations in ActiveRecord. Then you can ensure as programmer that only records that are valid for you are saved in your database.

To make the mechanism easier to understand, I am going to jump ahead a bit and use the presence helper. Please fill your app/models/product.rb with the following content:

app/models/product.rb
class Product < ApplicationRecord
  validates :name,
            presence: true

  validates :price,
            presence: true
end

Now we try again to create an empty record in the console:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> product = Product.create
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> #<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil,
expiration_date: nil, created_at: nil, updated_at: nil>

Watch out for the rollback transaction part and the missing id of the Product object! Rails began the transaction of creating a new record but for some reason it couldn’t do it. So it had to rollback the transaction. The validation method intervened before the record was saved. So validating happens before saving.

Can we access the errors? Yes, via the method errors or with errors.messages we can look at the errors that occurred:

>> product.errors
=> #<ActiveModel::Errors:0x007ff515a71680 @base=#<Product id: nil, name: nil,
price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil,
updated_at: nil>, @messages={:name=>["can't be blank"], :price=>["can't be
blank"]}>
>> product.errors.messages
=> {:name=>["can't be blank"], :price=>["can't be blank"]}

This error message was defined for a human and English-speaking user.

Only once we assign a value to the attributes name and price, we can save the object:

>> product.name = 'Milk (1 liter)'
=> "Milk (1 liter)"
>> product.price = 0.45
=> 0.45
>> product.save
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "products" ("name", "price", "created_at",
  "updated_at") VALUES (?, ?, ?, ?)  [["name", "Milk (1 liter)"], ["price",
  0.45], ["created_at", "2015-12-17 17:59:09.293831"], ["updated_at",
  "2015-12-17 17:59:09.293831"]]
   (9.0ms)  commit transaction
=> true

valid?

The method valid? indicates in boolean form if an object is valid. So you can check the validity already before you save:

>> product = Product.new
=> #<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil,
expiration_date: nil, created_at: nil, updated_at: nil>
>> product.valid?
=> false

save( validate: false )

As so often in life, you can find a way around everything. If you pass the parameter :validate ⇒ false to the method save, the data of Validation is saved:

>> product = Product.new
=> #<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil,
expiration_date: nil, created_at: nil, updated_at: nil>
>> product.valid?
=> false
>> product.save
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> false
>> product.save(validate: false)
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "products" ("created_at", "updated_at") VALUES (?,
  ?)  [["created_at", "2015-12-17 18:01:46.173590"], ["updated_at",
  "2015-12-17 18:01:46.173590"]]
   (9.1ms)  commit transaction
=> true
>> exit
Warning
I assume that you understand the problems involved here. Please only use this option if there is a really good reason to do so.

presence

In our model product there are a few fields that must be filled in in any case. We can achieve this via presence.

Note
Please excuse the duplication. I’m aware that I just used the very same code to give you an idea what validation does.
app/models/product.rb
class Product < ApplicationRecord
  validates :name,
            presence: true

  validates :price,
            presence: true
end

If we try to create an empty user record with this, we get lots of validation errors:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> product = Product.create
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> #<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil,
expiration_date: nil, created_at: nil, updated_at: nil>
>> product.errors.messages
=> {:name=>["can't be blank"], :price=>["can't be blank"]}

Only once we have entered all the data, the record can be saved:

>> product.name = 'Milk (1 liter)'
=> "Milk (1 liter)"
>> product.price = 0.45
=> 0.45
>> product.save
   (0.1ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "products" ("name", "price", "created_at",
  "updated_at") VALUES (?, ?, ?, ?)  [["name", "Milk (1 liter)"], ["price",
  0.45], ["created_at", "2015-12-17 18:04:26.587946"], ["updated_at",
  "2015-12-17 18:04:26.587946"]]
   (9.2ms)  commit transaction
=> true
>> exit

length

With length you can limit the length of a specific attribute. It’s easiest to explain using an example. Let us limit the maximum length of the name to 20 and the minimum to 2.

app/models/product.rb
class Product < ApplicationRecord
  validates :name,
            presence: true,
            length: { in: 2..20 }

  validates :price,
            :presence => true
end

If we now try to save a Product with a name that consists in one letter, we get an error message:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> product = Product.create(:name => 'M', :price => 0.45)
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> #<Product id: nil, name: "M", price:
#<BigDecimal:7ff735513400,'0.45E0',9(27)>, weight: nil, in_stock: nil,
expiration_date: nil, created_at: nil, updated_at: nil>
>> product.errors.messages
=> {:name=>["is too short (minimum is 2 characters)"]}

Options

length can be called with the following options.

minimum

The minimum length of an attribute. Example:

validates :name,
          presence: true,
          length: { minimum: 2 }

too_short

Defines the error message of :minimum. Default: "is too short (min is %d characters)". Example:

validates :name,
          presence: true,
          length: { minimum: 5 ,
          too_short: "must have at least %{count} characters"}

maximum

The maximum length of an attribute. Example:

validates :name,
          presence: true,
          length: { maximum: 20 }

too_long

Defines the error message of :maximum. Default: "is too long (maximum is %d characters)". Example:

validates :name,
          presence: true,
          length: { maximum: 20 ,
          too_long: "must have at most %{count} characters" }
Note
For all error messages, please note the chapter Internationalization.

is

Is exactly the specified number of characters long. Example:

validates :name,
          presence: true,
          length: { is: 8 }

:in or :within

Defines a length interval. The first number specifies the minimum number of the range and the second the maximum. Example:

validates :name,
          presence: true,
          length: { in: 2..20 }

tokenizer

You can use this to define how the attribute should be split for counting. Default: lambda{ |value| value.split(//) } (individual characters are counted). Example (for counting words):

validates :content,
          presence: true,
          length: { in: 2..20 },
          tokenizer: lambda {|str| str.scan(/\w+/)}

numericality

With numericality you can check if an attribute is a number. It’s easier to explain if we use an example.

app/models/product.rb
class Product < ApplicationRecord
  validates :name,
            presence: true,
            length: { in: 2..20 }

  validates :price,
            presence: true

  validates :weight,
            numericality: true
end

If we now use a weight that consists of letters or contains letters instead of numbers, we will get an error message:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> product = Product.create(name: 'Milk (1 liter)',
   price: 0.45, weight: 'abc')
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> #<Product id: nQil, name: "Milk (1 liter)",
price: #<BigDecimal:7fca1ec90ed8,'0.45E0',9(27)>, weight: 0,
in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>
>> product.errors.messages
=> {:weight=>["is not a number"]}
>> exit
Tip
You can use numericality to define the content as number even if an attribute is saved as string in the database.

Options

numericality can be called with the following options.

only_integer

The attribute can only contain an integer. Default: false. Example:

validates :weight,
          numericality: { only_integer: true }

greater_than

The number saved in the attribute must be greater than the specified value. Example:

validates :weight,
          numericality: { greater_than: 100 }

greater_than_or_equal_to

The number saved in the attribute must be greater than or equal to the specified value. Example:

validates :weight,
          numericality: { greater_than_or_equal_to: 100 }

equal_to

Defines a specific value that the attribute must have. Example:

validates :weight,
          numericality: { equal_to: 100 }

less_than

The number saved in the attribute must be less than the specified value. Example:

validates :weight,
          numericality: { less_than: 100 }

less_than_or_equal_to

The number saved in the attribute must be less than or equal to the specified value. Example:

validates :weight,
          numericality: { less_than_or_equal_to: 100 }

odd

The number saved in the attribute must be an odd number. Example:

validates :weight,
          numericality: { odd: true }

even

The number saved in the attribute must be an even number. Example:

validates :weight,
          numericality: { even: true }

uniqueness

With uniqueness you can define that the value of this attribute must be unique in the database. If you want a product in the database to have a unique name that appears nowhere else, then you can use this validation:

app/models/product.rb
class Product < ApplicationRecord
  validates :name,
            presence: true,
            uniqueness: true
end

If we now try to create a new Product with a name that already exists, then we get an error message:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> Product.last
  Product Load (0.2ms)  SELECT  "products".* FROM "products"
  ORDER BY "products"."id" DESC LIMIT 1
=> #<Product id: 4, name: "Milk (1 liter)", price:
#<BigDecimal:7fdccb1960b8,'0.45E0',9(27)>, weight: nil,
in_stock: nil, expiration_date: nil,
created_at: "2015-12-17 18:04:26",
updated_at: "2015-12-17 18:04:26">
>> product = Product.create(name: 'Milk (1 liter)')
   (0.1ms)  begin transaction
  Product Exists (0.2ms)  SELECT  1 AS one FROM "products"
  WHERE "products"."name" = 'Milk (1 liter)' LIMIT 1
   (0.1ms)  rollback transaction
=> #<Product id: nil, name: "Milk (1 liter)", price: nil,
weight: nil, in_stock: nil, expiration_date: nil,
created_at: nil, updated_at: nil>
>> product.errors.messages
=> {:name=>["has already been taken"]}
>> exit
Warning
The validation via uniqueness is no absolute guarantee that the attribute is unique in the database. A race condition could occur (see http://en.wikipedia.org/wiki/Race_condition). A detailled discussion of this effect would go beyond the scope of book aimed at beginners (this phenomenon is extremely rare).

Options

uniqueness can be called with the following options.

scope

Defines a scope for the uniqueness. If we had a differently structured phone number database (with just one field for the phone number), then we could use this option to specify that a phone number must only be saved once per user. Here is what it would look like:

validates :name,
          presence: true,
          uniqueness: { scope: :user_id }

case_sensitive

Checks for uniqueness of upper and lower case as well. Default: false. Example:

validates :name,
          presence: true,
          uniqueness: { case_sensitive: true }

inclusion

With inclusion you can define from which values the content of this attribute can be created. For our example, we can demonstrate it using the attribute in_stock.

app/models/product.rb
class Product < ApplicationRecord
  validates :name,
            presence: true

  validates :in_stock,
            inclusion: { in: [true, false] }
end

In our data model, a Product must be either true or false for in_stock (there must not be a nil). If we enter a different value than true or false, a validation error is returned:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> product = Product.create(name: 'Milk low-fat (1 liter)')
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> #<Product id: nil, name: "Milk low-fat (1 liter)", price: nil, weight: nil,
in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>
>> product.errors.messages
=> {:in_stock=>["is not included in the list"]}
>> exit
Tip
Always remember the power of Ruby! For example, you can generate the enumerable object always live from another database. In other words, the validation is not defined statically.

Options

inclusion can be called with the following option.

message

For outputting custom error messages. Default: "is not included in the list". Example:

validates :in_stock,
          inclusion: { in: [true, false],
                       message: 'this one is not allowed' }
Note
For all error messages, please note the chapter Internationalization.

exclusion

exclusion is the inversion of inclusion. You can define from which values the content of this attribute must not be created.

app/models/product.rb
class Product < ApplicationRecord
  validates :name,
            presence: true

  validates :in_stock,
            exclusion: { in: [nil] }
end
Tip
Always remember the power of Ruby! For example, you can generate the enumerable object always live from another database. In other words, the validation does not have to be defined statically.

Options

exclusion can be called with the following option.

message

For outputting custom error messages. Example:

validates :in_stock,
          inclusion: { in: [nil],
                       message: 'this one is not allowed' }
Note
For all error messages, please note the chapter Internationalization.

format

With format you can define via a regular expression (see http://en.wikipedia.org/wiki/Regular_expression) how the content of an attribute can be structured.

With format you can for example carry out a simple validation of the syntax of an e-mail address:

validates :email,
          format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }
Warning
It should be obvious that the e-mail address validation shown here is not complete. It is just meant to be an example. You can only use it to check the syntactic correctness of an e-mail address.

Options

validates_format_of can be called with the following options:

  • :message

    For outputting a custom error message. Default: "is invalid". Example:

validates :email,
          format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
                       message: 'is not a valid email address' }
Note
For all error messages, please note the chapter Internationalization.

General Validation Options

There are some options that can be used for all validations.

allow_nil

Allows the value nil. Example:

validates :email,
          format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i },
          allow_nil: true

allow_blank

As allow_nil, but additionally with an empty string. Example:

validates :email,
          format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i },
          allow_blank: true
on

With on, a validation can be limited to the events create, update or safe. In the following example, the validation only takes effect when the record is initially created (during the create):

validates :email,
          format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i },
          on: :create

if and unless

if or unless call the specified method and only execute the validation if the result of the method is true:

validates :name,
          presence: true,
          if: :today_is_monday?

def today_is_monday?
  Date.today.monday?
end

proc

:proc calls a Proc object. The functionality of a Proc object is beyond the scope of this book. I give you an example how to use it without describing the magic behind.

validates :name,
          presence: true,
          if: Proc.new { |a| a.email == 'test@test.com' }
Note
If you want to dive more into Proc you’ll find documentation about it at https://ruby-doc.org/core-2.5.0/Proc.html

Writing Custom Validations

Now and then, you want to do a validation where you need custom program logic. For such cases, you can define custom validations.

Defining Validations with Your Own Methods

Let’s assume you are a big shot hotel mogul and need a reservation system.

$ rails new my_hotel
  [...]
$ cd my_hotel
$ rails generate model reservation \
start_date:date end_date:date room_type
  [...]
$ rails db:migrate
  [...]
$

Then we specify in the app/models/reservation.rb that the attributes start_date and end_date must be present in any case, plus we use the method reservation_dates_must_make_sense to make sure that the start_date is before the end_date:

app/models/reservation.rb
class Reservation < ApplicationRecord
  validates :start_date,
            presence: true

  validates :end_date,
            presence: true

  validate :reservation_dates_must_make_sense

  private
  def reservation_dates_must_make_sense
    if end_date <= start_date
      errors.add(:start_date, 'has to be before the end date')
    end
  end
end

With errors.add we can add error messages for individual attributes. With errors.add_to_base you can add error messages for the whole object.

Let’s test the validation in the console by introducing Date.today + 1.day to you. It does exactly what you’d expect from it. ;-)

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> reservation = Reservation.new(start_date: Date.today, end_date: Date.today)
=> #<Reservation id: nil, start_date: "2015-12-17", end_date: "2015-12-17",
room_type: nil, created_at: nil, updated_at: nil>
>> reservation.valid?
=> false
>> reservation.errors.messages
=> {:start_date=>["has to be before the end date"]}
>> reservation.end_date = Date.today + 1.day
=> Sat, 18 Apr 2015
>> reservation.valid?
=> true
>> reservation.save
[...]
=> true
>> exit

Further Documentation

The topic validations is described very well in the official Rails documentation at http://guides.rubyonrails.org/active_record_validations.html.

Migrations

SQL database tables are generated in Rails with migrations and they should also be changed with migrations. If you create a model with rails generate model, a corresponding migration file is automatically created in the directory db/migrate/. I am going to show you the principle using the example of a shop application. Let’s create one first:

$ rails new shop
  [...]
$ cd shop

Then we generate a Product model:

$ rails generate model product name 'price:decimal{7,2}' \
weight:integer in_stock:boolean expiration_date:date
      invoke  active_record
      create    db/migrate/20151217184823_create_products.rb
      create    app/models/product.rb
      invoke    test_unit
      create      test/models/product_test.rb
      create      test/fixtures/products.yml
$

The migrations file db/migrate/20151217184823_create_products.rb was created. Let’s have a closer look at it:

db/migrate/20151217184823_create_products.rb
class CreateProducts < ActiveRecord::Migration[5.1]
  def change
    create_table :products do |t|
      t.string :name
      t.decimal :price, precision: 7, scale: 2
      t.integer :weight
      t.boolean :in_stock
      t.date :expiration_date

      t.timestamps null: false
    end
  end
end

The method change creates and deletes the database table in case of a rollback. The migration files have embedded the current time in the file name and are processed in chronological order during a migration (in other words, when you call rails db:migrate).

$ rails db:migrate
== 20151217184823 CreateProducts: migrating ===================================
-- create_table(:products)
   -> 0.0015s
== 20151217184823 CreateProducts: migrated (0.0016s) ==========================
$

Only those migrations that have not been executed yet are processed. If we call rails db:migrate again, nothing happens, because the corresponding migration has already been executed:

$ rails db:migrate
$

But if we manually delete the database with rm and then call rails db:migrate again, the migration is repeated:

$ rm db/development.sqlite3
$ rails db:migrate
== 20151217184823 CreateProducts: migrating ===================================
-- create_table(:products)
   -> 0.0017s
== 20151217184823 CreateProducts: migrated (0.0018s) ==========================
$

After a while we realise that we want to save not just the weight for some products, but also the height. So we need another database field. There is an easy to remember syntax for this, rails generate migration add*:

$ rails generate migration addHeightToProduct height:integer
      invoke  active_record
      create    db/migrate/20151217185307_add_height_to_product.rb
$

In the migration file db/migrate/20151217185307_add_height_to_product.rb we once again find a change method:

db/migrate/20151217185307_add_height_to_product.rb
class AddHeightToProduct < ActiveRecord::Migration
  def change
    add_column :products, :height, :integer
  end
end

With rails db:migrate we can start in the new migration:

$ rails db:migrate
== 20151217185307 AddHeightToProduct: migrating ===============================
-- add_column(:products, :height, :integer)
   -> 0.0086s
== 20151217185307 AddHeightToProduct: migrated (0.0089s) ======================
$

In the console we can look at the new field. It was added after the field updated_at:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> Product.column_names
=> ["id", "name", "price", "weight", "in_stock", "expiration_date",
"created_at", "updated_at", "height"]
>> exit

What if you want to look at the previous state of things? No problem. You can easily go back to the previous version with rails db:rollback:

$ rails db:rollback
== 20151217185307 AddHeightToProduct: reverting ===============================
-- remove_column(:products, :height, :integer)
   -> 0.0076s
== 20151217185307 AddHeightToProduct: reverted (0.0192s) ======================
$

Each migration has its own version number. You can find out the version number of the current status via rails db:version:

$ rails db:version
Current version: 20151217184823
$
Important
Please note that all version numbers and timestamps only apply to the example printed here. If you recreate the example, you will of course get a different timestamp for your own example.

You will find the corresponding version in the directory db/migrate:

$ ls db/migrate/
20151217184823_create_products.rb
20151217185307_add_height_to_product.rb
$

You can go to a specific migration via rails db:migrate VERSION= and add the appropriate version number after the equals sign. The number zero represents the version zero, in other words the start.

Let’s try it out:

$ rails db:migrate VERSION=0
== 20151217184823 CreateProducts: reverting ===================================
-- drop_table(:products)
   -> 0.0007s
== 20151217184823 CreateProducts: reverted (0.0032s) ==========================
$

The table was deleted with all data. We are back to square one.

Which Database is Used?

The database table is created through the migration. As you can see, the table names automatically get the plural of the model’s name (Person vs. people). But in which database are the tables created? This is defined in the configuration file config/database.yml:

config/database.yml
# SQLite version 3.x
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
#
default: &default
  adapter: sqlite3
  pool: 5
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: db/test.sqlite3

production:
  <<: *default
  database: db/production.sqlite3

Three different databases are defined there in YAML format (see http://www.yaml.org/ or http://en.wikipedia.org/wiki/YAML). For us, only the development database is relevant for now (first item). By default, Rails uses SQLite3 there. SQLite3 may not be the correct choice for the analysis of the weather data collected worldwide, but for a quick and straightforward development of Rails applications you will quickly learn to appreciate it. In the production environment, you can later still switch to "big" databases such as MySQL or PostgreSQL.

To satisfy your curiosity, let’s have a quick look at the database with the command line tool sqlite3:

$ sqlite3 db/development.sqlite3
SQLite version 3.8.5 2014-08-15 22:37:57
Enter ".help" for usage hints.
sqlite> .tables
schema_migrations
sqlite> .quit
$

Nothing in it. Of course not, as we have not yet run the migration:

$ rails db:migrate
== 20151217184823 CreateProducts: migrating ===================================
-- create_table(:products)
   -> 0.0019s
== 20151217184823 CreateProducts: migrated (0.0020s) ==========================

== 20151217185307 AddHeightToProduct: migrating ===============================
-- add_column(:products, :height, :integer)
   -> 0.0007s
== 20151217185307 AddHeightToProduct: migrated (0.0008s) ======================

$ sqlite3 db/development.sqlite3
SQLite version 3.8.5 2014-08-15 22:37:57
Enter ".help" for usage hints.
sqlite> .tables
products           schema_migrations
sqlite> .schema products
CREATE TABLE "products" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"name" varchar, "price" decimal(7,2), "weight" integer, "in_stock" boolean,
"expiration_date" date, "created_at" datetime NOT NULL, "updated_at" datetime
NOT NULL, "height" integer);
sqlite> .quit

The table schema_migrations is used for the versioning of the migrations. This table is created during the first migration carried out by Rails, if it does not yet exist.

Creating Index

I assume that you know what a database index is. If not, you will find a brief introduction at http://en.wikipedia.org/wiki/Database_index. In brief: you can use it to quickly search for a specific table column.

In our production database, we should index the field name in the products table. We create a new migration for that purpose:

$ rails generate migration create_index
      invoke  active_record
      create    db/migrate/20151217190442_create_index.rb
$

In the file db/migrate/20121120142002_create_index.rb we create the index with add_index in the method self.up, and in the method self.down we delete it again with remove_index:

db/migrate/20121120142002_create_index.rb
class CreateIndex < ActiveRecord::Migration
  def up
    add_index :products, :name
  end

  def down
    remove_index :products, :name
  end
end

With rails db:migrate we create the index:

$ rails db:migrate
==  CreateIndex: migrating ====================================================
-- add_index(:products, :name)
   -> 0.0010s
==  CreateIndex: migrated (0.0011s) ===========================================
$

Of course we don’t have to use the up and down method. We can use change too. The migration for the new index would look like this:

class CreateIndex < ActiveRecord::Migration[5.1]
  def change
    add_index :products, :name
  end
end
Tip

You can also create an index directly when you generate the model. In our case (an index for the attribute name) the command would look like this:

$ rails generate model product name:string:index
$ cat db/migrate/20151217191435_create_products.rb
class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t|
      t.string :name

      t.timestamps null: false
    end
    add_index :products, :name
  end
end

Automatically Added Fields (id, created_at and updated_at)

Rails kindly adds the following fields automatically in the default migration:

  • id:integer

    This is the unique ID of the record. The field is automatically incremented by the database. For all SQL fans: NOT NULL AUTO_INCREMENT

  • created_at:datetime

    The field is filled automatically by ActiveRecord when a record is created.

  • updated_at:datetime

    The field is automatically updated to the current time whenever the record is edited.

So you don’t have to enter these fields yourself when generating the model.

At first you may ask yourself: "Is that really necessary? Does it make sense?". But after a while you will learn to appreciate these automatic fields. Omitting them would usually be false economy.

Further Documentation

The following webpages provide excellent further information on the topic migration:

Callbacks

Callbacks are defined programming hooks in the life of an ActiveRecord object. You can find a list of all callbacks at http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html. Here are the most frequently used callbacks:

  • before_validation

    Executed before the validation.

  • after_validation

    Executed after the validation.

  • before_save

    Executed before each save.

  • before_create

    Executed before the first save.

  • after_save

    Executed after every save.

  • after_create

    Executed after the first save.

A callback is always executed in the model. Let’s assume you always want to save an e-mail address in a User model in lower case, but also give the user of the web interface the option to enter upper case letters. You could use a before_save callback to convert the attribute email to lower case via the method downcase.

The Rails application:

$ rails new shop
  [...]
$ cd shop
$ rails generate model user email login
  [...]
$ rails db:migrate
  [...]

Here is what the model app/models/user.rb would look like. The interesting stuff is the before_save part:

app/models/user.rb
class User < ApplicationRecord
  validates :login,
            presence: true

  validates :email,
            presence: true,
            format: { :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }

  before_save :downcase_email

  private

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

Let’s see in the console if it really works as we want it to:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> User.create(login: 'smith', email: 'SMITH@example.com')
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "users" ("login", "email", "created_at",
  "updated_at") VALUES (?, ?, ?, ?)  [["login", "smith"], ["email",
  "smith@example.com"], ["created_at", "2015-12-17 19:22:20.928994"],
  ["updated_at", "2015-12-17 19:22:20.928994"]]
   (9.0ms)  commit transaction
=> #<User id: 1, email: "smith@example.com", login: "smith", created_at:
"2015-12-17 19:22:20", updated_at: "2015-12-17 19:22:20">
>> exit

Even though the e-mail address was entered partly with a capital letters, ActiveRecord has indeed converted all letters automatically to lower case via the before_save callback.

In the section "Default Values" you will find an example for defining a default value for a new object via an after_initialize callback.

Default Values

If you need specific default values for an ActiveRecord object, you can easily implement this with the after_initialize callback. This method is called by ActiveRecord when a new object is created. Let’s assume we have a model Order and the minimum order quantity is always 1, so we can enter 1 directly as default value when creating a new record.

Let’s set up a quick example:

$ rails new shop
  [...]
$ cd shop
$ rails generate model order product_id:integer quantity:integer
  [...]
$ rails db:migrate
  [...]

We write an after_initialize callback into the file app/models/order.rb:

app/models/order.rb
class Order < ApplicationRecord
  after_initialize :set_defaults

  private
  def set_defaults
    self.quantity ||= 1
  end
end

||= 1 sets the value to 1 if it isn’t set already.

And now we check in the console if a new order object automatically contains the quantity 1:

$ rails console
Running via Spring preloader in process 27927
Loading development environment (Rails 5.2.0)
>> order = Order.new
=> #<Order id: nil, product_id: nil, quantity: 1, created_at: nil,
updated_at: nil>
>> order.quantity
=> 1
>> exit

That’s working fine.