intro to rails activerecord

Post on 10-May-2015

3.693 Views

Category:

Documents

2 Downloads

Preview:

Click to see full reader

DESCRIPTION

An introduction to Ruby on Rails ORM ActiveRecord.

TRANSCRIPT

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

mark@mjm.nethttp://www.vitarara.net/518 369 7356

top related