Contents 

Ruby on Rails:
Table of Contents
Preface
Zero to Sixty: Introducing Rails
1.1. Rails Strengths
1.2. Putting Rails into Action
1.3. Organization
1.4. The Web Server
1.5. Creating a Controller
1.6. Building a View
1.7. Tying the Controller to the View
1.8. Under the Hood
1.9. What's Next?
Active Record Basics
2.1. Active Record Basics
2.2. Introducing Photo Share
2.3. Schema Migrations
2.4. Basic Active Record Classes
2.5. Attributes
2.6. Complex Classes
2.7. Behavior
2.8. Moving Forward
Active Record Relationships
3.1. belongs_to
3.2. has_many
3.3. has_one
3.4. What You Haven't Seen
3.5. Looking Ahead
Scaffolding
4.1. Using the Scaffold Method
4.2. Replacing Scaffolding
4.3. Generating Scaffolding Code
4.4. Moving Forward
Extending Views
5.1. The Big Picture
5.2. Seeing Real Photos
5.3. View Templates
5.4. Setting the Default Root
5.5. Stylesheets
5.6. Hierarchical Categories
5.7. Styling the Slideshows
Ajax
6.1. How Rails Implements Ajax
6.2. Playing a Slideshow
6.3. Using Drag-and-Drop to Reorder Slides
6.4. Drag and Drop Everything (Almost Everything)
6.5. Filtering by Category
Testing
7.1. Background
7.2. Ruby's Test::Unit
7.3. Testing in Rails
7.4. Wrapping Up
Installing Rails
1.1. Windows
2.1. OS X
3.1. Linux
Quick Reference
5.1. General
5.2. Testing
5.3. RJS (Ruby JavaScript)
5.4. Active Record
5.5. Controllers
5.6. Views
5.7. Ajax
5.8. Configuring Your Application
About the Authors
Colophon
Index
A
B
C
D
E
F
G
H
I
J
L
M
N
O
P
R
S
T
U
V
W
X
Y
Z

Ruby on Rails manual

Prev Page Next Page
Previous Page
Next Page

5.4. Active Record

5.4.1. Automated Mapping

Automatically maps:

  • Tables classes

  • Rows objects (instances of model classes)

  • Columns object attributes

Table to class mapping uses English plurals:

  • An Invoice model class maps to an invoices table.

  • A Person model class maps to a people table.

  • A Country model class maps to a countries table.

  • A SecurityLevel model class maps to a security_levels table.

Learn more: http://api.rubyonrails.com/classes/ActiveRecord/Base.html.

5.4.2. Associations

Four ways of associating models (Figures B-1 and "#rubyrails-app-b-fig-2">B-2):

Figure B-1. One-to-one and one-to-many relationships

Figure B-2. Many-to-many relationships

has_one
has_many
belongs_to
has_and_belongs_to_many
def Order < ActiveRecord::Base
  has_many :line_items
  belongs_to :customer   # there's a column "customer_id" in the db table
end

def LineItem < ActiveRecord::Base
  belongs_to :order # there's a column "order_id" in the db table
end

def Customer < ActiveRecord::Base
  has_many :orders
  has_one :address
end

def Address < ActiveRecord::Base
  belongs_to :customer
end

belongs_to  :some_model,
        :class_name  => 'MyClass',      # specifies other class name
        :foreign_key => 'my_real_id',   # and primary key
        :conditions  => 'column = 0'    # only finds when this condition met

has_one :some_model,
        # as belongs_to and additionally:
        :dependent   => :destroy        # deletes associated object
        :order       => 'name ASC'      # SQL fragment for sorting

has_many :some_model
        # as has_one and additionally:
        :dependent => :destroy          # deletes all dependent data
                                        # calling each objects destroy
        :dependent => :delete_all       # deletes all dependent data
                                        # without calling the destroy methods
        :dependent => :nullify          # set association to null, not
                                        # destroying objects
        :group => 'name'                # adds GROUP BY fragment
        :finder_sql => 'select ....'    # instead of the Rails finders
        :counter_sql => 'select ...'    # instead of the Rails counters
def Category < ActiveRecord::Base
  has_and_belongs_to_many :products
end
def Product < ActiveRecord::Base
  has_and_belongs_to_many :categories
end

