Extracting unique value generation out of an ActiveRecord model

I needed to generate unique values for several models. The most common method to do so is as follows:

class ModelName < ActiveRecord::Base

before_create :generate_token

protected

def generate_token
self.token = loop do
random_token = SecureRandom.urlsafe_base64(nil, false)
break random_token unless ModelName.exists?(token: random_token)
end
end

end

This combines both random value generation and checking for uniqueness in the same method. That’s not a problem in this implementation, but I needed to generate different token formats for different models.

Let’s copy some random value generation code into a module

module RandomValue
class << self
def string(length)
Array.new(length){ [*'a'..'z'].sample }.join
end
end
end

Now, to figure out the code for ActiveRecord uniqueness. We need to be able to pass different generation routines into this code. Looks like we need:

  • one method call to configure the generator used
  • one method call to check whether the generated value is unique.

An example of configuration would look like:

class Model < ActiveRecord::Base
generates_unique :coupon_code, proc { RandomValue.string(6) }
end

And the resulting method call to get a unique value should look like

Model.generate_unique_coupon_code

Let’s implement it as a mixin.

module GenerateUniqueAttribute 
extend ActiveSupport::Concern
  included do 
def self.generates_unique(attr_name, generator)
method_name = “generate_unique_#{attr_name}”
      define_singleton_method(method_name) do 
begin
value = generator.call
exists?(attr_name => value) ? send(method_name) : value
rescue SystemStackError
raise “Could not generate a unique #{attr_name} for #{name}”
end
end
end
end
end

That’s a slightly intimidating chunk of metaprogramming. 
Breaking it down:

This chunk implements the `generates_unique` class method

extend ActiveSupport::Concern
included do 
def self.generates_unique(attr_name, generator)
...
end
end

This takes the attribute name provided and defines a class method on the model as `generate_unique_attribute_name`. 
Equivalent to`def self.generate_unique_attribute_name`, except it allows us to customize the name of the defined method at runtime.

method_name = “generate_unique_#{attr_name}”
define_singleton_method(method_name) do
...
end

We call the generator and check whether the value exists. If it does, we call this method recursively until we get a unique value.

The usual drawback of using recursion is that too many calls will cause a stack overflow.

In this case, we turn that into a feature. If the generator never returns a unique value, the stack overflow will help us terminate execution and raise an error. A normal `loop..do` construct would run forever.

begin 
value = generator.call
exists?(attr_name => value) ? send(method_name) : value
rescue SystemStackError
raise “Could not generate a unique #{attr_name} for #{name}”
end

All that remains is to include this module into an ActiveRecord class.

class Model
include GenerateUniqueAttribute

generates_unique :attribute, proc { RandomValue.string }
end
Like what you read? Give Looi Ferng Yi a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.