Sie sind auf Seite 1von 104

Topics

Part-1

Part-1

Migrations

Migrations

Active

Active Record

Record

Part Part -2-2

Associations

Associations

Validations

Validations

Callbacks

Callbacks

Part Part -3-3

Testing

Testing

Migrations

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

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

One Class Per Table  CREATE TABLE `people` ( `id` int(11) NOT NULL auto_increment, `login` varchar(255),

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

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 belongs_to Association

The has_one Association

The has_one Association

One to One

The has_many Association

The has_many Association

One to Many

Its:

has_many :orders

and

has_one :order

has_many :through

has_many :through

has_and_belongs_to_many

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

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

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

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

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

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

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 '

protected

+ 'or PNG image.'

def price_must_be_at_least_a_cent

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

end

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" ,

assert !product.save

:price

=>

1,

:image_url

=> "fred.gif" )

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