Topics
Part-1
✔
Migrations
✔
Active Record
Part -2
✔
Associations
✔
Validations
✔
Callbacks
Part -3
✔
Testing
Migrations
What is migration?
Migration allows you to define
changes to your database
schema
Why Migration?
•
iteratively improving schema
•
keep things synchronized
Auto generated migrations
The model and scaffold
generators will create migrations
appropriate for adding a new
model.
Creating a model
ruby script/generate model Product name:string description:text
my_app/db/migrate/001_create_products.rb
Migration generated
class CreateProducts < ActiveRecord::Migration
def self.up
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
def self.down
drop_table :products
end
end
self.up / self.down
Creating a Standalone
Migration
ruby script/generate migration
AddPartNumberToProducts part_number:string
my_app/db/migrate/002_AddPartNumberToProducts.rb
Migration generated
class AddPartNumberToProducts < ActiveRecord::Migration
def self.up
add_column :products, :part_number, :string
end
def self.down
remove_column :products, :part_number
end
end
rake db:migrate
also invokes the db:schema:dump task
schema_migrations table
Other Transformations
Creating a table
def self.up
create_table :people do |t|
t.string :username, :null => false
t.string :fname,lname
t.text :notes
t.timestamps
end
end
def self.down
drop_table :people
end
More transformations
rename_column :people, :fname, :first_name
rename_table old_name, new_name
change_column table_name, column_name, type
remove_column table_name, column_name
add_index table_name, column_name
drop_table table_name
More...
change_table :products do |t|
t.remove :description, :name
t.string :part_number
t.index :part_number
t.rename :upccode, :upc_code
end
Rollback
rake db:rollback
rake db:rollback STEP=3
rake db:migrate VERSION=20080906120000
ActiveRecord
ActiveRecord is a
Module
ActiveRecord (note no space) could be classed as a module, since it's an implementation of the Active Record
design pattern. (* need to ask)
One Class Per Table
CREATE TABLE `people` (
`id` int(11) NOT NULL
auto_increment,
`login` varchar(255),
`email` varchar(255),
`password` varchar(40),
`created_at` datetime default NULL,
`updated_at` datetime default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
One Object / instance Per Row
Table columns map to Object
attributes
Some Key Points
mapping class names to table names.
pluralized table names.
integer primary keys.
classname_id as foreign keys.
Why Active Record
for simplicity and ease of use
hides low level implementation
The Basics
Select * from people where id='2'
class Person < ActiveRecord::Base
limit='1'
end
person=Person.find(2)
find method
Examples:
User.find(23)
User.find(:first)
User.find(:all, :offset => 10, :limit => 10)
User.find(:first).articles
Find :select
list = Person.find(:all, :select => "fname, lname")
Find with :conditions
Student.find(:all, :conditions =>
[‘first_name = ? and status = ?’ ,‘mohit’, 1])
Why this ?
Find: Order By
Person.find(:all, :order => ‘updated_at DESC’)
SQL:
SELECT * FROM people ORDER BY created_at;
Find: Group By
Person.find(:all, :group => ‘designation’)
SQL:
SELECT * FROM people GROUP BY designation;
Find: Limit & Offset
Person.find(:all, :limit => 10, :offset => 0)
SQL:
SELECT * FROM people LIMIT 0, 10
Dynamic find methods
person = Person.find_by_fname("mohit")
all_mohit = Person.find_all_by_fname("mohit")
person =
Person.find_by_fname_and_lname("mohit",”jain”)
Creating a new record
user = User.new
user.name = "David"
user.occupation = "Code Artist“
user.save
OR
user =User.new(:name => "David", :occupation
=> "Code Artist")
user.save
OR
user =User.create(:name => "David",
:occupation => "Code Artist")
Update a record
person = Person.find(123)
person.update_attribute(:name, "Barney" )
OR
person= Person.find(123)
person.name = "mohit”
person.save
OR
person=Person.update(12, :name => "jigar" , :email
=> "
[email protected]" )
update_all and update_attribute bypass the validations.
Delete a record
Person.delete(123)
OR
person = Person.find_by_name("Mohit")
person.destroy
Named and Anonymous Scopes
Named scope
class Organization < ActiveRecord::Base
has_many :people
named_scope :active, :conditions => { :active => 'Yes' }
end
class Person < ActiveRecord::Base
belongs_to :organization
end
Usage
Organization.active
Organization.active.people
Named scope example 2
class Order < ActiveRecord::Base
named_scope :last_n_days, lambda { |days| :condition =>
['updated < ?' , days] }
named_scope :checks, :conditions => {:pay_type => :check}
end
orders = Orders.last_n_days(7)
orders = Orders.checks.last_n_days(7)
Anonymous scopes
in_house = Orders.scoped(:conditions => 'email LIKE
"%@pragprog.com"' )
in_house.checks.last_n_days(7)
Thank You.
Associations
Types of associations
➢
belongs_to
➢
has_one
➢
has_many
➢
has_many :through
➢
has_one :through
➢
has_and_belongs_to_many
Whats the need?
Comparison: without and with
class Customer < ActiveRecord::Base
end
class Order < ActiveRecord::Base
end
OR
class Customer < ActiveRecord::Base
has_many :orders, :dependent => :destroy
end
class Order < ActiveRecord::Base
belongs_to :customer
end
The belongs_to Association
The has_one Association
One to One
The has_many Association
One to Many
Its:
has_many :orders
and
has_one :order
has_many :through
has_and_belongs_to_many
has_and_belongs_to_many vs has_many :through
has_and_belongs_to_many
Stories can belong to many categories. Categories can have many stories.
Categories_Stories Table
story_id | category_id
has_many through:-- gives you a third model
Person can subscribe to many magazines. Magazines can have many
subscribers.
Subscriptions Table
person_id | magazine_id | subscription_type |
subscription_length | subscription_date
Many to many
has_one :through
has_one:through*
class Magazine < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :magazine
belongs_to :user
end
class User < ActiveRecord::Base
has_many :subscriptions
has_one :magazine, :through => : subscriptions, :conditions =>
['subscriptions.active = ?', true]
end
Polymorphic Associations
Self refrentional Joins
class Employee < ActiveRecord::Base
has_many :subordinates, :class_name => "Employee", :foreign_key =>
"manager_id"
belongs_to :manager, :class_name => "Employee"
end
When Things Get Saved
class Order < ActiveRecord::Base
has_one :invoice
end
class Invoice < ActiveRecord::Base
belongs_to :order
end
continue...
When things get saved (continue)
If you assign an object to a has_one/has_many
association in an existing object, that associated object
will be automatically saved.
order = Order.find(some_id)
an_invoice = Invoice.new(...)
order.invoice = an_invoice # invoice gets saved
continue...
When things get saved (continue)
If instead you assign a new object to a belongs_to
association, it will never be automatically saved.
order = Order.new(...)
an_invoice.order = order # Order will not be saved here
an_invoice.save # both the invoice and the order get saved
Validations
Validation Helpers
validates_acceptance_of
validates_confirmation_of
validates_length_of
validates_numericality_of
validates_presence_of
Example of validator helper
class Person < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name,
:on => :create,
:message => "is already used"
end
When Does Validation Happen?
new_record?
instance method
Method triggers validations
* create
* create!
* save
* save!
* update
* update_attributes
* update_attributes!
Method skips validations
* update_all
* update_attribute
* update_counters
* save(false)
valid? and invalid?
Validation Errors
errors.add_to_base
errors.add
errors.clear
errors.size
Displaying Validation Errors
error_messages
error_messages_for
Showing error message
<% form_for(@product) do |f| %>
<%= f.error_messages %>
#-----
<% end %>
OR
<%= error_messages_for :product %>
Callbacks
What are callbacks
monitoring the process.
The life cycle of a model object
methods that get called at certain moments of an
object’s lifecycle
Active Record defines 20
callbacks.
18 call backs
Rest two callbacks
after_initialize and after_find
Define callbacks:- in two ways
1.Instance method
2.Handlers
callback as instance method
class class_name < ActiveRecord::Base
# ..
def before_save
# ..
end
end
Callbacks as handlers
class Order < ActiveRecord::Base
before_save:normalize_credit_card
protected
def normalize_credit_card
#......
end
end
Observers
●
Same as callbacks
●
It's about separation of concerns.
●
factor out code that doesn't really belong in
models
class OrderObserver < ActiveRecord::Observer
observe Order
end
Instantiating Observers
config.active_record.observers= :order_observer
( in environment.rb )
Thank you
Testing
Testing type
1. Unit
2. Functional
3. Integration
Unit testing?
What and why
rake db:test:prepare
copy the schema
rake test:units
1.copies the schema
2. runs all the tests in the test/unit
Test file?
require 'test_helper'
class ProductTest < ActiveSupport::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end
Running a test
ruby -I test test/unit/product_test.rb
Understanding with example of Product model
Product Model
validates_presence_of :title, :description, :image_url
validates_numericality_of :price
validate :price_must_be_at_least_a_cent
validates_uniqueness_of :title
validates_format_of :image_url,
:with => %r{\.(gif|jpg|png)$}i,
:message => 'must be a URL for GIF, JPG '
+ 'or PNG image.'
protected
def price_must_be_at_least_a_cent
errors.add(:price, 'should be at least 0.01' ) if price.nil? || price <
0.01
end
Test 1
test "invalid with empty attributes" do
product = Product.new
assert !product.valid?
assert product.errors.invalid?(:title)
assert product.errors.invalid?(:description)
assert product.errors.invalid?(:price)
assert product.errors.invalid?(:image_url)
end
Run the test case
ruby -I test test/unit/product_test.rb
#Loaded suite test/unit/product_test
#Started
#..
#Finished in 0.092314 seconds.
2 tests, 6 assertions, 0 failures, 0 errors
Test 2
test "positive price" do
product = Product.new(:title => "My Book Title" ,
:description => "yyy" ,
:image_url => "zzz.jpg" )
product.price = -1
assert !product.valid?
assert_equal "should be at least 0.01" , product.errors.on(:price)
product.price = 0
assert !product.valid?
assert_equal "should be at least 0.01" , product.errors.on(:price)
product.price = 1
assert product.valid?
end
Fixtures
sample data
YAML fixtures
ruby_book:
title: Programming Ruby
description: Dummy description
price: 1234
image_url: ruby.png
rails_book:
title: Agile Web Development with Rails
description: Dummy description
price: 2345
image_url: rails.png
Naming convention & usage
fixtures :products
Mention this in the ProductTest class
:products => name of table and YML file
Fixture loading
Loading involves three steps:
* Remove any existing data
* Load the fixture data
* Dump the fixture data into a variable in case you want to access
it directly
Test 3 (using fixture)
test "unique title1" do
product = Product.new(:title => products(:ruby_book).title,
:description => "yyy" ,
:price => 1,
:image_url => "fred.gif" )
assert !product.save
Assertions Available
➢
assert( boolean, [msg] )
➢
assert_equal( obj1, obj2, [msg] )
➢
assert_not_equal( obj1, obj2, [msg] )
➢
assert_same( obj1, obj2, [msg] )
➢
assert_instance_of( class, obj, [msg] )
Thank you
WEB v2.0