I have an Item model that uses acts_as_nested_set (via
better_nested_set plugin) to maintain tree structures of Items. There
can potentially be thousands of “root” Items. A requirement is that
each “root” Item have a unique identifier in the form ‘YYMMDD-nnnnnn’
where nnnnnn is a sequential counter (e.g. 071106-000024). There can
be no gaps in this sequential counter.
For example, a “root” Item with 57 children is added to the database.
The “root” has a unique identifier of ‘071106-000001’ because it is
the first “root” item (Item#id = 1). The next “root” item (Item#id =
58) must have a unique identifier of ‘071106-000002’ and NOT
‘071106-000058’.
How would you implement this “sequential counter”?
On Nov 6, 2007, at 8:25 PM, [email protected] wrote:
- must have a unique identifier of ‘071106-000002’ and NOT
‘071106-000058’.
The rows have their own regular integer keys, and that’s an additional
column that is NULL for non-root nodes?
– fxn
- must have a unique identifier of ‘071106-000002’ and NOT
‘071106-000058’.
How would you implement this “sequential counter”?
Have a table with two columns. The first is the date. The second is an
integer.
Write a routine that gets you the next highest value for the given date
and then updates the table with that new value for next time. And put a
gigantic lock around the entire thing so that you don’t have any race
conditions.
If it were me, I’d spend some time trying to remove that restriction,
but
I’m guessing you’re stuck with it.
What happens in this situation:
-
Lock.
-
Get the next NNNN value.
-
Update the database for next time.
-
Unlock.
-
Try and use it, but it freaks out, entire Rails app crashes.
-
Restart app.
At this point you have a hole in your sequence… how do you even know
if
you have a hole? What do you do?
Sometimes writing it out in the form of a question gets the creative
juices flowing.What if I added an attribute to the items table named
“sequence”. Then I could use an after_create callback and two private
methods such as:
def after_create
if self.root?
update_attribute(:sequence, generate_sequence)
update_attribute(:identifier, generate_identifier)
end
end
private
def generate_sequence
Item.maximum(:sequence, :conditions => [‘parent_id != ?’, ‘NULL’])
Here is my working solution:
class AddSequenceToItems < ActiveRecord::Migration
def self.up
add_column :items, :sequence, :integer
end
def self.down
remove_column :items, :sequence
end
end
class Item < ActiveRecord::Base
def after_create
# This item doesn’t already have a root, so this is a new root
item
update_attribute(:root_id, self.id) if self.root_id.nil?
if root?
update_attribute(:sequence, generate_sequence)
update_attribute(:identifier, generate_identifier)
end
end
def sequence(padded = false)
padded ? padded_sequence : self[:sequence]
end
private
def self.maximum_sequence
Item.maximum(:sequence) || 0
end
def generate_sequence
self.class.maximum_sequence + 1
end
def generate_identifier
“#{self.created_on.strftime(’%y%m%d’)}-#{padded_sequence}”
end
def padded_sequence(length = 6, str = ‘0’)
self[:sequence].to_s.rjust(length, str)
end
end