Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

nested_attributes: validates_uniqueness_of fails

#2160

class Product < ActiveRecord::Base
  has_many :categorizations
  accepts_nested_attributes_for :categorizations
end

class Categorization < ActiveRecord::Base
  belongs_to :product
  belongs_to :category
  validates_uniqueness_of :product_id, :scope => [:category_id]
end

If I create/edit a product and add two new (new record) "categorizations" with the same category_id, it will validate and save.

In 2.3-RC1 double new records were ignored and not saved.

Reported by mo · March 7th, 2009 @ 12:36 AM

State: invalid
Milestone: 2.x
Assigned to: Eloy Duran Eloy Duran
Importance: none

Activity

  1. mo
    mo

    hmm.. this only happens with "@product.categorizations.build" in the products_controller.

    I noticed something else. If I create/edit a product, choose a category and, lets say, type in an invalid product title, i get an "missing product_id" error for the chosen category.

    All these bugs were not in 2.3-RC1.. that's really annoying. :/

    March 7th, 2009 @ 01:05 AM

  2. Eloy Duran
    Eloy Duran
    • Assigned user set to Eloy Duran
    • State changed from new to open

    Could you please try to distill a failing test for the exact problem you are having? That would be greatly appreciated.

    Thanks

    March 7th, 2009 @ 11:51 AM

  3. Eloy Duran
  4. Eloy Duran
    Eloy Duran
    • State changed from open to incomplete

    March 13th, 2009 @ 08:09 AM

  5. Ed Lebert
    Ed Lebert

    This is really a problem with a core rails philosophy. It assumes that, no matter what, if "valid?" returns true, then it will save successfully. That is a false presumption.

    I think rails needs to completely rethink validation and incorporate database constraint detection. And it should go on the assumption that it might return true for "valid?", but not for "save". This is obviously beyond the scope of this ticket, but this is a pretty big problem.

    May 22nd, 2009 @ 02:46 PM

  6. oleg dashevskii
    oleg dashevskii
    • Tag changed from 2.3-rc2 to 2.3.x

    I've run into same problem. Test is attached.

    July 6th, 2009 @ 08:29 AM

  7. bterkuile
    bterkuile

    I have the same problems. Shortly said: validations are not taken into account when accept_nested_attributes_for is used. I will give my problem, since I use a dirty trick on the column. I think it is not the cause, but good to add anyway: My 'short' situation:

    class Employee < ActiveRecord::Base
    require 'digest/sha1' attr_protected :id, :salt attr_accessor :password, :password_confirmation

    belongs_to :address accepts_nested_attributes_for :address # Always have an address as employee alias :old_address :address def address

    {mkd-extraction-e5528c92a92dba8e9a7c761cf9e2a0a4}

    end

    def validate_on_create

    {mkd-extraction-97fed57be83556b54a4f5d7e75b7a4a0}

    end ... end

    When a hash containing :address_attributes => {.....} is given to Employee.new, and no password or password_confirmation, it will save => true. Without the :address_attri butes the validations work.

    July 29th, 2009 @ 04:24 PM

  8. Andrew France
    Andrew France
    • Tag changed from 2.3.x to nested attributes, 2.3.x, accepts_nested_attributes_for, validates_uniqueness_of

    No one seems to have said this explicitly but I believe the problem is simply that validates_uniqueness_of cannot work with nested attributes because the validation only checks against saved records. I am currently handling this problem by doing an in-memory uniqueness check during the validation phase on the parent object.

    October 8th, 2009 @ 05:22 AM

  9. Eloy Duran
    Eloy Duran

    @Andrew Thanks for your comment. I'm still thinking about if and how we could fix this as simple as possible, but it might also be handy for people if you could document your workaround in either the NestedAttributes or validates_uniqueness_of docs.

    Care to write such a doc patch and do you think it's a valid approach?

    October 8th, 2009 @ 07:17 PM

  10. Andrew France
    Andrew France

    Hi Eloy, I think my workaround is a bit of a kludge:

    class Author
      has_many :books
    
      # Could easily be made a validation-style class method of course
      validate :validate_unique_books
    
      def validate_unique_books
        validate_uniqueness_of_in_memory(
          books, [:title, :isbn], 'Duplicate book.')
      end
    end
    
    module ActiveRecord
      class Base
        # Validate that the the objects in +collection+ are unique
        # when compared against all their non-blank +attrs+. If not
        # add +message+ to the base errors.
        def validate_uniqueness_of_in_memory(collection, attrs, message)
          hashes = collection.inject({}) do |hash, record|
            key = attrs.map {|a| record.send(a).to_s }.join
            if key.blank? || record.marked_for_destruction?
              key = record.object_id
            end
            hash[key] = record unless hash[key]
            hash
          end
          if collection.length > hashes.length
            self.errors.add_to_base(message)
          end
        end
      end
    end
    

    If this is useful to people I'd be happy to tidy it up and document it somewhere, not really worth making a plugin out of it, I just put it in my initializers. It moves the validation responsibility to the parent object, which is probably bad too, but I don't think there's any way for a new instance to find out about its other unsaved instances.

    Best of luck finding an elegant solution though, its got me stumped!

    October 9th, 2009 @ 01:05 AM

  11. Andrew France
    Andrew France

    OMG Lighthouse is so shit, sorry about the mess.

    October 9th, 2009 @ 01:11 AM

  12. Pratik
    Pratik

    I've fixed the formatting there.

    October 9th, 2009 @ 01:38 AM

  13. Eloy Duran
    Eloy Duran
    • State changed from incomplete to invalid

    Going to close this ticket now. Though, if people keep running into this, I'd love to see Andrew's solution integrated in ActiveRecord.

    January 8th, 2010 @ 02:45 PM

  14. Hervé Tatche
    Hervé Tatche

    Same problem on rails (2.3.2), validates_uniqueness_of is bypassed.

    January 8th, 2010 @ 04:14 PM

  15. Tamer Salama
    Tamer Salama

    Yes! People are still running into problem. Why would the status be "invalid" if a validation is not working as it is supposed to be?

    March 3rd, 2010 @ 06:45 AM

  16. Eloy Duran
    Eloy Duran

    Why would the status be "invalid" if a validation is not working as it is supposed to be?

    How about reading the comments? (that's rhetorical btw.)

    It works as it currently should:

    “the problem is simply that validates_uniqueness_of cannot work with nested attributes because the validation only checks against saved records”

    If you have a good solution/patch to your own problem, please share it with us. But your message as is, doesn't add much, sorry.

    March 3rd, 2010 @ 10:17 AM

  17. Piotr Mąsior
    Piotr Mąsior

    I run into similar BUT not SAME problem. Fully explained here: http://railsforum.com/viewtopic.php?id=38343 , I attached there quick fix, but I think it is not elegant. Basing on Eloy summary I assume that is not already any "built in" solution, to use validates_uniqueness_of at model which is used like "dictionary" in context of accepts_nested_attributes_for? I think that filter could somehow discover from what context is it fired up and then check given data against DB and decide is @new_record or not.

    April 13th, 2010 @ 08:35 AM

  18. Piotr Mąsior
  19. Roel van der Hoorn
    Roel van der Hoorn

    Ran into exactly the same problem. Had a uniqueness constraint on a date of a ProductRate, scoped for a Product, but it wasn't validated. Andrew's patch works like a charm.

    May 7th, 2010 @ 12:40 PM

  20. Ryan Bigg
    Ryan Bigg
    • Tag cleared.

    Automatic cleanup of spam.

    October 9th, 2010 @ 09:57 PM