4.2. Creating Database/Model

Note

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
Usage:
  rails generate model NAME [field[:type][:index] field[:type][:index]] [options]

Options:
      [--skip-namespace]  # Skip namespace (affects only isolated applications)
      [--old-style-hash]  # Force using old style hash (:foo => 'bar') on Ruby >= 1.9
  -o, --orm=NAME          # Orm to be invoked
                          # Default: active_record

ActiveRecord options:
      [--migration]            # Indicates when to generate migration
                               # Default: true
      [--timestamps]           # Indicates when to generate timestamps
                               # Default: true
      [--parent=PARENT]        # The parent class for the generated model
      [--indexes]              # Add indexes for references and belongs_to columns
                               # Default: true
  -t, [--test-framework=NAME]  # Test framework to be invoked
                               # Default: test_unit

TestUnit options:
      [--fixture]                   # Indicates when to generate fixture
                                    # Default: true
  -r, [--fixture-replacement=NAME]  # Fixture replacement to be invoked

Runtime options:
  -f, [--force]    # Overwrite files that already exist
  -p, [--pretend]  # Run but do not make any changes
  -q, [--quiet]    # Suppress status output
  -s, [--skip]     # Skip files that already exist

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

    Attribute pairs are field:type arguments specifying the
    model's attributes. Timestamps are added by default, so you don't have to
    specify them by hand as 'created_at:datetime updated_at:datetime'.

    You don't have to think up every attribute up front, but it helps to
    sketch out a few so you can start working with the model immediately.

    This generator invokes your configured ORM and test framework, which
    defaults to ActiveRecord and TestUnit.

    Finally, if --parent option is given, it's used as superclass of the
    created model. This allows you create Single Table Inheritance models.

    If you pass a namespaced model name (e.g. admin/account or Admin::Account)
    then the generator will create a module with a table_name_prefix method
    to prefix the model's table name with the module name (e.g. admin_account)

Examples:
    `rails generate model account`

        For ActiveRecord and TestUnit it creates:

            Model:      app/models/account.rb
            Test:       test/unit/account_test.rb
            Fixtures:   test/fixtures/accounts.yml
            Migration:  db/migrate/XXX_add_accounts.rb

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

        Creates a Post model with a string title, text body, and published flag.

    `rails generate model admin/account`

        For ActiveRecord and TestUnit it creates:

            Module:     app/models/admin.rb
            Model:      app/models/admin/account.rb
            Test:       test/unit/admin/account_test.rb
            Fixtures:   test/fixtures/admin/accounts.yml
            Migration:  db/migrate/XXX_add_admin_accounts.rb

$
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 by default. Let's create the model country:
$ rails generate model country name population:integer
      invoke  active_record
      create    db/migrate/20121114110230_create_countries.rb
      create    app/models/country.rb
      invoke    test_unit
      create      test/unit/country_test.rb
      create      test/fixtures/countries.yml
$
The generator has created a database migration file with the name db/migrate/20121114110230_create_countries.rb. It provides the following code:
class CreateCountries < ActiveRecord::Migration
  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 daughter of ActiveRecord::Migration. The class method change is used to define a migration and the associated roll-back.
With rake db:migrate we can apply the migrations, in other words, create the corresponding database table:
$ rake db:migrate
==  CreateCountries: migrating ================================================
-- create_table(:countries)
   -> 0.0014s
==  CreateCountries: migrated (0.0015s) =======================================

$ 

Note

You will find more details on migrations in Section 4.16, “Migrations”.
Let's have a look at the file app/models/country.rb:
class Country < ActiveRecord::Base
  attr_accessible :name, :population
end
Hmmm … the class Country is a daughter of ActiveRecord::Base. Makes sense, as we are discussing ActiveRecord in this chapter. ;-)
attr_accessible defines a whitelist with model attributes that can be filled with values via mass assignment.[22]Very simply put, these are the attributes that we can later populate or change via Web interface.

Note

The integrated help page on ActiveRecord::Base is always available in case you want to look something up:
$ ri ActiveRecord::Base

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 entering the class name:
$ rails console
Loading development environment (Rails 3.2.9)
>> Country
=> Country(id: integer, name: string, population: integer, created_at: datetime, updated_at: datetime)
>> exit
$
The attribute created_at stores the time when the record was initially created. updated_at shows 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 record.

