On Thu, Jan 5, 2012 at 2:35 PM, Dmitry Suzdalev <dim...@gmail.com> 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@googlegroups.com.
To unsubscribe from this group, send email to 
rubyonrails-talk+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/rubyonrails-talk?hl=en.

Reply via email to