Slides from a lecture I just gave on ActiveRecord 2.3. Describes configuration, methods, CRUD, finders, updating, associations, and a bunch of things that I wish I had known when I started with ActiveRecord.
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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