Save! not allowed after validates_uniqueness

I have this in a model

 validates_uniqueness_of :aspect, :scope => :user_id

In an instance method of the same model I have “save!” but I don’t touch
the :aspect attribute in that instance method at all.

Whenever that save! command is run however I get this error:

  ActiveRecord::RecordInvalid: Validation failed:  Aspect has

already been taken

I don’t understand. The validation should not skip the instance from
which the method is called. Please help. Thanks.

Sorry, I meant The validation should SKIP the instance from which the
method is called. It appears that save! tries to validate the object
against itself and fails because of course it is not unique from itself.

Can you please explain why you think the validation should be
“skipped”?

Cheers,
Nicholas

On Jul 4, 5:59 am, Learn by Doing [email protected]

2009/7/4 Learn by Doing [email protected]:

 ActiveRecord::RecordInvalid: Validation failed:  Aspect has
already been taken

Why? Â I am just trying to update an existing object, not create a new
one that would violate the uniqueness validation.

What code are you using for creating and modifying the object before
saving it?

Colin

Hi Nicholas,

Thanks for your response. What I meant was that validation should not
compare the object that is being updated against its own existing copy.
For example, I am editting an existing object:

[:aspect => “Aspect-A”, :user_id => 5, :third_field => “morning”].

Now, the object is unique in the database according to

validates_uniqueness_of :aspect, :scope => :user_id

That’s all good. However, when I update the object with the following
statements:

self.third_field = “night”
save!

Rails returns an error:

ActiveRecord::RecordInvalid: Validation failed: Aspect has
already been taken

Why? I am just trying to update an existing object, not create a new
one that would violate the uniqueness validation.

Thanks.

Hi Colin,

Thanks for asking that question. I had preloaded the database so I
didn’t have to create a new record. So now I just tried doing that and
it wouldn’t let me create a new record although the :aspect value I used
does not exist at all in the database. Console output is below:

stat0 = Stat.new(:user => user0, :aspect => “aspect0”)
=> #<Stat id: nil, user_id: 1000, aspect: “aspect0”, . . .>

stat0.save!
ActiveRecord::RecordInvalid: Validation failed: Aspect has already been
taken
from
/opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/validations.rb:1021:in
save_without_dirty!' from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/dirty.rb:87:insave_without_transactions!’
from
/opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/transactions.rb:200:in
save!' from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/connection_adapters/abstract/database_statements.rb:136:intransaction’
from
/opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/transactions.rb:182:in
transaction' from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/transactions.rb:200:insave!’
from
/opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/transactions.rb:208:in
rollback_active_record_state!' from /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/transactions.rb:200:insave!’
from (irb):70

“aspect0” is something I completely made up and does not exist in the
database. :user_id 1000 does exist in different rows with different
values of :aspect however.

On Sat, Jul 4, 2009 at 2:57 AM, Learn by Doing <
[email protected]> wrote:

 ActiveRecord::RecordInvalid: Validation failed:  Aspect has

already been taken

I don’t understand. The validation should not skip the instance from
which the method is called. Please help. Thanks.

Posted via http://www.ruby-forum.com/.

What version of Rails are you using? Did you try this in Edge Rails?

-Conrad

2009/7/4 Learn by Doing [email protected]:

records. Â Only their :Stat_ids are different. Â Something wrong with my
uniqueness validation?

 validates_uniqueness_of :aspect, :scope => :user_id

Can you post the model code and the db structure for this table please?

Colin

Hi Conrad,

I am using Rails 2.3.2 and Ruby 1.8.7, the latest stable combination
recommended by rubyonrails.org.

I am continuing to troubleshoot this. I have deleted all the sample
data that I preloaded so that my database is empty. Now, the opposite
is happening, it is letting me create records in violation of the
uniqueness validation:

stat0 = Stat.new(:user_id => 1000, :aspect => “aspect0”)
=> #<Stat id: nil, user_id: 1000, aspect: “aspect0”>
stat0.save!
=> true
stat0 = Stat.new(:user_id => 1000, :aspect => “aspect0”)
=> #<Stat id: nil, user_id: 1000, aspect: “aspect0”>
stat0.save!
=> true

I check the database and yes it contains both of the above identical
records. Only their :Stat_ids are different. Something wrong with my
uniqueness validation?

validates_uniqueness_of :aspect, :scope => :user_id

Thanks.

I haven’t been able to reproduce the issue that you have been having
(on 2.3.2). I created a test suite for your model. Can you run that to
see if you get the same problem.

Requires mocha, update references to fixtures and fields as required.
It’s based on the migration and model you provided.

HTH,
Nicholas

On Jul 5, 11:59 pm, Learn by Doing [email protected]

Nicholas,

Thank you very much for working on this.

I ran the test suit but got errors. I modified the test suite:
StatTest.rb · GitHub , using the users.yml:
https://gist.github.com/1c282b5b006326cca497

Note that I changed the attribute name “aspect” to “score_aspect”

Below are the errors. Thank you for your help.

ruby test/unit/stat_test2.rb
Loaded suite test/unit/stat_test2
Started
E…EEEE
Finished in 1.227252 seconds.

  1. Error:
    test_should_add_error_on_score_aspect_when_validating_with_the_same_score_aspect
    (StatTest):
    ActiveRecord::RecordInvalid: Validation failed: Score aspect has
    already been taken
    test/unit/stat_test2.rb:33:in
    `test_should_add_error_on_score_aspect_when_validating_with_the_same_score_aspect’

  2. Error:
    test_should_not_add_error_on_score_aspect_when_validating_an_updated_stat
    (StatTest):
    ActiveRecord::RecordInvalid: Validation failed: Score aspect has
    already been taken
    test/unit/stat_test2.rb:41:in
    `test_should_not_add_error_on_score_aspect_when_validating_an_updated_stat’

  3. Error:
    test_should_not_add_error_on_score_aspect_when_validating_with_a_different_score_aspect
    (StatTest):
    ActiveRecord::RecordInvalid: Validation failed: Score aspect has
    already been taken
    test/unit/stat_test2.rb:25:in
    `test_should_not_add_error_on_score_aspect_when_validating_with_a_different_score_aspect’

  4. Error:
    test_should_score_aspect_be_read_only(StatTest):
    ActiveRecord::RecordInvalid: Validation failed: Score aspect has
    already been taken
    test/unit/stat_test2.rb:49:in
    `test_should_score_aspect_be_read_only’

  5. Error:
    test_should_user_be_read_only(StatTest):
    ActiveRecord::RecordInvalid: Validation failed: Score aspect has
    already been taken
    test/unit/stat_test2.rb:57:in `test_should_user_be_read_only’

8 tests, 3 assertions, 0 failures, 5 errors

Well you have me stumped, I ran your test suite against mine (after
modifying the data structure) and it works. I’m assuming there is
something in the mix that I’m not aware of. I’m not sure if it’s much
help, but you can download my test app to see if that runs for you.

http://files.me.com/nicholas.henry/0v2drf

Cheers,
Nicholas

Colin:

Below are the relevant code. Thanks for your help.

class CreateStats < ActiveRecord::Migration

def self.up
create_table :stats do |t|

  t.integer   :user_id,     :null => false
  t.string    :aspect,      :null => false, :default => "default 

aspect"

  t.timestamps
end

end

def self.down
drop_table :stats
end
end

class Stat < ActiveRecord::Base

belongs_to :user
validates_presence_of :user_id
validates_associated :user

validates_uniqueness_of :aspect, :scope => :user_id

attr_readonly :user_id, :aspect

end