what happens when you call includes on a relation/collection in Rails? - ruby-on-rails

I have a situation where I call includes on a collection and it's exhibiting weird behavior.
In a test, I have this scenario where
=> apples
[]
=> apples.rotten
[
[0] #<Apples:0x007fdff8b49b70> {
:id => 1,
:user_id => 1,
}
]
and rotten is a scope on apple that uses includes and joins some tables together. The test that I'm running does indeed create an apple associated with the model with the calling methods correctly... but apples is still returning => []
=> self.apples
[]
=> self
{
:id => 1
...
}

The answer is to #reload apples.rotten. Oops.

Related

Add Discount to Braintree Rails Subscription

I'm trying to add a discount object to a subscription with the braintree-rails gem, but it is not applied. I'm guessing my code must be wrong, but I can't find a working example.
discount = BraintreeRails::Discount.find(params[:subscription_promo])
subscription = #plan.subscriptions.build permitted_params[:subscription]
subscription.discounts << discount
# ...
subscription.save
When I dump discount, it is loaded properly. The subscription is created just fine, but at full price. The discount is not there. How can I add the discount to the subscription?
Update: I tried modifying the direct query, but that has not helped.
#subscription.raw_object.discounts = {add:[{inherited_from_id: discount.id}]}
Update 2: I also ran a direct Braintree request against the API with the request expected of the above code, and it worked. There's something wrong happening between setting it and saving.
Update 3: A workaround is possible by extracting the attributes of the BraintreeRails::Subscription object, using Braintree::Subscription to call the API, and using BraintreeRails::Subscription.find to load it back into the object. This is definitely not optimal, though, since it's not very clean, and requires an extra API call.
gem author here.
Unfortunately neither BraintreeRails nor the Braintree ruby gem supports the subscription.discounts << discount style of adding discounts to subscriptions at the moment.
As you can see in braintree ruby doc, the adding/updating/overriding addon/discounts API is a little too flexible to be wrapped in a single subscription.discounts << discount line.
If your setup of addon/discounts for subscription is simple and doesn't vary much, you can try create one plan for each desired combination, and then use the right plan to create the subscription.
If your setup is quite dynamic(in terms of price, billing cycle, quantity etc), use the Braintree API directly is probably your best option. E.g.:
result = Braintree::Subscription.create(
:payment_method_token => "the_payment_method_token",
:plan_id => "the_plan_id",
:add_ons => {
:add => [
{
:inherited_from_id => "add_on_id_1",
:amount => BigDecimal.new("20.00")
}
],
:update => [
{
:existing_id => "add_on_id_2",
:quantity => 2
}
],
:remove => ["add_on_id_3"]
},
:discounts => {
:add => [
{
:inherited_from_id => "discount_id_1",
:amount => BigDecimal.new("15.00")
}
],
:update => [
{
:existing_id => "discount_id_2",
:quantity => 3
}
],
:remove => ["discount_id_3"]
}
)

ActiveRecord Include, how to use in nested records?

I currently have the following:
#threads = current_user.threads.includes(:user, :thread_members)
I then take threads and do the following:
#threads.each do |thread|
thread_members = thread.thread_members_active(current_user)
#threadList << {
:id => thread.id,
:uuid => thread.uuid,
:user_id => thread.user.id,
:last_activity_at => thread.last_activity_at,
:user_count => thread_members.length,
:user_photos => thread_members.collect { |thread_member|
{
:id => thread_member.user.id,
:photo => thread_member.user.photo(:thumb),
:name => thread_member.user.full_name
}
},
:caption => thread.caption
}
end
The issue here is that every EACH loop, rails is hitting the DB for the same basic records. Rails sees to be caching as I see CACHE in the log but it's mighty messy. Leaves me wishing I could do some type of includes so there wasn't so many db requests.
Any ideas on how this can be optimized? Something around including all the users in one db hit?
Thanks
If you don't want any DB queries in the loop, you have to define everything that's used there in the named associations that are included, so instead of a thread_members_active method you'd define a thread_members_active association which has the same behavior. Note that the association also needs to use includes on user. Can't give you more right now, but maybe that helps a bit.
Edit: Check out the "Eager loading of associations" part of this doc:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

Need to return all users with genre 'Acoustic'

Is there a better way to write this:
User.where(:genre_id => Genre.where(:name => 'Acoustic').first.id).first.first_name
Basically, I am trying to return all users with genre 'Acoustic'.
Above will work if there are users with the Acoustic setting. But if i do:
User.where(:genre_id => Genre.where(:name => 'Pop').first.id).first.first_name
I will get an error, since there are no users associated with the pop
genre...
Any suggestions to get this to work?
In a general way, many-to-many relationships really suck in mongo (the price you pay for has_one/has_many being so awesome)
I am assuming the problem is that Genre.where(:name => 'Pop').first returns nil? I would do this
User.where(:genre_id => g).first.first_name if g = Genre.where(:name => 'Pop').first.try(:id)
or if massive one line expressions aren't your thing
g = Genre.where(:name => 'Pop').first.try(:id)
if g
User.where(:genre_id => g).first.first_name
end
Doesn't the usual nested where work?
User.where(:genre => { :name => 'Pop' })
You could try with find instead of where for Genre:
User.where(:genre_id => Genre.find(:first, :conditions => { :name => 'Pop' }))
This should work even when Genre.find returns nil.

How can I pass multiple attributes to find_or_create_by in Rails 3?

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?
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.
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)
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.
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
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])

How can I add multiple should_receive expectations on an object using RSpec?

In my Rails controller, I'm creating multiple instances of the same model class. I want to add some RSpec expectations so I can test that it is creating the correct number with the correct parameters. So, here's what I have in my spec:
Bandmate.should_receive(:create).with(:band_id => #band.id, :user_id => #user.id, :position_id => 1, :is_leader => true)
Bandmate.should_receive(:create).with(:band_id => #band.id, :user_id => "2222", :position_id => 2)
Bandmate.should_receive(:create).with(:band_id => #band.id, :user_id => "3333", :position_id => 3)
Bandmate.should_receive(:create).with(:band_id => #band.id, :user_id => "4444", :position_id => 4)
This is causing problems because it seems that the Bandmate class can only have 1 "should_receive" expectation set on it. So, when I run the example, I get the following error:
Spec::Mocks::MockExpectationError in 'BandsController should create all the bandmates when created'
Mock 'Class' expected :create with ({:band_id=>1014, :user_id=>999, :position_id=>1, :is_leader=>true}) but received it with ({:band_id=>1014, :user_id=>"2222", :position_id=>"2"})
Those are the correct parameters for the second call to create, but RSpec is testing against the wrong parameters.
Does anyone know how I can set up my should_receive expectations to allow multiple different calls?
Multiple expectations are not a problem at all. What you're running into are ordering problems, given your specific args on unordered expectations. Check this page for details on ordering expectations.
The short story is that you should add .ordered to the end of each of your expectations.
Mock Receive Counts
my_mock.should_receive(:sym).once
my_mock.should_receive(:sym).twice
my_mock.should_receive(:sym).exactly(n).times
my_mock.should_receive(:sym).at_least(:once)
my_mock.should_receive(:sym).at_least(:twice)
my_mock.should_receive(:sym).at_least(n).times
my_mock.should_receive(:sym).at_most(:once)
my_mock.should_receive(:sym).at_most(:twice)
my_mock.should_receive(:sym).at_most(n).times
my_mock.should_receive(:sym).any_number_of_times
Works for rspec 2.5 too.

Resources