SlideShare une entreprise Scribd logo
1  sur  215
Télécharger pour lire hors ligne
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 with Ruby objects!
• Incidentally, SQL-in-something-else was
the paradigm for years...
5Monday, October 25, 2010
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, October 25, 2010
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
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
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
end
end
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
end
end
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
end
end
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
end
end
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
end
end
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 what
about 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 this
easier 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 this
easier to read
Add “all” to
get 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 :through
connects 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 violation
is 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: Reuven
attributes_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":"R
euven","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_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
>> 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

Contenu connexe

En vedette

Big Data — Your new best friend
Big Data — Your new best friendBig Data — Your new best friend
Big Data — Your new best friendReuven Lerner
 
Git talk from Open 2011 conference in Israel
Git talk from Open 2011 conference in IsraelGit talk from Open 2011 conference in Israel
Git talk from Open 2011 conference in IsraelReuven Lerner
 
What's new in Rails 4
What's new in Rails 4What's new in Rails 4
What's new in Rails 4Lucas Caton
 
What can Ruby learn from Python (and vice versa)?
What can Ruby learn from Python (and vice versa)?What can Ruby learn from Python (and vice versa)?
What can Ruby learn from Python (and vice versa)?Reuven Lerner
 
Web APIs: The future of software
Web APIs: The future of softwareWeb APIs: The future of software
Web APIs: The future of softwareReuven Lerner
 
Functional Python Webinar from October 22nd, 2014
Functional Python Webinar from October 22nd, 2014Functional Python Webinar from October 22nd, 2014
Functional Python Webinar from October 22nd, 2014Reuven Lerner
 
Ruby on Rails 中級者を目指して - 大場寧子
Ruby on Rails 中級者を目指して - 大場寧子Ruby on Rails 中級者を目指して - 大場寧子
Ruby on Rails 中級者を目指して - 大場寧子Yasuko Ohba
 
Dynamic languages, for software craftmanship group
Dynamic languages, for software craftmanship groupDynamic languages, for software craftmanship group
Dynamic languages, for software craftmanship groupReuven Lerner
 
Python's magic methods
Python's magic methodsPython's magic methods
Python's magic methodsReuven Lerner
 
Technical training business talk.key
Technical training business talk.keyTechnical training business talk.key
Technical training business talk.keyReuven Lerner
 
Intro to cloud computing — MegaCOMM 2013, Jerusalem
Intro to cloud computing — MegaCOMM 2013, JerusalemIntro to cloud computing — MegaCOMM 2013, Jerusalem
Intro to cloud computing — MegaCOMM 2013, JerusalemReuven Lerner
 

En vedette (13)

Big Data — Your new best friend
Big Data — Your new best friendBig Data — Your new best friend
Big Data — Your new best friend
 
Git talk from Open 2011 conference in Israel
Git talk from Open 2011 conference in IsraelGit talk from Open 2011 conference in Israel
Git talk from Open 2011 conference in Israel
 
What's new in Rails 4
What's new in Rails 4What's new in Rails 4
What's new in Rails 4
 
What can Ruby learn from Python (and vice versa)?
What can Ruby learn from Python (and vice versa)?What can Ruby learn from Python (and vice versa)?
What can Ruby learn from Python (and vice versa)?
 
Web APIs: The future of software
Web APIs: The future of softwareWeb APIs: The future of software
Web APIs: The future of software
 
Functional Python Webinar from October 22nd, 2014
Functional Python Webinar from October 22nd, 2014Functional Python Webinar from October 22nd, 2014
Functional Python Webinar from October 22nd, 2014
 
Rails console
Rails consoleRails console
Rails console
 
Ruby on Rails 中級者を目指して - 大場寧子
Ruby on Rails 中級者を目指して - 大場寧子Ruby on Rails 中級者を目指して - 大場寧子
Ruby on Rails 中級者を目指して - 大場寧子
 
Dynamic languages, for software craftmanship group
Dynamic languages, for software craftmanship groupDynamic languages, for software craftmanship group
Dynamic languages, for software craftmanship group
 
