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
  def locked(lock)
    lock = "#{current_database}_#{lock}"

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

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


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

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

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.


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

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 )

Google+ photo

You are commenting using your Google+ 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 )


Connecting to %s