Upload
mark-menard
View
3.703
Download
2
Tags:
Embed Size (px)
DESCRIPTION
An introduction to Ruby on Rails ORM ActiveRecord.
Citation preview
Introduction to ActiveRecord
The Rails Object Relational Mapper
by Mark Menard, Vita Rara, Inc.
2© Vita Rara, Inc.
Rails ActiveRecordActiveRecord 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 MapperSequel
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.
3© Vita Rara, Inc.
Definition
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© Vita Rara, Inc.
Rails ActiveRecordActiveRecord 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 ControllersFat ModelsRarely a need for a service layer.
ActiveRecord can be used as a persistence layer in a Domain Driven Design.
5© Vita Rara, Inc.
A Leaky AbstractionActiveRecord will not isolate you from SQL.
It’s not HibernateIt’s not JPAIt’s not NHibernateIt’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.
6© 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 classAttributes 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
7© Vita Rara, Inc.
ActiveRecord Model Example
create_table "persons" do |t| t.string :first_name, last_name t.timestamps end
class Person < ActiveRecord::Baseendp = Person.newp.first_name = ‘Mark’p.last_name = ‘Menard’p.save
8© Vita Rara, Inc.
Working with Legacy SchemasTable 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_nameupdated_at
I have personally mapped study caps style tables to Rails defaults.firstName => first_name
© Vita Rara, Inc.
CRUD: Create, Read, Update, DeleteCreate
p = Person.create(:first_name => ‘Mark’)p = Person.new(:first_name => ‘Mark’)
Readp = Person.find(1)p = Person.find_by_first_name(‘Mark’)
Updatep.savep.update_attributes(:last_name => ‘Menard’)
Deletep.destroy
10
© Vita Rara, Inc.
ActiveRecord::Base.new# Instantiate a new Person, which can be persisted.p = Person.newp.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
© Vita Rara, Inc.
ActiveRecord::Base.create# 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.
© Vita Rara, Inc.
Finding ModelsBuilt 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 attributesActiveRecord can build basic finders by
convention based on the attributes of a model.User.find_by_login(‘mark’)
© Vita Rara, Inc.
Advanced FindingBecause ActiveRecord is a leaky abstraction
it provides straight forward ways to access SQL in its finders
User.find(:all, :conditions => [ “login = ? AND password = ?”, login, password], :limit => 10, :offset => 10, :order => 'login', :joins => 'accounts on user.account_id = accounts.id')
14
© Vita Rara, Inc.
Advanced Finding (con’t)Finders also support:
:select:group:include (optimize n+1 queries)
15
© Vita Rara, Inc.
Eager Loading: Avoid N+1 Issue<% # 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 beorders = Order.find_pending_by_account(current_account, :include => [ :line_items ])%>
© Vita Rara, Inc.
Updating Modelsuser = User.find(1)user.first_name = ‘Mark’user.save # returns true on successuser.last_name = ‘Menard’user.save! # throws an exception if it fails# Immediately update the attributes and call #save# returns true on successuser.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’)
© Vita Rara, Inc.
TransactionsAccount.transaction do
account1.deposit(100)
account2.withdraw(100)
end
ActiveRecord Associations
© Vita Rara, Inc.
ActiveRecord AssociationsTwo primary types of associations:
belongs_tohas_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 MethodsAssociations 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# Has Many
class Order < ActiveRecord::Base has_many :order_line_itemsend
class OrderLineItem < ActiveRecord::Base belongs_to :orderend
# Has One
class Party < ActiveRecord::Base has_one :login_credentialend
class LoginCredential < ActiveRecord::Base belongs_to :partyend
22
© Vita Rara, Inc.
has_many & belongs_toUsed to model one-to-many associations.The belongs_to side has the foreign key.
class Order < ActiveRecord::Base has_many :line_itemsend
class LineItem < ActiveRecord::Base belongs_to :orderend
create_table :orders do |t| t.string :numberend
create_table :line_items do |t| t.integer :order_idend
23
© Vita Rara, Inc.
Has Many Exampleshas_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'
24
© Vita Rara, Inc.
has_many Methodsclass Firm has_many :clientsend
firm = Firm.find(1)
firm.clientsfirm.clients<< firm.clients.delete firm.clients= firm.client_ids firm.client_ids= firm.clients.clear firm.clients.empty?firm.clients.count firm.clients.findfirm.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
© Vita Rara, Inc.
has_and_belongs_to_manyUsed to model many-to-many associations.
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 :categoriesend
class Category < ActiveRecord::Base has_and_belongs_to_many :productsend
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
© Vita Rara, Inc.
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.
27
© Vita Rara, Inc.
has_many :foos, :through => :barshas_many :through is used to model
has_many relationships through a “join” model.
class Blog < ActiveRecord::Base has_many :subscriptions has_many :users, :through => :subscriptionsend
class User < ActiveRecord::Base has_many :subscriptions has_many :blogs, :through => :subscriptionsend
class Subscription < ActiveRecord::Base belongs_to :blog belongs_to :userend
28
© Vita Rara, Inc.
Polymorphic AssociationsEasiest to illustrate by example
class Person < ActiveRecord::Base has_one :address, :as => :addressableend
class Company < ActiveRecord::Base has_one :address, :as => :addressableend
class Address < ActiveRecord::Base belongs_to :addressable, :polymorphic => trueend
create_table :addresses do |t| # ... t.integer :addressable_id t.string :addressable_typeend
ActiveRecord Validations
Keeping Your Data Safe
© Vita Rara, Inc.
ValidationValidations are rules in your model objects
to help protect the integrity of your dataValidation 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_createCalled before a model is inserted into the
database.
#validate_on_updateCalled before an existing model is updated in
the database.
© Vita Rara, Inc.
Validation Callbacks (cont)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
© Vita Rara, Inc.
Declarative ValidationsRails 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
© Vita Rara, Inc.
validates_presence_ofUsed to denote required attributes.
class Person < ActiveRecord::Base validates_presence_of :first_name validates_presence_of :last_nameend
p = Person.newp.valid? #=> falsep.first_name = 'Mark'p.last_name = 'Menard'p.valid? #=> true
35
© Vita Rara, Inc.
validates_uniqueness_ofEnsures that the value of an attribute is
unique in the database.Can be constrained to work subsets of the data.
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
© Vita Rara, Inc.
validates_numericality_ofEnsures that an attribute is a number.
Can be constrained to integral values.
class User < ActiveRecord::Base validates_numericality_of :number, :integer_only => trueend
User.create!(:number => 'some number') #=> Throws Invalid
37
© Vita Rara, Inc.
validates_length_ofEnsures an attribute is the proper length
class User < ActiveRecord::Base validates_length_of :login, :within => 3..20 validates_length_of :password, :is => 8 validates_length_of :name, :minimum => 3end
38
© Vita Rara, Inc.
validates_format_ofEnsures the format of an attribute matches
regular expression.Can be used to validate email addresses.
class User < ActiveRecord::Base validates_format_of :email, :with => /^[\w\d]+$/end
39
© Vita Rara, Inc.
validates_inclusion_of & validates_exclusion_of
Ensures that a value is or is not in a collection of options.
class User < ActiveRecord::Base validates_inclusion_of :gender, :in => %w( male female ), :message => "Oh really...."
validates_exclusion_of :login, :in => %w( root admin super ), :message => "Tisk tisk..."end
40
© Vita Rara, Inc.
validates_associatedEnsures that an associated model is valid
prior to saving.
class User < ActiveRecord::Base belongs_to :account validates_associated :account, :on => :createend
class Account < ActiveRecord::Base validates_presence_of :nameend
user = User.new(:login => 'mark', :name => 'Mark Menard')user.account = Account.new # invalid missing nameuser.save! #=> Throws RecordInvalid exception.
© Vita Rara, Inc.
Other Declarative Validations validates_acceptance_of validates_confirmation_of validates_each validates_size_of
You can also create your own declarative validations that match your problem domain.
42
© Vita Rara, Inc.
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.
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.') endend
43
© Vita Rara, Inc.
Using Validation CallbacksClass methods for defining validation call
backs:validate :method_namevalidate_on_update :method_namevalidate_on_create :method_name
Instance methods for defining validations:validatevalidate_on_updatevalidate_on_create
Model Life Cycle
45
© Vita Rara, Inc.
New Model CallbacksActiveRecord calls these methods prior to
saving a new record:before_validationbefore_validation_on_create
validation is performed
after_validationafter_validation_on_createbefore_savebefore_create
ActiveRecord saves the record
after_createafter_save
46
© Vita Rara, Inc.
Existing Model CallbacksActiveRecord calls these methods prior to
saving an existing recordbefore_validation
ActiveRecord performs validation
after_validationbefore_savebefore_update
ActiveRecord saves the record
after_updateafter_save
47
© Vita Rara, Inc.
Destroy Model CallbacksActiveRecord calls these methods when
destroying a model:before_destroy
ActiveRecord performs the DELETE
after_destroy
48
© Vita Rara, Inc.
Callback Use CasesCleaning up attributes prior to savingStarting followup processesSending notificationsGeocodingParanoia: Don’t delete anything just mark it
deleted.Clean up associated files, avatars, other
assets.
49
© Vita Rara, Inc.
Cleaning up Attributes Prior to Saving
class CreditCard before_validation :cleanup_number private def cleanup_number self.number = number.gsub(/[^0-9]/, "") true # I like to be explicit endend
Observers
51
© Vita Rara, Inc.
ObserversObservers 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.
52
© Vita Rara, Inc.
Creating an Audit Trail with an Observer# in config/environment.rbconfig.active_record_observers = [ :auditor ]
# in auditor.rbclass Auditor < ActiveRecord::Observer observe User def after_create (model) log_info("New #{model.class.name} created.", model) end def after_update (model) log_info("Update #{model.class.name}", model) end def after_destroy (model) log_info("Destroy #{model.class.name}", model) end private def log_info (model, info) log.info(info) log.info(model.inspect) endend
Shameless Self Promotion
54
© Vita Rara, Inc.
Ruby and Rails TrainingOne day to three day programs. Introduction to RubyAdvanced Ruby Introduction to RailsAdvanced RailsTest Driven DevelopmentBehavior Driven DevelopmentTest Anything with CucumberAdvanced Domain Modeling with
ActiveRecordDomain Driven Development with Rails
55
© Vita Rara, Inc.
Ruby on Rails ConsultingFull Life Cycle Project Development
InceptionImplementationDeploymentLong Term Support
Ruby on Rails MentoringGet your team up to speed using Rails
56
© Vita Rara, Inc.
Contact InformationMark Menard
[email protected]://www.vitarara.net/518 369 7356