ActiveRecord 2.3
Reuven 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 here
CREATE TABLE
INSERT
UPDATE
DELETE
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 wi...
Solution: ORM (object-
relational mapper)
ORM
Ruby
Database
6Monday, October 25, 2010
Solution: ORM (object-
relational mapper)
ORM
Ruby
Database
Write in Ruby
6Monday, October 25, 2010
Solution: ORM (object-
relational mapper)
ORM
Ruby
Database
Write in Ruby
ORM translates to SQL,
sends to database
6Monday...
Solution: ORM (object-
relational mapper)
ORM
Ruby
Database
Write in Ruby
ORM translates to SQL,
sends to database
Results...
Solution: ORM (object-
relational mapper)
ORM
Ruby
Database
Write in Ruby
ORM translates to SQL,
sends to database
Results...
ActiveRecord
• By far, the most popular ORM for Ruby
• Not the only one — e.g., DataMapper
• We work with objects whenever...
ActiveRecord and Rails
• The idea of ActiveRecord preceded Rails
• Mark Fowler, from Thoughtworks (and
the “Refactoring” b...
Opinionated!
• ActiveRecord has some ideas about how
your application should work
• If you work in the same way, the work ...
Using ActiveRecord
• Typically, we subclass ActiveRecord::Base in
each of our model files
• That is, all of the classes defi...
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 la...
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(...
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 y...
Migrations
• Ruby program that describes how to
change the database schema
• Add tables
• Rename columns
• Remove columns
...
Platform independent
• Because migrations are written in Ruby,
they’re platform independent
• Well, mostly...
• ... they t...
Create a migration
./script/generate migration
create_person
./script/generate model person
./script/generate model person...
The migration file
• Migrations are in db/migrate
• Each has a unique filename, and a
timestamp
• (The odds of two developer...
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.string :first_name
t.string :last_n...
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.string :first_name
t.string :last_n...
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.string :first_name
t.string :last_n...
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.string :first_name
t.string :last_n...
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.string :first_name
t.string :last_n...
Run the migration
• To run all pending migrations:
rake db:migrate
• To run the “down” method until we get to a
certain mi...
Changing migrations
• Changing the migration file is OK!
• Add indexes, defaults, etc.
• But don’t change a table structure...
rake db:migrate
• A table, schema_migrations, is automatically
created in the database
• It has one column,“version”, whic...
Migrations and models
• Column names and types are defined in the
database, not in the model
• This means that column names...
Migrating
~/Downloads/foo$ rake db:migrate
(in /Users/reuven/Downloads/foo)
== CreatePeople: migrating
===================...
Migrating
~/Downloads/foo$ rake db:migrate
(in /Users/reuven/Downloads/foo)
== CreatePeople: migrating
===================...
Let’s check again
>> Person
=> Person(id: integer,
first_name: string, last_name:
string, email: string,
created_at: datet...
Let’s check again
>> Person
=> Person(id: integer,
first_name: string, last_name:
string, email: string,
created_at: datet...
Let’s check again
>> Person
=> Person(id: integer,
first_name: string, last_name:
string, email: string,
created_at: datet...
Assumptions
• Convention over configuration!
• Tables are plural, classes are singular
• class “Person”, but table “People”...
Wait! Where’s the DB?
• When did we tell Rails how to connect to
the database?
• Look in config/database.yml
• The only con...
development:
adapter: postgresql
encoding: unicode
database: foo_development
pool: 5
username: reuven
password: reuven
29M...
Wait, that’s it?
• Well, mostly.
• There are additional (optimal) parameters
• And some configuration is done in the
enviro...
Console reloading
• If you use the console (and you should!)
then modifying ActiveRecord models may
cause issues
• Use “re...
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, upd...
Wait a second!
• That person record we just created is
pretty useless.
• We really don’t want nameless people in
our datab...
Better creation
• The “new” method creates a new object,
but doesn’t save it to the database
• This is why it has nil for ...
save! and create!
• save returns true or false
• create returns the object or false
• save! and create! are the same as th...
Missing attributes?
• If you fail to set an attribute, then Ruby will
pass it nil
• However, if you have a default value s...
The Java way
>> p = Person.new
=> #<Person id: nil, first_name: nil,
last_name: nil, email: nil, created_at: nil,
updated_...
The Ruby way
>> p = Person.new(:first_name =>
'Reuven', :last_name =>
'Lerner', :email =>
'reuven@lerner.co.il')
=> #<Pers...
The Ruby way
>> p = Person.new(:first_name =>
'Reuven', :last_name =>
'Lerner', :email =>
'reuven@lerner.co.il')
=> #<Pers...
It’s not a free-for-all
>> p10 = Person.new(:eye_color => 'brown')
ActiveRecord::UnknownAttributeError: unknown attribute:...
It’s not a free-for-all
>> p10 = Person.new(:eye_color => 'brown')
ActiveRecord::UnknownAttributeError: unknown attribute:...
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...
update_attributes
• It’s easier and safer to update both the
object and the database simultaneously
>> p.update_attributes...
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...
By the way...
• Remember our model file?
• It’s still empty.
• And yet, it allows us to create, save, and
update models nat...
Semi-protection
• attr_protected :first_name
• first_name cannot be changed with
update_attributes, but it can be updated
wi...
find
• This is the workhorse of ActiveRecord
• The “find” method is really a lot of different
methods with a single interfac...
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 ...
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 arr...
Conditions
• We can add conditions
• turned into WHERE clause in SQL
• You’ll almost always want conditions
51Monday, Octo...
Conditions, Ruby style
>> Person.all(:conditions => {:first_name =>
'foo'})
=> []
>> Person.all(:conditions => {:first_nam...
Conditions, Ruby style
>> Person.all(:conditions => {:first_name =>
'foo'})
=> []
>> Person.all(:conditions => {:first_nam...
Conditions, SQL style
>> Person.all(:conditions => "first_name =
'Reuven'")
=> [#<Person id: 6, first_name: "Reuven",
last...
Conditions, SQL style
>> Person.all(:conditions => "first_name =
'Reuven'")
=> [#<Person id: 6, first_name: "Reuven",
last...
Conditions, SQL style
>> Person.all(:conditions => "first_name = '#
{@person.first_name}'")
=> [#<Person id: 6, first_name...
Conditions, SQL style
>> Person.all(:conditions => "first_name = '#
{@person.first_name}'")
=> [#<Person id: 6, first_name...
Don’t do this!
• SQL injection attacks can happen
• There’s no reason for it
• If someone hands you a string containing a
...
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:
>> Per...
Interpolating
parameters
• Instead of:
>> Person.all(:conditions => "first_name = '#
{@person.first_name}'")
• Use:
>> Per...
Interpolating
parameters
• Instead of:
>> Person.all(:conditions => "first_name = '#
{@person.first_name}'")
• Use:
>> Per...
Interpolating
parameters
• Instead of:
>> Person.all(:conditions => "first_name = '#
{@person.first_name}'")
• Use:
>> Per...
Ordering results
• Remember:A relational database doesn’t
store its rows in any order
• If you don’t specify an order, you...
Ascending order
>> Person.all(:conditions => "first_name =
'Reuven'", :order => 'created_at')
=> [#<Person id: 6, first_na...
Descending order
>> Person.all(:conditions => "first_name =
'Reuven'", :order => 'created_at DESC')
=> [#<Person id: 7, fi...
Combining
>> Person.all(:order => 'last_name ASC,
created_at DESC')
=> [#<Person id: 7, first_name: "Reuven",
last_name: "...
Where to order?
• The database is almost certainly faster at
ordering
• So invoking #all and then #sort is probably
not a ...
first
• Returns the first row (object) from the
database — or nil, if none was found
Person.first
• Of course, without an or...
Transforming results
• Person.all returns an array — so you can
invoke whatever you want on that array!
• Get an array of ...
Iterate over results
Person.all.each {|p| puts
p.inspect}
Person.all.each {|p|
p.update_attributes(:admin =>
false)}
66Mon...
Dynamic finders
• Remember method_missing? ActiveRecord
uses this to provide “dynamic finders” —
versions of find that can ma...
find_by_first_name
>> Person.find_by_first_name('Reuven')
=> #<Person id: 6, first_name: "Reuven", last_name:
"Lerner", emai...
find_by_first_name
>> Person.find_by_first_name('Reuven')
=> #<Person id: 6, first_name: "Reuven", last_name:
"Lerner", emai...
find_by_first_name
>> Person.find_by_first_name('Reuven')
=> #<Person id: 6, first_name: "Reuven", last_name:
"Lerner", emai...
Negative results
>> Person.find_by_first_name('blah')
=> nil
>> Person.find_all_by_first_name
('blah')
=> []
69Monday, Oct...
Negative results
>> Person.find_by_first_name('blah')
=> nil
>> Person.find_all_by_first_name
('blah')
=> []
No exception!...
Multiple attributes
>> Person.find_by_first_name_and_last_name
('Reuven', 'Lerner')
=> #<Person id: 6, first_name: "Reuven...
find_or_create_by_...
• Use a dynamic finder... and if you don’t find
a result, then create a new object
• If the object fail...
Caching
• ActiveRecord caches results on a per-
session basis
• So if you have already retrieved an object
with the curren...
Look in the log!
• In the development environment, you’ll see
your queries rewritten using SQL.
• This is a great way to s...
Associations
• ActiveRecord really shines when it comes
to “associations”
• The object equivalent of primary/foreign
keys ...
Pets!
• Let’s make it possible for people to have pets
./script/generate model pet
animal_type:string name:string
person_i...
Pets!
• Let’s make it possible for people to have pets
./script/generate model pet
animal_type:string name:string
person_i...
belongs_to
• Declaration (aka a class method) in the
model file
• Meaning:There is a foreign key pointing
from self to anot...
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
• I...
Creating a pet
>> spot = Pet.new(:animal_type =>
'dog', :name => 'Spot', :person_id =>
Person.first.id)
=> #<Pet id: nil, ...
Creating a pet
>> spot = Pet.new(:animal_type =>
'dog', :name => 'Spot', :person_id =>
Person.first.id)
=> #<Pet id: nil, ...
Creating a pet
>> spot = Pet.new(:animal_type =>
'dog', :name => 'Spot', :person_id =>
Person.first.id)
=> #<Pet id: nil, ...
New “person” method!
>> spot.person
=> #<Person id: 6, first_name:
"Reuven", last_name: "Lerner",
email: "reuven@lerner.co...
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...
With our email method
class Pet < ActiveRecord::Base
belongs_to :person
def email
person.email
end
end
82Monday, October 2...
Easier: Delegation!
class Pet < ActiveRecord::Base
belongs_to :person
delegate :email, :to => :person
end
83Monday, Octobe...
By the way...
>> rover = Pet.new
=> #<Pet id: nil, animal_type: nil, name:
nil, person_id: nil, created_at: nil,
updated_a...
By the way...
>> rover = Pet.new
=> #<Pet id: nil, animal_type: nil, name:
nil, person_id: nil, created_at: nil,
updated_a...
Avoid nil problems
class Pet < ActiveRecord::Base
belongs_to :person
delegate :email, :to => :person,
:allow_nil => true
e...
Problem solved
>> rover = Pet.new
=> #<Pet id: nil, animal_type: nil,
name: nil, person_id: nil,
created_at: nil, updated_...
The other side
• So far, pets know about their owners...
• ... but owners don’t know about their pets
>> spot.person.pets
...
one-to-one: has_one
If each person can have one pet, then we
could change person.rb to read:
class Person < ActiveRecord::...
Using has_one
>> p = Person.first
=> #<Person id: 6, first_name: "Reuven",
last_name: "Lerner", email:
"reuven@lerner.co.i...
Using has_one
>> p = Person.first
=> #<Person id: 6, first_name: "Reuven",
last_name: "Lerner", email:
"reuven@lerner.co.i...
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)...
has_many
• More interesting, and trickier, is has_many
• Perhaps we have many pets!
class Person < ActiveRecord::Base
has_...
has_many
• More interesting, and trickier, is has_many
• Perhaps we have many pets!
class Person < ActiveRecord::Base
has_...
has_many
• With a has_many relationship in place, we
get a method (plural!) for pets
• It always returns an array (perhaps...
Adding
>> p.pets
=> []
>> p.pets << Pet.new(:animal_type =>
'fish', :name => "Charlie")
=> [#<Pet id: 2, animal_type: "fis...
Adding
>> p.pets
=> []
>> p.pets << Pet.new(:animal_type =>
'fish', :name => "Charlie")
=> [#<Pet id: 2, animal_type: "fis...
Adding
>> p.pets
=> []
>> p.pets << Pet.new(:animal_type =>
'fish', :name => "Charlie")
=> [#<Pet id: 2, animal_type: "fis...
Array fun
>> Person.first.pets.select {|p|
p.animal_type == 'fish'}
=> []
>> Person.first.pets.select {|p|
p.animal_type =...
many-to-many
• What if each person can have multiple
pets, and each pet can have multiple
owners?
• For that, we need a “j...
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
99Mo...
Update person.rb
class Person < ActiveRecord::Base
has_many :person_pets
has_many :pets, :through => :person_pets
end has_...
Update pet.rb
class Pet < ActiveRecord::Base
has_many :person_pets
has_many :people, :through => :person_pets
end
100Monda...
Now it all works!
>> spot.people
=> []
>> spot.people << Person.first
=> [#<Person id: 6, first_name: "Reuven",
last_name:...
From the other side...
>> Person.first.pets
=> [#<Pet id: 1, animal_type: "dog", name:
"Spot", person_id: 6, created_at: "...
Join model
• It’s a full ActiveRecord model
• You can hang other attributes on it, if you
want
• However, it’s often there...
Association options
• has_many, belongs_to, and has_one all take
a bunch of options
• Some of them are to handle ActiveRec...
Example: Order
• Perhaps you always want to list pets in the
order that they were created:
has_many :pets, :order =>
'crea...
Example:Auto-destroy
class Person < ActiveRecord::Base
has_many :person_pets, :dependent
=> :destroy
has_many :pets, :thro...
Example:Auto-destroy
class Person < ActiveRecord::Base
has_many :person_pets, :dependent
=> :destroy
has_many :pets, :thro...
delete vs. destroy
• There are two ways to destroy an object
p.destroy
p.delete
• Both delete the row in the database
• Bo...
delete vs. destroy
• But:
• destroy runs before_destroy and
after_destroy callbacks
• destroy handles dependent associatio...
Validations
• “Validations” are the ActiveRecord way to
ensure that your data is valid
• You can get around them!
• So the...
Built-in validations
• Rails comes with a large number of
validations
• declarations (i.e., class methods) put into
the Ac...
validates_presence_of
• Let’s ensure that every person has first and
last names:
class Person < ActiveRecord::Base
validate...
Or, on a single line
class Person < ActiveRecord::Base
validates_presence_of :first_name, :last_name
end
• I prefer the mu...
So, what now?
>> p = Person.new
=> #<Person id: nil, first_name: nil,
last_name: nil, email: nil,
created_at: nil, updated...
So, what now?
>> p = Person.new
=> #<Person id: nil, first_name: nil,
last_name: nil, email: nil,
created_at: nil, updated...
What errors occurred?
>> p.errors
=> #<ActiveRecord::Errors:0x1085d1020 @base=#<Person id: nil,
first_name: nil, last_name...
Let’s try that again...
>> p.errors.class
=> ActiveRecord::Errors
>> p.errors.each_error {|attr, error|
puts "[#{attr}] #{...
ActiveRecord::Errors
• When a validation fails, it adds an element
to #errors — an enumerable instance of
ActiveRecord::Er...
Built-in validations
• validates_acceptance_of
• The attribute must exist (e.g., a checkbox
indicating user acceptance of ...
Built-in validations
• validates_confirmation_of
• Did PARAM equal PARAM_confirmation?
(Think of password confirmation...)
• ...
Built-in validations
• validates inclusion of
• validates_exclusion_of
• The attribute must (or may not) be a
member of a ...
Built-in validations
• validates_length_of / validates_size_of
• The attribute may be no more (and/or no
less) than a spec...
Validator options
• Many validators can take options
• For example:
validates_numericality_of :age,
:only_integer =>
true,...
Messages
• Each validation has a default message
• We saw those messages when looking at the
errors object
• Every validat...
Checking validity
• The #valid? method returns true or false
• It also sets the errors object
>> q.valid?
=> false
>> q.er...
Custom validators
• Sometimes, you need to validate in a
particular way
• The easiest way is to define a new method
in the ...
Custom validator
validate :last_name_must_be_lerner
def last_name_must_be_lerner
errors.add_to_base("Sorry, but your
last ...
Testing our validator
>> p = Person.new(:first_name =>
'Reuven', :last_name => 'Lerner')
=> #<Person id: nil, first_name: ...
Use validations!
• They’re not database-level constraints, but
they can be extremely flexible and powerful
• The built-in v...
Callbacks
• Validations fire automatically when we save
or update our model. How?
• Answer:They’re a form of “callback,” a
...
Uses for callbacks
• Update a counter, or total column (and
avoid doing so in the controller)
• Encrypt user passwords
• W...
When callbacks can run
before_validation
before_validation_on_create / ...on_update
after_validation
after_validation_on_c...
Downcase e-mail
>> p = Person.new(:first_name => 'Reuven', :last_name
=> 'Lerner', :email => 'Reuven@Lerner.co.IL')
=> #<P...
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, Oct...
How did we do that?
before_save :downcase_email
def downcase_email
email.downcase!
end
Declare the callback
Define the
call...
Declaring callbacks
• Don’t invoke them!
• They’re invoked automatically
• Don’t define them!
• Redefining them will have we...
Multiple callbacks
• You can have as many callbacks as you want
• You can run more than one callback on a
given hook
• You...
Good uses of callbacks
• Automatic transformations
• Automatic calculations (e.g., price totals)
• Logging
• Creation of b...
Bad uses of callbacks
• Additional validations
• Use a validator instead! (Which is a form
of callback, after all)
• Handl...
Return values from
callbacks
• Normally, callbacks don’t return values
• But if you return false:
• In a before_* callback...
Oh, yeah
• Don’t call “save” or “update_attribute”
inside of a callback.
• It’ll really hurt. A lot.
138Monday, October 25...
Looking at callbacks
• FYI, the callback on a model are stored in a
“callback chain” object
• You can get at it with
Perso...
Observers
• We won’t go into this today
• Each AR object can have an observer
• Observer method names are the same as
call...
Dirty objects
>> p = Person.first
=> #<Person id: 6, first_name: "Reuven", last_name:
"Lerner", email: "reuven@lerner.co.i...
More with dirty objects
>> p.changes
=> {"first_name"=>["Reuven", "Bibi"]}
>> p.first_name_changed?
=> true
>> p.first_nam...
More with dirty objects
>> p.changes
=> {"first_name"=>["Reuven", "Bibi"]}
>> p.first_name_changed?
=> true
>> p.first_nam...
Defining methods
• It’s common (and expected) to write
methods for your model
• No model methods:Your controller is
probabl...
Common methods
• Return a particular piece of information
about the model
• String, calculation, result of a database
quer...
Named scopes
• An easy way to create methods
• Basically, a wrapper around “find”
• Example, from a forum-posting model:
na...
Parameterized scopes
named_scope :created_since,
lambda { |since| { :conditions =>
['created_at >= ? ', since] }}
named_sc...
Parameterized scopes
named_scope :created_since,
lambda { |since| { :conditions =>
['created_at >= ? ', since] }}
named_sc...
When?
• When should you create a named scope?
• Simple answer:Whenever you invoke “find”
in a controller, replace it with a...
Chaining scopes
# in class Shirt
named_scope :red, :conditions => {:color
=> 'red'}
named_scope :dry_clean_only, :conditio...
Chaining scopes
# in class Shirt
named_scope :red, :conditions => {:color
=> 'red'}
named_scope :dry_clean_only, :conditio...
Transactions
Group.transaction do
group = Group.create!(:name => group_name)
Membership.create!(:person => @person,
:group...
Transactions
Group.transaction do
group = Group.create!(:name => group_name)
Membership.create!(:person => @person,
:group...
Transaction tips
• Transactions are per connection, not model
• So use whatever class you want
• Failure raises ActiveReco...
Declarations
• has_one, has_many, and belongs_to are
class methods
• (I think of them as declarations)
• All they do is de...
Adding declarations
• Add a module to the lib directory
• (Automatically included)
• Use Module#included? to create one or...
:include
• When you perform a “find”,
consider :include
• It retrieves another object with the current
one
• Since the resu...
:include example
Person.all.each {|p| puts
p.pets.inspect}
Person.all(:include
=> :pets).each {|p| puts
p.pets.inspect}
15...
Optimistic locking
• Add a lock_version field to your model,
with a default value of 0
• Voila! Now you can stop people fro...
Pessimistic locking
• If you pass :lock => true to find, you’ll get
an exclusive lock on the row
• Uses SELECT .. FOR UPDAT...
Seed data
• Don’t put data in a migration file!
• Instead, use the special db:seed Rake task
• File is db/seeds.rb
• Add lo...
Changing behavior
• Don’t write an “initialize” method for your
ActiveRecord object. This will probably fail.
• Instead, u...
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: 2...
JSON
>> puts Person.first.to_json
{"person":
{"created_at":"2010-10-13T18:01:03Z","
updated_at":"2010-10-13T18:01:03Z","id...
XML
>> puts Person.first.to_xml
<?xml version="1.0" encoding="UTF-8"?>
<person>
<created-at type="datetime">2010-10-13T18:...
:include
• If you want to include one or more
associated objects in the JSON or XML
output, just use :include
163Monday, O...
JSON with :include
>> puts Person.first.to_json(:include => :pets)
{"person":
{"created_at":"2010-10-13T18:01:03Z","update...
>> puts Person.first.to_xml(:include => :pets)
<?xml version="1.0" encoding="UTF-8"?>
<person>
<created-at type="datetime"...
Other options
• :except — ignore certain attributes/tags
• :only — we only want some attributes
• :methods — invoke method...
Better XML
• If you want to customize the XML, then use
an XML view (instead of an HTML view)
• “Builder” allows you to cr...
Plugins
• Plugins modify default Rails behavior
• They go in /vendor/plugins
• Many modify ActiveRecord’s behavior
• Be ca...
Example: acts_as_tree
• Create a table with a “parent” attribute
• If you say “acts_as_tree”, then you get
methods for “pa...
Some others
• acts_as_list
• acts_as_nested_set
• acts_as_taggable
• acts_as_taggable_on_steroids
• acts_as_state_machine
...
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
• Inter...
Upcoming SlideShare
Loading in...5
×

ActiveRecord 2.3

1,041

Published on

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.

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
1,041
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
14
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

ActiveRecord 2.3

  1. 1. ActiveRecord 2.3 Reuven M. Lerner October 20th, 2010 1Monday, October 25, 2010
  2. 2. What is a database? Database Store data confidently Retrieve data flexibly 2Monday, October 25, 2010
  3. 3. Relational databases Define tables, store data in them Database Retrieve data from related tables 3Monday, October 25, 2010
  4. 4. Database communication SQL goes here CREATE TABLE INSERT UPDATE DELETE Database 4Monday, October 25, 2010
  5. 5. 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
  6. 6. Solution: ORM (object- relational mapper) ORM Ruby Database 6Monday, October 25, 2010
  7. 7. Solution: ORM (object- relational mapper) ORM Ruby Database Write in Ruby 6Monday, October 25, 2010
  8. 8. Solution: ORM (object- relational mapper) ORM Ruby Database Write in Ruby ORM translates to SQL, sends to database 6Monday, October 25, 2010
  9. 9. Solution: ORM (object- relational mapper) ORM Ruby Database Write in Ruby ORM translates to SQL, sends to database Results go to ORM 6Monday, October 25, 2010
  10. 10. Solution: ORM (object- relational mapper) ORM Ruby Database Write in Ruby ORM translates to SQL, sends to database Results go to ORM ORM turns results into Ruby objects 6Monday, October 25, 2010
  11. 11. 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
  12. 12. 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
  13. 13. 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
  14. 14. 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
  15. 15. 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
  16. 16. Person model class Person < ActiveRecord::Base end 12Monday, October 25, 2010
  17. 17. Person model class Person < ActiveRecord::Base end Singular class name 12Monday, October 25, 2010
  18. 18. Person model class Person < ActiveRecord::Base end Singular class name Standard parent class 12Monday, October 25, 2010
  19. 19. 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
  20. 20. Object ≠ Table • The object exists, but the table doesn’t. • So let’s create the table! 14Monday, October 25, 2010
  21. 21. 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
  22. 22. Migrations • Ruby program that describes how to change the database schema • Add tables • Rename columns • Remove columns • Set defaults 16Monday, October 25, 2010
  23. 23. 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
  24. 24. 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
  25. 25. 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
  26. 26. 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 end end 20Monday, October 25, 2010
  27. 27. 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 end end Migrate forward 20Monday, October 25, 2010
  28. 28. 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 end end Migrate forward Migrate backward 20Monday, October 25, 2010
  29. 29. 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 end end Migrate forward Migrate backward Data types 20Monday, October 25, 2010
  30. 30. 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 end end Migrate forward Migrate backward Data types Block! 20Monday, October 25, 2010
  31. 31. 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
  32. 32. 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
  33. 33. 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
  34. 34. 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
  35. 35. 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
  36. 36. 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
  37. 37. 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
  38. 38. 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
  39. 39. 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 what about these? 26Monday, October 25, 2010
  40. 40. 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
  41. 41. 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
  42. 42. development: adapter: postgresql encoding: unicode database: foo_development pool: 5 username: reuven password: reuven 29Monday, October 25, 2010
  43. 43. 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
  44. 44. 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
  45. 45. How many records? ?> Person.count => 0 32Monday, October 25, 2010
  46. 46. 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
  47. 47. 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
  48. 48. 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
  49. 49. 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
  50. 50. 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
  51. 51. 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
  52. 52. 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
  53. 53. 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
  54. 54. 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
  55. 55. 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
  56. 56. Updating fields >> p.first_name = 'Bibi' => "Bibi" >> p.save => true 41Monday, October 25, 2010
  57. 57. 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
  58. 58. 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
  59. 59. Multiple attributes • p.update_attributes( :first_name => 'Bibi', :last_name => 'Netanyahu') 43Monday, October 25, 2010
  60. 60. 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
  61. 61. 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
  62. 62. 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
  63. 63. 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
  64. 64. 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
  65. 65. Get them all! Person.find(:all) Person.all # same thing 49Monday, October 25, 2010
  66. 66. 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
  67. 67. Conditions • We can add conditions • turned into WHERE clause in SQL • You’ll almost always want conditions 51Monday, October 25, 2010
  68. 68. 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
  69. 69. 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
  70. 70. 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
  71. 71. 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
  72. 72. 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
  73. 73. 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
  74. 74. 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
  75. 75. XKCD 56Monday, October 25, 2010
  76. 76. Sweden, last month 57Monday, October 25, 2010
  77. 77. 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
  78. 78. 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
  79. 79. 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
  80. 80. 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
  81. 81. 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
  82. 82. 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
  83. 83. 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
  84. 84. 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
  85. 85. 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
  86. 86. 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
  87. 87. 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
  88. 88. Iterate over results Person.all.each {|p| puts p.inspect} Person.all.each {|p| p.update_attributes(:admin => false)} 66Monday, October 25, 2010
  89. 89. 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
  90. 90. 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
  91. 91. 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 this easier to read 68Monday, October 25, 2010
  92. 92. 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 this easier to read Add “all” to get an array 68Monday, October 25, 2010
  93. 93. Negative results >> Person.find_by_first_name('blah') => nil >> Person.find_all_by_first_name ('blah') => [] 69Monday, October 25, 2010
  94. 94. Negative results >> Person.find_by_first_name('blah') => nil >> Person.find_all_by_first_name ('blah') => [] No exception! 69Monday, October 25, 2010
  95. 95. 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
  96. 96. 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
  97. 97. 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
  98. 98. 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
  99. 99. Associations • ActiveRecord really shines when it comes to “associations” • The object equivalent of primary/foreign keys connecting database tables 74Monday, October 25, 2010
  100. 100. 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
  101. 101. 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
  102. 102. 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
  103. 103. Change Pet.rb class Pet < ActiveRecord::Base belongs_to :person end 77Monday, October 25, 2010
  104. 104. 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
  105. 105. 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
  106. 106. 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
  107. 107. 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
  108. 108. 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
  109. 109. 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
  110. 110. With our email method class Pet < ActiveRecord::Base belongs_to :person def email person.email end end 82Monday, October 25, 2010
  111. 111. Easier: Delegation! class Pet < ActiveRecord::Base belongs_to :person delegate :email, :to => :person end 83Monday, October 25, 2010
  112. 112. 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
  113. 113. 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
  114. 114. Avoid nil problems class Pet < ActiveRecord::Base belongs_to :person delegate :email, :to => :person, :allow_nil => true end 85Monday, October 25, 2010
  115. 115. 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
  116. 116. 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
  117. 117. 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
  118. 118. 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
  119. 119. 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
  120. 120. 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
  121. 121. 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
  122. 122. 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
  123. 123. 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
  124. 124. 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
  125. 125. 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
  126. 126. 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
  127. 127. 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
  128. 128. 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
  129. 129. Join table People Person -Pets Pets foreign keys: person_id pet_id 96Monday, October 25, 2010
  130. 130. Migration ./script/generate model person_pet person_id:integer pet_id:integer 97Monday, October 25, 2010
  131. 131. person_pet.rb class PersonPet < ActiveRecord::Base belongs_to :person belongs_to :pet end 98Monday, October 25, 2010
  132. 132. Update person.rb class Person < ActiveRecord::Base has_many :person_pets has_many :pets, :through => :person_pets end 99Monday, October 25, 2010
  133. 133. Update person.rb class Person < ActiveRecord::Base has_many :person_pets has_many :pets, :through => :person_pets end has_many :through connects our models via the join table 99Monday, October 25, 2010
  134. 134. Update pet.rb class Pet < ActiveRecord::Base has_many :person_pets has_many :people, :through => :person_pets end 100Monday, October 25, 2010
  135. 135. 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
  136. 136. 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
  137. 137. 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
  138. 138. 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
  139. 139. 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
  140. 140. Example:Auto-destroy class Person < ActiveRecord::Base has_many :person_pets, :dependent => :destroy has_many :pets, :through => :person_pets end 106Monday, October 25, 2010
  141. 141. 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
  142. 142. 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
  143. 143. 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
  144. 144. 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
  145. 145. 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
  146. 146. 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
  147. 147. 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
  148. 148. 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
  149. 149. 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 violation is listed 113Monday, October 25, 2010
  150. 150. 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
  151. 151. 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
  152. 152. 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
  153. 153. 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
  154. 154. 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
  155. 155. 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
  156. 156. 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
  157. 157. 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
  158. 158. 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
  159. 159. 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
  160. 160. 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
  161. 161. 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
  162. 162. 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
  163. 163. 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
  164. 164. 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
  165. 165. 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
  166. 166. 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
  167. 167. 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
  168. 168. How did we do that? before_save :downcase_email def downcase_email email.downcase! end 132Monday, October 25, 2010
  169. 169. How did we do that? before_save :downcase_email def downcase_email email.downcase! end Declare the callback 132Monday, October 25, 2010
  170. 170. 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
  171. 171. 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
  172. 172. 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
  173. 173. 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
  174. 174. 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
  175. 175. 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
  176. 176. Oh, yeah • Don’t call “save” or “update_attribute” inside of a callback. • It’ll really hurt. A lot. 138Monday, October 25, 2010
  177. 177. 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
  178. 178. 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
  179. 179. 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
  180. 180. More with dirty objects >> p.changes => {"first_name"=>["Reuven", "Bibi"]} >> p.first_name_changed? => true >> p.first_name_was => "Reuven" 142Monday, October 25, 2010
  181. 181. 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
  182. 182. 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
  183. 183. 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
  184. 184. 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
  185. 185. 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
  186. 186. 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
  187. 187. 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
  188. 188. 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
  189. 189. 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
  190. 190. 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
  191. 191. 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
  192. 192. 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
  193. 193. 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
  194. 194. 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
  195. 195. :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
  196. 196. :include example Person.all.each {|p| puts p.pets.inspect} Person.all(:include => :pets).each {|p| puts p.pets.inspect} 154Monday, October 25, 2010
  197. 197. 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
  198. 198. 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
  199. 199. 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
  200. 200. 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
  201. 201. YAML • You can turn any ActiveRecord object into YAML with the .to_yaml 159Monday, October 25, 2010
  202. 202. 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: Reuven attributes_cache: {} 160Monday, October 25, 2010
  203. 203. 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":"R euven","email":"reuven@lerner.co.il"}} => nil 161Monday, October 25, 2010
  204. 204. 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
  205. 205. :include • If you want to include one or more associated objects in the JSON or XML output, just use :include 163Monday, October 25, 2010
  206. 206. JSON with :include >> puts Person.first.to_json(:include => :pets) {"person": {"created_at":"2010-10-13T18:01:03Z","updated_a t":"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","e mail":"reuven@lerner.co.il"}} => nil 164Monday, October 25, 2010
  207. 207. >> 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
  208. 208. 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
  209. 209. 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
  210. 210. 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
  211. 211. 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
  212. 212. 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
  213. 213. 171Monday, October 25, 2010
  214. 214. 190 acts_as gems! 171Monday, October 25, 2010
  215. 215. 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
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×