ActiveRecord 2.3

Post on 26-Jan-2015

998 views 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 of ActiveRecord 2.3

ActiveRecord 2.3Reuven M. Lerner

October 20th, 2010

1Monday, October 25, 2010

What is a database?

Database

Store data confidently

Retrieve data flexibly

2Monday, October 25, 2010

Relational databases

Define tables, store data in them

Database

Retrieve data from related tables

3Monday, October 25, 2010

Database communication

SQL goes hereCREATE TABLE

INSERTUPDATEDELETE

Database

4Monday, October 25, 2010

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

Solution: ORM (object-relational mapper)

ORMRuby

Database

6Monday, October 25, 2010

Solution: ORM (object-relational mapper)

ORMRuby

Database

Write in Ruby

6Monday, October 25, 2010

Solution: ORM (object-relational mapper)

ORMRuby

Database

Write in Ruby

ORM translates to SQL, sends to database

6Monday, October 25, 2010

Solution: ORM (object-relational mapper)

ORMRuby

Database

Write in Ruby

ORM translates to SQL, sends to database

Results goto ORM

6Monday, October 25, 2010

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

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

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

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

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

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

Person model

class Person < ActiveRecord::Base

end

12Monday, October 25, 2010

Person model

class Person < ActiveRecord::Base

end

Singular class name

12Monday, October 25, 2010

Person model

class Person < ActiveRecord::Base

end

Singular class name Standard parent class

12Monday, October 25, 2010

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

Object ≠ Table

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

• So let’s create the table!

14Monday, October 25, 2010

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

Migrations

• Ruby program that describes how to change the database schema

• Add tables

• Rename columns

• Remove columns

• Set defaults

16Monday, October 25, 2010

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

29Monday, October 25, 2010

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

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

How many records?

?> Person.count

=> 0

32Monday, October 25, 2010

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

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

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

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

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

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 = 'reuven@lerner.co.il' => "reuven@lerner.co.il">> p.save! => true

38Monday, October 25, 2010

The Ruby way

>> p = Person.new(:first_name => 'Reuven', :last_name => 'Lerner', :email => 'reuven@lerner.co.il')

=> #<Person id: nil, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: nil, updated_at: nil>

39Monday, October 25, 2010

The Ruby way

>> p = Person.new(:first_name => 'Reuven', :last_name => 'Lerner', :email => 'reuven@lerner.co.il')

=> #<Person id: nil, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: nil, updated_at: nil>

Hash of name-value pairs

39Monday, October 25, 2010

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

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

Updating fields

>> p.first_name = 'Bibi'

=> "Bibi"

>> p.save

=> true

41Monday, October 25, 2010

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

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

Multiple attributes

• p.update_attributes(

:first_name => 'Bibi',

:last_name => 'Netanyahu')

43Monday, October 25, 2010

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

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

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

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

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

Get them all!

Person.find(:all)

Person.all # same thing

49Monday, October 25, 2010

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

Conditions

• We can add conditions

• turned into WHERE clause in SQL

• You’ll almost always want conditions

51Monday, October 25, 2010

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: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

52Monday, October 25, 2010

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: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

Hash

52Monday, October 25, 2010

Conditions, SQL style

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

=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

53Monday, October 25, 2010

Conditions, SQL style

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

=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

String

53Monday, October 25, 2010

Conditions, SQL style

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

=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

54Monday, October 25, 2010

Conditions, SQL style

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

=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

Variable

54Monday, October 25, 2010

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

XKCD

56Monday, October 25, 2010

Sweden, last month

57Monday, October 25, 2010

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

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

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

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

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

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

=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

60Monday, October 25, 2010

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

