Contents 

Ruby on Rails: Up and Running
Table of Contents
Copyright
Preface
Chapter 1. Zero to Sixty: Introducing Rails
Section 1.1. Rails Strengths
Section 1.2. Putting Rails into Action
Section 1.3. Organization
Section 1.4. The Web Server
Section 1.5. Creating a Controller
Section 1.6. Building a View
Section 1.7. Tying the Controller to the View
Section 1.8. Under the Hood
Section 1.9. What's Next?
Chapter 2. Active Record Basics
Section 2.1. Active Record Basics
Section 2.2. Introducing Photo Share
Section 2.3. Schema Migrations
Section 2.4. Basic Active Record Classes
Section 2.5. Attributes
Section 2.6. Complex Classes
Section 2.7. Behavior
Section 2.8. Moving Forward
Chapter 3. Active Record Relationships
Section 3.1. belongs_to
Section 3.2. has_many
Section 3.3. has_one
Section 3.4. What You Haven't Seen
Section 3.5. Looking Ahead
Chapter 4. Scaffolding
Section 4.1. Using the Scaffold Method
Section 4.2. Replacing Scaffolding
Section 4.3. Generating Scaffolding Code
Section 4.4. Moving Forward
Chapter 5. Extending Views
Section 5.1. The Big Picture
Section 5.2. Seeing Real Photos
Section 5.3. View Templates
Section 5.4. Setting the Default Root
Section 5.5. Stylesheets
Section 5.6. Hierarchical Categories
Section 5.7. Styling the Slideshows
Chapter 6. Ajax
Section 6.1. How Rails Implements Ajax
Section 6.2. Playing a Slideshow
Section 6.3. Using Drag-and-Drop to Reorder Slides
Section 6.4. Drag and Drop Everything (Almost Everything)
Section 6.5. Filtering by Category
Chapter 7. Testing
Section 7.1. Background
Section 7.2. Ruby's Test::Unit
Section 7.3. Testing in Rails
Section 7.4. Wrapping Up
Appendix A. Installing Rails
Section 1.1. Windows
Section 2.1. OS X
Section 3.1. Linux
Appendix B. Quick Reference
Section 5.1. General
Section 5.2. Testing
Section 5.3. RJS (Ruby JavaScript)
Section 5.4. Active Record
Section 5.5. Controllers
Section 5.6. Views
Section 5.7. Ajax
Section 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 for all.

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 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 (Figure B-3)

Figure B-3. Through model

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

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

class Book < ActiveRecord::Base
  has_one :authorship
end

@author = Author.find :first
@author.authorships.collect { |a| a.book } # selects all books that the author's
                                           # authorships belong to.
@author.books                              # selects all books 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 (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