Python's magic methods
Python's magic methodsPython's magic methods
Python's magic methods
 
PostgreSQL
PostgreSQLPostgreSQL
PostgreSQL
 
Technical training business talk.key
Technical training business talk.keyTechnical training business talk.key
Technical training business talk.key
 
Intro to cloud computing — MegaCOMM 2013, Jerusalem
Intro to cloud computing — MegaCOMM 2013, JerusalemIntro to cloud computing — MegaCOMM 2013, Jerusalem
Intro to cloud computing — MegaCOMM 2013, Jerusalem
 

Similaire à ActiveRecord 2.3

Couchdbkit djangocong-20100425
Couchdbkit djangocong-20100425Couchdbkit djangocong-20100425
Couchdbkit djangocong-20100425guest4f2eea
 
Introduction to Node.js: perspectives from a Drupal dev
Introduction to Node.js: perspectives from a Drupal devIntroduction to Node.js: perspectives from a Drupal dev
Introduction to Node.js: perspectives from a Drupal devmcantelon
 
Webinar 2017. Supercharge your analytics with ClickHouse. Alexander Zaitsev
Webinar 2017. Supercharge your analytics with ClickHouse. Alexander ZaitsevWebinar 2017. Supercharge your analytics with ClickHouse. Alexander Zaitsev
Webinar 2017. Supercharge your analytics with ClickHouse. Alexander ZaitsevAltinity Ltd
 
xjtrutdctrd5454drxxresersestryugyufy6rythgfytfyt
xjtrutdctrd5454drxxresersestryugyufy6rythgfytfytxjtrutdctrd5454drxxresersestryugyufy6rythgfytfyt
xjtrutdctrd5454drxxresersestryugyufy6rythgfytfytWrushabhShirsat3
 
Rails Tips and Best Practices
Rails Tips and Best PracticesRails Tips and Best Practices
Rails Tips and Best PracticesDavid Keener
 
Chris Lea - What does NoSQL Mean for You
Chris Lea - What does NoSQL Mean for YouChris Lea - What does NoSQL Mean for You
Chris Lea - What does NoSQL Mean for YouCarsonified Team
 
Products.intro.forum version
Products.intro.forum versionProducts.intro.forum version
Products.intro.forum versionsqlserver.co.il
 
Solr At Scale For Time-Oriented Data: Presented by Brett Hoerner, Rocana
Solr At Scale For Time-Oriented Data: Presented by Brett Hoerner, RocanaSolr At Scale For Time-Oriented Data: Presented by Brett Hoerner, Rocana
Solr At Scale For Time-Oriented Data: Presented by Brett Hoerner, RocanaLucidworks
 
Code for Startup MVP (Ruby on Rails) Session 2
Code for Startup MVP (Ruby on Rails) Session 2Code for Startup MVP (Ruby on Rails) Session 2
Code for Startup MVP (Ruby on Rails) Session 2Henry S
 
Your backend architecture is what matters slideshare
Your backend architecture is what matters slideshareYour backend architecture is what matters slideshare
Your backend architecture is what matters slideshareColin Charles
 
Conceptos básicos. Seminario web 1: Introducción a NoSQL
Conceptos básicos. Seminario web 1: Introducción a NoSQLConceptos básicos. Seminario web 1: Introducción a NoSQL
Conceptos básicos. Seminario web 1: Introducción a NoSQLMongoDB
 
RethinkDB - the open-source database for the realtime web
RethinkDB - the open-source database for the realtime webRethinkDB - the open-source database for the realtime web
RethinkDB - the open-source database for the realtime webAlex Ivanov
 
Rapid Application Development using Ruby on Rails
Rapid Application Development using Ruby on RailsRapid Application Development using Ruby on Rails
Rapid Application Development using Ruby on RailsSimobo
 
MongoDB: a gentle, friendly overview
MongoDB: a gentle, friendly overviewMongoDB: a gentle, friendly overview
MongoDB: a gentle, friendly overviewAntonio Pintus
 
