Rails: Attempting to speed up slow polymorphic query - ruby-on-rails

I have a polymorphic association that I am attempting to load but it's taking several seconds and I'm not sure if there's a way to speed it up. I've attempted to eager load the association and filter the results but it's slower than avoiding the eager load altogether.
Here are the models:
class LocationOrderable < ApplicationRecord
belongs_to :location
belongs_to :product, -> { where( location_orderables: { orderable_type: 'Product' } ).includes(:order_items) }, foreign_key: 'orderable_id', optional: true
belongs_to :orderable, polymorphic: true
enum orderable_status: [:out_of_stock, :in_stock]
end
class Product < ApplicationRecord
scope :active, -> { where(active: true) }
has_many :location_orderables, as: :orderable
end
Here's the original query (w/o eager load):
#location_orderables = LocationOrderable.where(location_id: #location.id)
Here's the slower query (w/ eager load):
#location_orderables = LocationOrderable.includes(:product).where(product: {active: true}, location_id: #location.id)
I loop through these location_orderables and display the product.name and I wanted to avoid an N+1 issue so I thought the second query would be faster (there are only 30-40 products) but it is not the case.
How might I speed up that query?

Related

Comparing records to find improvements

I have 2 Rails models Client and HealthProfile:
class Client < ApplicationRecord
has_many :health_profiles
end
class HealthProfile < ApplicationRecord
belongs_to :client, optional: true
scope :start, -> { where(status: 'start' ) }
scope :end, -> { where(status: 'end' ) }
end
HealthProfile contains information such as :panic_attacks (there are 25 other conditions) and I want to see what percentage of clients show improvement. To show improvement they would have panic_attacks: true in their :start health_profile and panic_attacks: false in their :end health_profile. The issue is that :panic_attacks defaults to false.
Is there a query I can perform to capture all clients that responded true to :panic_attacks in their :start and false to :panic_attacks in their :end? Ideally this solution will work for all 25 conditions.
What you want to do instead is to create a a many to many association and query it:
class HealthProfile < ApplicationRecord
belongs_to :client, optional: true
has_many :panic_attacks
end
class PanicAttack
belongs_to :health_profile
end
This would let you for example count the number of attacks by using grouping:
profile = HealthProfile.find(1)
# this is postgres specific - use the group_date gem if you need something polyglot
per_month = profile.panic_attacks
.group("date_trunc('month', panic_attacks.started_at)")
.count
And you can also use other sorts of aggregate functions like averages and means to get meaningful stats. For example an imrovement could be a reduction in the count or average length.
Boolean columns are a scurge.

Rails 5: How to allow model create through when polymorphic reference also carries distinct association

