ActiveRecord Validations

•February 8, 2009 • Leave a Comment

The API provided for ActiveRecord validations gives developers a way to describe valid model object state.  The validations hook into the object life cycle and inspect object state to determine the validity of attributes.  If any validations fail, they will be reported as errors in the model, which are accessible through the errors attribute, an instance of ActiveRecord::Errors.  If there are no errors on an ActiveRecord model, the errors collection is empty, and the valid? method would return true.  The validations you can use will appear like declarations; they are simply class methods.  Each validation method accepts n attributes, along with an optional set of options.  Any bang! methods that are called on the model that fail will raise an error with the description of the failures.

validates_acceptance_of :some_attribute # attribute must be accepted.
validates_acceptance_of :some_attribute, :accept=>’YES’ # considered acceptance. default=1

validates_associated :has_many_association # the valid? method will be called on each object

# creates a virtual attribute_confirmation – gets compared to attribute.
validates_confirmation_of :attribute # the user interface must use attribute_confirmation


validates_each
:attribute_one, :attribute_two, :attribute_etc do |record, attr, value|
# you can give validates_each an array of attributes and a block to perform.
# The block marks the object as valid or not by adding to the errors array or not.
# example…. record.errors.add(attr) unless PurchasingSystem.validate(attr,value)
end
validates_inclusion_of :gender, :in=>['m','f'], :message => ‘Enter m or f.’
validates_exclusion_of :login, :in=>['admin', 'root', 'superuser'], :message=>’No no no.’
# Available as a plugin… optional :allow_nil => false
validates_existence_of :attribute # checks foreign key in belongs_to refrences existing record
# script/plugin install http://svn.hasmanythrough.com/public/plugins/validates_existence
validates_format_of :email, :with=> … #some regular expression

# provides optional :wrong_length, :too_long, and :too_short options
validates_length_of :login, :minimum => 5  # minimum value
validates_length_of :login, :within => 5..20  # a Ruby Range
validates_length_of :account_number, :is => 16  # exact value
validates_length_of :account_number, :is => 16, :wrong_length => “should be %d characters.”

validates_numericality_of :some_numeric_attribute, :integer_only => true

validates_presence_of :some_attr_checked_if_blank # won’t allow nil or “”

validates_uniqueness_of :attribute_is_unique_for_all_models_of_same_type_via_query
validates_uniqueness_of :line_two, :scope => [:line_one, :city, :zip]
validates_uniqueness_of :login_name, :case_sensitive => false

COMMON VALIDATION OPTIONS

:allow_nil => true # skips validation if the value of the attribute is nil
:if => … # conditional validation
:message => … #specific message describing the validation failure, overriding the default msg
: on => … # by default, validations are run on save.  specify create for validates_uniqueness_of

THE ERRORS COLLECTION

Errors for attribute validations will be created by prepending the canned validation messages with the name of the associated model.

add_to_base(msg) # adds complete msg to the object state for self; not any particular attribute
add(attribute,msg) # adds partial msg to a particular attribute
clear # clears the errors collection
invalid?(attribute) # true or false depending on if validation errors are associated
on(attribute) # nil,string or array depending on the errors collection state for the attribute

CUSTOM VALIDATION – Rails provides three hook methods for custom validation

validate # called after standard Rails validations
validate_on_create # called if new_record?
validate_on_update # called if not a new_record?

SKIPPING VALIDATION

The ActiveRecord::Validations module mixed into ActiveRecord::Base affects three instance methods; save, save! and update_attribute are affected.  Validations can be skipped by passing in false as a method parameter.  As a result of ActiveRecord::Validations calling :alias_method_chain :save, :validation … you end up with save_without_validations.  Be careful — update_attributes does use validations, while the singular form, update_attribute, does not.

ActiveRecord one-to-one Relationships

•February 1, 2009 • Leave a Comment

has_one is used to declare one-to-one relationships (along with the belongs_to method).
Call belongs_to on the model that has the foreign key column in the database.  Use has_one in the other model.  LIMIT 1 clause is appended to the generated SQL that activerecord uses to find the relationship.

Examples
:has_one :last_timesheet
:has_one :primary_account
:has_one :profile_photo

class Avatar < ActiveRecord::Base; belongs_to :user; end
class User < ActiveRecord::Base; has_one :avatar; end
user = User.find(:first)
user.avatar # => nil
user.build_avatar(:url => ‘/avatars/smiling’)
user.avatar.save # => true

