Introduction to Active Record at MySQL Conference 2007

  • 8,148 views
Uploaded on

An introduction to the ruby on rails Active Record library presented at the MySQL Users Conference in Santa Clara 2007.

An introduction to the ruby on rails Active Record library presented at the MySQL Users Conference in Santa Clara 2007.

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
  • outstanding slideshow..convinced me to have a hardlook at my company model..brilliant
    Teisha
    http://dashinghealth.com http://healthimplants.com
    Are you sure you want to
    Your message goes here
No Downloads

Views

Total Views
8,148
On Slideshare
0
From Embeds
0
Number of Embeds
9

Actions

Shares
Downloads
374
Comments
1
Likes
11

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. Introduction to Active Record Evan ‘Rabble’ Henshaw-Plath evan@protest.net - Yahoo! Brickhouse anarchogeek.com - testingrails.com
  • 2. Active Record is a Design Pattern An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.
  • 3. Active Record the Pattern Person last_name first_name dependents_count insert update get_exemption is_flagged_for_audit? get_taxable_earnings? Active Record uses the most obvious approach, putting data access logic in the domain object. - Martin Fowler
  • 4. One Class Per Table The Model Code The Database class User < ActiveRecord::Base CREATE TABLE `users` ( `id` int(11) NOT NULL auto_increment, end `login` varchar(255), `email` varchar(255), `crypted_password` varchar(40), `salt` varchar(40), `created_at` datetime default NULL, `updated_at` datetime default NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB;
  • 5. One Object Per Row
  • 6. There Are Other Ways To Do it Active Record is just one ‘Data Source Architectural Pattern’ • Table Data Gateway • Row Data Gateway • Data Mapper • The Anti-Patterns
  • 7. Standard Active Record • Direct mapping to the DB • Class to table • Object to row • Simple, no relationship between objects • Just a finder method with getters and setters
  • 8. ActiveRecord the ruby library Active Record is a library built for Ruby on Rails. Makes CRUD Easy Create Read Update Delete
  • 9. ActiveRecord the ruby library I have never seen an Active Record implementation as complete or as useful as rails. - Martin Fowler
  • 10. Rails’ ActiveRecord • DRY Conventions & Assumptions • Validations • Before and after filters • Database Agnostic (mostly) • Migrations • Model relationships • has_many, belongs_to, etc...
  • 11. What Active Record Likes • mapping class names to table names • pluralized table names • integer primary keys • classname_id foreign keys • simple schemas • single table inheritance
  • 12. Active Record Doesn’t Like • views • stored procedures • foreign key constraints • cascading commits • split or clustered db’s • enums
  • 13. The Basics ./app/models/user.rb Loading a user class User < ActiveRecord::Base >> user_obj = User.find(2) end => #<User:0x352e8bc @attributes= {"salt"=>"d9ef...", The SQL Log "updated_at"=>"2007-04-19 10:49:15", "crypted_password"=>"9c1...", User Load (0.003175) "id"=>"2", SELECT * FROM users "remember_token"=>"a8d...", WHERE (users.id = 2) LIMIT 1 "login"=>"rabble", "created_at"=>"2007-04-19 10:49:15", "email"=>"evan@protest.net"}>
  • 14. The Find Method Find is the primary method of Active Record Examples: User.find(23) User.find(:first) User.find(:all, :offset => 10, :limit => 10) User.find(:all, :include => [:account, :friends]) User.find(:all, :conditions => [“category in (?), categories, :limit => 50) User.find(:first).articles
  • 15. The Four Ways of Find Find by id: This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). Find first: This will return the first record matched by the options used. Find all: This will return all the records matched by the options used. Indirectly: The find method is used for AR lookups via associations.
  • 16. Understanding Find Model#find(:all, { parameters hash } What Find Does: * generates sql * executes sql * returns an enumerable (array like object) * creates an AR model object for each row
  • 17. Find with :conditions :conditions - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. Student.find(:all, :conditions => [‘first_name = ? and status = ?’ ‘rabble’, 1]) New Style (Edge Rails Only) Student.find(:all, :conditions => {:first_name => “rabble”, :status => 1}) SQL Executed: SELECT * FROM students WHERE (first_name = 'rabble' and status = 1);
  • 18. Order By :order - An SQL fragment like "created_at DESC, name". Student.find(:all, :order => ‘updated_at DESC’) SQL Executed: SELECT * FROM users ORDER BY created_at;
  • 19. Group By :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. Student.find(:all, :group => ‘graduating_class’) SQL Executed: SELECT * FROM users GROUP BY graduating_class;
  • 20. Limit & Offset :limit - An integer determining the limit on the number of rows that should be returned. :offset- An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. Student.find(:all, :limit => 10, :offset => 0) SQL Executed: SELECT * FROM users LIMIT 0, 10;
  • 21. Joins :joins - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed). Student.find(:all, :join => "LEFT JOIN comments ON comments.post_id = id") SQL Executed: SELECT users.* FROM users, comments LEFT JOIN comments ON comments.post_id = users.id; Returns read only objects unless you say :readonly => false
  • 22. Alternative Finds find_by_sql find_by_attribute_and_attribute2 find_or_create Depreciated Find’s find_first find_all find_on_conditions
  • 23. Associations The Four Primary Associations belongs_to has_one has_many has_and_belongs_to_many class Project < ActiveRecord::Base belongs_to :portfolio has_one :project_manager has_many :milestones has_and_belongs_to_many :categories end
  • 24. Associations One to One has_one & belongs_to Many to One has_many & belongs_to Many to Many has_and_belongs_to_many has_many :through
  • 25. One to One Use has_one in the base, and belongs_to in the associated model. class Employee < ActiveRecord::Base has_one :office end class Office < ActiveRecord::Base belongs_to :employee # foreign key - employee_id end
  • 26. One To One Example >> joe_employee = Employee.find_by_first_name('joe') SELECT * FROM employees WHERE (employees.`first_name` = 'joe') LIMIT 1 => #<Employee:0x36beb14 @attributes={"id"=>"1", "first_name"=>"joe", "last_name"=>"schmo", "created_at"=>"2007-04-21 09:08:59"}> >> joes_office = joe_employee.office SELECT * FROM offices WHERE (offices.employee_id = 1) LIMIT 1 => #<Office:0x36bc06c @attributes={"employee_id"=>"1", "id"=>"1", "created_at"=>"2007-04-21 09:11:44", "location"=>"A4302"}> >> joes_office.employee SELECT * FROM employees WHERE (employees.`id` = 1) => #<Employee:0x36b6ef0 @attributes={"id"=>"1", "first_name"=>"joe", "last_name"=>"schmo", "created_at"=>"2007-04-21 09:08:59"}>
  • 27. belongs_to One to One Relationship. Use belong to when the foreign key is in THIS table. • Post#author (similar to Author.find(author_id) ) • Post#author=(author) (similar to post.author_id = author.id) • Post#author? (similar to post.author == some_author) • Post#author.nil? • Post#build_author (similar to post.author = Author.new) • Post#create_author (similar to post.author = Author; post.author.save;
  • 28. Defining belongs_to class Employee < ActiveRecord::Base belongs_to :firm, :foreign_key => "client_of" belongs_to :author, :class_name => "Person", :foreign_key => "author_id" belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id", :conditions => 'discounts > #{payments_count}' belongs_to :attachable, :polymorphic => true end
  • 29. has_one One to One Relationship. Use has_one when the foreign key is in the OTHER table. • Account#beneficiary (similar to Beneficiary.find (:first, :conditions => "account_id = #{id}")) • Account#beneficiary=(beneficiary) (similar to beneficiary.account_id = account.id; beneficiary.save) • Account#beneficiary.nil? • Account#build_beneficiary (similar to Beneficiary.new ("account_id" => id)) • Account#create_beneficiary (similar to b = Beneficiary.new("account_id" => id); b.save; b)
  • 30. Defining has_one class Employee < ActiveRecord::Base # destroys the associated credit card has_one :credit_card, :dependent => :destroy # updates the associated records foreign key value to null rather than destroying it has_one :credit_card, :dependent => :nullify has_one :last_comment, :class_name => "Comment", :order => "posted_on" has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'" has_one :attachment, :as => :attachable end
  • 31. One to Many One-to-many Use has_many in the base, and belongs_to in the associated model. class Manager < ActiveRecord::Base has_many :employees end class Employee < ActiveRecord::Base belongs_to :manager # foreign key - manager_id end
  • 32. One to Many >> benevolent_dictator = Manager.find(:first, :conditions => ['name = "DHH"']) SELECT * FROM managers WHERE (name = "DHH") LIMIT 1 => #<Manager:0x369b7b8 @attributes={"name"=>"DHH", "id"=>"1", "created_at"=>"2007-04-21 09:59:24"}> >> minions = benevolent_dictator.employees SELECT * FROM employees WHERE (employees.manager_id = 1) => [#<Employee:0x36926a4 @attributes={"manager_id"=>"1", "id"=>"1", "first_name"=>"joe", "last_name"=>"schmo", "created_at"=>"2007-04-21 09:08:59"}>, #<Employee:0x36925f0 @attributes={"manager_id"=>"1", "id"=>"2", "first_name"=>"funky", "last_name"=>"monkey", "created_at"=>"2007-04-21 09:58:20"}>]
  • 33. has_many Augmenting the Model • Firm#clients (similar to Clients.find :all, :conditions => "firm_id = #{id}") • Firm#clients<< • Firm#clients.delete • Firm#client_ids • Firm#client_ids= • Firm#clients=
  • 34. has_many • Firm#client.clear • Firm#clients.empty? (similar to firm.clients.size == 0) • Firm#clients.size (similar to Client.count "firm_id = #{id}") • Firm#clients.find (similar to Client.find(id, :conditions => "firm_id = #{id}")) • Firm#clients.build (similar to Client.new ("firm_id" => id)) • Firm#clients.create (similar to c = Client.new ("firm_id" => id); c.save; c)
  • 35. has_many examples class Employee < ActiveRecord::Base has_many :comments, :order => "posted_on" has_many :comments, :include => :author has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name" has_many :tracks, :order => "position", :dependent => :destroy has_many :comments, :dependent => :nullify has_many :tags, :as => :taggable has_many :subscribers, :through => :subscriptions, :source => :user has_many :subscribers, :class_name => "Person", :finder_sql => 'SELECT DISTINCT people.* ' + 'FROM people p, post_subscriptions ps ' + 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' + 'ORDER BY p.first_name' end
  • 36. Many to Many Simple Joiner Table has_and_belongs_to_many Joiner Model has_many :through
  • 37. has_and_belongs_to_many The Simple Joiner Table Way
  • 38. has_and_belongs_to_many neglected by rails-core
  • 39. has_and_belongs_to_many Augmenting the Model • Developer#projects • Developer#projects<< • Developer#projects.delete • Developer#projects= • Developer#projects_ids • Developer#projects_ids= • Developer#clear
  • 40. has_and_belongs_to_many • Developer#projects.empty? • Developer#projects.size • Developer#projects.find(id) # Also find(:first / :all) • Developer#projects.build #(similar to Project.new ("project_id" => id)) • Developer#projects.create (similar to c = Project.new("project_id" => id); c.save; c)
  • 41. habtm example create_table :developers do |t| t.column :name, :string t.column :created_at, :datetime end create_table :projects do |t| t.column :name, :string t.column :created_at, :datetime end create_table(:developers_projects, :id => false) do |t| t.column :developer_id, :integer t.column :project_id, :integer end
  • 42. habtm example >> d = Developer.find(1) SELECT * FROM developers WHERE (developers.`id` = 1) => #<Developer:0x32bc7dc @attributes={"name"=>"rabble", "id"=>"1", "created_at"=>nil}> >> d.projects SELECT * FROM projects INNER JOIN developers_projects ON projects.id = developers_projects.project_id WHERE (developers_projects.developer_id = 1 ) => [#<Project:0x3257cc4 @attributes= {"name"=>"ragi", "project_id"=>"1", "id"=>"1", "developer_id"=>"1", "created_at"=>nil}>, #<Project:0x3257c10 @attributes= {"name"=>"acts_as_autenticated", "project_id"=>"3", "id"=>"3", "developer_id"=>"1", "created_at"=>nil}>]
  • 43. has_many :through DHH’s One True Way of Many to Many
  • 44. has_many :through Full Joiner Model
  • 45. has_many :through class Appearance < ActiveRecord::Base belongs_to :dancer belongs_to :movie end class Dancer < ActiveRecord::Base has_many :appearances, :dependent => true has_many :movies, :through => :appearances end class Movie < ActiveRecord::Base has_many :appearances, :dependent => true has_many :dancers, :through => :appearances end
  • 46. Validations class User < ActiveRecord::Base validates_confirmation_of :login, :password validates_confirmation_of :email, :message => "should match confirmation" validates_format_of :email, :with => /A([^@s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})/i, :on => :create end
  • 47. Validations • Keeping Data Clean • In object validation of fields, calculated validations • Instead of key constraints • The database is for storage, the model is for the business logic • Kinds of validations, custom validations, etc...
  • 48. But Wait? • Aren’t format, presence, relationship validations supposed to be the database’s job? • Traditionally, yes. • ActiveRecord does constraints in the model, not the database
  • 49. But Why? • Validations & Constraints are Business Logic • Business logic should be in the model • It makes things easy • End users can get useful error messages • Makes the postback pattern work well
  • 50. Data Integrity? • It’s still possible to do constraints in the db • But it’s not as necessary • Validations are constraints which make sense in terms of functionality of the app • The rails ways is to just use validations • Most DBA’s insist on foreign_key constraints
  • 51. What AR Returns? • Arrays of Model Objects • Preselects and instantiates objects • Nifty methods: to_yaml, to_xml, to_json
  • 52. Output Formats ruby - inspect to_yaml #<Employee:0x36926a4 --- !ruby/object:Employee @attributes= attributes: {"manager_id"=>"1", manager_id: "1" "id"=>"1", id: "1" "first_name"=>"joe", first_name: joe "last_name"=>"schmo", last_name: schmo "created_at"=>"2007-04-21 09:08:59"}> created_at: 2007-04-21 09:08:59 to_xml to_json <?xml version="1.0" encoding="UTF-8"?> {attributes: <employee> {manager_id: "1", <created-at id: "1", type="datetime">2007-04-21T09:08:59-07:00</ created-at> first_name: "joe", <first-name>joe</first-name> last_name: "schmo", <id type="integer">1</id> created_at: "2007-04-21 09:08:59"}} <last-name>schmo</last-name> <manager-id type="integer">1</manager-id> </employee>
  • 53. Before & After Callbacks * (-) save class Subscription < ActiveRecord::Base * (-) valid? before_create :record_signup * (1) before_validation private * (2) before_validation_on_create def record_signup * (-) validate self.signed_up_on = Date.today * (-) validate_on_create end * (3) after_validation end * (4) after_validation_on_create * (5) before_save class Firm < ActiveRecord::Base # Destroys the associated clients and * (6) before_create #people when the firm is destroyed * (-) create before_destroy { * (7) after_create |record| Person.destroy_all "firm_id = #{record.id}" } * (8) after_save before_destroy { |record| Client.destroy_all "client_of = #{record.id}" } end
  • 54. Optimizing AR • Eager Loading • Use Memecached • Add index to your migrations
  • 55. Security
  • 56. Doing it Securely class User < ActiveRecord::Base def self.authenticate_unsafely(user_name, password) find(:first, :conditions => "user_name = '#{user_name}' AND password = '#{password}'") end def self.authenticate_safely(user_name, password) find(:first, :conditions => [ "user_name = ? AND password = ?", user_name, password ]) end # Edge Rails Only (Rails 2.0) def self.authenticate_safely_simply(user_name, password) find(:first, :conditions => { :user_name => user_name, :password => password }) end end
  • 57. Use The ActiveRecord Relationships Anti-Pattern #1: Manually specifying the IDs when you construct the queries; def show unless @todo_list = TodoList.find_by_id_and_user_id(params[:id], current_user.id) redirect_to '/' end Anti-Pattern #2: Querying globally, then checking ownership after the fact; def show @todo_list = TodoList.find(params[:id]) redirect_to '/' unless @todo_list.user_id = current_user.id end Anti-Pattern #3: Abusing with_scope for a this simple case either directly, or in an around_filter. def show with_scope(:find=>{:user_id=>current_user.id}) do @todo_list = TodoList.find(params[:id]) end end Best Practice: The most effective way to do this is to call find on the todo_lists association. def show @todo_list = current_user.todo_lists.find(params[:id]) end Examples Stolen From: http://www.therailsway.com/2007/3/26/association-proxies-are-your-friend
  • 58. Create Via Association Proxies The Bad Way def create @todo_list = TodoList.new(params[:todo_list]) @todo_list.user_id = current_user.id @todo_list.save! redirect_to todo_list_url(@todo_list) end A Better Way: Use association proxies for creation. def create @todo_list = current_user.todo_lists.create! params[:todo_list] redirect_to todo_list_url(@todo_list) end The Best Practice - Handle exceptions for the user. def create @todo_list = current_user.todo_lists.build params[:todo_list] if @todo_list.save redirect_to todo_list_url(@todo_list) else render :action=>'new' end end Examples Stolen From: http://www.therailsway.com/2007/3/26/association-proxies-are-your-friend
  • 59. Special Fields * created_at * #{table_name}_count * position * created_on * parent_id * updated_at * lft * updated_on * rgt * lock_version * quote * type * template * id
  • 60. Table Inheritance Class Table Inheritance: Represents an inheritance hierarchy of classes with one table for each class1. Single Table Inheritance: Represents an inheritance hierarchy of classes as a single table that has columns for all the fields of the various classes2. Concrete Table Inheritance: Represents an inheritance hierarchy of classes with one table per concrete class in the hierarchy
  • 61. STI - Single Table Inheritance Represents an inheritance hierarchy of classes as a single table that has columns for all the fields of the various classes.
  • 62. STI - Single Table Inheritance CREATE TABLE `companies` ( class Company < ActiveRecord::Base; end `id` int(11) default NULL, class Firm < Company; end `name` varchar(255) default NULL, class Client < Company; end `type` varchar(32) default NULL class PriorityClient < Client; end ) Company.find(:first) SELECT * FROM companies LIMIT 1; Firm.find(:first) SELECT * FROM companies WHERE type = ‘firm’ LIMIT 1;
  • 63. Legacy Databases How to do legacy databases with Active Record? http://sl33p3r.free.fr/tutorials/rails/legacy/legacy_databases.html
  • 64. Supporting Legacy DB’s class CustomerNote < ActiveRecord::Base set_primary_key "client_comment_id" set_sequence_name "FooBarSequences" def self.table_name() "client_comment" end def body read_attribute "client_comment_body" end def body=(value) write_attribute "client_comment_body", value end end Thanks to: http://www.robbyonrails.com/articles/2005/07/25/the-legacy-of-databases-with-rails
  • 65. Changing ActiveRecord Modify Active Record ActiveRecord::Base.table_name_prefix = "my_" ActiveRecord::Base.table_name_suffix = "_table" ActiveRecord::Base.pluralize_table_names = false Fixing the Auto-Increment / Sequence Problem module ActiveRecord module ActiveRecord module ConnectionAdapters class Base class MysqlAdapter class << self def prefetch_primary_key?(table_name = nil) def reset_sequence_name true "#{table_name}_sequence" end end end end end end end end Thanks to: http://fora.pragprog.com/rails-recipes/write-your-own/post/84
  • 66. Changing ActiveRecord Telling ActiveRecord to fetch the primary key module ActiveRecord module ConnectionAdapters class MysqlAdapter def prefetch_primary_key?(table_name = nil) true end def next_sequence_value(sequence_name) sql = "UPDATE #{ sequence_name} SET Id=LAST_INSERT_ID(Id+1);" update(sql, "#{sequence_name} Update") select_value("SELECT Id from #{ sequence_name}",'Id') end end end Thanks to: http://fora.pragprog.com/rails-recipes/write-your-own/post/84
  • 67. Ruby on Rails AR Alternatives Ruby DataMapper iBatis - rBatis
  • 68. Ruby DataMapper http://rubyforge.org/projects/datamapper class FasterAuthor < DataMapper::Base set_table_name 'authors' property :name, :string, :size => 100 property :url, :string, :size => 255 property :is_active?, :boolean property :email, :string, :size => 255 property :hashed_pass, :string, :size => 40 property :created_at, :datetime property :modified_at, :datetime has_many :posts, :class => 'FasterPost' # :foreign_key => 'post_id' # prepends HTTP to a URL if necessary def self.prepend_http(url = '') if url and url != '' and not(url =~ /^http/i) url = 'http://' + url end return url end end
  • 69. iBatis - rBatis iBatis for Ruby (RBatis) is a port of Apache's iBatis library to Ruby and Ruby on Rails. It is an O/R-mapper that allows for complete customization of SQL. http://ibatis.apache.org Not Very DRY / Rails Like
  • 70. Drink the Kool aid?
  • 71. Flickr Photos Used: http://flickr.com/photos/brraveheart/114402291/ http://flickr.com/photos/ryangreenberg/57722319/ http://flickr.com/photos/bright/253175260/ http://flickr.com/photos/benandliz/11065337/ http://flickr.com/photos/good_day/63617697/ http://flickr.com/photos/gaspi/12944421/ http://flickr.com/photos/rickharris/416150393/ http://flickr.com/photos/thomashawk/221827536/ http://flickr.com/photos/babasteve/3322247/ http://flickr.com/photos/brianboulos/7707518/ http://flickr.com/photos/olivander/28058685/ http://flickr.com/photos/ross/28330560/ http://flickr.com/photos/brraveheart/44052308/ http://flickr.com/photos/emdot/45249090/ http://flickr.com/photos/ednothing/142393509/ http://flickr.com/photos/farhang/428136695/ http://flickr.com/photos/alltheaces/87505524/ http://flickr.com/photos/belljar/67877047/ http://flickr.com/photos/alfr3do/7436142/ http://flickr.com/photos/pulpolux/34545782/ http://flickr.com/photos/gdominici/57975123/ http://flickr.com/photos/monkeyc/107979135/ http://flickr.com/photos/josefstuefer/72512671/ http://flickr.com/photos/pedrosimoes7/449314732/ http://flickr.com/photos/uqbar/105440294/ http://flickr.com/photos/dincordero/405452471/ http://flickr.com/photos/auntiep/17135231/ http://flickr.com/photos/andidfl/203883534/ http://flickr.com/photos/einsame_spitze/406992131/ http://flickr.com/photos/ivanomak/434387836/ http://flickr.com/photos/beija-flor/63758047/ http://flickr.com/photos/nrvica/23858419/ http://flickr.com/photos/amerune/174617912/ http://flickr.com/photos/thespeak/137012632/ http://flickr.com/photos/hungry_i/47938311/ http://flickr.com/photos/thowi/31533027/ http://flickr.com/photos/santos/13952912/ http://flickr.com/photos/thelifeofbryan/468557520/ http://flickr.com/photos/supermietzi/179962496/ http://flickr.com/photos/eecue/289208982/ http://flickr.com/photos/traveller2020/206931940/ http://flickr.com/photos/estherase/14110154/ http://flickr.com/photos/ko_an/318906221/ http://flickr.com/photos/ehnmark/118117670/
  • 72. Questions? Introduction to Active Record Evan ‘Rabble’ Henshaw-Plath evan@protest.net - Yahoo! Brickhouse anarchogeek.com - testingrails.com