activerecord 2.3

215
ActiveRecord 2.3 Reuven M. Lerner October 20 th , 2010 1 Monday, October 25, 2010

Upload: reuven-lerner

Post on 26-Jan-2015

998 views

Category:

Technology


1 download

DESCRIPTION

Slides from a lecture I just gave on ActiveRecord 2.3. Describes configuration, methods, CRUD, finders, updating, associations, and a bunch of things that I wish I had known when I started with ActiveRecord.

TRANSCRIPT

Page 1: ActiveRecord 2.3

ActiveRecord 2.3Reuven M. Lerner

October 20th, 2010

1Monday, October 25, 2010

Page 2: ActiveRecord 2.3

What is a database?

Database

Store data confidently

Retrieve data flexibly

2Monday, October 25, 2010

Page 3: ActiveRecord 2.3

Relational databases

Define tables, store data in them

Database

Retrieve data from related tables

3Monday, October 25, 2010

Page 4: ActiveRecord 2.3

Database communication

SQL goes hereCREATE TABLE

INSERTUPDATEDELETE

Database

4Monday, October 25, 2010

Page 5: ActiveRecord 2.3

SQL is great! But...

• It adds a second language to existing Ruby

• It’s a totally different paradigm

• We want to work with Ruby objects!

• Incidentally, SQL-in-something-else was the paradigm for years...

5Monday, October 25, 2010

Page 6: ActiveRecord 2.3

Solution: ORM (object-relational mapper)

ORMRuby

Database

6Monday, October 25, 2010

Page 7: ActiveRecord 2.3

Solution: ORM (object-relational mapper)

ORMRuby

Database

Write in Ruby

6Monday, October 25, 2010

Page 8: ActiveRecord 2.3

Solution: ORM (object-relational mapper)

ORMRuby

Database

Write in Ruby

ORM translates to SQL, sends to database

6Monday, October 25, 2010

Page 9: ActiveRecord 2.3

Solution: ORM (object-relational mapper)

ORMRuby

Database

Write in Ruby

ORM translates to SQL, sends to database

Results goto ORM

6Monday, October 25, 2010

Page 10: ActiveRecord 2.3

Solution: ORM (object-relational mapper)

ORMRuby

Database

Write in Ruby

ORM translates to SQL, sends to database

Results goto ORM

ORM turns results into Ruby objects

6Monday, October 25, 2010

Page 11: ActiveRecord 2.3

ActiveRecord

• By far, the most popular ORM for Ruby

• Not the only one — e.g., DataMapper

• We work with objects whenever possible

• We define as little as possible

• Our objects act as intelligent representations of what’s in the database

7Monday, October 25, 2010

Page 12: ActiveRecord 2.3

ActiveRecord and Rails

• The idea of ActiveRecord preceded Rails

• Mark Fowler, from Thoughtworks (and the “Refactoring” book)

• You can use ActiveRecord without Rails

• ActiveRecord was written for Rails

• And let’s face it — nearly everyone uses ActiveRecord within Rails applications

8Monday, October 25, 2010

Page 13: ActiveRecord 2.3

Opinionated!

• ActiveRecord has some ideas about how your application should work

• If you work in the same way, the work is both easy and fun

• If you try to work in a different way, it’ll be very difficult and frustrating

• “Syntactic vinegar”

9Monday, October 25, 2010

Page 14: ActiveRecord 2.3

Using ActiveRecord

• Typically, we subclass ActiveRecord::Base in each of our model files

