views:

20

answers:

2

Is there a method to save an object but return the object if it fails the validation_uniqueness_of for a given field? For example, I have the following:

class User
  has_many :words
...
end

class Word
  belongs_to :user
  validates_uniqueness_of :title
...
end  

And I have a situation where I want to either return the Word object if the user has already saved this word, or return the newly saved word object. I am finding myself checking if the word object exists through my own search and then performing a save if not:

existing_word = find_by_word_and_user_id(word, user)
!existing_word.nil? ? existing_word : user.words.create({:word => word})

But I feel like if the user does not have the word saved, rails is going to perform a redundant search for the title uniqueness validation after my check and I was wondering if I could just do the save and return the already existing object that rails finds for the validation.

+1  A: 

You can DRY up that statement a little by just saying:

find_by_word_and_user(word, user) || user.words.create(:word => word)

you could eliminate a db hit by reversing these, where it tries to create before trying to find. But that wouldn't be a good idea, because there are other reasons the create might fail, so you can't be guaranteed it's the uniqueness issue.

You're not going to see a noticeable performance hit by just doing it the first way, I'd stick with that. Your only other option is to remove the validation itself, and use the statement above. But then you have to make sure that everywhere you create words, you're checking for uniqueness.

Jaime Bellmyer
+1  A: 

You might be interested in #find_or_create_by_word_and_user, another of ActiveRecord's magic methods:

obj = Word.find_or_create_by_word_and_user(word, user)

# Preferred way
obj = user.words.find_or_create_by_word(word)

This will do 2 database requests anyway: there's no way around that. Also, if your Word instance has more validation that you've shown us, the create will fail, because #find_or_create_by does not allow passing more attributes.

For all the details on the dynamic finder methods, search for "Dynamic attribute-based finders" on http://api.rubyonrails.org/classes/ActiveRecord/Base.html.

François Beausoleil