Introduction to ActiveRecord The Rails Object Relational Mapper by Mark Menard, Vita Rara, Inc.
Rails ActiveRecord ActiveRecord is the Object Relational Mapping library that is built into Rails. ActiveRecord is the default ORM used in Rails, but others can be used as well, such as: Data Mapper Sequel ActiveRecord was started by David Heinemeier Hansson the creator of Rails. ActiveRecord has been enhanced and expanded by many developers. ActiveRecord is an implementation of the active record pattern. Rails’ ActiveRecord is a leaky SQL abstraction. © Vita Rara, Inc.
Definition © Vita Rara, Inc. Active Record: An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data. -Martin Fowler, Patterns of Enterprise Application Architecture (page 160)
Rails ActiveRecord ActiveRecord classes and the tables they wrap are referred to as models. ActiveRecord encourages a Model Driven style of development. ActiveRecord encourages a non-anemic model layer. Skinny Controllers Fat Models Rarely a need for a service layer. ActiveRecord can be used as a persistence layer in a Domain Driven Design. © Vita Rara, Inc.
A Leaky Abstraction ActiveRecord will not isolate you from SQL. It’s not Hibernate It’s not JPA It’s not NHibernate It’s not your daddy’s “make SQL go away ORM”. A knowledge of SQL is necessary to succeeding with ActiveRecord. ActiveRecord makes the easy SQL things easy, and makes the hard SQL stuff possible. © Vita Rara, Inc.
Fundamentals One database table maps to one Ruby class Table names are plural and class names are singular Database columns map to attributes, i.e. get and set methods, in the model class Attributes are not defined on the class. Attributes are inferred from the underlying table schema. All tables have an integer primary key called id Database tables are created with migrations © Vita Rara, Inc.
ActiveRecord Model Example © Vita Rara, Inc. create_table &quot;persons&quot; do |t| t.string :first_name, last_name t.timestamps end class Person < ActiveRecord::Base end p = Person.new p.first_name = ‘Mark’ p.last_name = ‘Menard’ p.save
Working with Legacy Schemas Table name can be set using set_table_name. Views can also be used to adapt to AR conventions Id column can be mapped using “configuration”. Different column names can be mapped with relative ease. Typically attribute names are lowercase with words separated by underscores. first_name updated_at I have personally mapped study caps style tables to Rails defaults. firstName => first_name © Vita Rara, Inc.
CRUD: Create, Read, Update, Delete Create p = Person.create(:first_name => ‘Mark’) p = Person.new(:first_name => ‘Mark’) Read p = Person.find(1) p = Person.find_by_first_name(‘Mark’) Update p.save p.update_attributes(:last_name => ‘Menard’) Delete p.destroy © Vita Rara, Inc.
ActiveRecord::Base.new © Vita Rara, Inc. # Instantiate a new Person, which can be persisted. p = Person. new p.save # Instantiate a new Person, with attributes set based on a # Map, which can be persisted. p = Person. new (:first_name => 'Mark' , :last_name => 'Menard' ) p.save
ActiveRecord::Base.create © Vita Rara, Inc. # Immediated create a record in the database. p = Person.create(:first_name => 'Mark' , :last_name => 'Menard' ) # This sets the attributes and calls #save on the instance.
Finding Models Built in finders Find the first record: User.find(:first) Find all records of a type: User.find(:all) Find by primary id: User.find(1) Dynamic finders using attributes ActiveRecord can build basic finders by convention based on the attributes of a model. User.find_by_login(‘mark’) © Vita Rara, Inc.
Advanced Finding Because ActiveRecord is a leaky abstraction it provides straight forward ways to access SQL in its finders © Vita Rara, Inc. User.find(:all, :conditions => [ “login = ? AND password = ?” , login, password], :limit => 10 , :offset => 10 , :order => 'login' , :joins => 'accounts on user.account_id = accounts.id' )
Advanced Finding (con’t) Finders also support: :select :group :include (optimize n+1 queries) © Vita Rara, Inc.
Eager Loading: Avoid N+1 Issue © Vita Rara, Inc. <% # Don't put code like this in your view. This is for illustration only! # Find and display order summary of all pending orders for an account. orders = Order.find_pending_by_account(current_account) %> <% orders.each do |order| -%> <%= render :partial => 'order_header' %> <!-- This fires off a query for each order! BAD BAD BAD --> <% order.line_items.each do |line_item| -%> <%= render :partial => 'line_item' %> <% end -%> <% end -%> <% # Better would be orders = Order.find_pending_by_account(current_account, :include => [ :line_items ]) %>
Updating Models © Vita Rara, Inc. user = User.find( 1 ) user.first_name = ‘Mark’ user.save # returns true on success user.last_name = ‘Menard’ user.save! # throws an exception if it fails # Immediately update the attributes and call #save # returns true on success user.update_attributes(:first_name => 'John' , :last_name => 'Doe' ) # Immediately update the attributes and call #save! # throws an exception on failure. user.update_attributes!(:password => ‘abccd1234’ )
Transactions Account.transaction do account1.deposit(100) account2.withdraw(100) end © Vita Rara, Inc.
ActiveRecord Associations
ActiveRecord Associations Two primary types of associations: belongs_to has_one / has_many There are others, but they are not commonly used. has_and_belongs_to_many Used to map many-to-many associations. Generally accepted practice is to use a join model. © Vita Rara, Inc.
Association Methods Associations add methods to the class. This is an excellent example of meta-programming. Added methods allow for easy management of the related models. © Vita Rara, Inc.
ActiveRecord Association Examples © Vita Rara, Inc. # Has Many class Order < ActiveRecord::Base has_many :order_line_items end class OrderLineItem < ActiveRecord::Base belongs_to :order end # Has One class Party < ActiveRecord::Base has_one :login_credential end class LoginCredential < ActiveRecord::Base belongs_to :party end
has_many & belongs_to Used to model one-to-many associations. The belongs_to side has the foreign key. © Vita Rara, Inc. class Order < ActiveRecord::Base has_many :line_items end class LineItem < ActiveRecord::Base belongs_to :order end create_table :orders do |t| t.string :number end create_table :line_items do |t| t.integer :order_id end
Has Many Examples © Vita Rara, Inc. has_many :comments, :order => &quot;posted_on&quot; has_many :comments, :include => :author has_many :people, :class_name => &quot;Person&quot; , :conditions => &quot;deleted = 0&quot; , :order => &quot;name&quot; has_many :tracks, :order => &quot;position&quot; , :dependent => :destroy has_many :comments, :dependent => :nullify has_many :tags, :as => :taggable has_many :subscribers, :through => :subscriptions, :source => :user has_many :subscribers, :class_name => &quot;Person&quot; , :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'
has_many Methods © Vita Rara, Inc. class Firm has_many :clients end firm = Firm.find( 1 ) firm.clients firm.clients << firm.clients.delete firm.clients = firm.client_ids firm.client_ids = firm.clients.clear firm.clients.empty? firm.clients.count firm.clients.find firm.clients.build(:first_name => 'Mark' ) # Like Party.new(:firm_id => firm.id) firm.clients.create(:first_name => 'Mark' ) # Like Party.create(:firm_id => firm.id)
has_and_belongs_to_many Used to model many-to-many associations. © Vita Rara, Inc. create_table :categories_posts, :id => false do t.column :category_id, :integer, :null => false t.column :post_id, :integer, :null => false end class Product < ActiveRecord::Base has_and_belongs_to_many :categories end class Category < ActiveRecord::Base has_and_belongs_to_many :products end product = Product.find_by_name(“Mac Book Pro”) category = Category.find_by_name(“Laptops”) product.categories.count # => 0 category.products.count # => 0 product.categories << category product.categories.count # => 1 category.products.count # => 1
Join Models vs. has_and_belongs_to_many Join models Are generally preferred. Make the joining table explicit. Allow domain logic to be added to the join model. Allow a more literate style of coding. has_many :foos, :through => :bars makes it trivial. Commonly has_and_belongs_to_many associations are refactored later to make the join model explicit. Better to just do it up front. © Vita Rara, Inc.
has_many :foos, :through => :bars has_many :through is used to model has_many relationships through a “join” model. © Vita Rara, Inc. class Blog < ActiveRecord::Base has_many :subscriptions has_many :users, :through => :subscriptions end class User < ActiveRecord::Base has_many :subscriptions has_many :blogs, :through => :subscriptions end class Subscription < ActiveRecord::Base belongs_to :blog belongs_to :user end
Polymorphic Associations Easiest to illustrate by example © Vita Rara, Inc. class Person < ActiveRecord::Base has_one :address, :as => :addressable end class Company < ActiveRecord::Base has_one :address, :as => :addressable end class Address < ActiveRecord::Base belongs_to :addressable, :polymorphic => true end create_table :addresses do |t| # ... t.integer :addressable_id t.string :addressable_type end
ActiveRecord Validations Keeping Your Data Safe
Validation Validations are rules in your model objects to help protect the integrity of your data Validation is invoked by the #save method. Save returns true if validations pass and false otherwise. If you invoke #save! then a RecordInvalid exception is raised if the object is not valid. Use save(false) if you need to turn off validation © Vita Rara, Inc.
Validation Callbacks #validate Called before a model is written to the database, either on initial save, or on updates. #validate_on_create Called before a model is inserted into the database. #validate_on_update Called before an existing model is updated in the database. © Vita Rara, Inc.
Validation Callbacks (cont) © Vita Rara, Inc. class Person < ActiveRecord::Base def validate puts “validate invoked” end def validate_on_create puts “validate_on_create invoked” end def validate_on_update puts “validate_on_update invoked” end end peter = Person.create(:name => “Peter”) # => peter.validate and peter.validate_on_create invoked peter.last_name = “Forsberg” peter.save # => peter.validate_on_update invoked
Declarative Validations © Vita Rara, Inc. Rails contains a large number of declarative validations that are applied to classes by convention. Declarative validations free developers from the drudgery of most model validation.
validates_presence_of Used to denote required attributes. © Vita Rara, Inc. class Person < ActiveRecord::Base validates_presence_of :first_name validates_presence_of :last_name end p = Person. new p.valid? #=> false p.first_name = 'Mark' p.last_name = 'Menard' p.valid? #=> true
validates_uniqueness_of Ensures that the value of an attribute is unique in the database. Can be constrained to work subsets of the data. © Vita Rara, Inc. class User < ActiveRecord::Base belongs_to :account validates_uniqueness_of :login, :scope => [ :account ] end account = Account.find(1) user_1 = account.users.create(:login => 'mark' ) user_2 = account.users.create!(:login => 'mark' ) #=> Throws InvalidRecord exceptoion
validates_numericality_of Ensures that an attribute is a number. Can be constrained to integral values. © Vita Rara, Inc. class User < ActiveRecord::Base validates_numericality_of :number, :integer_only => true end User.create!(:number => 'some number' ) #=> Throws Invalid
validates_length_of Ensures an attribute is the proper length © Vita Rara, Inc. class User < ActiveRecord::Base validates_length_of :login, :within => 3 .. 20 validates_length_of :password, :is => 8 validates_length_of :name, :minimum => 3 end
validates_format_of Ensures the format of an attribute matches regular expression. Can be used to validate email addresses. © Vita Rara, Inc. class User < ActiveRecord::Base validates_format_of :email, :with => /^[\w\d]+$/ end
validates_inclusion_of & validates_exclusion_of Ensures that a value is or is not in a collection of options. © Vita Rara, Inc. class User < ActiveRecord::Base validates_inclusion_of :gender, :in => %w( male female ) , :message => &quot;Oh really....&quot; validates_exclusion_of :login, :in => %w( root admin super ) , :message => &quot;Tisk tisk...&quot; end
validates_associated Ensures that an associated model is valid prior to saving. © Vita Rara, Inc. class User < ActiveRecord::Base belongs_to :account validates_associated :account, :on => :create end class Account < ActiveRecord::Base validates_presence_of :name end user = User. new (:login => 'mark' , :name => 'Mark Menard' ) user.account = Account. new # invalid missing name user.save! #=> Throws RecordInvalid exception.
Other Declarative Validations validates_acceptance_of validates_confirmation_of validates_each validates_size_of © Vita Rara, Inc. You can also create your own declarative validations that match your problem domain.
Using Validation Callbacks Sometimes validation is more complex than the declarative validations can handle. Validation may relate to the semantics of your problem domain. Validation may relate to more than one attribute. © Vita Rara, Inc. class Account validate :does_domain_exist private def does_domain_exist Resolv.getaddress(self.domain_name) rescue errors.add(:domain_name, 'Domain name does not exist.' ) end end
Using Validation Callbacks Class methods for defining validation call backs: validate :method_name validate_on_update :method_name validate_on_create :method_name Instance methods for defining validations: validate validate_on_update validate_on_create © Vita Rara, Inc.
Model Life Cycle
New Model Callbacks ActiveRecord calls these methods prior to saving a new record: before_validation before_validation_on_create validation is performed after_validation after_validation_on_create before_save before_create ActiveRecord saves the record after_create after_save © Vita Rara, Inc.
Existing Model Callbacks ActiveRecord calls these methods prior to saving an existing record before_validation ActiveRecord performs validation after_validation before_save before_update ActiveRecord saves the record after_update after_save © Vita Rara, Inc.
Destroy Model Callbacks ActiveRecord calls these methods when destroying a model: before_destroy ActiveRecord performs the DELETE after_destroy © Vita Rara, Inc.
Callback Use Cases Cleaning up attributes prior to saving Starting followup processes Sending notifications Geocoding Paranoia: Don’t delete anything just mark it deleted. Clean up associated files, avatars, other assets. © Vita Rara, Inc.
Cleaning up Attributes Prior to Saving © Vita Rara, Inc. class CreditCard before_validation :cleanup_number private def cleanup_number self.number = number.gsub( /[^0-9]/ , &quot;&quot; ) true # I like to be explicit end end
Observers
Observers Observers allow you to create classes that observe changes in your Models. Observers allow you classes to focus on a single responsibility. Observers can hook onto the standard rails life cycle call backs. © Vita Rara, Inc.
Creating an Audit Trail with an Observer © Vita Rara, Inc. # in config/environment.rb config.active_record_observers = [ :auditor ] # in auditor.rb class Auditor < ActiveRecord::Observer observe User def after_create (model) log_info( &quot;New #{model.class.name} created.&quot; , model) end def after_update (model) log_info( &quot;Update #{model.class.name}&quot; , model) end def after_destroy (model) log_info( &quot;Destroy #{model.class.name}&quot; , model) end private def log_info (model, info) log.info(info) log.info(model.inspect) end end
Shameless Self Promotion
Ruby and Rails Training One day to three day programs. Introduction to Ruby Advanced Ruby Introduction to Rails Advanced Rails Test Driven Development Behavior Driven Development Test Anything with Cucumber Advanced Domain Modeling with ActiveRecord Domain Driven Development with Rails © Vita Rara, Inc.
Ruby on Rails Consulting Full Life Cycle Project Development Inception Implementation Deployment Long Term Support Ruby on Rails Mentoring Get your team up to speed using Rails © Vita Rara, Inc.
Contact Information Mark Menard [email_address] http://www.vitarara.net / 518 369 7356 © Vita Rara, Inc.

