56
Introduction to ActiveRecord The Rails Object Relational Mapper by Mark Menard, Vita Rara, Inc.

Intro to Rails ActiveRecord

Embed Size (px)

DESCRIPTION

An introduction to Ruby on Rails ORM ActiveRecord.

Citation preview

Page 1: Intro to Rails ActiveRecord

Introduction to ActiveRecord

The Rails Object Relational Mapper

by Mark Menard, Vita Rara, Inc.

Page 2: Intro to Rails ActiveRecord

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.

Page 3: Intro to Rails ActiveRecord

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)

Page 4: Intro to Rails ActiveRecord

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.

Page 5: Intro to Rails ActiveRecord

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.

Page 6: Intro to Rails ActiveRecord

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

Page 7: Intro to Rails ActiveRecord

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

Page 8: Intro to Rails ActiveRecord

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

Page 9: Intro to Rails ActiveRecord

© 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

Page 10: Intro to Rails ActiveRecord

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

Page 11: Intro to Rails ActiveRecord

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.

Page 12: Intro to Rails ActiveRecord

© 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’)

Page 13: Intro to Rails ActiveRecord

© 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')

Page 14: Intro to Rails ActiveRecord

14

© Vita Rara, Inc.

Advanced Finding (con’t)Finders also support:

:select:group:include (optimize n+1 queries)

Page 15: Intro to Rails ActiveRecord

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 ])%>

Page 16: Intro to Rails ActiveRecord

© 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’)

Page 17: Intro to Rails ActiveRecord

© Vita Rara, Inc.

TransactionsAccount.transaction do

account1.deposit(100)

account2.withdraw(100)

end

Page 18: Intro to Rails ActiveRecord

ActiveRecord Associations

Page 19: Intro to Rails ActiveRecord

© 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.

Page 20: Intro to Rails ActiveRecord

© 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.

Page 21: Intro to Rails ActiveRecord

© 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

Page 22: Intro to Rails ActiveRecord

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

Page 23: Intro to Rails ActiveRecord

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'

Page 24: Intro to Rails ActiveRecord

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)

Page 25: Intro to Rails ActiveRecord

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

Page 26: Intro to Rails ActiveRecord

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.

Page 27: Intro to Rails ActiveRecord

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

Page 28: Intro to Rails ActiveRecord

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

Page 29: Intro to Rails ActiveRecord

ActiveRecord Validations

Keeping Your Data Safe

Page 30: Intro to Rails ActiveRecord

© 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

Page 31: Intro to Rails ActiveRecord

© 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.

Page 32: Intro to Rails ActiveRecord

© 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

Page 33: Intro to Rails ActiveRecord

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.

Page 34: Intro to Rails ActiveRecord

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

Page 35: Intro to Rails ActiveRecord

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

Page 36: Intro to Rails ActiveRecord

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

Page 37: Intro to Rails ActiveRecord

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

Page 38: Intro to Rails ActiveRecord

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

Page 39: Intro to Rails ActiveRecord

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

Page 40: Intro to Rails ActiveRecord

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.

Page 41: Intro to Rails ActiveRecord

© 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.

Page 42: Intro to Rails ActiveRecord

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

Page 43: Intro to Rails ActiveRecord

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

Page 44: Intro to Rails ActiveRecord

Model Life Cycle

Page 45: Intro to Rails ActiveRecord

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

Page 46: Intro to Rails ActiveRecord

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

Page 47: Intro to Rails ActiveRecord

47

© Vita Rara, Inc.

Destroy Model CallbacksActiveRecord calls these methods when

destroying a model:before_destroy

ActiveRecord performs the DELETE

after_destroy

Page 48: Intro to Rails ActiveRecord

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.

Page 49: Intro to Rails ActiveRecord

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

Page 50: Intro to Rails ActiveRecord

Observers

Page 51: Intro to Rails ActiveRecord

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.

Page 52: Intro to Rails ActiveRecord

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

Page 53: Intro to Rails ActiveRecord

Shameless Self Promotion

Page 54: Intro to Rails ActiveRecord

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

Page 55: Intro to Rails ActiveRecord

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

Page 56: Intro to Rails ActiveRecord

56

© Vita Rara, Inc.

Contact InformationMark Menard

[email protected]://www.vitarara.net/518 369 7356