Possible Data Types in ActiveRecord

ActiveRecord is a layer between Ruby and various relational databases. Unfortunately, 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.
  • Advantage:
    We can replace the database behind a Rails application without having to touch the program code.
  • Disadvantage:
    We cannot use all the features of the database concerned. We have to use the least common denominator, so to speak.
To generate a model, you can use the following field types:
  • binary
    This is a BLOB (Binary Large Object) in the classical sense. Never heard of it? Then you probably won't need it.
  • boolean
    A Boolean value. Can be either true or false.
  • date
    You can store a date here.
  • datetime
    Here you can store a date including a time.
  • float
    For storing a floating point number.
  • integer
    For storing an integer.
  • decimal
    For storing a decimal number.

    Tip

    You can also enter a decimal directly with the model generator. But you need to observe the special syntax. Example for creating a price with a decimal:
    $ rails generate model product name 'price:decimal{7,2}'
          invoke  active_record
          create    db/migrate/20121114110808_create_products.rb
          create    app/models/product.rb
          invoke    test_unit
          create      test/unit/product_test.rb
          create      test/fixtures/products.yml
    $
    That would generate the following migration (db/migrate/20121114110808_create_products.rb):
    class CreateProducts < ActiveRecord::Migration
      def change
        create_table :products do |t|
          t.string :name
          t.decimal :price, :precision => 7, :scale => 2
    
          t.timestamps
        end
      end
    end
  • 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.
  • string
    A string, in other words a sequence of any characters, up to a maximum of 28-1 (= 255) characters.
  • text
    Also a string - but considerably bigger. By default, up to 216 (= 65536) characters can be saved here.
  • time
    A time.
  • timestamp
    A time with date, filled in automatically by the database.
In Section 4.16, “Migrations” we will provide more information on the individual data types and discuss available options. Don't forget, this is a book for beginners, so this section just gives a brief overview. If you want to find out more about the various datatypes, please refer to the documentation listed in Appendix A, Further Rails Documentation.

Naming Conventions (Country vs. country vs. countries)

Rails newbies often find it hard to figure out when to use upper and lower case, for example, Country or country (one is a class, the other one a model). The problem is usually not the class itself, but purely the spelling or wording. For now, let's just say: it's all very logical and you will quickly get the hang of it. The important thing is that you keep using English words, even if you would normally be programming in another language (see the section called “Why Is It All in English?”).
Originally, my plan was to now start philosophizing at great length on naming conventions. But then I thought: Jeez, the readers want to get going and not sit here for ages reading about theory. So I am now going to introduce the methods with which you can find out the naming conventions yourself in the Rails console:
$ rails console
Loading development environment (Rails 3.2.9)
>> 'country'.classify
=> "Country"
>> 'country'.tableize
=> "countries"
>> 'country'.foreign_key
=> "country_id"
>>
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.
>> Country.name.tableize
=> "countries"
>> Country.name.foreign_key
=> "country_id"
>> exit
$ 
You will find a complete list of the corresponding methods at http://rails.rubyonrails.org/classes/ActiveSupport/CoreExtensions/String/Inflections.html. But I would recommend that, for now, you just go with the flow. If you are not sure, you can find out the correct notation with the methods shown above.

Database Configuration

Which database is used by default? Let's have a quick look at the configuration file for the database (config/database.yml):
# SQLite version 3.x
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
development:
  adapter: sqlite3
  database: db/development.sqlite3
  pool: 5
  timeout: 5000

# 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:
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000

production:
  adapter: sqlite3
  database: db/production.sqlite3
  pool: 5
  timeout: 5000
As we are working in development mode, Rails has created a new SQLite3 database db/development.sqlite3 as a result of rake db:migrate and saved all data there.

Note

Fans of command line clients can use sqlite3 for viewing this database:
$ sqlite3 db/development.sqlite3 
SQLite version 3.7.12 2012-04-03 19:43:07
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
countries          schema_migrations
sqlite> .schema countries
CREATE TABLE "countries" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "population" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
sqlite> .exit
$


[22] Ryan Bates has published an excellent screencast on the topic mass assignment and important security aspects at http://railscasts.com/episodes/26-hackers-love-mass-assignment-revised.