Specifying :dependent => :destroy will cause records no longer associated with the parent model to be destroyed.

class User < ActiveRecord::Base
has_one :avatar, :dependent => :destroy
end

has_one Options

:as => sets up a polymorphic association

:class_name => specify the class this association uses (useful when not using conventions)

:conditions => specify attributes the object must meet to be included in the association

:dependent => specifies how ActiveRecord should treat associated objects when the parent object is deleted.  There are a few different values that you can pass and they work just like the :dependent option of has_many.  Default is :nullify, :delete won’t use callbacks and :destroy will use callbacks.

:foreign_key => specifies the name of the fk column on the table of the association

:include => eagerloading purposes

:o rder => sql fragment used to order the results (used in the ORDER BY clause)

ActiveRecord many-to-many Relationships

•February 1, 2009 • Leave a Comment

There are two primary ways to create many-to-many relationships in Rails:

  1. has_and_belongs_to_many
  2. has_many :through

has_and_belongs_to_many
A join table is used to link the two activerecord models; unless specified, Rails assumes the tablename is a concatenation of the two joined classes in alphabetical order and joined with an underscore.  If a billing_code has_and_belongs_to_many timesheets, then billing_codes_timesheets would be the tablename.  The only columns in the table would be billing_code_id and timesheet_id.  There would be no :id column in the table.

Self-referential has_and_belongs_to_many (habtm) relationships are also possible.
class BillingCode
has_and_belongs_to_many :related, :join_table => ‘related_billing_codes’, :foreign_key => ‘first_billing_code_id’, :association_foreign_key => ‘second_billing_code_id’, :class_name => ‘BillingCode’
end

Bidirectional Relationships - habtm relationships do not work bi-directionally by default.
The :insert_sql option must be used to override the normal INSERT statment that Rails would use to associate objects with eachother.

