activerecord 2.3
Post on 26-Jan-2015
1.008 Views
Preview:
DESCRIPTION
TRANSCRIPT
ActiveRecord 2.3Reuven M. Lerner
October 20th, 2010
1Monday, October 25, 2010
What is a database?
Database
Store data confidently
Retrieve data flexibly
2Monday, October 25, 2010
Relational databases
Define tables, store data in them
Database
Retrieve data from related tables
3Monday, October 25, 2010
Database communication
SQL goes hereCREATE TABLE
INSERTUPDATEDELETE
Database
4Monday, October 25, 2010
SQL is great! But...
• It adds a second language to existing Ruby
• It’s a totally different paradigm
• We want to work with Ruby objects!
• Incidentally, SQL-in-something-else was the paradigm for years...
5Monday, October 25, 2010
Solution: ORM (object-relational mapper)
ORMRuby
Database
6Monday, October 25, 2010
Solution: ORM (object-relational mapper)
ORMRuby
Database
Write in Ruby
6Monday, October 25, 2010
Solution: ORM (object-relational mapper)
ORMRuby
Database
Write in Ruby
ORM translates to SQL, sends to database
6Monday, October 25, 2010
Solution: ORM (object-relational mapper)
ORMRuby
Database
Write in Ruby
ORM translates to SQL, sends to database
Results goto ORM
6Monday, October 25, 2010
Solution: ORM (object-relational mapper)
ORMRuby
Database
Write in Ruby
ORM translates to SQL, sends to database
Results goto ORM
ORM turns results into Ruby objects
6Monday, October 25, 2010
ActiveRecord
• By far, the most popular ORM for Ruby
• Not the only one — e.g., DataMapper
• We work with objects whenever possible
• We define as little as possible
• Our objects act as intelligent representations of what’s in the database
7Monday, October 25, 2010
ActiveRecord and Rails
• The idea of ActiveRecord preceded Rails
• Mark Fowler, from Thoughtworks (and the “Refactoring” book)
• You can use ActiveRecord without Rails
• ActiveRecord was written for Rails
• And let’s face it — nearly everyone uses ActiveRecord within Rails applications
8Monday, October 25, 2010
Opinionated!
• ActiveRecord has some ideas about how your application should work
• If you work in the same way, the work is both easy and fun
• If you try to work in a different way, it’ll be very difficult and frustrating
• “Syntactic vinegar”
9Monday, October 25, 2010
Using ActiveRecord
• Typically, we subclass ActiveRecord::Base in each of our model files
• That is, all of the classes defined in app/models/*.rb
• You don’t have to inherit from ActiveRecord::Base, of course! You can even mix and match with different models
10Monday, October 25, 2010
Version warning!
• Everything that I’m about to show is for Rails 2.3.8
• That’s the version we’re using here
• But the latest official release is 3.0, and much online documentation will reflect that
• So when you look online, check the version number!
11Monday, October 25, 2010
Person model
class Person < ActiveRecord::Base
end
12Monday, October 25, 2010
Person model
class Person < ActiveRecord::Base
end
Singular class name
12Monday, October 25, 2010
Person model
class Person < ActiveRecord::Base
end
Singular class name Standard parent class
12Monday, October 25, 2010
We can already start!
~/Downloads/foo$ ./script/console
Loading development environment (Rails 2.3.8)
>> Person
=> Person(Table doesn't exist)
13Monday, October 25, 2010
Object ≠ Table
• The object exists, but the table doesn’t.
• So let’s create the table!
14Monday, October 25, 2010
Non-Rails approach
• Create the table
• Keep the definition in a file
• Tell everyone that you’ve created the table
• When you make changes to the table, update the file and tell everyone again
• Hope that your changes don’t clash!
15Monday, October 25, 2010
Migrations
• Ruby program that describes how to change the database schema
• Add tables
• Rename columns
• Remove columns
• Set defaults
16Monday, October 25, 2010
Platform independent
• Because migrations are written in Ruby, they’re platform independent
• Well, mostly...
• ... they tend to use MySQL ideas and semantics
• No foreign keys, for example
• Works well enough with most databases
17Monday, October 25, 2010
Create a migration
./script/generate migration create_person
./script/generate model person
./script/generate model person first_name:string last_name:string email:string
18Monday, October 25, 2010
The migration file
• Migrations are in db/migrate
• Each has a unique filename, and a timestamp
• (The odds of two developers creating migrations at the same second, and with the same name, are slim)
19Monday, October 25, 2010
class CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :first_name t.string :last_name t.string :email
t.timestamps end end
def self.down drop_table :people endend
20Monday, October 25, 2010
class CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :first_name t.string :last_name t.string :email
t.timestamps end end
def self.down drop_table :people endend
Migrate forward
20Monday, October 25, 2010
class CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :first_name t.string :last_name t.string :email
t.timestamps end end
def self.down drop_table :people endend
Migrate forward
Migrate backward
20Monday, October 25, 2010
class CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :first_name t.string :last_name t.string :email
t.timestamps end end
def self.down drop_table :people endend
Migrate forward
Migrate backward
Data types
20Monday, October 25, 2010
class CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :first_name t.string :last_name t.string :email
t.timestamps end end
def self.down drop_table :people endend
Migrate forward
Migrate backward
Data types
Block!
20Monday, October 25, 2010
Run the migration
• To run all pending migrations:
rake db:migrate
• To run the “down” method until we get to a certain migration:
rake db:migrate VERSION=20101013095549
21Monday, October 25, 2010
Changing migrations
• Changing the migration file is OK!
• Add indexes, defaults, etc.
• But don’t change a table structure by editing a migration
• Rather, create a new migration that adds/renames/deletes the column
• Migrations are additive (and addictive)
22Monday, October 25, 2010
rake db:migrate
• A table, schema_migrations, is automatically created in the database
• It has one column, “version”, which contains one row for each run migration
• When you run db:migrate, it runs all of the migrations that are not in the table
• This allows for merges between developers
23Monday, October 25, 2010
Migrations and models
• Column names and types are defined in the database, not in the model
• This means that column names and types are set in the migrations
• The easiest way to find out what columns are in an ActiveRecord model class?
• The console, of course!
24Monday, October 25, 2010
Migrating~/Downloads/foo$ rake db:migrate (in /Users/reuven/Downloads/foo)== CreatePeople: migrating ===================================================-- create_table(:people) -> 0.2094s== CreatePeople: migrated (0.2097s) ==========================================
~/Downloads/foo$ rake db:migrate (in /Users/reuven/Downloads/foo)
25Monday, October 25, 2010
Migrating~/Downloads/foo$ rake db:migrate (in /Users/reuven/Downloads/foo)== CreatePeople: migrating ===================================================-- create_table(:people) -> 0.2094s== CreatePeople: migrated (0.2097s) ==========================================
~/Downloads/foo$ rake db:migrate (in /Users/reuven/Downloads/foo)
We’re up to date, so nothing happens
25Monday, October 25, 2010
Let’s check again
>> Person
=> Person(id: integer, first_name: string, last_name: string, email: string, created_at: datetime, updated_at: datetime)
26Monday, October 25, 2010
Let’s check again
>> Person
=> Person(id: integer, first_name: string, last_name: string, email: string, created_at: datetime, updated_at: datetime)
Hey, where did “id”come from?
26Monday, October 25, 2010
Let’s check again
>> Person
=> Person(id: integer, first_name: string, last_name: string, email: string, created_at: datetime, updated_at: datetime)
Hey, where did “id”come from?
And whatabout these?
26Monday, October 25, 2010
Assumptions
• Convention over configuration!
• Tables are plural, classes are singular
• class “Person”, but table “People”
• Primary key is always called “id”
• created_at, updated_at are set automatically by ActiveRecord upon creation or update to the record
27Monday, October 25, 2010
Wait! Where’s the DB?
• When did we tell Rails how to connect to the database?
• Look in config/database.yml
• The only configuration you need
• It tells ActiveRecord what database you have, and how to connect...
• ... for each environment
28Monday, October 25, 2010
development: adapter: postgresql encoding: unicode database: foo_development pool: 5 username: reuven password: reuven
29Monday, October 25, 2010
Wait, that’s it?
• Well, mostly.
• There are additional (optimal) parameters
• And some configuration is done in the environment config files
• We’ll ignore these for now
30Monday, October 25, 2010
Console reloading
• If you use the console (and you should!) then modifying ActiveRecord models may cause issues
• Use “reload!” to reload the environment
• You’ll then need to re-create all objects
• Better than having invalid objects...
31Monday, October 25, 2010
How many records?
?> Person.count
=> 0
32Monday, October 25, 2010
OK, we’ll add one>> p = Person.new
=> #<Person id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>
>> p.save!
=> true
>> Person.count
=> 1
33Monday, October 25, 2010
Wait a second!
• That person record we just created is pretty useless.
• We really don’t want nameless people in our database.
• We could (and should!) update the database definition with a new migration
• But we’ll ignore that for now. Don’t tell!
34Monday, October 25, 2010
Better creation
• The “new” method creates a new object, but doesn’t save it to the database
• This is why it has nil for an ID
• After you save, it has an ID
• To create an object and save it right away, use the “create” method instead
• Both “new” and “create” return the object
35Monday, October 25, 2010
save! and create!
• save returns true or false
• create returns the object or false
• save! and create! are the same as their “quiet” counterparts upon success
• But raise an exception if there is a problem
36Monday, October 25, 2010
Missing attributes?
• If you fail to set an attribute, then Ruby will pass it nil
• However, if you have a default value set in the database, then it’ll get that
• Don’t set created_at and updated_at; those are set automatically
37Monday, October 25, 2010
The Java way>> p = Person.new => #<Person id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>>> p.first_name = 'Reuven' => "Reuven">> p.last_name = 'Lerner' => "Lerner">> p.email = 'reuven@lerner.co.il' => "reuven@lerner.co.il">> p.save! => true
38Monday, October 25, 2010
The Ruby way
>> p = Person.new(:first_name => 'Reuven', :last_name => 'Lerner', :email => 'reuven@lerner.co.il')
=> #<Person id: nil, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: nil, updated_at: nil>
39Monday, October 25, 2010
The Ruby way
>> p = Person.new(:first_name => 'Reuven', :last_name => 'Lerner', :email => 'reuven@lerner.co.il')
=> #<Person id: nil, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: nil, updated_at: nil>
Hash of name-value pairs
39Monday, October 25, 2010
It’s not a free-for-all>> p10 = Person.new(:eye_color => 'brown')
ActiveRecord::UnknownAttributeError: unknown attribute: eye_color
from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2906:in `assign_attributes'
from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2902:in `each'
from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2902:in `assign_attributes'
from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2775:in `attributes='
from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2473:in `initialize'
from (irb):44:in `new'
from (irb):44
>>
40Monday, October 25, 2010
It’s not a free-for-all>> p10 = Person.new(:eye_color => 'brown')
ActiveRecord::UnknownAttributeError: unknown attribute: eye_color
from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2906:in `assign_attributes'
from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2902:in `each'
from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2902:in `assign_attributes'
from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2775:in `attributes='
from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2473:in `initialize'
from (irb):44:in `new'
from (irb):44
>>
40Monday, October 25, 2010
Updating fields
>> p.first_name = 'Bibi'
=> "Bibi"
>> p.save
=> true
41Monday, October 25, 2010
Updating fields
>> p.first_name = 'Bibi'
=> "Bibi"
>> p.save
=> true
If you don’t save the object,then you haven’t changed it
in the database!
41Monday, October 25, 2010
update_attributes
• It’s easier and safer to update both the object and the database simultaneously
>> p.update_attributes(
:first_name => 'Bibi')
=> true
42Monday, October 25, 2010
Multiple attributes
• p.update_attributes(
:first_name => 'Bibi',
:last_name => 'Netanyahu')
43Monday, October 25, 2010
Avoid this!
• update_attribute
• singular (not plural)
• takes two params (attribute, value), rather than a hash
• doesn’t go through any Rails validators!
• From my perspective, this method is dangerous, and should be avoided
44Monday, October 25, 2010
By the way...
• Remember our model file?
• It’s still empty.
• And yet, it allows us to create, save, and update models naturally and easily.
• Pretty cool, eh?
45Monday, October 25, 2010
Semi-protection
• attr_protected :first_name
• first_name cannot be changed with update_attributes, but it can be updated with a setter or update_attribute
• attr_accessible: Lists those attributes that are not protected
attr_accessible :email, :zip_code
46Monday, October 25, 2010
find
• This is the workhorse of ActiveRecord
• The “find” method is really a lot of different methods with a single interface
47Monday, October 25, 2010
find by ID
Person.find(3)
• If there is a Person object with ID = 3, that one object is returned
• If no object exists, an exception is raised
• Yes, this is annoying
Person.find(2, 6) # returns array
48Monday, October 25, 2010
Get them all!
Person.find(:all)
Person.all # same thing
49Monday, October 25, 2010
One object or many?
• Simple find with an ID — one object (or raises an exception)
• find with multiple IDs — returns an array of objects, or an exception if even one ID doesn’t exist
• all — always returns an array, and perhaps even an empty array
50Monday, October 25, 2010
Conditions
• We can add conditions
• turned into WHERE clause in SQL
• You’ll almost always want conditions
51Monday, October 25, 2010
Conditions, Ruby style>> Person.all(:conditions => {:first_name => 'foo'})=> []>> Person.all(:conditions => {:first_name => 'Reuven'})=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]
52Monday, October 25, 2010
Conditions, Ruby style>> Person.all(:conditions => {:first_name => 'foo'})=> []>> Person.all(:conditions => {:first_name => 'Reuven'})=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]
Hash
52Monday, October 25, 2010
Conditions, SQL style
>> Person.all(:conditions => "first_name = 'Reuven'")
=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]
53Monday, October 25, 2010
Conditions, SQL style
>> Person.all(:conditions => "first_name = 'Reuven'")
=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]
String
53Monday, October 25, 2010
Conditions, SQL style
>> Person.all(:conditions => "first_name = '#{@person.first_name}'")
=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]
54Monday, October 25, 2010
Conditions, SQL style
>> Person.all(:conditions => "first_name = '#{@person.first_name}'")
=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]
Variable
54Monday, October 25, 2010
Don’t do this!
• SQL injection attacks can happen
• There’s no reason for it
• If someone hands you a string containing a quote mark and then some SQL, it could be executed if you’re not careful
• Injection attacks should no longer occur!
• (This was true as far back as 1996...)
55Monday, October 25, 2010
XKCD
56Monday, October 25, 2010
Sweden, last month
57Monday, October 25, 2010
Interpolating parameters
• Instead of:>> Person.all(:conditions => "first_name = '#{@person.first_name}'")
• Use:>> Person.all(:conditions => ["first_name = ?", @person.first_name])
58Monday, October 25, 2010
Interpolating parameters
• Instead of:>> Person.all(:conditions => "first_name = '#{@person.first_name}'")
• Use:>> Person.all(:conditions => ["first_name = ?", @person.first_name])
Array of strings
58Monday, October 25, 2010
Interpolating parameters
• Instead of:>> Person.all(:conditions => "first_name = '#{@person.first_name}'")
• Use:>> Person.all(:conditions => ["first_name = ?", @person.first_name]) Question mark
Array of strings
58Monday, October 25, 2010
Interpolating parameters
• Instead of:>> Person.all(:conditions => "first_name = '#{@person.first_name}'")
• Use:>> Person.all(:conditions => ["first_name = ?", @person.first_name]) Question mark
No quotes!
Array of strings
58Monday, October 25, 2010
Ordering results
• Remember: A relational database doesn’t store its rows in any order
• If you don’t specify an order, you will almost certainly be surprised
59Monday, October 25, 2010
Ascending order>> Person.all(:conditions => "first_name = 'Reuven'", :order => 'created_at')
=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]
60Monday, October 25, 2010
Descending order>> Person.all(:conditions => "first_name = 'Reuven'", :order => 'created_at DESC')
=> [#<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">, #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">]
61Monday, October 25, 2010
Combining
>> Person.all(:order => 'last_name ASC, created_at DESC')
=> [#<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">, #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, ...
62Monday, October 25, 2010
Where to order?
• The database is almost certainly faster at ordering
• So invoking #all and then #sort is probably not a good idea
• In fact, the database is generally faster at filtering, too — so conditions are better than #all and #select
63Monday, October 25, 2010
first
• Returns the first row (object) from the database — or nil, if none was found
Person.first
• Of course, without an order, you don’t know which row you’ll get!
Person.first(:order => 'created_at')
64Monday, October 25, 2010
Transforming results
• Person.all returns an array — so you can invoke whatever you want on that array!
• Get an array of last names:
Person.all.map {|p| p.last_name}
65Monday, October 25, 2010
Iterate over results
Person.all.each {|p| puts p.inspect}
Person.all.each {|p| p.update_attributes(:admin => false)}
66Monday, October 25, 2010
Dynamic finders
• Remember method_missing? ActiveRecord uses this to provide “dynamic finders” — versions of find that can make our code more readable
• If you have a row named xxx, you can say find_by_xxx or find_all_by_xxx
67Monday, October 25, 2010
find_by_first_name>> Person.find_by_first_name('Reuven')=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">
>> Person.find_all_by_first_name('Reuven')=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]
68Monday, October 25, 2010
find_by_first_name>> Person.find_by_first_name('Reuven')=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">
>> Person.find_all_by_first_name('Reuven')=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]
Many find thiseasier to read
68Monday, October 25, 2010
find_by_first_name>> Person.find_by_first_name('Reuven')=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">
>> Person.find_all_by_first_name('Reuven')=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">, #<Person id: 7, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:04:09", updated_at: "2010-10-14 06:32:19">]
Many find thiseasier to read
Add “all” toget an array
68Monday, October 25, 2010
Negative results
>> Person.find_by_first_name('blah')
=> nil
>> Person.find_all_by_first_name('blah')
=> []
69Monday, October 25, 2010
Negative results
>> Person.find_by_first_name('blah')
=> nil
>> Person.find_all_by_first_name('blah')
=> []
No exception!
69Monday, October 25, 2010
Multiple attributes
>> Person.find_by_first_name_and_last_name('Reuven', 'Lerner')
=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">
70Monday, October 25, 2010
find_or_create_by_...
• Use a dynamic finder... and if you don’t find a result, then create a new object
• If the object fails validations, return a new (unsaved) object
Person.find_or_create_by_first_name('Reuven');
Person.find_or_create_by_first_name('Reuven', last_name => 'Lerner');
71Monday, October 25, 2010
Caching
• ActiveRecord caches results on a per-session basis
• So if you have already retrieved an object with the current request, it’ll be cached for further retrievals
• This doesn’t happen across requests, though
72Monday, October 25, 2010
Look in the log!
• In the development environment, you’ll see your queries rewritten using SQL.
• This is a great way to see what is happening in the underlying database
73Monday, October 25, 2010
Associations
• ActiveRecord really shines when it comes to “associations”
• The object equivalent of primary/foreign keys connecting database tables
74Monday, October 25, 2010
Pets!
• Let’s make it possible for people to have pets
./script/generate model pet animal_type:string name:string person_id:integer
rake db:migrate
75Monday, October 25, 2010
Pets!
• Let’s make it possible for people to have pets
./script/generate model pet animal_type:string name:string person_id:integer
rake db:migrateEach pet belongs to one person
75Monday, October 25, 2010
belongs_to
• Declaration (aka a class method) in the model file
• Meaning: There is a foreign key pointing from self to another object, via its ID
• The name of the foreign key is (by default) the other object’s name (singular) with _id
76Monday, October 25, 2010
Change Pet.rb
class Pet < ActiveRecord::Base
belongs_to :person
end
77Monday, October 25, 2010
What does this do?
• Doesn’t create the foreign key in the DB
• Doesn’t set the foreign key
• Doesn’t enforce anything
• It does, however, define a bunch of methods that we can now use on a pet
78Monday, October 25, 2010
Creating a pet>> spot = Pet.new(:animal_type => 'dog', :name => 'Spot', :person_id => Person.first.id)=> #<Pet id: nil, animal_type: "dog", name: "Spot", person_id: 6, created_at: nil, updated_at: nil>>> spot = Pet.new(:animal_type => 'dog', :name => 'Spot', :person => Person.first)=> #<Pet id: nil, animal_type: "dog", name: "Spot", person_id: 6, created_at: nil, updated_at: nil>
79Monday, October 25, 2010
Creating a pet>> spot = Pet.new(:animal_type => 'dog', :name => 'Spot', :person_id => Person.first.id)=> #<Pet id: nil, animal_type: "dog", name: "Spot", person_id: 6, created_at: nil, updated_at: nil>>> spot = Pet.new(:animal_type => 'dog', :name => 'Spot', :person => Person.first)=> #<Pet id: nil, animal_type: "dog", name: "Spot", person_id: 6, created_at: nil, updated_at: nil>
Here we use the ID
79Monday, October 25, 2010
Creating a pet>> spot = Pet.new(:animal_type => 'dog', :name => 'Spot', :person_id => Person.first.id)=> #<Pet id: nil, animal_type: "dog", name: "Spot", person_id: 6, created_at: nil, updated_at: nil>>> spot = Pet.new(:animal_type => 'dog', :name => 'Spot', :person => Person.first)=> #<Pet id: nil, animal_type: "dog", name: "Spot", person_id: 6, created_at: nil, updated_at: nil>
Here we use the ID
Here we use the object
79Monday, October 25, 2010
New “person” method!
>> spot.person
=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">
80Monday, October 25, 2010
Pet e-mail (p-mail?)
• Pets use their owner’s e-mail address
• One way is to define a new method on Pet.rb
• Every instance of a pet will now respond to the “email” method, and return the owner’s e-mail address
81Monday, October 25, 2010
With our email method
class Pet < ActiveRecord::Base
belongs_to :person
def email
person.email
end
end
82Monday, October 25, 2010
Easier: Delegation!
class Pet < ActiveRecord::Base
belongs_to :person
delegate :email, :to => :person
end
83Monday, October 25, 2010
By the way...>> rover = Pet.new
=> #<Pet id: nil, animal_type: nil, name: nil, person_id: nil, created_at: nil, updated_at: nil>
>> rover.email
RuntimeError: email delegated to person.email, but person is nil: #<Pet id: nil, animal_type: nil, name: nil, person_id: nil, created_at: nil, updated_at: nil>
84Monday, October 25, 2010
By the way...>> rover = Pet.new
=> #<Pet id: nil, animal_type: nil, name: nil, person_id: nil, created_at: nil, updated_at: nil>
>> rover.email
RuntimeError: email delegated to person.email, but person is nil: #<Pet id: nil, animal_type: nil, name: nil, person_id: nil, created_at: nil, updated_at: nil>We can’t delegate to nil!
84Monday, October 25, 2010
Avoid nil problems
class Pet < ActiveRecord::Base
belongs_to :person
delegate :email, :to => :person,
:allow_nil => true
end
85Monday, October 25, 2010
Problem solved
>> rover = Pet.new
=> #<Pet id: nil, animal_type: nil, name: nil, person_id: nil, created_at: nil, updated_at: nil>
>> rover.email
=> nil
86Monday, October 25, 2010
The other side
• So far, pets know about their owners...
• ... but owners don’t know about their pets>> spot.person.pets
NoMethodError: undefined method `pets' for #<ActiveRecord::Associations::BelongsToAssociation:0x1089bba50>
87Monday, October 25, 2010
one-to-one: has_one
If each person can have one pet, then we could change person.rb to read:
class Person < ActiveRecord::Base
has_one :pet
end
88Monday, October 25, 2010
Using has_one>> p = Person.first
=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">
>> p.pet
=> #<Pet id: 1, animal_type: "dog", name: "Spot", person_id: 6, created_at: "2010-10-14 07:43:59", updated_at: "2010-10-14 07:43:59">
89Monday, October 25, 2010
Using has_one>> p = Person.first
=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">
>> p.pet
=> #<Pet id: 1, animal_type: "dog", name: "Spot", person_id: 6, created_at: "2010-10-14 07:43:59", updated_at: "2010-10-14 07:43:59">
Each person has a pet
89Monday, October 25, 2010
How does Rails do it?
• It does what we would do manually — looks for all pets with our primary key value
Pet Load (1.2ms) SELECT * FROM "pets" WHERE ("pets".person_id = 6) LIMIT 1
90Monday, October 25, 2010
has_many
• More interesting, and trickier, is has_many
• Perhaps we have many pets!
class Person < ActiveRecord::Base
has_many :pets
end
91Monday, October 25, 2010
has_many
• More interesting, and trickier, is has_many
• Perhaps we have many pets!
class Person < ActiveRecord::Base
has_many :pets
end Notice plural!
91Monday, October 25, 2010
has_many
• With a has_many relationship in place, we get a method (plural!) for pets
• It always returns an array (perhaps empty)
>> p.pets
=> [#<Pet id: 1, animal_type: "dog", name: "Spot", person_id: 6, created_at: "2010-10-14 07:43:59", updated_at: "2010-10-14 07:43:59">]
92Monday, October 25, 2010
Adding
>> p.pets => []>> p.pets << Pet.new(:animal_type => 'fish', :name => "Charlie") => [#<Pet id: 2, animal_type: "fish", name: "Charlie", person_id: 7, created_at: "2010-10-14 09:21:41", updated_at: "2010-10-14 09:21:41">]>> Pet.count => 2
93Monday, October 25, 2010
Adding
>> p.pets => []>> p.pets << Pet.new(:animal_type => 'fish', :name => "Charlie") => [#<Pet id: 2, animal_type: "fish", name: "Charlie", person_id: 7, created_at: "2010-10-14 09:21:41", updated_at: "2010-10-14 09:21:41">]>> Pet.count => 2
Even though we used “new”, the object was saved
93Monday, October 25, 2010
Adding
>> p.pets => []>> p.pets << Pet.new(:animal_type => 'fish', :name => "Charlie") => [#<Pet id: 2, animal_type: "fish", name: "Charlie", person_id: 7, created_at: "2010-10-14 09:21:41", updated_at: "2010-10-14 09:21:41">]>> Pet.count => 2
Even though we used “new”, the object was saved
Automatically used our person
93Monday, October 25, 2010
Array fun
>> Person.first.pets.select {|p| p.animal_type == 'fish'}
=> []
>> Person.first.pets.select {|p| p.animal_type == 'dog'}
=> [#<Pet id: 1, animal_type: "dog", name: "Spot", person_id: 6, created_at: "2010-10-14 07:43:59", updated_at: "2010-10-14 07:43:59">]
94Monday, October 25, 2010
many-to-many
• What if each person can have multiple pets, and each pet can have multiple owners?
• For that, we need a “join” table
95Monday, October 25, 2010
Join table
People Person-Pets
Pets
foreign keys:person_id
pet_id
96Monday, October 25, 2010
Migration
./script/generate model person_pet person_id:integer pet_id:integer
97Monday, October 25, 2010
person_pet.rb
class PersonPet < ActiveRecord::Base
belongs_to :person
belongs_to :pet
end
98Monday, October 25, 2010
Update person.rb
class Person < ActiveRecord::Base
has_many :person_pets
has_many :pets, :through => :person_pets
end
99Monday, October 25, 2010
Update person.rb
class Person < ActiveRecord::Base
has_many :person_pets
has_many :pets, :through => :person_pets
end has_many :throughconnects our models
via the join table
99Monday, October 25, 2010
Update pet.rb
class Pet < ActiveRecord::Base
has_many :person_pets
has_many :people, :through => :person_pets
end
100Monday, October 25, 2010
Now it all works!
>> spot.people=> []>> spot.people << Person.first=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">]>> spot.people=> [#<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">]
101Monday, October 25, 2010
From the other side...
>> Person.first.pets=> [#<Pet id: 1, animal_type: "dog", name: "Spot", person_id: 6, created_at: "2010-10-14 07:43:59", updated_at: "2010-10-14 07:43:59">]
102Monday, October 25, 2010
Join model
• It’s a full ActiveRecord model
• You can hang other attributes on it, if you want
• However, it’s often there just for use as a connection, with no day-to-day direct use
103Monday, October 25, 2010
Association options
• has_many, belongs_to, and has_one all take a bunch of options
• Some of them are to handle ActiveRecord naming conventions
• Others can really help to shrink your code, making your models more powerful and expressive
104Monday, October 25, 2010
Example: Order
• Perhaps you always want to list pets in the order that they were created:
has_many :pets, :order => 'created_at'
• Person.first.pets will get the pets in order
• This is what we mean by pushing logic from the controller into the model
105Monday, October 25, 2010
Example: Auto-destroy
class Person < ActiveRecord::Base
has_many :person_pets, :dependent => :destroy
has_many :pets, :through => :person_pets
end
106Monday, October 25, 2010
Example: Auto-destroy
class Person < ActiveRecord::Base
has_many :person_pets, :dependent => :destroy
has_many :pets, :through => :person_pets
end
When we delete a person, we’ll also destroy the join
model person_pet
106Monday, October 25, 2010
delete vs. destroy
• There are two ways to destroy an object
p.destroy
p.delete
• Both delete the row in the database
• Both freeze the object, so that we cannot change it
107Monday, October 25, 2010
delete vs. destroy
• But:
• destroy runs before_destroy and after_destroy callbacks
• destroy handles dependent association options (i.e., you can set it such that dependent objects are deleted)
• So... use destroy, and not delete, OK?
108Monday, October 25, 2010
Validations
• “Validations” are the ActiveRecord way to ensure that your data is valid
• You can get around them!
• So these shouldn’t come in place of constraints and checks in the database
• When you save or update a model, the validations are checked and must pass
109Monday, October 25, 2010
Built-in validations
• Rails comes with a large number of validations
• declarations (i.e., class methods) put into the ActiveRecord class
• Use as many of these as you want
110Monday, October 25, 2010
validates_presence_of
• Let’s ensure that every person has first and last names:
class Person < ActiveRecord::Base
validates_presence_of :first_name
validates_presence_of :last_name
end
111Monday, October 25, 2010
Or, on a single line
class Person < ActiveRecord::Base
validates_presence_of :first_name, :last_name
end
• I prefer the multi-line version, for easier adding and removing of validations
112Monday, October 25, 2010
So, what now?
>> p = Person.new
=> #<Person id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>
>> p.save!
ActiveRecord::RecordInvalid: Validation failed: First name can't be blank, Last name can't be blank
113Monday, October 25, 2010
So, what now?
>> p = Person.new
=> #<Person id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>
>> p.save!
ActiveRecord::RecordInvalid: Validation failed: First name can't be blank, Last name can't be blank
Each violationis listed
113Monday, October 25, 2010
What errors occurred?
>> p.errors
=> #<ActiveRecord::Errors:0x1085d1020 @base=#<Person id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>, @errors=#<OrderedHash {"last_name"=>[#<ActiveRecord::Error:0x1085a2c70 @options={:default=>nil}, @base=#<Person id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>, @type=:blank, @message=:blank, @attribute=:last_name>], "first_name"=>[#<ActiveRecord::Error:0x1085a3170 @options={:default=>nil}, @base=#<Person id: nil, first_name: nil, last_name: nil, email: nil, created_at: nil, updated_at: nil>, @type=:blank, @message=:blank, @attribute=:first_name>]}>>
114Monday, October 25, 2010
Let’s try that again...
>> p.errors.class
=> ActiveRecord::Errors
>> p.errors.each_error {|attr, error| puts "[#{attr}] #{error}"}
[first_name] can't be blank
[last_name] can't be blank
=> ["first_name", "last_name"]
115Monday, October 25, 2010
ActiveRecord::Errors
• When a validation fails, it adds an element to #errors — an enumerable instance of ActiveRecord::Errors
• You could also call it the “which validations failed, and why” array
• If #errors.empty? is true, then the save/update takes place
116Monday, October 25, 2010
Built-in validations
• validates_acceptance_of
• The attribute must exist (e.g., a checkbox indicating user acceptance of site rules)
• validates_associated
• The object to which we’re connect via an association must also be valid
117Monday, October 25, 2010
Built-in validations
• validates_confirmation_of
• Did PARAM equal PARAM_confirmation? (Think of password confirmation...)
• validates_each
• Takes a block, and validates each named attribute against the block
118Monday, October 25, 2010
Built-in validations
• validates inclusion of
• validates_exclusion_of
• The attribute must (or may not) be a member of a particular array
• validates_format_of
• The attribute must match a regular expression to be valid
119Monday, October 25, 2010
Built-in validations
• validates_length_of / validates_size_of
• The attribute may be no more (and/or no less) than a specified length
• validates_numericality_of
• The attribute must be a valid number
• validates_uniqueness_of
120Monday, October 25, 2010
Validator options
• Many validators can take options
• For example:
validates_numericality_of :age, :only_integer => true, :greater_than => 0, :less_than_or_equal_to => 120
121Monday, October 25, 2010
Messages
• Each validation has a default message
• We saw those messages when looking at the errors object
• Every validation lets you customize the message with the :message parameter
validates_presence_of :last_name, :message => "What, you think you're Madonna?"
122Monday, October 25, 2010
Checking validity
• The #valid? method returns true or false
• It also sets the errors object
>> q.valid?
=> false
>> q.errors
=> #<ActiveRecord::Errors:0x1089712e8 @base=#<Person id: nil, first_ ...
123Monday, October 25, 2010
Custom validators
• Sometimes, you need to validate in a particular way
• The easiest way is to define a new method in the model class
• If the error exists, invoke errors.add_to_base, with a string containing the message
124Monday, October 25, 2010
Custom validator
validate :last_name_must_be_lerner
def last_name_must_be_lerner
errors.add_to_base("Sorry, but your last name must be 'Lerner'") unless last_name.downcase == 'lerner'
end
125Monday, October 25, 2010
Testing our validator>> p = Person.new(:first_name => 'Reuven', :last_name => 'Lerner') => #<Person id: nil, first_name: "Reuven", last_name: "Lerner", email: nil, created_at: nil, updated_at: nil>
>> p.valid? => true>> p.last_name = 'Smith' => "Smith">> p.valid? => false
126Monday, October 25, 2010
Use validations!
• They’re not database-level constraints, but they can be extremely flexible and powerful
• The built-in validators have a lot of options
• Use them!
• Only write a custom validator if you really need to do so
127Monday, October 25, 2010
Callbacks
• Validations fire automatically when we save or update our model. How?
• Answer: They’re a form of “callback,” a method that is invoked automatically when something happens
• ActiveRecord offers many “hooks” that let you define callbacks
128Monday, October 25, 2010
Uses for callbacks
• Update a counter, or total column (and avoid doing so in the controller)
• Encrypt user passwords
• Write to an audit trail about changes to a particular model
129Monday, October 25, 2010
When callbacks can run
before_validation
before_validation_on_create / ...on_update
after_validation
after_validation_on_create / ...on_update
before_save
before_create / before_update
after_create / after_update
after_save
130Monday, October 25, 2010
Downcase e-mail
>> p = Person.new(:first_name => 'Reuven', :last_name => 'Lerner', :email => 'Reuven@Lerner.co.IL') => #<Person id: nil, first_name: "Reuven", last_name: "Lerner", email: "Reuven@Lerner.co.IL", created_at: nil, updated_at: nil> >> p.save => true jruby-1.5.3 > p => #<Person id: 12, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-25 17:24:19", updated_at: "2010-10-25 17:24:19">
131Monday, October 25, 2010
How did we do that?
before_save :downcase_email
def downcase_email
email.downcase!
end
132Monday, October 25, 2010
How did we do that?
before_save :downcase_email
def downcase_email
email.downcase!
end
Declare the callback
132Monday, October 25, 2010
How did we do that?
before_save :downcase_email
def downcase_email
email.downcase!
end
Declare the callback
Define the callback method
132Monday, October 25, 2010
Declaring callbacks
• Don’t invoke them!
• They’re invoked automatically
• Don’t define them!
• Redefining them will have weird effects
• Class methods, not instance methods
• Executed in order of definition
133Monday, October 25, 2010
Multiple callbacks
• You can have as many callbacks as you want
• You can run more than one callback on a given hook
• You can run more than one callback on a given attribute
134Monday, October 25, 2010
Good uses of callbacks
• Automatic transformations
• Automatic calculations (e.g., price totals)
• Logging
• Creation of behind-the-scenes objects
• Actions that should occur when an object is saved or updated
135Monday, October 25, 2010
Bad uses of callbacks
• Additional validations
• Use a validator instead! (Which is a form of callback, after all)
• Handle session-related items
• Remember the M-V-C separation
136Monday, October 25, 2010
Return values from callbacks
• Normally, callbacks don’t return values
• But if you return false:
• In a before_* callback, all later callbacks and the action are cancelled!
• In an after_* callback, all later callbacks are cancelled
137Monday, October 25, 2010
Oh, yeah
• Don’t call “save” or “update_attribute” inside of a callback.
• It’ll really hurt. A lot.
138Monday, October 25, 2010
Looking at callbacks
• FYI, the callback on a model are stored in a “callback chain” object
• You can get at it with
Person.before_save_callback_chain
• Better yet:
Person.before_save_callback_chain.each {|c| puts c.method}; nil
139Monday, October 25, 2010
Observers
• We won’t go into this today
• Each AR object can have an observer
• Observer method names are the same as callbacks (after_save, etc.)
• So what’s the difference?
• Semantic — in/out of the model
140Monday, October 25, 2010
Dirty objects>> p = Person.first
=> #<Person id: 6, first_name: "Reuven", last_name: "Lerner", email: "reuven@lerner.co.il", created_at: "2010-10-13 18:01:03", updated_at: "2010-10-13 18:01:03">
>> p.first_name = 'Bibi'
=> "Bibi"
>> p.changed?
=> true
>> p.changed
=> ["first_name"]
141Monday, October 25, 2010
More with dirty objects
>> p.changes=> {"first_name"=>["Reuven", "Bibi"]}
>> p.first_name_changed? => true
>> p.first_name_was => "Reuven"
142Monday, October 25, 2010
More with dirty objects
>> p.changes=> {"first_name"=>["Reuven", "Bibi"]}
>> p.first_name_changed? => true
>> p.first_name_was => "Reuven"
Each changed attribute, with old and new values
142Monday, October 25, 2010
Defining methods
• It’s common (and expected) to write methods for your model
• No model methods: Your controller is probably doing too much!
• It’s OK for your model methods to talk to other models via associations...
• ... but your controller probably shouldn’t!
143Monday, October 25, 2010
Common methods
• Return a particular piece of information about the model
• String, calculation, result of a database query, statistics about the object
• Return an array, based on associations or other properties
• Associations are available for free!
144Monday, October 25, 2010
Named scopes
• An easy way to create methods
• Basically, a wrapper around “find”
• Example, from a forum-posting model:
named_scope :questions, :conditions => { :is_question => true }, :order => "created_at DESC"
145Monday, October 25, 2010
Parameterized scopes
named_scope :created_since,
lambda { |since| { :conditions => ['created_at >= ? ', since] }}
named_scope :search,
lambda { |term| { :conditions => ["lower(name) ilike ? ", term] } }
146Monday, October 25, 2010
Parameterized scopes
named_scope :created_since,
lambda { |since| { :conditions => ['created_at >= ? ', since] }}
named_scope :search,
lambda { |term| { :conditions => ["lower(name) ilike ? ", term] } }
Named scope is a procedure object taking one parameter
146Monday, October 25, 2010
When?
• When should you create a named scope?
• Simple answer: Whenever you invoke “find” in a controller, replace it with a named scope.
• It cleans up the controller code a lot.
• Note: Named scopes are class methods, not instance methods
147Monday, October 25, 2010
Chaining scopes# in class Shirt
named_scope :red, :conditions => {:color => 'red'}
named_scope :dry_clean_only, :conditions => ['dry_clean_only = ?', true]
Shirt.red
Shirt.dry_clean_only
Shirt.red.dry_clean_only
148Monday, October 25, 2010
Chaining scopes# in class Shirt
named_scope :red, :conditions => {:color => 'red'}
named_scope :dry_clean_only, :conditions => ['dry_clean_only = ?', true]
Shirt.red
Shirt.dry_clean_only
Shirt.red.dry_clean_only
Composition of scopes!
148Monday, October 25, 2010
Transactions
Group.transaction do
group = Group.create!(:name => group_name)
Membership.create!(:person => @person,
:group => group,
:is_administrator => true,
:status => 'approved')
"Successfully created the group '#{group_name}'."
end
149Monday, October 25, 2010
Transactions
Group.transaction do
group = Group.create!(:name => group_name)
Membership.create!(:person => @person,
:group => group,
:is_administrator => true,
:status => 'approved')
"Successfully created the group '#{group_name}'."
end
Class method “transaction”
149Monday, October 25, 2010
Transaction tips
• Transactions are per connection, not model
• So use whatever class you want
• Failure raises ActiveRecord::Rollback
• These only work in databases that support transactions (i.e., not MySQL’s ISAM)
• Nested transactions work, but are often translated into “savepoints”
150Monday, October 25, 2010
Declarations
• has_one, has_many, and belongs_to are class methods
• (I think of them as declarations)
• All they do is define methods!
• So has_many might seem magical, but all it’s doing is defining a bunch of methods on your object
151Monday, October 25, 2010
Adding declarations
• Add a module to the lib directory
• (Automatically included)
• Use Module#included? to create one or more class methods in the including class
• Voila! Now you can do it, too
• e.g., adds_priority_tags_to_errors
152Monday, October 25, 2010
:include
• When you perform a “find”, consider :include
• It retrieves another object with the current one
• Since the result is cached for this request, no more database retrievals are needed
• A major speedup in many cases
153Monday, October 25, 2010
:include example
Person.all.each {|p| puts p.pets.inspect}
Person.all(:include => :pets).each {|p| puts p.pets.inspect}
154Monday, October 25, 2010
Optimistic locking
• Add a lock_version field to your model, with a default value of 0
• Voila! Now you can stop people from saving older versions on top of newer ones
• Each save/update increments lock_version
• If an older version is saved/updated, a StaleObjectError exception is raised
155Monday, October 25, 2010
Pessimistic locking
• If you pass :lock => true to find, you’ll get an exclusive lock on the row
• Uses SELECT .. FOR UPDATE
• If you need a different string, then pass a string, rather than “true”
• I’ve never used this
• But hey, I use PostgreSQL...
156Monday, October 25, 2010
Seed data
• Don’t put data in a migration file!
• Instead, use the special db:seed Rake task
• File is db/seeds.rb
• Add lots of calls to “create” in here
• It only adds data — no doubles, erasing, or otherwise touching of existing data
157Monday, October 25, 2010
Changing behavior
• Don’t write an “initialize” method for your ActiveRecord object. This will probably fail.
• Instead, use the after_initialize hook
• Or write a plugin that monkey-patches ActiveRecord!
158Monday, October 25, 2010
YAML
• You can turn any ActiveRecord object into YAML with the .to_yaml
159Monday, October 25, 2010
YAML
>> puts Person.first.to_yaml--- !ruby/object:Person attributes: created_at: 2010-10-13 18:01:03.330099 updated_at: 2010-10-13 18:01:03.330099 id: "6" last_name: Lerner email: reuven@lerner.co.il first_name: Reuvenattributes_cache: {}
160Monday, October 25, 2010
JSON
>> puts Person.first.to_json
{"person":{"created_at":"2010-10-13T18:01:03Z","updated_at":"2010-10-13T18:01:03Z","id":6,"last_name":"Lerner","first_name":"Reuven","email":"reuven@lerner.co.il"}}
=> nil
161Monday, October 25, 2010
XML>> puts Person.first.to_xml<?xml version="1.0" encoding="UTF-8"?><person> <created-at type="datetime">2010-10-13T18:01:03Z</created-at> <email>reuven@lerner.co.il</email> <first-name>Reuven</first-name> <id type="integer">6</id> <last-name>Lerner</last-name> <updated-at type="datetime">2010-10-13T18:01:03Z</updated-at></person>=> nil
162Monday, October 25, 2010
:include
• If you want to include one or more associated objects in the JSON or XML output, just use :include
163Monday, October 25, 2010
JSON with :include
>> puts Person.first.to_json(:include => :pets)
{"person":{"created_at":"2010-10-13T18:01:03Z","updated_at":"2010-10-13T18:01:03Z","pets":[{"name":"Spot","created_at":"2010-10-14T07:43:59Z","updated_at":"2010-10-14T07:43:59Z","id":1,"person_id":6,"animal_type":"dog"}],"id":6,"last_name":"Lerner","first_name":"Reuven","email":"reuven@lerner.co.il"}}
=> nil
164Monday, October 25, 2010
>> puts Person.first.to_xml(:include => :pets)
<?xml version="1.0" encoding="UTF-8"?>
<person>
<created-at type="datetime">2010-10-13T18:01:03Z</created-at>
<email>reuven@lerner.co.il</email>
<first-name>Reuven</first-name>
<id type="integer">6</id>
<last-name>Lerner</last-name>
<updated-at type="datetime">2010-10-13T18:01:03Z</updated-at>
<pets type="array">
<pet>
<animal-type>dog</animal-type>
<created-at type="datetime">2010-10-14T07:43:59Z</created-at>
<id type="integer">1</id>
<name>Spot</name>
<person-id type="integer">6</person-id>
<updated-at type="datetime">2010-10-14T07:43:59Z</updated-at>
</pet>
</pets>
</person>
=> nil
165Monday, October 25, 2010
Other options
• :except — ignore certain attributes/tags
• :only — we only want some attributes
• :methods — invoke methods and include their output in the XML
• Or hand a block to to_xml, and then you can use builder (Ruby’s XML-generating facility) to create whatever you want!
166Monday, October 25, 2010
Better XML
• If you want to customize the XML, then use an XML view (instead of an HTML view)
• “Builder” allows you to create XML files very easily, with any tags and attributes
• We’ll talk about this further when we discuss views
167Monday, October 25, 2010
Plugins
• Plugins modify default Rails behavior
• They go in /vendor/plugins
• Many modify ActiveRecord’s behavior
• Be careful before installing a plugin... they’re quite useful, but you don’t want clashes
168Monday, October 25, 2010
Example: acts_as_tree
• Create a table with a “parent” attribute
• If you say “acts_as_tree”, then you get methods for “parent,” “children,” and so forth
• In very widespread use (written by DHH)
169Monday, October 25, 2010
Some others
• acts_as_list
• acts_as_nested_set
• acts_as_taggable
• acts_as_taggable_on_steroids
• acts_as_state_machine
170Monday, October 25, 2010
171Monday, October 25, 2010
190 acts_as gems!
171Monday, October 25, 2010
Contacting me
• Call me in Israel: 054-496-8405
• Call me in the US: 847-230-9795
• E-mail me: reuven@lerner.co.il
• Interrupt me: reuvenlerner (Skype/AIM)
172Monday, October 25, 2010
top related