Intro to Rails ActiveRecord

  • 1.
    Introduction to ActiveRecordThe Rails Object Relational Mapper by Mark Menard, Vita Rara, Inc.
  • 2.
    Rails ActiveRecord ActiveRecordis the Object Relational Mapping library that is built into Rails. ActiveRecord is the default ORM used in Rails, but others can be used as well, such as: Data Mapper Sequel ActiveRecord was started by David Heinemeier Hansson the creator of Rails. ActiveRecord has been enhanced and expanded by many developers. ActiveRecord is an implementation of the active record pattern. Rails’ ActiveRecord is a leaky SQL abstraction. © Vita Rara, Inc.
  • 3.
    Definition © VitaRara, Inc. Active Record: An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data. -Martin Fowler, Patterns of Enterprise Application Architecture (page 160)
  • 4.
    Rails ActiveRecord ActiveRecordclasses and the tables they wrap are referred to as models. ActiveRecord encourages a Model Driven style of development. ActiveRecord encourages a non-anemic model layer. Skinny Controllers Fat Models Rarely a need for a service layer. ActiveRecord can be used as a persistence layer in a Domain Driven Design. © Vita Rara, Inc.
  • 5.
    A Leaky AbstractionActiveRecord will not isolate you from SQL. It’s not Hibernate It’s not JPA It’s not NHibernate It’s not your daddy’s “make SQL go away ORM”. A knowledge of SQL is necessary to succeeding with ActiveRecord. ActiveRecord makes the easy SQL things easy, and makes the hard SQL stuff possible. © Vita Rara, Inc.
  • 6.
    Fundamentals One databasetable maps to one Ruby class Table names are plural and class names are singular Database columns map to attributes, i.e. get and set methods, in the model class Attributes are not defined on the class. Attributes are inferred from the underlying table schema. All tables have an integer primary key called id Database tables are created with migrations © Vita Rara, Inc.
  • 7.
    ActiveRecord Model Example© Vita Rara, Inc. create_table &quot;persons&quot; do |t| t.string :first_name, last_name t.timestamps end class Person < ActiveRecord::Base end p = Person.new p.first_name = ‘Mark’ p.last_name = ‘Menard’ p.save
  • 8.
    Working with LegacySchemas Table name can be set using set_table_name. Views can also be used to adapt to AR conventions Id column can be mapped using “configuration”. Different column names can be mapped with relative ease. Typically attribute names are lowercase with words separated by underscores. first_name updated_at I have personally mapped study caps style tables to Rails defaults. firstName => first_name © Vita Rara, Inc.
  • 9.
    CRUD: Create, Read,Update, Delete Create p = Person.create(:first_name => ‘Mark’) p = Person.new(:first_name => ‘Mark’) Read p = Person.find(1) p = Person.find_by_first_name(‘Mark’) Update p.save p.update_attributes(:last_name => ‘Menard’) Delete p.destroy © Vita Rara, Inc.
  • 10.
    ActiveRecord::Base.new © VitaRara, Inc. # Instantiate a new Person, which can be persisted. p = Person. new p.save # Instantiate a new Person, with attributes set based on a # Map, which can be persisted. p = Person. new (:first_name => 'Mark' , :last_name => 'Menard' ) p.save
  • 11.
    ActiveRecord::Base.create © VitaRara, Inc. # Immediated create a record in the database. p = Person.create(:first_name => 'Mark' , :last_name => 'Menard' ) # This sets the attributes and calls #save on the instance.
  • 12.
    Finding Models Builtin finders Find the first record: User.find(:first) Find all records of a type: User.find(:all) Find by primary id: User.find(1) Dynamic finders using attributes ActiveRecord can build basic finders by convention based on the attributes of a model. User.find_by_login(‘mark’) © Vita Rara, Inc.
  • 13.
    Advanced Finding BecauseActiveRecord is a leaky abstraction it provides straight forward ways to access SQL in its finders © Vita Rara, Inc. User.find(:all, :conditions => [ “login = ? AND password = ?” , login, password], :limit => 10 , :offset => 10 , :order => 'login' , :joins => 'accounts on user.account_id = accounts.id' )
  • 14.
    Advanced Finding (con’t)Finders also support: :select :group :include (optimize n+1 queries) © Vita Rara, Inc.
  • 15.
    Eager Loading: AvoidN+1 Issue © Vita Rara, Inc. <% # Don't put code like this in your view. This is for illustration only! # Find and display order summary of all pending orders for an account. orders = Order.find_pending_by_account(current_account) %> <% orders.each do |order| -%> <%= render :partial => 'order_header' %> <!-- This fires off a query for each order! BAD BAD BAD --> <% order.line_items.each do |line_item| -%> <%= render :partial => 'line_item' %> <% end -%> <% end -%> <% # Better would be orders = Order.find_pending_by_account(current_account, :include => [ :line_items ]) %>
  • 16.
    Updating Models ©Vita Rara, Inc. user = User.find( 1 ) user.first_name = ‘Mark’ user.save # returns true on success user.last_name = ‘Menard’ user.save! # throws an exception if it fails # Immediately update the attributes and call #save # returns true on success user.update_attributes(:first_name => 'John' , :last_name => 'Doe' ) # Immediately update the attributes and call #save! # throws an exception on failure. user.update_attributes!(:password => ‘abccd1234’ )
  • 17.
    Transactions Account.transaction doaccount1.deposit(100) account2.withdraw(100) end © Vita Rara, Inc.
  • 18.
  • 19.
    ActiveRecord Associations Twoprimary types of associations: belongs_to has_one / has_many There are others, but they are not commonly used. has_and_belongs_to_many Used to map many-to-many associations. Generally accepted practice is to use a join model. © Vita Rara, Inc.
  • 20.
    Association Methods Associationsadd methods to the class. This is an excellent example of meta-programming. Added methods allow for easy management of the related models. © Vita Rara, Inc.
  • 21.
    ActiveRecord Association Examples© Vita Rara, Inc. # Has Many class Order < ActiveRecord::Base has_many :order_line_items end class OrderLineItem < ActiveRecord::Base belongs_to :order end # Has One class Party < ActiveRecord::Base has_one :login_credential end class LoginCredential < ActiveRecord::Base belongs_to :party end
  • 22.
    has_many & belongs_toUsed to model one-to-many associations. The belongs_to side has the foreign key. © Vita Rara, Inc. class Order < ActiveRecord::Base has_many :line_items end class LineItem < ActiveRecord::Base belongs_to :order end create_table :orders do |t| t.string :number end create_table :line_items do |t| t.integer :order_id end
  • 23.
    Has Many Examples© Vita Rara, Inc. has_many :comments, :order => &quot;posted_on&quot; has_many :comments, :include => :author has_many :people, :class_name => &quot;Person&quot; , :conditions => &quot;deleted = 0&quot; , :order => &quot;name&quot; has_many :tracks, :order => &quot;position&quot; , :dependent => :destroy has_many :comments, :dependent => :nullify has_many :tags, :as => :taggable has_many :subscribers, :through => :subscriptions, :source => :user has_many :subscribers, :class_name => &quot;Person&quot; , :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'
  • 24.
    has_many Methods ©Vita Rara, Inc. class Firm has_many :clients end firm = Firm.find( 1 ) firm.clients firm.clients << firm.clients.delete firm.clients = firm.client_ids firm.client_ids = firm.clients.clear firm.clients.empty? firm.clients.count firm.clients.find firm.clients.build(:first_name => 'Mark' ) # Like Party.new(:firm_id => firm.id) firm.clients.create(:first_name => 'Mark' ) # Like Party.create(:firm_id => firm.id)
  • 25.
    has_and_belongs_to_many Used tomodel many-to-many associations. © Vita Rara, Inc. create_table :categories_posts, :id => false do t.column :category_id, :integer, :null => false t.column :post_id, :integer, :null => false end class Product < ActiveRecord::Base has_and_belongs_to_many :categories end class Category < ActiveRecord::Base has_and_belongs_to_many :products end product = Product.find_by_name(“Mac Book Pro”) category = Category.find_by_name(“Laptops”) product.categories.count # => 0 category.products.count # => 0 product.categories << category product.categories.count # => 1 category.products.count # => 1
  • 26.
    Join Models vs.has_and_belongs_to_many Join models Are generally preferred. Make the joining table explicit. Allow domain logic to be added to the join model. Allow a more literate style of coding. has_many :foos, :through => :bars makes it trivial. Commonly has_and_belongs_to_many associations are refactored later to make the join model explicit. Better to just do it up front. © Vita Rara, Inc.
  • 27.
    has_many :foos, :through=> :bars has_many :through is used to model has_many relationships through a “join” model. © Vita Rara, Inc. class Blog < ActiveRecord::Base has_many :subscriptions has_many :users, :through => :subscriptions end class User < ActiveRecord::Base has_many :subscriptions has_many :blogs, :through => :subscriptions end class Subscription < ActiveRecord::Base belongs_to :blog belongs_to :user end
  • 28.
    Polymorphic Associations Easiestto illustrate by example © Vita Rara, Inc. class Person < ActiveRecord::Base has_one :address, :as => :addressable end class Company < ActiveRecord::Base has_one :address, :as => :addressable end class Address < ActiveRecord::Base belongs_to :addressable, :polymorphic => true end create_table :addresses do |t| # ... t.integer :addressable_id t.string :addressable_type end
  • 29.
  • 30.
    Validation Validations arerules in your model objects to help protect the integrity of your data Validation is invoked by the #save method. Save returns true if validations pass and false otherwise. If you invoke #save! then a RecordInvalid exception is raised if the object is not valid. Use save(false) if you need to turn off validation © Vita Rara, Inc.
  • 31.
    Validation Callbacks #validateCalled before a model is written to the database, either on initial save, or on updates. #validate_on_create Called before a model is inserted into the database. #validate_on_update Called before an existing model is updated in the database. © Vita Rara, Inc.
  • 32.
    Validation Callbacks (cont)© Vita Rara, Inc. class Person < ActiveRecord::Base def validate puts “validate invoked” end def validate_on_create puts “validate_on_create invoked” end def validate_on_update puts “validate_on_update invoked” end end peter = Person.create(:name => “Peter”) # => peter.validate and peter.validate_on_create invoked peter.last_name = “Forsberg” peter.save # => peter.validate_on_update invoked
  • 33.
    Declarative Validations ©Vita Rara, Inc. Rails contains a large number of declarative validations that are applied to classes by convention. Declarative validations free developers from the drudgery of most model validation.
  • 34.
    validates_presence_of Used todenote required attributes. © Vita Rara, Inc. class Person < ActiveRecord::Base validates_presence_of :first_name validates_presence_of :last_name end p = Person. new p.valid? #=> false p.first_name = 'Mark' p.last_name = 'Menard' p.valid? #=> true
  • 35.
    validates_uniqueness_of Ensures thatthe value of an attribute is unique in the database. Can be constrained to work subsets of the data. © Vita Rara, Inc. class User < ActiveRecord::Base belongs_to :account validates_uniqueness_of :login, :scope => [ :account ] end account = Account.find(1) user_1 = account.users.create(:login => 'mark' ) user_2 = account.users.create!(:login => 'mark' ) #=> Throws InvalidRecord exceptoion
  • 36.
    validates_numericality_of Ensures thatan attribute is a number. Can be constrained to integral values. © Vita Rara, Inc. class User < ActiveRecord::Base validates_numericality_of :number, :integer_only => true end User.create!(:number => 'some number' ) #=> Throws Invalid
  • 37.
    validates_length_of Ensures anattribute is the proper length © Vita Rara, Inc. class User < ActiveRecord::Base validates_length_of :login, :within => 3 .. 20 validates_length_of :password, :is => 8 validates_length_of :name, :minimum => 3 end
  • 38.
    validates_format_of Ensures theformat of an attribute matches regular expression. Can be used to validate email addresses. © Vita Rara, Inc. class User < ActiveRecord::Base validates_format_of :email, :with => /^[\w\d]+$/ end
  • 39.
    validates_inclusion_of & validates_exclusion_ofEnsures that a value is or is not in a collection of options. © Vita Rara, Inc. class User < ActiveRecord::Base validates_inclusion_of :gender, :in => %w( male female ) , :message => &quot;Oh really....&quot; validates_exclusion_of :login, :in => %w( root admin super ) , :message => &quot;Tisk tisk...&quot; end
  • 40.
    validates_associated Ensures thatan associated model is valid prior to saving. © Vita Rara, Inc. class User < ActiveRecord::Base belongs_to :account validates_associated :account, :on => :create end class Account < ActiveRecord::Base validates_presence_of :name end user = User. new (:login => 'mark' , :name => 'Mark Menard' ) user.account = Account. new # invalid missing name user.save! #=> Throws RecordInvalid exception.
  • 41.
    Other Declarative Validationsvalidates_acceptance_of validates_confirmation_of validates_each validates_size_of © Vita Rara, Inc. You can also create your own declarative validations that match your problem domain.
  • 42.
    Using Validation CallbacksSometimes validation is more complex than the declarative validations can handle. Validation may relate to the semantics of your problem domain. Validation may relate to more than one attribute. © Vita Rara, Inc. class Account validate :does_domain_exist private def does_domain_exist Resolv.getaddress(self.domain_name) rescue errors.add(:domain_name, 'Domain name does not exist.' ) end end
  • 43.
    Using Validation CallbacksClass methods for defining validation call backs: validate :method_name validate_on_update :method_name validate_on_create :method_name Instance methods for defining validations: validate validate_on_update validate_on_create © Vita Rara, Inc.
  • 44.
  • 45.
    New Model CallbacksActiveRecord calls these methods prior to saving a new record: before_validation before_validation_on_create validation is performed after_validation after_validation_on_create before_save before_create ActiveRecord saves the record after_create after_save © Vita Rara, Inc.
  • 46.
    Existing Model CallbacksActiveRecord calls these methods prior to saving an existing record before_validation ActiveRecord performs validation after_validation before_save before_update ActiveRecord saves the record after_update after_save © Vita Rara, Inc.
  • 47.
    Destroy Model CallbacksActiveRecord calls these methods when destroying a model: before_destroy ActiveRecord performs the DELETE after_destroy © Vita Rara, Inc.
  • 48.
    Callback Use CasesCleaning up attributes prior to saving Starting followup processes Sending notifications Geocoding Paranoia: Don’t delete anything just mark it deleted. Clean up associated files, avatars, other assets. © Vita Rara, Inc.
  • 49.
    Cleaning up AttributesPrior to Saving © Vita Rara, Inc. class CreditCard before_validation :cleanup_number private def cleanup_number self.number = number.gsub( /[^0-9]/ , &quot;&quot; ) true # I like to be explicit end end
  • 50.
  • 51.
    Observers Observers allowyou to create classes that observe changes in your Models. Observers allow you classes to focus on a single responsibility. Observers can hook onto the standard rails life cycle call backs. © Vita Rara, Inc.
  • 52.
    Creating an AuditTrail with an Observer © Vita Rara, Inc. # in config/environment.rb config.active_record_observers = [ :auditor ] # in auditor.rb class Auditor < ActiveRecord::Observer observe User def after_create (model) log_info( &quot;New #{model.class.name} created.&quot; , model) end def after_update (model) log_info( &quot;Update #{model.class.name}&quot; , model) end def after_destroy (model) log_info( &quot;Destroy #{model.class.name}&quot; , model) end private def log_info (model, info) log.info(info) log.info(model.inspect) end end
  • 53.
  • 54.
    Ruby and RailsTraining One day to three day programs. Introduction to Ruby Advanced Ruby Introduction to Rails Advanced Rails Test Driven Development Behavior Driven Development Test Anything with Cucumber Advanced Domain Modeling with ActiveRecord Domain Driven Development with Rails © Vita Rara, Inc.
  • 55.
    Ruby on RailsConsulting Full Life Cycle Project Development Inception Implementation Deployment Long Term Support Ruby on Rails Mentoring Get your team up to speed using Rails © Vita Rara, Inc.
  • 56.
    Contact Information MarkMenard [email_address] http://www.vitarara.net / 518 369 7356 © Vita Rara, Inc.

Editor's Notes

  • #4 This definition supposes a relational data store. The active record pattern would not apply to document oriented systems such as CouchDB, Mongo, and other No-SQL databases, as they tend to store aggregates.
  • #15 I rarely use these options. :select can lead to confusion if you select columns from more than one table because the returned objects will be read-only. I have yet to find a common use case for :group
  • #16 Many times complex views will want to be backed by a custom finder to avoid N+1 issues. I usually place these in a finder module mixed into my model class to keep the model class clean.
  • #19 This is where we begin joining models to other models to reflect the domain space.
  • #26 I very rarely use this type of association. I find that my join models eventually begin to a
  • #49 To halt the chain return false. To continue return true.