:insert_sql => ‘INSERT INTO related_billing_codes (`first_billing_code_id`, `second_billing_code_id`) VALUES (#{id}, #{record.id}), (#{record.id}, #{id})’

def delete_records(records)
if sql = @reflection.options[:delete_sql]
records.each { |record| @owner.connection.execute(interpolate_sql(sql,record)) }
else
ids = quoted_record_ids(records)
sql = “DELETE FROM #{@reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})”
@owner.connection.execute(sql)
end
end

Rails will let you add any columns you want to the join table, but those fields will be marked as read-only; it’s not possible to save changes to those additional attributes.  habtm relationships should be thought of as the simplest way to establish a many-to-many relationship between models, but problems with habtm begin when you want to add extra data associated with the relationship.

has_many :through
Rails documentation strongly suggests to upgrade habtm relationships with a has_many :through model, which does a much better job of supporting additional attributes on relationships.

John Susser – “The has_many :through association allows you to specify a one-to-many relationshipt indirectly via an intermediate join table.  In fact, you can specify more than one relationshipt via the same table, which effectively makes it a replacement for has_and_belongs_to_many.  The biggest advantage is that the join table contains full-fledged model objects complete with primary keys and ancillary data.  No more push_with_attributes; join models just work the same way all your other ActiveRecord models do.

Example –

class Client < ActiveRecord::Base
has_many :billable_weeks
has_many :timesheets, :through => :billable_weeks # relationship
end

class BillableWeek < ActiveRecord::Base # the join model.
belongs_to :client
belongs_to :timesheet
end

class Timesheet < ActiveRecord::Base
has_many :billable_weeks
has_many :clients, :through => :billable_weeks # inverse relationship
end

# Therefore, the join model has been upgraded to a full object model.

Considerations…
has_many :through will not let you add an object to the association if both ends of the relationship are unsaved records.  If you use the create method to save the record rather than the << method, you should be fine.  If you use has_many :through, ActiveRecord will take care of managing the instances of the join model for you.

Aggregating Associations

When using has_many :through to create multiple child associations, you can query the data using find, etc… but you can’t append or create new records through them.  If you try creating an instance of an object through its association, the new object’s database table does not have this object’s primary key field; it won’t work. (BillableWeek doesn’t belong to a user, it belongs to a timesheet, so it doesn’t have a user_id field).

Join Models & Validations
You can add validations to the join models, but be careful when creating new instances.

validates_uniqueness_of :client_id, :scope => :timesheet_id # only 1 of each client per timesheet
validates_presence_of :start_date

timesheet.billable_weeks.create(:start_date => 1.week.ago) # required field

has_many :through Options

:source => specifies which association to use .. example:
has_many :timesheets, :through => :billable_weeks, :source => :sheet # use BillableWeek#sheet

:source_type => Used when has_many :through usus a polymorphic belongs_to join model.
The value of the option is the symbol name of the target class.

:uniq => ensures that only unique objects are in the association collection

ActiveRecord has_many Association

•January 31, 2009 • Leave a Comment

The has_many association is intuitive; it adds a one-to-many relationship with a class that would declare a belongs_to relationship.  By convention, the singularized form of the relationship name is assumed to be the model class name.

class User
has_many :timesheets  # by convention, the model would be Timesheet
has_many :expense_reports # by convention, the model would be ExpenseReport
end

# A lot of customization and power is available to those who know the options you can use in the relationship.

has_many Options

:after_add # specifies the name of a callback method or Proc to be called after the << method is used (not create).

:after_remove # specifies the name of a callback to be called after the delete method is used

:as # specifies the polymorphic belongs_to association for the related class

:before_add # specifies the name of callbacks or Procs to be called before the << method (or concat or push) is called

:before_remove #specifies the name of callbacks or Procs to be called before the delete method is used

:class_name # specify a non-conventional classname for the model used in the association

:conditions # Add extra conditions to the SQL that brings back objects into the association

:counter_sql # Overrides the query…. has_many :things, :finder_sql => ‘select * from t where id = #{id}’

:delete_sql # Overrides the SQL used to break associations

:dependent => :delete_all # all associated objects are deleted w/ 1 SQL command.  No destroy triggers are used.
:dependent => :destroy_all
# all objects destroyed one at a time with the destroy method.
:dependent => :nullify
# Default behavior – nullify (clear) the fk that joins them to the parent.

:extend => ExtensionModule # methods that will extend the association collection proxy

:finder_sql => specifies complete SQL to fetch the association.  Count operations are based on this sql as well.

:foreign_key => overrides the convention fk that would normally be used in the SQL for the association

:group => attribute for which to use in group by sql

:include => array of names used for eager loading (as mentioned in a previous post)

:insert_sql => Overrides the SQL to be used to create associtations

:limit => appends LIMIT clause to the SQL generated for loading the association

: offset => Integer determining the offset from where rows should be fetched

: order => used in the ORDER BY clause

:select => is SELECT * by default, but can be changed to add additional calculated columns or columns from joins onto the associated object as it is loaded.

:source and :source_type => used as additional options to help in has_many :through

:table_name => overrides the table name that would be used by default in the FROM clause

:through => creates a collection association through another association

:uniq => true # removes duplicate objects from the collection

Proxy Methods

build(attributes={}) # => instantiates and links new object in the collection by fk, but doesn’t save it.

count(*args) # => counts the number of associated records in the database with SQL

find(*args) # => same as the usual ActiveRecord.find method; scope is constrained to associated records and additional conditions specified in the relationship

ActiveRecord belongs_to Association

•January 31, 2009 • Leave a Comment

Using belongs_to will create a one-to-one relationship from one ActiveRecord object to a single associated object for which it has a foreign key value.  Assigning an object to a belongs_to association will set the fk to the owner object, however the record will not be saved automatically in the database.  Calling belongs_to on a class establishes an attribute of the same name on instances of the class.  The attribute is really a proxy to the related ActiveRecord object.

Reloading the Association
Calling the accessor method with a parameter of true will force the association to be reloaded from the database.

Building and Creating Related Objects with the Association
belongs_to adds factory methods for creating new instances and connecting them with foreign keys automatically.
build_association does not save a new object, but create_association does.

belongs_to Options
:class_name # => specifies the name of the class for the association. Example:…

class Timesheet
belongs_to :user
belongs_to :approver, :class_name => ‘User’  # not an instance of Approver
end

:conditions # => Add conditions to a relationship that must be correct in order to be a valid relationship. Example..

# Note: conditions only affect how relationships are read from the database, not how they are created.

class Timesheet
belongs_to :approver, :class_name => ‘User’, :conditions = ['user.authorized_approver = ?', true]
end

:foreign_key # => Specifies the fk column used as opposed to the conventional <association>_id column.
belongs_to :administrator :foreign_key => ‘admin_user_id’

:counter_cache # => used to make Rails maintain a counter field automatically called <associations>_count
:counter_cache => true # use the default name for the counter field.
:counter_cache => ‘number_of_children’ # alternate name for the counter field (unconventional)
# be careful to initialize your counter cache column to 0 in the database

:include # => creates a left outer join to include associations in one query rather than N+1 queries.
# (I have talked about the :include parameter in the post about eager loading.)

:polymorphic => true
# The type of the related object is stored in the database along with its foreign key.
# When you make a :belongs_to association polymorphic, any other model in the system can fill the role. Example….

class Comment < ActiveRecord::Base
belongs_to :subject, :polymorphic => true
end

class ExpenseReport < ActiveRecord::Base
belongs_to :user
has_many :comments, :as => :subject
end

class Timesheet < ActiveRecord::Base
belongs_to :user
has_many :comments, :as => :subject
end

# In the database, the comments table would store a subject_type column to store the class name of the associated class.

ActiveRecord Associations

•January 31, 2009 • Leave a Comment

Associations in Rails declaratively express relationships between model classes.  Although they appear as macro-like statements in model classes, they are actually methods called on the classes where they are used.  It can be unclear to beginning Rails developers what type of objects are returned by association with these methods.  The objects returned tend to behave like regular Ruby objects and arrays, so it can be easy to think of them actually as arrays or direct model classes; however, it is important to know that these objects are actually associations.

has_many :somethings # would actually be an instance of HasManyAssociation

The superclass of all associations is AssociationProxy, which holds the basic structure and functionality of all association proxies.  The proxy object starts out by undef_method most instance methods and relies on sending most method calls to the target of the proxy via method_missing.

object.somethings.respond_to? :proxy_owner # => true proves that somethings is a proxy.

Warning about Associations
Don’t name associations the same as ActiveRecord::Base instance method names.  When an association is created, a method with that name is added to the model, which would override the inherited method (BAD).

One-to-Many Relationships
Consider the following…

class User < ActiveRecord::Base
has_many :timesheets
has_many :expense_reports
end

class Timesheet < ActiveRecord::Base
belongs_to :user # makes it possible to reference the user to which this belongs
end

class ExpenseReport < ActiveRecord::Base
belongs_to :user  # makes it possible to reference the user to which this belongs
end

# When the code is evaluated, Ruby/Rails uses some metaprogramming techniques to add code to your models dynamically add code to your models.  Proxy objects are created to let you manipulate the relationship easily.

dan = User.create :login => ‘demmons’, :password => ‘xajrwgsdf3′, :password_confirmation => ‘xajrwgsdf3′, :email => ‘someone@somewhere.com’

dan.timesheets # => ActiveRecord::StatementInvalid: … no such column: timesheets.user_id…
Remember – you have to add a foreign key column to the timesheets and expense_reports tables.
dan = User.find(1) # => <User:0x35dc85a … >
dan.timesheets << Timesheet.new # => [<Timesheet:0x1234 .. @new_record=true, @attributes={}>]
dan.timesheets # => [<Timesheet:0x1234 .. @new_record=true, @attributes={}>]

Adding Associated Objects to a Collection

Adding an object to a has_many collection saves the object automatically unless the parent object is not yet stored in the collection.

dan.timesheets.reload # re-fetches the attributes of an object from the database…

The << method set the user_id attribute on the timesheet automatically for you when the item was added.
The << method takes one or more association objects, and adds each object to the collection.

AssociationCollection Methods

HasManyAssociation < AssociationCollection
HasAndBelongsToManyAssociation < AssociationCollection

<<(*records)
create(attributes = {})

Both methods will ad either a single object or many, depending on the input. << is transactional, and create is not.
The << method will trigger the :before_add and :after_add callbacks, but the create method does not.  The create method returns the new instance, while the << method returns the association for easy chaining.  If any records fail to save, the << method returns false.

clear # removes all records from the association by clearing the fk.  If the association is configured with the :dependent option set to :delete_all, then clear calls destroy on each one.  The clear method is transactional.

delete(*records) and delete_all # remove associations transactionally.
# Note: delete_all will first load all of the objects in the collection from the database before each SQL UPDATE
# The :dependent option on the association defaults to :nullify.  The fk will be updated to null, but won’t be deleted.
# If the association is configured with :dependent => :delete_all or :destroy, the records will be deleted.

destroy_all # begins a transaction & calls destroy on each element, invoking individual DELETE sql

length # returns size of the collection by loading it and calling size on the array.
# length.zero? is more efficient than calling .emtpy on the array directly.

replace(other_array) # first deletes objects in the current collection that are not in other_array, and then concat each item in the other_array

size # returns the size of the array if it has been loaded (otherwise select count(*) query is used).
# use length instead if you will need to access the elements of the collection later anyway.

sum(column, *options) # Computes a summed value on the attribute using SQL.  Requires :group option.

uniq # Iterates over the collection and creates a Set with the unique values.  id is used for equality.

ActiveRecord Advanced Finding

•January 25, 2009 • Leave a Comment

Conditions
It is common to need to filter a set of results based on some attribute values.
You can specify these conditions in a hash as a parameter to the ActiveRecord.find method.
They also be specified as an array or directly as a string.

User.find(:first, :conditions => “login=’#{login}’ AND password=’#{password}’”)
User.find(:first, :conditions => ["login = ? and password = ?", login, password])
User.find(:first, :conditions => {:login => login, :password => password})

The array form should be used when input data needs to be sanitized first.
The string form can be used when you know the parameters are not tainted.
The hash form works similar to the array form, but only equality is a possible condition.

The authenticate_unsafely  method inserts parameters directly into the query; it is vulnerable to SQL-injection attacks.  Malicious users could inject delete statements into the sql, or do other harm.  The authenticate_safely  and authenticate_safely_simply methods will sanitize the parameters before putting them in queries.  Instead of using question marks (?) in the queries, you can use named placeholders and supply a hash of values.

Company.find( :first, [ " name = :name AND division = :div AND created_at > :date ",
{:name => "37signals", :div => "First", :date => '2005-01-01' } ] )

Boolean Conditions
Since databases have different ways of representing boolean values, you should leverage the built in support for converting between Ruby true/false values and the database native type used by the adapter.  Rails will handle the conversion for you transparently as long as you stick with using the helper methods provided to you.  Example:
Timesheet.find(:all, :conditions => ['submitted=?', true])

Order of Results
Timesheet.find(:all, :o rder => “created_at desc” )  # you can pass anything; the value of :o rder is not validated.

Limit and Offset
The :limit parameter takes an integer, denoting the amount of records to fetch from the database.  The :o ffset parameter indicates where the records should start.  If your offset is 40, and your limit is 10, you should obtain the fifth page of 10 results (assuming each page had 10 results previously).

Select Option
The :select option allows you to specify which columns are used in a result set, and also allows you to perform joins to other tables without bringing in columns from those tables back into the results.  If you attempt to obtain the value of an attribute you left out of your select option, a NoMethodError will be raised.  You can select all columns and an additional calculated column by setting your select value with…. :select => “*, ” + “some_column + some_other_column as some_computed_column”.

From Option
The :from option allows you to specify the table name of the SQL.  This is where you could also include other tables and views to join with.  Why might you need a parameterized table_name?  Imagine the case where you had module code to be mixed-into a target class.

Group by Option
Use this option if you’d like the group by clause to be added to your query.  You’d normally see this option being used in combination with the :select option; grouping by an attribute requires some sort of operation on an attribute to combine row data.  For example, you may need to :select => ‘name, sum(cash) as money’, :group => ‘name’.

Explicit Joins
The :join option is useful when doing group by and retrieving data from other tables, but you don’t want to load the associaed objects from those tables.  Example: Buyer.find(:all, :select => ‘buyers.id, count(carts.id) as cart_count’, :joins => ‘left join carts on carts.buyer_id=buyers.id’, :group => ‘buyers.id’)

Read Only
Specify the :readonly => true flag if you want to mark the objects as read-only.  You can make local changes to the attribute values, but ActiveRecord will prevent you from saving the changes back to the database.

ActiveRecord Connection Settings

•January 25, 2009 • Leave a Comment

ActiveRecord::Base.connection.execute( sql )
There might be times when you need to use ActiveRecord’s underlying database connections outside of the scope of normal use, as in custom scripts or ad-hoc query reporting.  If all of your models that extend ActiveRecord use the same connection, then you can simply access the ActiveRecord::Base.connection attribute.  After obtaining a reference to the connection, you can call the execute(string_sql) method on the connection object.

ActiveRecord::ConnectionAdapters::DatabaseStatements
.begin_db_transaction  # begins a txn mannually and turns off auto-commit
.rollback_db_transaction # rolls back the active txn and turns on auto-commit
.commit_db_transaction # commits the transaction and turns on auto-commit
.delete(sql)  # executes a delete; returns number of rows affected
.execute(sql) # executes the sql; returns the result set object for the adapter used.
.insert(sql) # executes the insert; returns the id from the table
.reset_sequence!(table, column, sequence = nil) # sequence = max(column) value
.select_all(sql) # returns an array of record hashes w/ columns as keys.
.select_one(sql) # returns a single hash of keys/values.  use this with limit 1.
.select_value(sql) # returns a single value; the first column of the first row.
.select_values(sql) # returns an array of values in the first column in all rows.
.update(sql) # executes the update; returns the number of rows affected.
.adapter_name # human-readable name of the adapter; e.g. SQLite
.disconnect! # disconnects the active connection
.reconnect!  # closes and opens a new connection in place
.raw_connection # useful for executing proprietary statements or database procs
.supports_count_distcinct? # indicates whether adapter supports count(distinct(…))
.supports_migrations?  # indicates if the adapter can run migrations
.tables # returns an array of tables in the database.
.verify!(timeout) # verify the connection is valid, only if called > timeout seconds

More configuration options
ActiveRecord::Base.default_timezone = :utc # default is :local
ActiveRecord::Base.allow_concurrency = true #default is false.  just leave it alone. http://permalink.gmane.org/gmane.comp.lang.ruby.mongrel.general/245 for Zed Shaw’s explanation of the dangers of allow_concurrency.
ActiveRecord::Base.generate_read_methods = true # if false, relies on method_missing (slower)
ActiveRecord::Base.schema_format = :sql # default is ruby.  if it is sql, schema is dumped as db specific sql.
When :ruby is used as the schema_format, an ActiveRecord::Schema file is generated, which could be used to load the schema into any database that supports migrations.

ActiveRecord with Multiple Databases

•January 25, 2009 • Leave a Comment

All classes that inherit from ActiveRecord::Base will use the ActiveRecord::Base.connection established through ActiveRecord::Base.establish_connection, unless otherwise specified in your model.

Perform the following steps to create models that use a different database.

1. Edit the config/database.yml file to include details for the additional_database.
2. Create an AdditonalDatabaseRecord < ActiveRecord::Base.
establish_connection :additional_database
self.abstract_class = true
end
3. Make your model classes that need to use the additonal_database extend the new class you made in step two.  Example: SomeName < AdditionalDatabaseRecord.

Rails will automatically keep database connections in a connection pool inside the ActiveRecord::Base class instance.  Whenever a connection is needed, the ActiveRecord::Base.retrieve_connection method is called, and the appropriate connection is found in the pool.

ActiveRecord Locking

•January 24, 2009 • Leave a Comment

If you have an application where data may be updated by more than one user at a time, you should consider concurrency issues and race conditions that may arise.  ActiveRecord supports both optimistic and pessimistic locking; there are pros and cons to both locking approaches, and it is to your advantage to know about them.

Optimistic Locking

When collisions are considered infrequent, optimistic locking is considered the way to go.  It takes the approach of leaving the record unlocked, but detects and resolves collisions if they occur.  Implementing this strategy is fairly trivial; add an integer column to the database called lock_version, with a default value of 0.  ActiveRecord’s behavior will change automatically for you; if two instances of the same record are loaded and saved differently, the first update will succeed, and the second update will raise an ActiveRecord::StaleObjectError.  You can customize the name of the locking column by calling the class method set_locking_column on your model, or globally in your environment.rb file using ActiveRecord::Base.set_locking_column = ‘alternate_lock’.  You will need to catch the raised exception and present the user with an appropriate message, etc.  The main disadvantage to using optimistic locking is that updates are a little slower; validation must be performed by checking the lock column prior to update.  The user also doesn’t find out about failure until after the attempt to save the data.  That could create an unpleasant user experience.

Pessimistic Locking

Database support is required for pessimistic locking to work.  The database is responsible for locking down specific rows during updates, effectively preventing other users from reading data that is about to be updated, rendering it impossible to read stale data.  Rails takes the approach of working with transactions as in the following example:

TripExpenseReport.transaction do
report = TripExpenseReport.find(1, :lock => true)
report.approved = true
report.save!
end

Alternatively, you could call an instance method called lock!, which calls reload( :lock => true ) behind the scenes.  Be sure to call lock! before making attribute changes, as they would obviously be lost by the call to reload.

When using pessimistic locking, the select statement that is generated will append FOR UPDATE to the sql, which causes all other connections to be blocked access to the rows returned by the query.  The lock is released once the transaction completes (is committed).  Beware of deadlocks; prevent it by capturing cases where a process fails to complete, etc.  Keep transactions small in scope and time to complete.

 
Follow

Get every new post delivered to your Inbox.