Ruby on Rails[c] Up and Running

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:

  • http://www.codyfauser.com/articles/2006/02/01/using-with_scope-to-refactor-messy-finders

  • http://blog.caboo.se/articles/2006/02/22/nested-with_scope

5.4.7. Acts

acts_as_l ist:

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:

  • http://api.rubyonrails.com/classes/ActiveRecord/Acts/List/ClassMethods.html

  • http://api.rubyonrails.com/classes/ActiveRecord/Acts/List/InstanceMethods.html

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=1 4 > rake db:migrate RAILS_ENV=production

Learn more:

  • http://api.rubyonrails.org/classes/ActiveRecord/Migration.html

  • http://glu.ttono.us/articles/2005/10/27/the-joy-of-migrations

  • http://jamis.jamisbuck.org/articles/2005/09/27/ getting-started -with-activerecord-migrations

Категории