Dev buchan leveraging
Dev buchan leveragingDev buchan leveraging
Dev buchan leveragingBill Buchan
 

Similaire à ActiveRecord 2.3 (20)

Couchdbkit & Dango
Couchdbkit & DangoCouchdbkit & Dango
Couchdbkit & Dango
 
Couchdbkit djangocong-20100425
Couchdbkit djangocong-20100425Couchdbkit djangocong-20100425
Couchdbkit djangocong-20100425
 
Introduction to Node.js: perspectives from a Drupal dev
Introduction to Node.js: perspectives from a Drupal devIntroduction to Node.js: perspectives from a Drupal dev
Introduction to Node.js: perspectives from a Drupal dev
 
No sql Database
No sql DatabaseNo sql Database
No sql Database
 
Webinar 2017. Supercharge your analytics with ClickHouse. Alexander Zaitsev
Webinar 2017. Supercharge your analytics with ClickHouse. Alexander ZaitsevWebinar 2017. Supercharge your analytics with ClickHouse. Alexander Zaitsev
Webinar 2017. Supercharge your analytics with ClickHouse. Alexander Zaitsev
 
unit-ii.pptx
unit-ii.pptxunit-ii.pptx
unit-ii.pptx
 
xjtrutdctrd5454drxxresersestryugyufy6rythgfytfyt
xjtrutdctrd5454drxxresersestryugyufy6rythgfytfytxjtrutdctrd5454drxxresersestryugyufy6rythgfytfyt
xjtrutdctrd5454drxxresersestryugyufy6rythgfytfyt
 
Hadoop - Introduction to Hadoop
Hadoop - Introduction to HadoopHadoop - Introduction to Hadoop
Hadoop - Introduction to Hadoop
 
noSQL @ QCon SP
noSQL @ QCon SPnoSQL @ QCon SP
noSQL @ QCon SP
 
Rails Tips and Best Practices
Rails Tips and Best PracticesRails Tips and Best Practices
Rails Tips and Best Practices
 
Chris Lea - What does NoSQL Mean for You
Chris Lea - What does NoSQL Mean for YouChris Lea - What does NoSQL Mean for You
Chris Lea - What does NoSQL Mean for You
 
Products.intro.forum version
Products.intro.forum versionProducts.intro.forum version
Products.intro.forum version
 
Solr At Scale For Time-Oriented Data: Presented by Brett Hoerner, Rocana
Solr At Scale For Time-Oriented Data: Presented by Brett Hoerner, RocanaSolr At Scale For Time-Oriented Data: Presented by Brett Hoerner, Rocana
Solr At Scale For Time-Oriented Data: Presented by Brett Hoerner, Rocana
 
Code for Startup MVP (Ruby on Rails) Session 2
Code for Startup MVP (Ruby on Rails) Session 2Code for Startup MVP (Ruby on Rails) Session 2
Code for Startup MVP (Ruby on Rails) Session 2
 
Your backend architecture is what matters slideshare
Your backend architecture is what matters slideshareYour backend architecture is what matters slideshare
Your backend architecture is what matters slideshare
 
Conceptos básicos. Seminario web 1: Introducción a NoSQL
Conceptos básicos. Seminario web 1: Introducción a NoSQLConceptos básicos. Seminario web 1: Introducción a NoSQL
Conceptos básicos. Seminario web 1: Introducción a NoSQL
 
RethinkDB - the open-source database for the realtime web
RethinkDB - the open-source database for the realtime webRethinkDB - the open-source database for the realtime web
RethinkDB - the open-source database for the realtime web
 
Rapid Application Development using Ruby on Rails
Rapid Application Development using Ruby on RailsRapid Application Development using Ruby on Rails
Rapid Application Development using Ruby on Rails
 
MongoDB: a gentle, friendly overview
MongoDB: a gentle, friendly overviewMongoDB: a gentle, friendly overview
MongoDB: a gentle, friendly overview
 
