44
votes

I want to use find_or_create_by, but this statement does NOT work. It does not "find" or "create" with the other attributes.

productproperty = ProductProperty.find_or_create_by_product_id(:product_id => product.id, :property_id => property.id, :value => d[descname])

There seems to be very little, or no, information on the use of dynamic finders in Rails 3. "and"-ing these together gives me a an unknown method error.

UPDATE:

Originally I couldn't get the following to work. Please assume I'm not an idiot and "product" is an instance of Product AR model.

product.product_properties.find_or_create_by_property_id_and_value(:property_id => 1, :value => "X")

The error methods was:

no such keys: property_id, value

I couldn't figure that out. Only this morning did I find the reference to passing the values like this instead:

product.product_properties.find_or_create_by_property_id_and_value(1, "X")

And voilá, it works fine. I would have expected a hash to work in the same situation but I guess not.

So I guess you get a down vote if you miss something on the internet?

5
By "anding" them you mean something like described here: Rails find_or_create by more than one attribute?? - Matt
I tried this and it works fine. What exactly not not work? Do you get an error? What is the value of productproperty? The documentation for this is here: api.rubyonrails.org/classes/ActiveRecord/Base.html (under Dynamic attribute-based finders) - Mischa

5 Answers

52
votes

If you want to search by multiple attributes, you can use "and" to append them. For example:

productproperty = ProductProperty.find_or_create_by_product_id_and_property_id_and_value(:product_id => product.id, :property_id => property.id, :value => d[descname])

There is one minor catch to be aware of. It will always return the object you've specified, even if that object can't be saved due to validation errors. So make sure you check to see if the returned object has an id (or is_valid?). Don't assume its in the database.

Alternatively, you can use the 'bang' version of the method to raise an error if the object cannot be saved:

http://guides.rubyonrails.org/active_record_querying.html#find-or-create-by-bang

This applies to Rails 3.

23
votes

See http://api.rubyonrails.org/classes/ActiveRecord/Base.html:

With single query parameter:

productproperty = ProductProperty.find_or_create_by_product_id(product.id) { |u| u.property_id => property_id, u.value => d[descname] } )

or extended with multiple parameters:

productproperty = ProductProperty.find_or_create_by_product_id(:product_id => product.id, :property_id => property_id, :value => d[descname]) { |u| u.property_id => property_id, u.value => d[descname] } )

Would work with:

conditions = { :product_id => product.id, 
               :property_id => property.id,
               :value => d[descname] }

pp = ProductProperty.find(:first, :conditions => conditions) || ProductProperty.create(conditions) 
11
votes

You could also use where(...).first_or_create - ActiveRecord::Relation#first_or_create.

product_property_attrs  = { product_id: product.id, 
                            property_id: property.id, 
                            value: d[descname] }

product_property = ProductProperty.where(product_property_attrs).first_or_create
9
votes

I've found in Rails 3.1 you do not need to pass the attributes in as a hash. You just pass the values themselves.

ProductProperty.find_or_create_by_product_id_and_property_id_and_value(
  product.id, property.id, d[descname])
9
votes

In Rails 4, you can use find_or_create_by(attr1: 1, attr2: 2) to find or create by multiple attributes.

You can also do something like:

User.create_with(
  password: 'secret', 
  password_confirmation: 'secret',
  confirmation_date: DateTime.now
).find_or_create_by(
  email: '[email protected]',
  admin: true
)

If you need to create the user with some attributes, but cannot search by those attributes.