Dmitry Suzdalev
2012-Jan-05 13:35 UTC
Exchanging values of two records with unique-validated fields
Hi fellows! Is there a way to atomically exchange values of records which are validated using ''validates_uniqueness_of''? Some details follow. Imagine I have a model: class Item < ActiveRecord::Base validates_uniqueness_of :weight end ''weight'' is a sorting weight. I have an index.html view which has a list of "Item" records wrapped in jQueryUI''s sortable container. My goal is to do some ajax request (POST i guess) whenever user changes items'' order. I.e. i plan to do end up with controller action that calls function like: class Item def self.exchange_weights(id1, id2) item1 = Item.find(id1) item2 = Item.find(id2) weight1 = Item.find(id1).weight weight2 = Item.find(id2).weight item1.weight = some_temp_weight item2.weight = weight1 item1.weight = weight2 end end Is this the right way to do this? Or can I somehow just do a separate POST requests on these Item''s instances by triggering ItemsController#update for each of them and passing weight - but here validations would not allow me to do this unless I miss something - that''s why I desided to ask :) Thanks. -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To view this discussion on the web visit https://groups.google.com/d/msg/rubyonrails-talk/-/xOuZts86q58J. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
Peter Vandenabeele
2012-Jan-05 14:09 UTC
Re: Exchanging values of two records with unique-validated fields
On Thu, Jan 5, 2012 at 2:35 PM, Dmitry Suzdalev <dimsuz-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> Hi fellows! > > Is there a way to atomically exchange values of records which are > validated using ''validates_uniqueness_of''? > Some details follow. > > Imagine I have a model: > > class Item < ActiveRecord::Base > validates_uniqueness_of :weight > end > > ''weight'' is a sorting weight. > > I have an index.html view which has a list of "Item" records wrapped in > jQueryUI''s sortable container. > My goal is to do some ajax request (POST i guess) whenever user changes > items'' order. I.e. i plan to do end up with controller action that calls > function like: > > class Item > def self.exchange_weights(id1, id2) > item1 = Item.find(id1) > item2 = Item.find(id2) > weight1 = Item.find(id1).weight > weight2 = Item.find(id2).weight > item1.weight = some_temp_weight > item2.weight = weight1 > item1.weight = weight2 > end > end > > Is this the right way to do this? > Or can I somehow just do a separate POST requests on these Item''s > instances by triggering ItemsController#update for each of them and passing > weight - but here validations would not allow me to do this unless I miss > something - that''s why I desided to ask :) >If you mean "atomic" on the level of saving to the database (all or nothing), you would need a database transaction. http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html Second, to circumvent the problem of the uniqueness validation, I see 2 solutions: 1) what you propose above: UNTESTED: def self.exchange_weights(id1, id2) item1 = Item.find(id1) item2 = Item.find(id2) weight1 = item1.weight weight2 = item2.weight Item.transaction do item1.weight = some_temp_weight item1.save! item2.weight = weight1 item2.save! item1.weight = weight2 item1.save! end # you still need to catch the exception here end This could fail ... e.g. if 2 parallel processed each write the same some_temp_weight exactly at the same time ... 2) Alternative: Make the validation dependent on an instance variable UNTESTED class Item attr_accessor :no_weight_uniqueness_validation validates :weight, :uniqueness => true, :unless => @no_weight_uniqueness_validation end def self.exchange_weights(id1, id2) item1 = Item.find(id1) item2 = Item.find(id2) weight1 = item1.weight weight2 = item2.weight Item.transaction do item2.weight = weight1 item2.no_weight_uniqueness_validation = true item2.save! item1.weight = weight2 item1.save! end # you still need to catch the exception here end This will NOT work if you also have the uniqueness validation in the database itself ... which you should, since the uniqueness validation in Rails is not automatically protected against race conditions when multiple parallell processes write to the same database. HTH, Peter -- Peter Vandenabeele http://twitter.com/peter_v http://rails.vandenabeele.com -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
Dmitry Suzdalev
2012-Jan-05 14:16 UTC
Re: Exchanging values of two records with unique-validated fields
On 5 January 2012 18:09, Peter Vandenabeele <peter-jNuWw7i2w7syMbTcgqFhxg@public.gmane.org> wrote:> If you mean "atomic" on the level of saving to the database > (all or nothing), you would need a database transaction. > > http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html > > Second, to circumvent the problem of the uniqueness validation, > I see 2 solutions: > <snip> >Oh, it''s clearer now. And, yes I have *just* saw in rails'' apidocs that I should do add_index on the DB level too - to prevent possible clashes. Didn''t know that until now, thanks. I guess that I will start with the first solution then. And maybe will improve it to be the latter if find the need too. Thanks, Peter! -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.