Topics

Part-1

Migrations Active Record

Associations ✔ Validations ✔ Callbacks

Part -2

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' limit='1'

class Person < ActiveRecord::Base 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@vinsol.com" ) => "jigar" , :email

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 end

errors.add(:price, 'should be at least 0.01' ) if price.nil? || price < 0.01

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

Sign up to vote on this title
UsefulNot useful