=> [#<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">]

61Monday, October 25, 2010

Combining

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

=> [#<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, ...

62Monday, October 25, 2010

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

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

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

Iterate over results

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

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

66Monday, October 25, 2010

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

find_by_first_name>> Person.find_by_first_name('Reuven')=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]

68Monday, October 25, 2010

find_by_first_name>> Person.find_by_first_name('Reuven')=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", 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

find_by_first_name>> Person.find_by_first_name('Reuven')=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", 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

Negative results

>> Person.find_by_first_name('blah')

=> nil

>> Person.find_all_by_first_name('blah')

=> []

69Monday, October 25, 2010

Negative results

>> Person.find_by_first_name('blah')

=> nil

>> Person.find_all_by_first_name('blah')

=> []

No exception!

69Monday, October 25, 2010

Multiple attributes

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

=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">

70Monday, October 25, 2010

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

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

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

Associations

• ActiveRecord really shines when it comes to “associations”

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

74Monday, October 25, 2010

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

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

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

Change Pet.rb

class Pet < ActiveRecord::Base

belongs_to :person

end

77Monday, October 25, 2010

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

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

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

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

New “person” method!

>> spot.person

=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">

80Monday, October 25, 2010

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

With our email method

class Pet < ActiveRecord::Base

belongs_to :person

def email

person.email

end

end

82Monday, October 25, 2010

Easier: Delegation!

class Pet < ActiveRecord::Base

belongs_to :person

delegate :email, :to => :person

end

83Monday, October 25, 2010

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

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

Avoid nil problems

class Pet < ActiveRecord::Base

belongs_to :person

delegate :email, :to => :person,

:allow_nil => true

end

85Monday, October 25, 2010

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

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

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

Using has_one>> p = Person.first

=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", 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

Using has_one>> p = Person.first

=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", 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

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

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

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

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

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

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

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

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

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

Join table

People Person-Pets

Pets

foreign keys:person_id

pet_id

96Monday, October 25, 2010

Migration

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

97Monday, October 25, 2010

person_pet.rb

class PersonPet < ActiveRecord::Base

belongs_to :person

belongs_to :pet

end

98Monday, October 25, 2010

Update person.rb

class Person < ActiveRecord::Base

has_many :person_pets

has_many :pets, :through => :person_pets

end

99Monday, October 25, 2010

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

Update pet.rb

class Pet < ActiveRecord::Base

has_many :person_pets

has_many :people, :through => :person_pets

end

100Monday, October 25, 2010

Now it all works!

>> spot.people=> []>> spot.people << Person.first=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", 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: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">]

101Monday, October 25, 2010

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

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

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

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

Example: Auto-destroy

class Person < ActiveRecord::Base

has_many :person_pets, :dependent => :destroy

has_many :pets, :through => :person_pets

end

106Monday, October 25, 2010

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Downcase e-mail

>> p = Person.new(:first_name => 'Reuven', :last_name => 'Lerner', :email => 'Reuven@Lerner.co.IL') => #<Person id: nil, first_name: "Reuven", last_name: "Lerner", email: "Reuven@Lerner.co.IL", created_at: nil, updated_at: nil> >> p.save => true jruby-1.5.3 > p => #<Person id: 12, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-25 17:24:19", updated_at: "2010-10-25 17:24:19">

131Monday, October 25, 2010

How did we do that?

before_save :downcase_email

def downcase_email

email.downcase!

end

132Monday, October 25, 2010

How did we do that?

before_save :downcase_email

def downcase_email

email.downcase!

end

Declare the callback

132Monday, October 25, 2010

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

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

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

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

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

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

Oh, yeah

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

• It’ll really hurt. A lot.

138Monday, October 25, 2010

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

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

Dirty objects>> p = Person.first

=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", 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

More with dirty objects

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

>> p.first_name_changed? => true

>> p.first_name_was => "Reuven"

142Monday, October 25, 2010

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

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

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

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

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

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

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

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

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

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

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

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

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

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

: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

:include example

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

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

154Monday, October 25, 2010

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

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

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

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

YAML

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

159Monday, October 25, 2010

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: reuven@lerner.co.il first_name: Reuvenattributes_cache: {}

160Monday, October 25, 2010

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":"reuven@lerner.co.il"}}

=> nil

161Monday, October 25, 2010

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>reuven@lerner.co.il</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

:include

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

163Monday, October 25, 2010

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":"reuven@lerner.co.il"}}

=> nil

164Monday, October 25, 2010

>> 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>reuven@lerner.co.il</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

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

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

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

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

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

171Monday, October 25, 2010

190 acts_as gems!

171Monday, October 25, 2010

Contacting me

• Call me in Israel: 054-496-8405

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

• E-mail me: reuven@lerner.co.il

• Interrupt me: reuvenlerner (Skype/AIM)

172Monday, October 25, 2010