Dev buchan leveraging
Dev buchan leveragingDev buchan leveraging
Dev buchan leveraging
 

Plus de Reuven Lerner

PostgreSQL, your NoSQL database
PostgreSQL, your NoSQL databasePostgreSQL, your NoSQL database
PostgreSQL, your NoSQL databaseReuven Lerner
 
Modern Web technologies (and why you should care): Megacomm, Jerusalem, Febru...
Modern Web technologies (and why you should care): Megacomm, Jerusalem, Febru...Modern Web technologies (and why you should care): Megacomm, Jerusalem, Febru...
Modern Web technologies (and why you should care): Megacomm, Jerusalem, Febru...Reuven Lerner
 
Rails development environment talk
Rails development environment talkRails development environment talk
Rails development environment talkReuven Lerner
 
Modern Web Technologies — Jerusalem Web Professionals, January 2011
Modern Web Technologies — Jerusalem Web Professionals, January 2011Modern Web Technologies — Jerusalem Web Professionals, January 2011
Modern Web Technologies — Jerusalem Web Professionals, January 2011Reuven Lerner
 

Plus de Reuven Lerner (9)

PostgreSQL, your NoSQL database
PostgreSQL, your NoSQL databasePostgreSQL, your NoSQL database
PostgreSQL, your NoSQL database
 
Rails israel 2013
Rails israel 2013Rails israel 2013
Rails israel 2013
 
Rails traps
Rails trapsRails traps
Rails traps
 
Modern Web technologies (and why you should care): Megacomm, Jerusalem, Febru...
Modern Web technologies (and why you should care): Megacomm, Jerusalem, Febru...Modern Web technologies (and why you should care): Megacomm, Jerusalem, Febru...
Modern Web technologies (and why you should care): Megacomm, Jerusalem, Febru...
 
Rails development environment talk
Rails development environment talkRails development environment talk
Rails development environment talk
 
Modern Web Technologies — Jerusalem Web Professionals, January 2011
Modern Web Technologies — Jerusalem Web Professionals, January 2011Modern Web Technologies — Jerusalem Web Professionals, January 2011
Modern Web Technologies — Jerusalem Web Professionals, January 2011
 
Ruby objects
Ruby objectsRuby objects
Ruby objects
 
Rails tools
Rails toolsRails tools
Rails tools
 
Why ruby and rails
Why ruby and railsWhy ruby and rails
Why ruby and rails
 

Dernier

Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Scott Keck-Warren
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningLars Bell
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostZilliz
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Enterprise Knowledge
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxhariprasad279825
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenHervé Boutemy
 
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo DayH2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo DaySri Ambati
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clashcharlottematthew16
 

Dernier (20)

Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine Tuning
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptx
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache Maven
 
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo DayH2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
H2O.ai CEO/Founder: Sri Ambati Keynote at Wells Fargo Day
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clash
 

