0

I have an inventory system, where a User has many inventory. We have a barcode column which needs to be sequential for each user.

For example:

Inventory Table:

id | user_id | barcode
 1 |       1 |       1
 2 |       1 |       2
 3 |       2 |       1
 4 |       2 |       2
 5 |       1 |       3

In the Inventory model I have

before_validation :assign_barcode, on: :create

def assign_barcode
  self.barcode = (user.inventories.order(barcode: :desc).first.try(:barcode) || 0) + 1
end

It generally works, but ran into a problem when seeding my db:

(1..5).each do
  user.inventories.build(...)
end
user.save

I end up with a bunch of inventories for user that have the same barcode. How can I ensure that inventories have unique barcodes even when adding inventories in bulk?

Alex Marchant
  • 2,490
  • 27
  • 49
  • 1
    You'll need one of the suggestions that another user has made below, but in order to guarantee uniqueness you need to have database-level constraints or use table locking. Checkout the ActiveRecord documentation about concurrency and integrity http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of – Wizard of Ogz Nov 01 '13 at 13:03
  • @WizardofOgz nice reference, thank you. – Alex Marchant Nov 01 '13 at 20:37

3 Answers3

1

You can build and save the object without saving the nested user object, which seems to cleanly call your validate method. I'm pretty sure this is not as intended, but I am doing something similar for a project and seeded associated data as such.

Something like:

(1..5).each do
  inv = user.inventories.build
  inv.save
end
trh
  • 7,186
  • 2
  • 29
  • 41
  • My situation is actually a bit more complicated than i described. Inventories belong to an Order sometimes, and there are some circular validation things going on that make `order.inventories.build` then `order.save` the only way to save an order. – Alex Marchant Nov 01 '13 at 04:36
1

Calling 'build' will create a new inventory object but not save it automatically. Since the new created inventory was not saved into database, your query in method "assign_barcode" won't be able to find them, that's why they all end up with the same barcode: 1. What @trh suggested are correct, you need to save (either inv.save or user.save will do) a inventory immediately after build it.

tuo
  • 586
  • 1
  • 4
  • 20
0

Changed from:

before_validation :assign_barcode, on: :create

to:

before_create :assign_barcode

and it works as expected now, even when adding many .builds then calling save. Only downside is had to remove the barcode presence validator, but since it's automatically generated we should be OK.

Alex Marchant
  • 2,490
  • 27
  • 49
  • Your method fixed the issue cuz before_create works as before_save in this scenario. It delays the barcode assignment to "save record" phase, which will do the work after all inventory objects are created. Here is a detailed [explaination](http://stackoverflow.com/questions/6249475/ruby-on-rails-callback-what-is-difference-between-before-save-and-before-crea). – tuo Nov 04 '13 at 09:23