Table categories_products:

  • Has category_id column

  • Has product_id column

  • Does not have id column

5.4.3. Association Join Models ( "#rubyrails-app-b-fig-3">Figure B-3)

Figure B-3. Through model

class Author < ActiveRecord::Base
  has_many :authorships
  has_many :sites, :through => :authorships
end

class Authorship < ActiveRecord::Base
  belongs_to :author
  belongs_to :site
end

class site < ActiveRecord::Base
  has_one :authorship
end

@author = Author.find :first
@author.authorships.collect { |a| a.site } # selects all sites that the author's
                                           # authorships belong to.
@author.sites                              # selects all sites by using the Authorship
                                           # join model

Also works through has_many associations:

class Firm < ActiveRecord::Base
  has_many   :clients
  has_many   :invoices, :through => :clients
  has_many   :paid_invoices, :through => :clients, :source => :invoice
end

class Client < ActiveRecord::Base
  belongs_to :firm
  has_many   :invoices
end

class Invoice < ActiveRecord::Base
  belongs_to :client
end

@firm = Firm.find :first
@firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients
                                                 # of the firm
@firm.invoices                                   # selects all invoices by going
                                                 # through the Client join model.

Learn more at the following address: http://api.rubyonrails.com/classes/ActiveRecord/Associations/ClassMethods.html.

5.4.4. Validations

validates_presence_of :firstname, :lastname     # must be filled out

validates_length_of :password,
                    :minimum => 8           # more than 8 characters
                    :maximum => 16          # shorter than 16 characters
                    :in => 8..16            # between 8 and 16 characters
                    :too_short => 'way too short'
                    :too_long => 'way to long'

validates_acceptance_of :eula               # Must accept a condition
                        :accept => 'Y'      # default: 1 (ideal for a checkbox)

validates_confirmation_of :password
# the fields password and password_confirmation must match

validates_uniqueness_of :user_name              # user_name has to be unique
                        :scope => 'account_id'  # Condition:
                                                # account_id = user.account_id

validates_format_of :email          # field must match a regular expression
                    :with => /^(+)@((?:[-a-z0-9]+/.)+[a-z]{2,})$/i

validates_numericality_of   :value              # value is numeric
                            :only_integer => true
                            :allow_nil => true

validates_inclusion_in  :gender,    # value is in enumeration
                        :in => %w( m, f )

validates_exclusion_of  :age            # value is not in Enumeration
                        :in => 13..19   # don't want any teenagers

validates_associated :relation
# validates that the associated object is valid

Validation options:

:message => 'my own errormessage'
:on      => :create                 # or :update (validates only then)
:if      => ...                     # call method oder Proc

Learn more: http://api.rubyonrails.com/classes/ActiveRecord/Validations.html.

5.4.5. Calculations

Person.average :age
Person.minimum :age
Person.maximum :age
Person.count
Person.count(:conditions => "age > 26")
Person.sum :salary, :group => :last_name

Learn more at the following address: http://api.rubyonrails.com/classes/ActiveRecord/Calculations/ClassMethods.html.

5.4.6. Finders

find(42)                                     # object with ID 42
find([37, 42])                               # Array with the objects with id 37, 42
find :all
find :first,
     :conditions => [ "name = ?", "Hans" ]   # finds the first record
                                             # with matching condition

More parameters for find:

:order => 'name DESC'               # sql fragment for sorting
:offset => 20                       # starts with entry 20
:limit => 10                        # only return 10 objects
:group => 'name'                    # sql fragment GROUP BY
:joins => 'LEFT JOIN ...'           # additional LEFT JOIN (rarely used)
:include => [:account, :friends]    # LEFT OUTER JOIN with these model
:include => { :groups => { :members=> { :favorites } } }
:select => [:name, :adress]         # instead of SELECT * FROM
:readonly => true                   # objects are write protected

5.4.6.1. Dynamic attribute-based finders
Person.find_by_user_name(user_name)
Person.find_all_by_last_name(last_name)
Person.find_by_user_name_and_password(user_name, password)
Order.find_by_name("Joe Blow")
Order.find_by_email("jb@gmail.com")
Slideshow.find_or_create_by_name("Winter")

Learn more: http://api.rubyonrails.com/classes/ActiveRecord/Base.html.

