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 :slight_smile:

Thanks.

On Thu, Jan 5, 2012 at 2:35 PM, Dmitry S. [email protected]
wrote:

end
def self.exchange_weights(id1, id2)
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 :slight_smile:

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 …

  1. 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 V.
http://twitter.com/peter_v
http://rails.vandenabeele.com

On 5 January 2012 18:09, Peter V. [email protected]
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:

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!

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs