ActiveRecord 2.3

1,101
-1

Published on

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

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

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

No notes for slide

ActiveRecord 2.3

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

×