45

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?

RubyRedGrapefruit
  • 12,066
  • 16
  • 92
  • 193
  • 3
    By "anding" them you mean something like described here: [Rails find_or_create by more than one attribute?](http://stackoverflow.com/questions/3046607/rails-find-or-create-by-more-than-one-attribute)? – Matt Oct 22 '10 at 13:15
  • 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: http://api.rubyonrails.org/classes/ActiveRecord/Base.html (under Dynamic attribute-based finders) – Mischa Oct 22 '10 at 13:19

5 Answers5

52

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.

Community
  • 1
  • 1
Lelon
  • 905
  • 1
  • 8
  • 15
  • 2
    Yes this changed in Rails 4. Now you only need `find_or_create_by` and any parameters you'd like. See http://stackoverflow.com/questions/22520274/rails-4-find-or-create-by-method-doesnt-work – James Klein Mar 04 '16 at 00:12
24

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) 
BvuRVKyUVlViVIc7
  • 11,641
  • 9
  • 59
  • 111
  • But this will search only by `product_id` and when found/created will set the `property_id` and `value`. I guess @AKWF wants to search by all 3 values. – Matt Oct 22 '10 at 13:34
  • This is only necessary if `property_id` and `value` are protected attributes. If not his statement above should work like expected. From the API: *Protected attributes won’t be set unless they are given in a block.* – Mischa Oct 22 '10 at 13:50
  • Your last update is nonsense... read the API again carefully ;-) – Mischa Oct 22 '10 at 14:04
  • But now `pp` will be `nil` if the records *does* exist. Don't think it can be made into a one-liner... – Mischa Oct 22 '10 at 14:17
  • If you use `where(conditions).first` instead of `find(conditions)`, and `||` instead of `||=` it's perfect... `find(conditions)` doesn't work in Rails3. Almost there ;-) – Mischa Oct 22 '10 at 14:36
  • +1 Good call on using `||`. Although apparently you can also do: `pp = ProductProperty.find_or_create_by_product_id_and_property_id_and_value(product.id, property.id, d[descname])` – Mischa Oct 22 '10 at 14:54
14

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: 'admin@domain.com',
  admin: true
)

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

fbelanger
  • 3,522
  • 1
  • 17
  • 32
12

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
Pablo Cantero
  • 6,239
  • 4
  • 33
  • 44
9

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])
Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
David
  • 1,330
  • 17
  • 31