• That is, all of the classes defined in app/models/*.rb

• You don’t have to inherit from ActiveRecord::Base, of course! You can even mix and match with different models

10Monday, October 25, 2010

Page 15: ActiveRecord 2.3

Version warning!

• Everything that I’m about to show is for Rails 2.3.8

• That’s the version we’re using here

• But the latest official release is 3.0, and much online documentation will reflect that

• So when you look online, check the version number!

11Monday, October 25, 2010

Page 16: ActiveRecord 2.3

Person model

class Person < ActiveRecord::Base

end

12Monday, October 25, 2010

Page 17: ActiveRecord 2.3

Person model

class Person < ActiveRecord::Base

end

Singular class name

12Monday, October 25, 2010

Page 18: ActiveRecord 2.3

Person model

class Person < ActiveRecord::Base

end

Singular class name Standard parent class

12Monday, October 25, 2010

Page 19: ActiveRecord 2.3

We can already start!

~/Downloads/foo$ ./script/console

Loading development environment (Rails 2.3.8)

>> Person

=> Person(Table doesn't exist)

13Monday, October 25, 2010

Page 20: ActiveRecord 2.3

Object ≠ Table

• The object exists, but the table doesn’t.

• So let’s create the table!

14Monday, October 25, 2010

Page 21: ActiveRecord 2.3

Non-Rails approach

• Create the table

• Keep the definition in a file

• Tell everyone that you’ve created the table

• When you make changes to the table, update the file and tell everyone again

• Hope that your changes don’t clash!

15Monday, October 25, 2010

Page 22: ActiveRecord 2.3

Migrations

• Ruby program that describes how to change the database schema

• Add tables

• Rename columns

• Remove columns

• Set defaults

16Monday, October 25, 2010

Page 23: ActiveRecord 2.3

Platform independent

• Because migrations are written in Ruby, they’re platform independent

• Well, mostly...

• ... they tend to use MySQL ideas and semantics

• No foreign keys, for example

• Works well enough with most databases

17Monday, October 25, 2010

Page 24: ActiveRecord 2.3

Create a migration

./script/generate migration create_person

./script/generate model person

./script/generate model person first_name:string last_name:string email:string

18Monday, October 25, 2010

Page 25: ActiveRecord 2.3

The migration file

• Migrations are in db/migrate

• Each has a unique filename, and a timestamp

• (The odds of two developers creating migrations at the same second, and with the same name, are slim)

19Monday, October 25, 2010

Page 26: ActiveRecord 2.3

class CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :first_name t.string :last_name t.string :email

t.timestamps end end

def self.down drop_table :people endend

20Monday, October 25, 2010

Page 27: ActiveRecord 2.3

class CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :first_name t.string :last_name t.string :email

t.timestamps end end

def self.down drop_table :people endend

Migrate forward

20Monday, October 25, 2010

Page 28: ActiveRecord 2.3

class CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :first_name t.string :last_name t.string :email

t.timestamps end end

def self.down drop_table :people endend

Migrate forward

Migrate backward

20Monday, October 25, 2010

Page 29: ActiveRecord 2.3

class CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :first_name t.string :last_name t.string :email

t.timestamps end end

def self.down drop_table :people endend

Migrate forward

Migrate backward

Data types

20Monday, October 25, 2010

Page 30: ActiveRecord 2.3

class CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :first_name t.string :last_name t.string :email

t.timestamps end end

def self.down drop_table :people endend

Migrate forward

Migrate backward

Data types

Block!

20Monday, October 25, 2010

Page 31: ActiveRecord 2.3

Run the migration

• To run all pending migrations:

rake db:migrate

• To run the “down” method until we get to a certain migration:

rake db:migrate VERSION=20101013095549

21Monday, October 25, 2010

Page 32: ActiveRecord 2.3

Changing migrations

• Changing the migration file is OK!

• Add indexes, defaults, etc.

• But don’t change a table structure by editing a migration

• Rather, create a new migration that adds/renames/deletes the column

• Migrations are additive (and addictive)

22Monday, October 25, 2010

Page 33: ActiveRecord 2.3

rake db:migrate

• A table, schema_migrations, is automatically created in the database

• It has one column, “version”, which contains one row for each run migration

• When you run db:migrate, it runs all of the migrations that are not in the table

• This allows for merges between developers

23Monday, October 25, 2010

Page 34: ActiveRecord 2.3

Migrations and models

• Column names and types are defined in the database, not in the model

• This means that column names and types are set in the migrations

• The easiest way to find out what columns are in an ActiveRecord model class?

• The console, of course!

24Monday, October 25, 2010

Page 35: ActiveRecord 2.3

Migrating~/Downloads/foo$ rake db:migrate (in /Users/reuven/Downloads/foo)== CreatePeople: migrating ===================================================-- create_table(:people) -> 0.2094s== CreatePeople: migrated (0.2097s) ==========================================

~/Downloads/foo$ rake db:migrate (in /Users/reuven/Downloads/foo)

25Monday, October 25, 2010

Page 36: ActiveRecord 2.3

Migrating~/Downloads/foo$ rake db:migrate (in /Users/reuven/Downloads/foo)== CreatePeople: migrating ===================================================-- create_table(:people) -> 0.2094s== CreatePeople: migrated (0.2097s) ==========================================

~/Downloads/foo$ rake db:migrate (in /Users/reuven/Downloads/foo)

We’re up to date, so nothing happens

25Monday, October 25, 2010

Page 37: ActiveRecord 2.3

Let’s check again

>> Person

=> Person(id: integer, first_name: string, last_name: string, email: string, created_at: datetime, updated_at: datetime)

26Monday, October 25, 2010

Page 38: ActiveRecord 2.3

Let’s check again

>> Person

=> Person(id: integer, first_name: string, last_name: string, email: string, created_at: datetime, updated_at: datetime)

Hey, where did “id”come from?

26Monday, October 25, 2010

Page 39: ActiveRecord 2.3

Let’s check again

>> Person

=> Person(id: integer, first_name: string, last_name: string, email: string, created_at: datetime, updated_at: datetime)

Hey, where did “id”come from?

And whatabout these?

26Monday, October 25, 2010

Page 40: ActiveRecord 2.3

Assumptions

• Convention over configuration!

• Tables are plural, classes are singular

• class “Person”, but table “People”

• Primary key is always called “id”

• created_at, updated_at are set automatically by ActiveRecord upon creation or update to the record

27Monday, October 25, 2010

Page 41: ActiveRecord 2.3

Wait! Where’s the DB?

• When did we tell Rails how to connect to the database?

• Look in config/database.yml

• The only configuration you need

• It tells ActiveRecord what database you have, and how to connect...

• ... for each environment

28Monday, October 25, 2010

Page 42: ActiveRecord 2.3

development: adapter: postgresql encoding: unicode database: foo_development pool: 5 username: reuven password: reuven

29Monday, October 25, 2010

Page 43: ActiveRecord 2.3

Wait, that’s it?

• Well, mostly.

• There are additional (optimal) parameters

• And some configuration is done in the environment config files

• We’ll ignore these for now

30Monday, October 25, 2010

Page 44: ActiveRecord 2.3

Console reloading

• If you use the console (and you should!) then modifying ActiveRecord models may cause issues

• Use “reload!” to reload the environment

• You’ll then need to re-create all objects

• Better than having invalid objects...

31Monday, October 25, 2010

Page 45: ActiveRecord 2.3

How many records?

?> Person.count

=> 0

32Monday, October 25, 2010

Page 46: ActiveRecord 2.3

OK, we’ll add one>> p = Person.new

=> #<Person id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>

>> p.save!

=> true

>> Person.count

=> 1

33Monday, October 25, 2010

Page 47: ActiveRecord 2.3

Wait a second!

• That person record we just created is pretty useless.

• We really don’t want nameless people in our database.

• We could (and should!) update the database definition with a new migration

• But we’ll ignore that for now. Don’t tell!

34Monday, October 25, 2010

Page 48: ActiveRecord 2.3

Better creation

• The “new” method creates a new object, but doesn’t save it to the database

• This is why it has nil for an ID

• After you save, it has an ID

• To create an object and save it right away, use the “create” method instead

• Both “new” and “create” return the object

35Monday, October 25, 2010

Page 49: ActiveRecord 2.3

save! and create!

• save returns true or false

• create returns the object or false

• save! and create! are the same as their “quiet” counterparts upon success

• But raise an exception if there is a problem

36Monday, October 25, 2010

Page 50: ActiveRecord 2.3

Missing attributes?

• If you fail to set an attribute, then Ruby will pass it nil

• However, if you have a default value set in the database, then it’ll get that

• Don’t set created_at and updated_at; those are set automatically

37Monday, October 25, 2010

Page 51: ActiveRecord 2.3

The Java way>> p = Person.new => #<Person id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>>> p.first_name = 'Reuven' => "Reuven">> p.last_name = 'Lerner' => "Lerner">> p.email = '[email protected]' => "[email protected]">> p.save! => true

38Monday, October 25, 2010

Page 52: ActiveRecord 2.3

The Ruby way

>> p = Person.new(:first_name => 'Reuven', :last_name => 'Lerner', :email => '[email protected]')

=> #<Person id: nil, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: nil, updated_at: nil>

39Monday, October 25, 2010

Page 53: ActiveRecord 2.3

The Ruby way

>> p = Person.new(:first_name => 'Reuven', :last_name => 'Lerner', :email => '[email protected]')

=> #<Person id: nil, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: nil, updated_at: nil>

Hash of name-value pairs

39Monday, October 25, 2010

Page 54: ActiveRecord 2.3

It’s not a free-for-all>> p10 = Person.new(:eye_color => 'brown')

ActiveRecord::UnknownAttributeError: unknown attribute: eye_color

from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2906:in `assign_attributes'

from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2902:in `each'

from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2902:in `assign_attributes'

from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2775:in `attributes='

from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2473:in `initialize'

from (irb):44:in `new'

from (irb):44

>>

40Monday, October 25, 2010

Page 55: ActiveRecord 2.3

It’s not a free-for-all>> p10 = Person.new(:eye_color => 'brown')

ActiveRecord::UnknownAttributeError: unknown attribute: eye_color

from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2906:in `assign_attributes'

from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2902:in `each'

from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2902:in `assign_attributes'

from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2775:in `attributes='

from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2473:in `initialize'

from (irb):44:in `new'

from (irb):44

>>

40Monday, October 25, 2010

Page 56: ActiveRecord 2.3

Updating fields

>> p.first_name = 'Bibi'

=> "Bibi"

>> p.save

=> true

41Monday, October 25, 2010

Page 57: ActiveRecord 2.3

Updating fields

>> p.first_name = 'Bibi'

=> "Bibi"

>> p.save

=> true

If you don’t save the object,then you haven’t changed it

in the database!

41Monday, October 25, 2010

Page 58: ActiveRecord 2.3

update_attributes

• It’s easier and safer to update both the object and the database simultaneously

>> p.update_attributes(

:first_name => 'Bibi')

=> true

42Monday, October 25, 2010

Page 59: ActiveRecord 2.3

Multiple attributes

• p.update_attributes(

:first_name => 'Bibi',

:last_name => 'Netanyahu')

43Monday, October 25, 2010

Page 60: ActiveRecord 2.3

Avoid this!

• update_attribute

• singular (not plural)

• takes two params (attribute, value), rather than a hash

• doesn’t go through any Rails validators!

• From my perspective, this method is dangerous, and should be avoided

44Monday, October 25, 2010

Page 61: ActiveRecord 2.3

By the way...

• Remember our model file?

• It’s still empty.

• And yet, it allows us to create, save, and update models naturally and easily.

• Pretty cool, eh?

45Monday, October 25, 2010

Page 62: ActiveRecord 2.3

Semi-protection

• attr_protected :first_name

• first_name cannot be changed with update_attributes, but it can be updated with a setter or update_attribute

• attr_accessible: Lists those attributes that are not protected

attr_accessible :email, :zip_code

46Monday, October 25, 2010

Page 63: ActiveRecord 2.3

find

• This is the workhorse of ActiveRecord

• The “find” method is really a lot of different methods with a single interface

47Monday, October 25, 2010

Page 64: ActiveRecord 2.3

find by ID

Person.find(3)

• If there is a Person object with ID = 3, that one object is returned

• If no object exists, an exception is raised

• Yes, this is annoying

Person.find(2, 6) # returns array

48Monday, October 25, 2010

Page 65: ActiveRecord 2.3

Get them all!

Person.find(:all)

Person.all # same thing

49Monday, October 25, 2010

Page 66: ActiveRecord 2.3

One object or many?

• Simple find with an ID — one object (or raises an exception)

• find with multiple IDs — returns an array of objects, or an exception if even one ID doesn’t exist

• all — always returns an array, and perhaps even an empty array

50Monday, October 25, 2010

Page 67: ActiveRecord 2.3

Conditions

• We can add conditions

• turned into WHERE clause in SQL

• You’ll almost always want conditions

51Monday, October 25, 2010

Page 68: ActiveRecord 2.3

Conditions, Ruby style>> Person.all(:conditions => {:first_name => 'foo'})=> []>> Person.all(:conditions => {:first_name => 'Reuven'})=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

52Monday, October 25, 2010

Page 69: ActiveRecord 2.3

Conditions, Ruby style>> Person.all(:conditions => {:first_name => 'foo'})=> []>> Person.all(:conditions => {:first_name => 'Reuven'})=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

Hash

52Monday, October 25, 2010

Page 70: ActiveRecord 2.3

Conditions, SQL style

>> Person.all(:conditions => "first_name = 'Reuven'")

=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

53Monday, October 25, 2010

Page 71: ActiveRecord 2.3

Conditions, SQL style

>> Person.all(:conditions => "first_name = 'Reuven'")

=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

String

53Monday, October 25, 2010

Page 72: ActiveRecord 2.3

Conditions, SQL style

>> Person.all(:conditions => "first_name = '#{@person.first_name}'")

=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

54Monday, October 25, 2010

Page 73: ActiveRecord 2.3

Conditions, SQL style

>> Person.all(:conditions => "first_name = '#{@person.first_name}'")

=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

Variable

54Monday, October 25, 2010

Page 74: ActiveRecord 2.3

Don’t do this!

• SQL injection attacks can happen

• There’s no reason for it

• If someone hands you a string containing a quote mark and then some SQL, it could be executed if you’re not careful

• Injection attacks should no longer occur!

• (This was true as far back as 1996...)

55Monday, October 25, 2010

Page 75: ActiveRecord 2.3

XKCD

56Monday, October 25, 2010

Page 76: ActiveRecord 2.3

Sweden, last month

57Monday, October 25, 2010

Page 77: ActiveRecord 2.3

Interpolating parameters

• Instead of:>> Person.all(:conditions => "first_name = '#{@person.first_name}'")

• Use:>> Person.all(:conditions => ["first_name = ?", @person.first_name])

58Monday, October 25, 2010

Page 78: ActiveRecord 2.3

Interpolating parameters

• Instead of:>> Person.all(:conditions => "first_name = '#{@person.first_name}'")

• Use:>> Person.all(:conditions => ["first_name = ?", @person.first_name])

Array of strings

58Monday, October 25, 2010

Page 79: ActiveRecord 2.3

Interpolating parameters

• Instead of:>> Person.all(:conditions => "first_name = '#{@person.first_name}'")

• Use:>> Person.all(:conditions => ["first_name = ?", @person.first_name]) Question mark

Array of strings

58Monday, October 25, 2010

Page 80: ActiveRecord 2.3

Interpolating parameters

• Instead of:>> Person.all(:conditions => "first_name = '#{@person.first_name}'")

• Use:>> Person.all(:conditions => ["first_name = ?", @person.first_name]) Question mark

No quotes!

Array of strings

58Monday, October 25, 2010

Page 81: ActiveRecord 2.3

Ordering results

• Remember: A relational database doesn’t store its rows in any order

• If you don’t specify an order, you will almost certainly be surprised

59Monday, October 25, 2010

Page 82: ActiveRecord 2.3

Ascending order>> Person.all(:conditions => "first_name = 'Reuven'", :order => 'created_at')

=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

60Monday, October 25, 2010

Page 83: ActiveRecord 2.3

Descending order>> Person.all(:conditions => "first_name = 'Reuven'", :order => 'created_at DESC')

=> [#<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">, #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">]

61Monday, October 25, 2010

Page 84: ActiveRecord 2.3

Combining

>> Person.all(:order => 'last_name ASC, created_at DESC')

=> [#<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">, #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, ...

62Monday, October 25, 2010

Page 85: ActiveRecord 2.3

Where to order?

• The database is almost certainly faster at ordering

• So invoking #all and then #sort is probably not a good idea

• In fact, the database is generally faster at filtering, too — so conditions are better than #all and #select

63Monday, October 25, 2010

Page 86: ActiveRecord 2.3

first

• Returns the first row (object) from the database — or nil, if none was found

Person.first

• Of course, without an order, you don’t know which row you’ll get!

Person.first(:order => 'created_at')

64Monday, October 25, 2010

Page 87: ActiveRecord 2.3

Transforming results

• Person.all returns an array — so you can invoke whatever you want on that array!

• Get an array of last names:

Person.all.map {|p| p.last_name}

65Monday, October 25, 2010

Page 88: ActiveRecord 2.3

Iterate over results

Person.all.each {|p| puts p.inspect}

Person.all.each {|p| p.update_attributes(:admin => false)}

66Monday, October 25, 2010

Page 89: ActiveRecord 2.3

Dynamic finders

• Remember method_missing? ActiveRecord uses this to provide “dynamic finders” — versions of find that can make our code more readable

• If you have a row named xxx, you can say find_by_xxx or find_all_by_xxx

67Monday, October 25, 2010

Page 90: ActiveRecord 2.3

find_by_first_name>> Person.find_by_first_name('Reuven')=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">

>> Person.find_all_by_first_name('Reuven')=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

68Monday, October 25, 2010

Page 91: ActiveRecord 2.3

find_by_first_name>> Person.find_by_first_name('Reuven')=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">

>> Person.find_all_by_first_name('Reuven')=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

Many find thiseasier to read

68Monday, October 25, 2010

Page 92: ActiveRecord 2.3

find_by_first_name>> Person.find_by_first_name('Reuven')=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">

>> Person.find_all_by_first_name('Reuven')=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

Many find thiseasier to read

Add “all” toget an array

68Monday, October 25, 2010

Page 93: ActiveRecord 2.3

Negative results

>> Person.find_by_first_name('blah')

=> nil

>> Person.find_all_by_first_name('blah')

=> []

69Monday, October 25, 2010

Page 94: ActiveRecord 2.3

Negative results

>> Person.find_by_first_name('blah')

=> nil

>> Person.find_all_by_first_name('blah')

=> []

No exception!

69Monday, October 25, 2010

Page 95: ActiveRecord 2.3

Multiple attributes

>> Person.find_by_first_name_and_last_name('Reuven', 'Lerner')

=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">

70Monday, October 25, 2010

Page 96: ActiveRecord 2.3

find_or_create_by_...

• Use a dynamic finder... and if you don’t find a result, then create a new object

• If the object fails validations, return a new (unsaved) object

Person.find_or_create_by_first_name('Reuven');

Person.find_or_create_by_first_name('Reuven', last_name => 'Lerner');

71Monday, October 25, 2010

Page 97: ActiveRecord 2.3

Caching

• ActiveRecord caches results on a per-session basis

• So if you have already retrieved an object with the current request, it’ll be cached for further retrievals

• This doesn’t happen across requests, though

72Monday, October 25, 2010

Page 98: ActiveRecord 2.3

Look in the log!

• In the development environment, you’ll see your queries rewritten using SQL.

• This is a great way to see what is happening in the underlying database

73Monday, October 25, 2010

Page 99: ActiveRecord 2.3

Associations

• ActiveRecord really shines when it comes to “associations”

• The object equivalent of primary/foreign keys connecting database tables

74Monday, October 25, 2010

Page 100: ActiveRecord 2.3

Pets!

• Let’s make it possible for people to have pets

./script/generate model pet animal_type:string name:string person_id:integer

rake db:migrate

75Monday, October 25, 2010

Page 101: ActiveRecord 2.3

Pets!

• Let’s make it possible for people to have pets

./script/generate model pet animal_type:string name:string person_id:integer

rake db:migrateEach pet belongs to one person

75Monday, October 25, 2010

Page 102: ActiveRecord 2.3

belongs_to

• Declaration (aka a class method) in the model file

• Meaning: There is a foreign key pointing from self to another object, via its ID

• The name of the foreign key is (by default) the other object’s name (singular) with _id

76Monday, October 25, 2010

Page 103: ActiveRecord 2.3

Change Pet.rb

class Pet < ActiveRecord::Base

belongs_to :person

end

77Monday, October 25, 2010

Page 104: ActiveRecord 2.3

What does this do?

• Doesn’t create the foreign key in the DB

• Doesn’t set the foreign key

• Doesn’t enforce anything

• It does, however, define a bunch of methods that we can now use on a pet

78Monday, October 25, 2010

Page 105: ActiveRecord 2.3

Creating a pet>> spot = Pet.new(:animal_type => 'dog', :name => 'Spot', :person_id => Person.first.id)=> #<Pet id: nil, animal_type: "dog", name: "Spot", person_id: 6, created_at: nil, updated_at: nil>>> spot = Pet.new(:animal_type => 'dog', :name => 'Spot', :person => Person.first)=> #<Pet id: nil, animal_type: "dog", name: "Spot", person_id: 6, created_at: nil, updated_at: nil>

79Monday, October 25, 2010

Page 106: ActiveRecord 2.3

Creating a pet>> spot = Pet.new(:animal_type => 'dog', :name => 'Spot', :person_id => Person.first.id)=> #<Pet id: nil, animal_type: "dog", name: "Spot", person_id: 6, created_at: nil, updated_at: nil>>> spot = Pet.new(:animal_type => 'dog', :name => 'Spot', :person => Person.first)=> #<Pet id: nil, animal_type: "dog", name: "Spot", person_id: 6, created_at: nil, updated_at: nil>

Here we use the ID

79Monday, October 25, 2010

Page 107: ActiveRecord 2.3

Creating a pet>> spot = Pet.new(:animal_type => 'dog', :name => 'Spot', :person_id => Person.first.id)=> #<Pet id: nil, animal_type: "dog", name: "Spot", person_id: 6, created_at: nil, updated_at: nil>>> spot = Pet.new(:animal_type => 'dog', :name => 'Spot', :person => Person.first)=> #<Pet id: nil, animal_type: "dog", name: "Spot", person_id: 6, created_at: nil, updated_at: nil>

Here we use the ID

Here we use the object

79Monday, October 25, 2010

Page 108: ActiveRecord 2.3

New “person” method!

>> spot.person

=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">

80Monday, October 25, 2010

Page 109: ActiveRecord 2.3

Pet e-mail (p-mail?)

• Pets use their owner’s e-mail address

• One way is to define a new method on Pet.rb

• Every instance of a pet will now respond to the “email” method, and return the owner’s e-mail address

81Monday, October 25, 2010

Page 110: ActiveRecord 2.3

With our email method

class Pet < ActiveRecord::Base

belongs_to :person

def email

person.email

end

end

82Monday, October 25, 2010

Page 111: ActiveRecord 2.3

Easier: Delegation!

class Pet < ActiveRecord::Base

belongs_to :person

delegate :email, :to => :person

end

83Monday, October 25, 2010

Page 112: ActiveRecord 2.3

By the way...>> rover = Pet.new

=> #<Pet id: nil, animal_type: nil, name: nil, person_id: nil, created_at: nil, updated_at: nil>

>> rover.email

RuntimeError: email delegated to person.email, but person is nil: #<Pet id: nil, animal_type: nil, name: nil, person_id: nil, created_at: nil, updated_at: nil>

84Monday, October 25, 2010

Page 113: ActiveRecord 2.3

By the way...>> rover = Pet.new

=> #<Pet id: nil, animal_type: nil, name: nil, person_id: nil, created_at: nil, updated_at: nil>

>> rover.email

RuntimeError: email delegated to person.email, but person is nil: #<Pet id: nil, animal_type: nil, name: nil, person_id: nil, created_at: nil, updated_at: nil>We can’t delegate to nil!

84Monday, October 25, 2010

Page 114: ActiveRecord 2.3

Avoid nil problems

class Pet < ActiveRecord::Base

belongs_to :person

delegate :email, :to => :person,

:allow_nil => true

end

85Monday, October 25, 2010

Page 115: ActiveRecord 2.3

Problem solved

>> rover = Pet.new

=> #<Pet id: nil, animal_type: nil, name: nil, person_id: nil, created_at: nil, updated_at: nil>

>> rover.email

=> nil

86Monday, October 25, 2010

Page 116: ActiveRecord 2.3

The other side

• So far, pets know about their owners...

• ... but owners don’t know about their pets>> spot.person.pets

NoMethodError: undefined method `pets' for #<ActiveRecord::Associations::BelongsToAssociation:0x1089bba50>

87Monday, October 25, 2010

Page 117: ActiveRecord 2.3

one-to-one: has_one

If each person can have one pet, then we could change person.rb to read:

class Person < ActiveRecord::Base

has_one :pet

end

88Monday, October 25, 2010

Page 118: ActiveRecord 2.3

Using has_one>> p = Person.first

=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">

>> p.pet

=> #<Pet id: 1, animal_type: "dog", name: "Spot", person_id: 6, created_at: "2010-10-14 07:43:59", updated_at: "2010-10-14 07:43:59">

89Monday, October 25, 2010

Page 119: ActiveRecord 2.3

Using has_one>> p = Person.first

=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">

>> p.pet

=> #<Pet id: 1, animal_type: "dog", name: "Spot", person_id: 6, created_at: "2010-10-14 07:43:59", updated_at: "2010-10-14 07:43:59">

Each person has a pet

89Monday, October 25, 2010

Page 120: ActiveRecord 2.3

How does Rails do it?

• It does what we would do manually — looks for all pets with our primary key value

Pet Load (1.2ms) SELECT * FROM "pets" WHERE ("pets".person_id = 6) LIMIT 1

90Monday, October 25, 2010

Page 121: ActiveRecord 2.3

has_many

• More interesting, and trickier, is has_many

• Perhaps we have many pets!

class Person < ActiveRecord::Base

has_many :pets

end

91Monday, October 25, 2010

Page 122: ActiveRecord 2.3

has_many

• More interesting, and trickier, is has_many

• Perhaps we have many pets!

class Person < ActiveRecord::Base

has_many :pets

end Notice plural!

91Monday, October 25, 2010

Page 123: ActiveRecord 2.3

has_many

• With a has_many relationship in place, we get a method (plural!) for pets

• It always returns an array (perhaps empty)

>> p.pets

=> [#<Pet id: 1, animal_type: "dog", name: "Spot", person_id: 6, created_at: "2010-10-14 07:43:59", updated_at: "2010-10-14 07:43:59">]

92Monday, October 25, 2010

Page 124: ActiveRecord 2.3

Adding

>> p.pets => []>> p.pets << Pet.new(:animal_type => 'fish', :name => "Charlie") => [#<Pet id: 2, animal_type: "fish", name: "Charlie", person_id: 7, created_at: "2010-10-14 09:21:41", updated_at: "2010-10-14 09:21:41">]>> Pet.count => 2

93Monday, October 25, 2010

Page 125: ActiveRecord 2.3

Adding

>> p.pets => []>> p.pets << Pet.new(:animal_type => 'fish', :name => "Charlie") => [#<Pet id: 2, animal_type: "fish", name: "Charlie", person_id: 7, created_at: "2010-10-14 09:21:41", updated_at: "2010-10-14 09:21:41">]>> Pet.count => 2

Even though we used “new”, the object was saved

93Monday, October 25, 2010

Page 126: ActiveRecord 2.3

Adding

>> p.pets => []>> p.pets << Pet.new(:animal_type => 'fish', :name => "Charlie") => [#<Pet id: 2, animal_type: "fish", name: "Charlie", person_id: 7, created_at: "2010-10-14 09:21:41", updated_at: "2010-10-14 09:21:41">]>> Pet.count => 2

Even though we used “new”, the object was saved

Automatically used our person

93Monday, October 25, 2010

Page 127: ActiveRecord 2.3

Array fun

>> Person.first.pets.select {|p| p.animal_type == 'fish'}

=> []

>> Person.first.pets.select {|p| p.animal_type == 'dog'}

=> [#<Pet id: 1, animal_type: "dog", name: "Spot", person_id: 6, created_at: "2010-10-14 07:43:59", updated_at: "2010-10-14 07:43:59">]

94Monday, October 25, 2010

Page 128: ActiveRecord 2.3

many-to-many

• What if each person can have multiple pets, and each pet can have multiple owners?

• For that, we need a “join” table

95Monday, October 25, 2010

Page 129: ActiveRecord 2.3

Join table

People Person-Pets

Pets

foreign keys:person_id

pet_id

96Monday, October 25, 2010

Page 130: ActiveRecord 2.3

Migration

./script/generate model person_pet person_id:integer pet_id:integer

97Monday, October 25, 2010

Page 131: ActiveRecord 2.3

person_pet.rb

class PersonPet < ActiveRecord::Base

belongs_to :person

belongs_to :pet

end

98Monday, October 25, 2010

Page 132: ActiveRecord 2.3

Update person.rb

class Person < ActiveRecord::Base

has_many :person_pets

has_many :pets, :through => :person_pets

end

99Monday, October 25, 2010

Page 133: ActiveRecord 2.3

Update person.rb

class Person < ActiveRecord::Base

has_many :person_pets

has_many :pets, :through => :person_pets

end has_many :throughconnects our models

via the join table

99Monday, October 25, 2010

Page 134: ActiveRecord 2.3

Update pet.rb

class Pet < ActiveRecord::Base

has_many :person_pets

has_many :people, :through => :person_pets

end

100Monday, October 25, 2010

Page 135: ActiveRecord 2.3

Now it all works!

>> spot.people=> []>> spot.people << Person.first=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">]>> spot.people=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">]

101Monday, October 25, 2010

Page 136: ActiveRecord 2.3

From the other side...

>> Person.first.pets=> [#<Pet id: 1, animal_type: "dog", name: "Spot", person_id: 6, created_at: "2010-10-14 07:43:59", updated_at: "2010-10-14 07:43:59">]

102Monday, October 25, 2010

Page 137: ActiveRecord 2.3

Join model

• It’s a full ActiveRecord model

• You can hang other attributes on it, if you want

• However, it’s often there just for use as a connection, with no day-to-day direct use

103Monday, October 25, 2010

Page 138: ActiveRecord 2.3

Association options

• has_many, belongs_to, and has_one all take a bunch of options

• Some of them are to handle ActiveRecord naming conventions

• Others can really help to shrink your code, making your models more powerful and expressive

104Monday, October 25, 2010

Page 139: ActiveRecord 2.3

Example: Order

• Perhaps you always want to list pets in the order that they were created:

has_many :pets, :order => 'created_at'

• Person.first.pets will get the pets in order

• This is what we mean by pushing logic from the controller into the model

105Monday, October 25, 2010

Page 140: ActiveRecord 2.3

Example: Auto-destroy

class Person < ActiveRecord::Base

has_many :person_pets, :dependent => :destroy

has_many :pets, :through => :person_pets

end

106Monday, October 25, 2010

Page 141: ActiveRecord 2.3

Example: Auto-destroy

class Person < ActiveRecord::Base

has_many :person_pets, :dependent => :destroy

has_many :pets, :through => :person_pets

end

When we delete a person, we’ll also destroy the join

model person_pet

106Monday, October 25, 2010

Page 142: ActiveRecord 2.3

delete vs. destroy

• There are two ways to destroy an object

p.destroy

p.delete

• Both delete the row in the database

• Both freeze the object, so that we cannot change it

107Monday, October 25, 2010

Page 143: ActiveRecord 2.3

delete vs. destroy

• But:

• destroy runs before_destroy and after_destroy callbacks

• destroy handles dependent association options (i.e., you can set it such that dependent objects are deleted)

• So... use destroy, and not delete, OK?

108Monday, October 25, 2010

Page 144: ActiveRecord 2.3

Validations

• “Validations” are the ActiveRecord way to ensure that your data is valid

• You can get around them!

• So these shouldn’t come in place of constraints and checks in the database

• When you save or update a model, the validations are checked and must pass

109Monday, October 25, 2010

Page 145: ActiveRecord 2.3

Built-in validations

• Rails comes with a large number of validations

• declarations (i.e., class methods) put into the ActiveRecord class

• Use as many of these as you want

110Monday, October 25, 2010

Page 146: ActiveRecord 2.3

validates_presence_of

• Let’s ensure that every person has first and last names:

class Person < ActiveRecord::Base

validates_presence_of :first_name

validates_presence_of :last_name

end

111Monday, October 25, 2010

Page 147: ActiveRecord 2.3

Or, on a single line

class Person < ActiveRecord::Base

validates_presence_of :first_name, :last_name

end

• I prefer the multi-line version, for easier adding and removing of validations

112Monday, October 25, 2010

Page 148: ActiveRecord 2.3

So, what now?

>> p = Person.new

=> #<Person id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>

>> p.save!

ActiveRecord::RecordInvalid: Validation failed: First name can't be blank, Last name can't be blank

113Monday, October 25, 2010

Page 149: ActiveRecord 2.3

So, what now?

>> p = Person.new

=> #<Person id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>

>> p.save!

ActiveRecord::RecordInvalid: Validation failed: First name can't be blank, Last name can't be blank

Each violationis listed

113Monday, October 25, 2010

Page 150: ActiveRecord 2.3

What errors occurred?

>> p.errors

=> #<ActiveRecord::Errors:0x1085d1020 @base=#<Person id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>, @errors=#<OrderedHash {"last_name"=>[#<ActiveRecord::Error:0x1085a2c70 @options={:default=>nil}, @base=#<Person id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>, @type=:blank, @message=:blank, @attribute=:last_name>], "first_name"=>[#<ActiveRecord::Error:0x1085a3170 @options={:default=>nil}, @base=#<Person id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>, @type=:blank, @message=:blank, @attribute=:first_name>]}>>

114Monday, October 25, 2010

Page 151: ActiveRecord 2.3

Let’s try that again...

>> p.errors.class

=> ActiveRecord::Errors

>> p.errors.each_error {|attr, error| puts "[#{attr}] #{error}"}

[first_name] can't be blank

[last_name] can't be blank

=> ["first_name", "last_name"]

115Monday, October 25, 2010

Page 152: ActiveRecord 2.3

ActiveRecord::Errors

• When a validation fails, it adds an element to #errors — an enumerable instance of ActiveRecord::Errors

• You could also call it the “which validations failed, and why” array

• If #errors.empty? is true, then the save/update takes place

116Monday, October 25, 2010

Page 153: ActiveRecord 2.3

Built-in validations

• validates_acceptance_of

• The attribute must exist (e.g., a checkbox indicating user acceptance of site rules)

• validates_associated

• The object to which we’re connect via an association must also be valid

117Monday, October 25, 2010

Page 154: ActiveRecord 2.3

Built-in validations

• validates_confirmation_of

• Did PARAM equal PARAM_confirmation? (Think of password confirmation...)

• validates_each

• Takes a block, and validates each named attribute against the block

118Monday, October 25, 2010

Page 155: ActiveRecord 2.3

Built-in validations

• validates inclusion of

• validates_exclusion_of

• The attribute must (or may not) be a member of a particular array

• validates_format_of

• The attribute must match a regular expression to be valid

119Monday, October 25, 2010

Page 156: ActiveRecord 2.3

Built-in validations

• validates_length_of / validates_size_of

• The attribute may be no more (and/or no less) than a specified length

• validates_numericality_of

• The attribute must be a valid number

• validates_uniqueness_of

120Monday, October 25, 2010

Page 157: ActiveRecord 2.3

Validator options

• Many validators can take options

• For example:

validates_numericality_of :age, :only_integer => true, :greater_than => 0, :less_than_or_equal_to => 120

121Monday, October 25, 2010

Page 158: ActiveRecord 2.3

Messages

• Each validation has a default message

• We saw those messages when looking at the errors object

• Every validation lets you customize the message with the :message parameter

validates_presence_of :last_name, :message => "What, you think you're Madonna?"

122Monday, October 25, 2010

Page 159: ActiveRecord 2.3

Checking validity

• The #valid? method returns true or false

• It also sets the errors object

>> q.valid?

=> false

>> q.errors

=> #<ActiveRecord::Errors:0x1089712e8 @base=#<Person id: nil, first_ ...

123Monday, October 25, 2010

Page 160: ActiveRecord 2.3

Custom validators

• Sometimes, you need to validate in a particular way

• The easiest way is to define a new method in the model class

• If the error exists, invoke errors.add_to_base, with a string containing the message

124Monday, October 25, 2010

Page 161: ActiveRecord 2.3

Custom validator

validate :last_name_must_be_lerner

def last_name_must_be_lerner

errors.add_to_base("Sorry, but your last name must be 'Lerner'") unless last_name.downcase == 'lerner'

end

125Monday, October 25, 2010

Page 162: ActiveRecord 2.3

Testing our validator>> p = Person.new(:first_name => 'Reuven', :last_name => 'Lerner') => #<Person id: nil, first_name: "Reuven", last_name: "Lerner", email: nil, created_at: nil, updated_at: nil>

>> p.valid? => true>> p.last_name = 'Smith' => "Smith">> p.valid? => false

126Monday, October 25, 2010

Page 163: ActiveRecord 2.3

Use validations!

• They’re not database-level constraints, but they can be extremely flexible and powerful

• The built-in validators have a lot of options

• Use them!

• Only write a custom validator if you really need to do so

127Monday, October 25, 2010

Page 164: ActiveRecord 2.3

Callbacks

• Validations fire automatically when we save or update our model. How?

• Answer: They’re a form of “callback,” a method that is invoked automatically when something happens

• ActiveRecord offers many “hooks” that let you define callbacks

128Monday, October 25, 2010

Page 165: ActiveRecord 2.3

Uses for callbacks

• Update a counter, or total column (and avoid doing so in the controller)

• Encrypt user passwords

• Write to an audit trail about changes to a particular model

129Monday, October 25, 2010

Page 166: ActiveRecord 2.3

When callbacks can run

before_validation

before_validation_on_create / ...on_update

after_validation

after_validation_on_create / ...on_update

before_save

before_create / before_update

after_create / after_update

after_save

130Monday, October 25, 2010

Page 167: ActiveRecord 2.3

Downcase e-mail

>> p = Person.new(:first_name => 'Reuven', :last_name => 'Lerner', :email => '[email protected]') => #<Person id: nil, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: nil, updated_at: nil> >> p.save => true jruby-1.5.3 > p => #<Person id: 12, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-25 17:24:19", updated_at: "2010-10-25 17:24:19">

131Monday, October 25, 2010

Page 168: ActiveRecord 2.3

How did we do that?

before_save :downcase_email

def downcase_email

email.downcase!

end

132Monday, October 25, 2010

Page 169: ActiveRecord 2.3

How did we do that?

before_save :downcase_email

def downcase_email

email.downcase!

end

Declare the callback

132Monday, October 25, 2010

Page 170: ActiveRecord 2.3

How did we do that?

before_save :downcase_email

def downcase_email

email.downcase!

end

Declare the callback

Define the callback method

132Monday, October 25, 2010

Page 171: ActiveRecord 2.3

Declaring callbacks

• Don’t invoke them!

• They’re invoked automatically

• Don’t define them!

• Redefining them will have weird effects

• Class methods, not instance methods

• Executed in order of definition

133Monday, October 25, 2010

Page 172: ActiveRecord 2.3

Multiple callbacks

• You can have as many callbacks as you want

• You can run more than one callback on a given hook

• You can run more than one callback on a given attribute

134Monday, October 25, 2010

Page 173: ActiveRecord 2.3

Good uses of callbacks

• Automatic transformations

• Automatic calculations (e.g., price totals)

• Logging

• Creation of behind-the-scenes objects

• Actions that should occur when an object is saved or updated

135Monday, October 25, 2010

Page 174: ActiveRecord 2.3

Bad uses of callbacks

• Additional validations

• Use a validator instead! (Which is a form of callback, after all)

• Handle session-related items

• Remember the M-V-C separation

136Monday, October 25, 2010

Page 175: ActiveRecord 2.3

Return values from callbacks

• Normally, callbacks don’t return values

• But if you return false:

• In a before_* callback, all later callbacks and the action are cancelled!

• In an after_* callback, all later callbacks are cancelled

137Monday, October 25, 2010

Page 176: ActiveRecord 2.3

Oh, yeah

• Don’t call “save” or “update_attribute” inside of a callback.

• It’ll really hurt. A lot.

138Monday, October 25, 2010

Page 177: ActiveRecord 2.3

Looking at callbacks

• FYI, the callback on a model are stored in a “callback chain” object

• You can get at it with

Person.before_save_callback_chain

• Better yet:

Person.before_save_callback_chain.each {|c| puts c.method}; nil

139Monday, October 25, 2010

Page 178: ActiveRecord 2.3

Observers

• We won’t go into this today

• Each AR object can have an observer

• Observer method names are the same as callbacks (after_save, etc.)

• So what’s the difference?

• Semantic — in/out of the model

140Monday, October 25, 2010

Page 179: ActiveRecord 2.3

Dirty objects>> p = Person.first

=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "[email protected]", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">

>> p.first_name = 'Bibi'

=> "Bibi"

>> p.changed?

=> true

>> p.changed

=> ["first_name"]

141Monday, October 25, 2010

Page 180: ActiveRecord 2.3

More with dirty objects

>> p.changes=> {"first_name"=>["Reuven", "Bibi"]}

>> p.first_name_changed? => true

>> p.first_name_was => "Reuven"

142Monday, October 25, 2010

Page 181: ActiveRecord 2.3

More with dirty objects

>> p.changes=> {"first_name"=>["Reuven", "Bibi"]}

>> p.first_name_changed? => true

>> p.first_name_was => "Reuven"

Each changed attribute, with old and new values

142Monday, October 25, 2010

Page 182: ActiveRecord 2.3

Defining methods

• It’s common (and expected) to write methods for your model

• No model methods: Your controller is probably doing too much!

• It’s OK for your model methods to talk to other models via associations...

• ... but your controller probably shouldn’t!

143Monday, October 25, 2010

Page 183: ActiveRecord 2.3

Common methods

• Return a particular piece of information about the model

• String, calculation, result of a database query, statistics about the object

• Return an array, based on associations or other properties

• Associations are available for free!

144Monday, October 25, 2010

Page 184: ActiveRecord 2.3

Named scopes

• An easy way to create methods

• Basically, a wrapper around “find”

• Example, from a forum-posting model:

named_scope :questions, :conditions => { :is_question => true }, :order => "created_at DESC"

145Monday, October 25, 2010

Page 185: ActiveRecord 2.3

Parameterized scopes

named_scope :created_since,

lambda { |since| { :conditions => ['created_at >= ? ', since] }}

named_scope :search,

lambda { |term| { :conditions => ["lower(name) ilike ? ", term] } }

146Monday, October 25, 2010

Page 186: ActiveRecord 2.3

Parameterized scopes

named_scope :created_since,

lambda { |since| { :conditions => ['created_at >= ? ', since] }}

named_scope :search,

lambda { |term| { :conditions => ["lower(name) ilike ? ", term] } }

Named scope is a procedure object taking one parameter

146Monday, October 25, 2010

Page 187: ActiveRecord 2.3

When?

• When should you create a named scope?

• Simple answer: Whenever you invoke “find” in a controller, replace it with a named scope.

• It cleans up the controller code a lot.

• Note: Named scopes are class methods, not instance methods

147Monday, October 25, 2010

Page 188: ActiveRecord 2.3

Chaining scopes# in class Shirt

named_scope :red, :conditions => {:color => 'red'}

named_scope :dry_clean_only, :conditions => ['dry_clean_only = ?', true]

Shirt.red

Shirt.dry_clean_only

Shirt.red.dry_clean_only

148Monday, October 25, 2010

Page 189: ActiveRecord 2.3

Chaining scopes# in class Shirt

named_scope :red, :conditions => {:color => 'red'}

named_scope :dry_clean_only, :conditions => ['dry_clean_only = ?', true]

Shirt.red

Shirt.dry_clean_only

Shirt.red.dry_clean_only

Composition of scopes!

148Monday, October 25, 2010

Page 190: ActiveRecord 2.3

Transactions

Group.transaction do

group = Group.create!(:name => group_name)

Membership.create!(:person => @person,

:group => group,

:is_administrator => true,

:status => 'approved')

"Successfully created the group '#{group_name}'."

end

149Monday, October 25, 2010

Page 191: ActiveRecord 2.3

Transactions

Group.transaction do

group = Group.create!(:name => group_name)

Membership.create!(:person => @person,

:group => group,

:is_administrator => true,

:status => 'approved')

"Successfully created the group '#{group_name}'."

end

Class method “transaction”

149Monday, October 25, 2010

Page 192: ActiveRecord 2.3

Transaction tips

• Transactions are per connection, not model

• So use whatever class you want

• Failure raises ActiveRecord::Rollback

• These only work in databases that support transactions (i.e., not MySQL’s ISAM)

• Nested transactions work, but are often translated into “savepoints”

150Monday, October 25, 2010

Page 193: ActiveRecord 2.3

Declarations

• has_one, has_many, and belongs_to are class methods

• (I think of them as declarations)

• All they do is define methods!

• So has_many might seem magical, but all it’s doing is defining a bunch of methods on your object

151Monday, October 25, 2010

Page 194: ActiveRecord 2.3

Adding declarations

• Add a module to the lib directory

• (Automatically included)

• Use Module#included? to create one or more class methods in the including class

• Voila! Now you can do it, too

• e.g., adds_priority_tags_to_errors

152Monday, October 25, 2010

Page 195: ActiveRecord 2.3

:include

• When you perform a “find”, consider :include

• It retrieves another object with the current one

• Since the result is cached for this request, no more database retrievals are needed

• A major speedup in many cases

153Monday, October 25, 2010

Page 196: ActiveRecord 2.3

:include example

Person.all.each {|p| puts p.pets.inspect}

Person.all(:include => :pets).each {|p| puts p.pets.inspect}

154Monday, October 25, 2010

Page 197: ActiveRecord 2.3

Optimistic locking

• Add a lock_version field to your model, with a default value of 0

• Voila! Now you can stop people from saving older versions on top of newer ones

• Each save/update increments lock_version

• If an older version is saved/updated, a StaleObjectError exception is raised

155Monday, October 25, 2010

Page 198: ActiveRecord 2.3

Pessimistic locking

• If you pass :lock => true to find, you’ll get an exclusive lock on the row

• Uses SELECT .. FOR UPDATE

• If you need a different string, then pass a string, rather than “true”

• I’ve never used this

• But hey, I use PostgreSQL...

156Monday, October 25, 2010

Page 199: ActiveRecord 2.3

Seed data

• Don’t put data in a migration file!

• Instead, use the special db:seed Rake task

• File is db/seeds.rb

• Add lots of calls to “create” in here

• It only adds data — no doubles, erasing, or otherwise touching of existing data

157Monday, October 25, 2010

Page 200: ActiveRecord 2.3

Changing behavior

• Don’t write an “initialize” method for your ActiveRecord object. This will probably fail.

• Instead, use the after_initialize hook

• Or write a plugin that monkey-patches ActiveRecord!

158Monday, October 25, 2010

Page 201: ActiveRecord 2.3

YAML

• You can turn any ActiveRecord object into YAML with the .to_yaml

159Monday, October 25, 2010

Page 202: ActiveRecord 2.3

YAML

>> puts Person.first.to_yaml--- !ruby/object:Person attributes: created_at: 2010-10-13 18:01:03.330099 updated_at: 2010-10-13 18:01:03.330099 id: "6" last_name: Lerner email: [email protected] first_name: Reuvenattributes_cache: {}

160Monday, October 25, 2010

Page 203: ActiveRecord 2.3

JSON

>> puts Person.first.to_json

{"person":{"created_at":"2010-10-13T18:01:03Z","updated_at":"2010-10-13T18:01:03Z","id":6,"last_name":"Lerner","first_name":"Reuven","email":"[email protected]"}}

=> nil

161Monday, October 25, 2010

Page 204: ActiveRecord 2.3

XML>> puts Person.first.to_xml<?xml version="1.0" encoding="UTF-8"?><person> <created-at type="datetime">2010-10-13T18:01:03Z</created-at> <email>[email protected]</email> <first-name>Reuven</first-name> <id type="integer">6</id> <last-name>Lerner</last-name> <updated-at type="datetime">2010-10-13T18:01:03Z</updated-at></person>=> nil

162Monday, October 25, 2010

Page 205: ActiveRecord 2.3

:include

• If you want to include one or more associated objects in the JSON or XML output, just use :include

163Monday, October 25, 2010

Page 206: ActiveRecord 2.3

JSON with :include

>> puts Person.first.to_json(:include => :pets)

{"person":{"created_at":"2010-10-13T18:01:03Z","updated_at":"2010-10-13T18:01:03Z","pets":[{"name":"Spot","created_at":"2010-10-14T07:43:59Z","updated_at":"2010-10-14T07:43:59Z","id":1,"person_id":6,"animal_type":"dog"}],"id":6,"last_name":"Lerner","first_name":"Reuven","email":"[email protected]"}}

=> nil

164Monday, October 25, 2010

Page 207: ActiveRecord 2.3

>> puts Person.first.to_xml(:include => :pets)

<?xml version="1.0" encoding="UTF-8"?>

<person>

<created-at type="datetime">2010-10-13T18:01:03Z</created-at>

<email>[email protected]</email>

<first-name>Reuven</first-name>

<id type="integer">6</id>

<last-name>Lerner</last-name>

<updated-at type="datetime">2010-10-13T18:01:03Z</updated-at>

<pets type="array">

<pet>

<animal-type>dog</animal-type>

<created-at type="datetime">2010-10-14T07:43:59Z</created-at>

<id type="integer">1</id>

<name>Spot</name>

<person-id type="integer">6</person-id>

<updated-at type="datetime">2010-10-14T07:43:59Z</updated-at>

</pet>

</pets>

</person>

=> nil

165Monday, October 25, 2010

Page 208: ActiveRecord 2.3

Other options

• :except — ignore certain attributes/tags

• :only — we only want some attributes

• :methods — invoke methods and include their output in the XML

• Or hand a block to to_xml, and then you can use builder (Ruby’s XML-generating facility) to create whatever you want!

166Monday, October 25, 2010

Page 209: ActiveRecord 2.3

Better XML

• If you want to customize the XML, then use an XML view (instead of an HTML view)

• “Builder” allows you to create XML files very easily, with any tags and attributes

• We’ll talk about this further when we discuss views

167Monday, October 25, 2010

Page 210: ActiveRecord 2.3

Plugins

• Plugins modify default Rails behavior

• They go in /vendor/plugins

• Many modify ActiveRecord’s behavior

• Be careful before installing a plugin... they’re quite useful, but you don’t want clashes

168Monday, October 25, 2010

Page 211: ActiveRecord 2.3

Example: acts_as_tree

• Create a table with a “parent” attribute

• If you say “acts_as_tree”, then you get methods for “parent,” “children,” and so forth

• In very widespread use (written by DHH)

169Monday, October 25, 2010

Page 212: ActiveRecord 2.3

Some others

• acts_as_list

• acts_as_nested_set

• acts_as_taggable

• acts_as_taggable_on_steroids

• acts_as_state_machine

170Monday, October 25, 2010

Page 213: ActiveRecord 2.3

171Monday, October 25, 2010

Page 214: ActiveRecord 2.3

190 acts_as gems!

171Monday, October 25, 2010

Page 215: ActiveRecord 2.3

Contacting me

• Call me in Israel: 054-496-8405

• Call me in the US: 847-230-9795

• E-mail me: [email protected]

• Interrupt me: reuvenlerner (Skype/AIM)

172Monday, October 25, 2010