ActiveRecord 2.3

  • 1. ActiveRecord 2.3 Reuven M. Lerner October 20th, 2010 1Monday, October 25, 2010
  • 2. What is a database? Database Store data confidently Retrieve data flexibly 2Monday, October 25, 2010
  • 3. Relational databases Define tables, store data in them Database Retrieve data from related tables 3Monday, October 25, 2010
  • 4. Database communication SQL goes here CREATE TABLE INSERT UPDATE DELETE Database 4Monday, October 25, 2010
  • 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. Solution: ORM (object- relational mapper) ORM Ruby Database 6Monday, October 25, 2010
  • 7. Solution: ORM (object- relational mapper) ORM Ruby Database Write in Ruby 6Monday, October 25, 2010
  • 8. Solution: ORM (object- relational mapper) ORM Ruby Database Write in Ruby ORM translates to SQL, sends to database 6Monday, October 25, 2010
  • 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. 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. 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. 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. 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. 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. 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. Person model class Person < ActiveRecord::Base end 12Monday, October 25, 2010
  • 17. Person model class Person < ActiveRecord::Base end Singular class name 12Monday, October 25, 2010
  • 18. Person model class Person < ActiveRecord::Base end Singular class name Standard parent class 12Monday, October 25, 2010
  • 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. Object ≠ Table • The object exists, but the table doesn’t. • So let’s create the table! 14Monday, October 25, 2010
  • 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. Migrations • Ruby program that describes how to change the database schema • Add tables • Rename columns • Remove columns • Set defaults 16Monday, October 25, 2010
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. development: adapter: postgresql encoding: unicode database: foo_development pool: 5 username: reuven password: reuven 29Monday, October 25, 2010
  • 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. 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. How many records? ?> Person.count => 0 32Monday, October 25, 2010
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. Updating fields >> p.first_name = 'Bibi' => "Bibi" >> p.save => true 41Monday, October 25, 2010
  • 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. 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. Multiple attributes • p.update_attributes( :first_name => 'Bibi', :last_name => 'Netanyahu') 43Monday, October 25, 2010
  • 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. 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. 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. 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. 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. Get them all! Person.find(:all) Person.all # same thing 49Monday, October 25, 2010
  • 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. Conditions • We can add conditions • turned into WHERE clause in SQL • You’ll almost always want conditions 51Monday, October 25, 2010
  • 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. 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. 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. 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. 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. 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. 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
  • 76. Sweden, last month 57Monday, October 25, 2010
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. Iterate over results Person.all.each {|p| puts p.inspect} Person.all.each {|p| p.update_attributes(:admin => false)} 66Monday, October 25, 2010
  • 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. 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. 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. 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. Negative results >> Person.find_by_first_name('blah') => nil >> Person.find_all_by_first_name ('blah') => [] 69Monday, October 25, 2010
  • 94. Negative results >> Person.find_by_first_name('blah') => nil >> Person.find_all_by_first_name ('blah') => [] No exception! 69Monday, October 25, 2010
  • 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. 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. 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. 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. Associations • ActiveRecord really shines when it comes to “associations” • The object equivalent of primary/foreign keys connecting database tables 74Monday, October 25, 2010
  • 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. 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. 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. Change Pet.rb class Pet < ActiveRecord::Base belongs_to :person end 77Monday, October 25, 2010
  • 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. 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. 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. 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. 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. 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. With our email method class Pet < ActiveRecord::Base belongs_to :person def email person.email end end 82Monday, October 25, 2010
  • 111. Easier: Delegation! class Pet < ActiveRecord::Base belongs_to :person delegate :email, :to => :person end 83Monday, October 25, 2010
  • 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. 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. Avoid nil problems class Pet < ActiveRecord::Base belongs_to :person delegate :email, :to => :person, :allow_nil => true end 85Monday, October 25, 2010
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  • 131. person_pet.rb class PersonPet < ActiveRecord::Base belongs_to :person belongs_to :pet end 98Monday, October 25, 2010
  • 132. Update person.rb class Person < ActiveRecord::Base has_many :person_pets has_many :pets, :through => :person_pets end 99Monday, October 25, 2010
  • 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. Update pet.rb class Pet < ActiveRecord::Base has_many :person_pets has_many :people, :through => :person_pets end 100Monday, October 25, 2010
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. How did we do that? before_save :downcase_email def downcase_email email.downcase! end 132Monday, October 25, 2010
  • 169. How did we do that? before_save :downcase_email def downcase_email email.downcase! end Declare the callback 132Monday, October 25, 2010
  • 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. 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. 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. 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. 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. 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. Oh, yeah • Don’t call “save” or “update_attribute” inside of a callback. • It’ll really hurt. A lot. 138Monday, October 25, 2010
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. :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. :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. 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. 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. 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. 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. YAML • You can turn any ActiveRecord object into YAML with the .to_yaml 159Monday, October 25, 2010
  • 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
  • 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. :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. 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. >> 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. 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. 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. 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. 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. 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
  • 214. 190 acts_as gems! 171Monday, October 25, 2010
  • 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