0x17 – Atomic science, revisited

I lamented earlier about the absence of a racecondition-free find_or_create implementation and somewhat promised to check back with a working implementation. YES, WE CAN: NOW is the time, folks!

As stated there the first thing missing is an advisory locking implementation. With the database as the natural choice for a synchronisation point that implementation would be DB dependant, the following being a MySQL-implementation:


module ActiveRecord::ConnectionAdapters::MysqlAdapter::AdvisoryLock
  TIMEOUT=10
  
  def locked(lock)
    lock = "#{current_database}_#{lock}"

    begin
      execute "SELECT GET_LOCK(#{quote(lock)},#{TIMEOUT})"
      yield
    ensure
      execute "SELECT RELEASE_LOCK(#{quote(lock)})"
    end
  end
end
  
class ActiveRecord::ConnectionAdapters::MysqlAdapter
  include AdvisoryLock
end

Now, whats still left is a find_or_create implementation. I like that one:


ActiveRecord::Base

class ActiveRecord::Base
  def self.find_first_by_hash(hash)
    find :first, :conditions => hash
  end

  def self.find_or_create(hash)
    find(hash) || connection.locked("#{self.table_name}.find_or_create") do
      find(hash) || create(hash)
    end
  end
end

Where is the “find_or_create_by_attribute_names”, you might ask? Well, I am not too fond of those anyways, because writing


find_by_name_and zipcode name, zipcode

adds several hundred line to the ActiveRecord implementation, doesn’t work with attributes that have an underscore in its name, and saves only a few typestrokes over


find :first, :name => name, :zipcode => zipcode

which could even be reduced down to one by an extended find() implementation, which takes a Hash as a possible first argument.

Advertisements

One response to “0x17 – Atomic science, revisited

  1. First off, thanks for this interesting post (combined with your older posts on this subject).

    If you do the effort of writing a “find_first_by_hash”, which not make sure the “first” is actually repeatable (find :first does by default not enforce an ORDER BY clause, so the returned row could be any random row, which could be causing flickering tests and not fun for users).

    I suggest:

    def self.find_first_by_hash(hash)
    find :first, :conditions => hash, :order => :id
    end

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s