I have model with polymorhphic reference to two other models. I've also included distinct references per this article eager load polymorphic so I can still do model-specific queries as part of my .where clause. My queries work so I can search for scores doing Score.where(athlete: {foo}), however, when I try to do a .create, I get an error because the distinct reference alias seems to be blinding Rails of my polymorphic reference during validation.
Given that athletes can compete individually and as part of a team:
class Athlete < ApplicationRecord
has_many :scores, as: :scoreable, dependent: :destroy
end
class Team < ApplicationRecord
has_many :scores, as: :scoreable, dependent: :destroy
end
class Score < ApplicationRecord
belongs_to :scoreable, polymorphic: true
belongs_to :athlete, -> { where(scores: {scoreable_type: 'Athlete'}) }, foreign_key: 'scoreable_id'
belongs_to :team, -> { where(scores: {scoreable_type: 'Team'}) }, foreign_key: 'scoreable_id'
def athlete
return unless scoreable_type == "Athlete"
super
end
def team
return unless scoreable_type == "Team"
super
end
end
When I try to do:
Athlete.first.scores.create(score: 5)
...or...
Score.create(score: 5, scoreable_id: Athlete.first.id, scoreable_type: "Athlete")
I get the error:
ActiveRecord::StatementInvalid (SQLite3::SQLException: no such column: scores.scoreable_type
Thanks!
#blazpie, using your scoping suggestion worked for me.
"those scoped belongs_to can be easily substituted by scopes in Score: scope :for_teams, -> { where(scorable_type: 'Team') }

Active Record eagerly load polymorphic association

I have the following models:
class User < ApplicationRecord
has_one :card, as: :cardable
scope :active, -> { where(active: true) }
end
class Organisation < ApplicationRecord
has_one :card, as: :cardable
scope :active, -> { where(active: true) }
end
class Card < ApplicationRecord
belongs_to :cardable, polymorphic: true
end
I want to find all the cards whose associated User or Organisation is active.
I thought the following would work:
Card.includes(:cardable).where(cardable: {active: true})
But this throws an error:
ActiveRecord::EagerLoadPolymorphicError: Cannot eagerly load the polymorphic association :cardable
Is what I'm trying to do even possible with ActiveRecord?
I've looked other questions with a similar title, but I am not sure the scenarios are similar enough to this one.
I think this should work:
cardable_ids = User.active.pluck(:id) + Organisation.active.pluck(:id)
Card.where(cardable_id: cardable_ids)

can I add an includes extension to a belongs_to association?

I'm having trouble getting a belongs_to association to eager load it's children. I have:
class User < ActiveRecord::Base
has_many :campaigns, -> { includes :campaign_shirts, :arts, :selected_campaign_shirt }
belongs_to :selected_campaign, {class_name: "Campaign", inverse_of: :user}, -> { includes :campaign_shirts, :arts, :selected_campaign_shirt }
end
which results in:
// GOOD
u.campaigns.first.campaign_shirts.first.to_s
=> "#<CampaignShirt:0x007fc023a9abb0>"
u.campaigns.first.campaign_shirts.first.to_s
=> "#<CampaignShirt:0x007fc023a9abb0>"
// NOT GOOD
u.selected_campaign.campaign_shirts.first.to_s
(queries db)
=> "#<CampaignShirt:0x007fc023d7c630>"
(queries db)
u.selected_campaign.campaign_shirts.first.to_s
=> "#<CampaignShirt:0x007fc01af528a0>"
Am I running afoul of this issue? Is there a way to achieve what I want, which is to be able to refer to current_user.selected_campaign and have eager-loaded/frozen current_user.selected_campaign.campaign_shirts.first etc.?
Try moving the lambda scope before other association options like follows:
# app/models/users.rb
belongs_to :selected_campaign, -> { includes :campaign_shirts, :arts, :selected_campaign_shirt }, {class_name: "Campaign", inverse_of: :user},

Force where close on every query of a model plus nesting

I first had in mind to use the default_scope to force a condition on every requests but it doesn't seems to work.
For example, if I have Product.include(:prices) it will no use the default scope. I'm not sure if its the correct behavior.
My default_scope for prices is something like that:
default_scope where(type: "normal")
I'm working on a "crm" ported to rails and the only type of price that interest us is the "normal" one. I could edit the code everywhere but it would be much simpler to "enforce" a condition whenever the price needs to be queried from the database. And for that same reason, using named_scope isn't an alternative either for now since it might require a lot of refactor.
Some more information as it "should" work but doesn't...
class Ts::Price < ActiveRecord::Base
acts_as_terrasoft
default_scope where(PriceKindID: "{43D5117A-D52D-4E72-B33B-7D3158524BF1}")
..
other scopes
..
end
The actual call
products = Product.includes(:supplier, :prices, :user)
... some more where unrelated to prices
products = products.find(:all, {
conditions: {
:vendors_Products => {
AccountTypeID: type
}
},
limit: #count,
offset: #offset * #count
})
The Products
class Ts::Product < ActiveRecord::Base
acts_as_terrasoft
self.table_name = "Products"
self.primary_key = "ID"
has_many :prices, class_name: "::Ts::Price", foreign_key: "ProductID", :dependent => :destroy
end
Edit
Rails 3.2.8

Resources