5.4.6.2. Scope
Employee.with_scope(
    :find => { :conditions => "salary > 10000",
               :limit => 10 }) do
  Employee.find(:all)     # => SELECT * FROM employees
                          #             WHERE (salary > 10000)
                          #             LIMIT 10

  # scope is cumulative
  Employee.with_scope(
    :find => { :conditions => "name = 'Jamis'" }) do
    Employee.find(:all)   # => SELECT * FROM employees
                          #             WHERE ( salary > 10000 )
                          #             AND ( name = 'Jamis' ))
                          #             LIMIT 10
  end

  # all previous scope is ignored
  Employee.with_exclusive_scope(
    :find => { :conditions => "name = 'Jamis'" }) do
    Employee.find(:all)   # => SELECT * FROM employees
                          #             WHERE (name = 'Jamis')
  end
end

Learn more:

5.4.7. Acts

acts_as_list:

class TodoList < ActiveRecord::Base
    has_many :todo_items, :order => "position"
  end

  class TodoItem < ActiveRecord::Base
    belongs_to :todo_list
    acts_as_list :scope => :todo_list
  end

  todo_list.first.move_to_bottom
  todo_list.last.move_higher

Learn more:

acts_as_tree:

class Category < ActiveRecord::Base
    acts_as_tree :order => "name"
  end

  Example :
  root
   /_ child1
        /_ subchild1
        /_ subchild2

  root      = Category.create("name" => "root")
  child1    = root.children.create("name" => "child1")
  subchild1 = child1.children.create("name" => "subchild1")

  root.parent   # => nil
  child1.parent # => root
  root.children # => [child1]
  root.children.first.children.first # => subchild1

Learn more: http://api.rubyonrails.com/classes/ActiveRecord/Acts/Tree/ClassMethods.html.

5.4.8. Callbacks

Callbacks are hooks into the life cycle of an Active Record object that allows you to trigger logic before or after an alteration of the object state ( "#rubyrails-app-b-table-1">Table B-1).

Table B-1. Active Record object life cycle

Object state

Callback

save

 

valid?

 
 

before_validation

 

before_validation_on_create

validate

 

validate_on_create

 
 

after_validation

 

after_validation_on_create

 

before_save

 

before_create

create

 
 

after_create

 

after_save


Example:

class Subscription < ActiveRecord::Base
  before_create :record_signup
private
  def record_signup
    self.signed_up_on = Date.today
  end
end

class Firm < ActiveRecord::Base
  # Destroys the associated clients and people when the firm is destroyed
  before_destroy { |record| Person.destroy_all "firm_id = #{record.id}"   }
  before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
end

Learn more: http://api.rubyonrails.com/classes/ActiveRecord/Callbacks.html.

5.4.9. Observers

The Observer classes let you extract the functionality of the callbacks:

class CommentObserver < ActiveRecord::Observer
  def after_save(comment)
    Notifications.deliver_comment("admin@do.com", "New comment was posted", comment)
  end
end

  • Store observers in app/model/model_observer.rb.

  • Enable observer by putting this in config/environment.rb:

config.active_record.observers = :comment_observer, :signup_observer

Learn more: http://api.rubyonrails.com/classes/ActiveRecord/Observer.html.

5.4.10. Migration

> ruby script/generate migration MyAddTables

Creates the file db/migrations/001_my_add_tables.rb. The methods up( ) and down( ) change the db schema:

def self.up     # brings db schema to the next version
  create_table :table, :force => true do |t|
    t.column :name, :string
    t.column :age, :integer, { :default => 42 }
    t.column :description, :text
    # :string, :text, :integer, :float, :datetime, :timestamp, :time, :date,
    # :binary, :boolean
  end
  add_column :table, :column, :type
  rename_column :table, :old_name, :new_name
  change_column :table, :column, :new_type
  execute "SQL Statement"
  add_index :table, :column, :unique => true, :name => 'some_name'
  add_index :table, [ :column1, :column2 ]
end

def self.down   # rollbacks changes
  rename_column :table, :new_name, :old_name
  remove_column :table, :column
  drop_table :table
  remove_index :table, :column
end

To execute the migration:

> rake db:migrate
> rake db:migrate VERSION=14
> rake db:migrate RAILS_ENV=production

Learn more:


Previous Page
Next Page