Your SlideShare is downloading. ×
ActiveRecord 2.3
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Saving this for later?

Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime - even offline.

Text the download link to your phone

Standard text messaging rates apply

ActiveRecord 2.3

991
views

Published on

Slides from a lecture I just gave on ActiveRecord 2.3. Describes configuration, methods, CRUD, finders, updating, associations, and a bunch of things that I wish I had known when I started with …

Slides from a lecture I just gave on ActiveRecord 2.3. Describes configuration, methods, CRUD, finders, updating, associations, and a bunch of things that I wish I had known when I started with ActiveRecord.

Published in: Technology

0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

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

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 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
  • 75. XKCD 56Monday, 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
  • 129. Join table People Person -Pets Pets foreign keys: person_id pet_id 96Monday, October 25, 2010
  • 130. Migration ./script/generate model person_pet person_id:integer pet_id:integer 97Monday, 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
  • 203. JSON >> puts Person.first.to_json {"person": {"created_at":"2010-10-13T18:01:03Z"," updated_at":"2010-10-13T18:01:03Z","id ": 6,"last_name":"Lerner","first_name":"R euven","email":"reuven@lerner.co.il"}} => nil 161Monday, October 25, 2010
  • 204. 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
  • 213. 171Monday, 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