5.4. Active
Record
5.4.1. Automated
Mapping
Automatically maps:
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):
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)
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
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:
|