Rails relation overload - sql

Let's say I have the following models:
Class Wishlist
belongs_to :user # User class is irrelevant here
has_many :inclusions
has_many :products, :through => :inclusions
end
Class Product
has_many :inclusions
has_many :wishlists, :through => :inclusions
end
Class Inclusion
belongs_to :product
belongs_to :wishlist
end
inclusions/index.html.erb
<%= render #inclusions %>
inclusions/_inclusion.html.erb
<%= "#{ inclusion.quantity } #{ inclusion.product.name } #{ inclusion.wishlist.user.name }"%>
This example is trivial, but the point is: the amount of database queries is overwhelming. For every instance of _inclusion.html.erb, at least three new queries seem to be created.
Is there a way to prefetch this information beforehand, maybe through a JOIN command?

Maybe this can help:
#inclusions = Inclusions.includes(:product, { :wishlist => :user })
Read about eager loading.

have you thinking about creating sql view, called for example ProductsWishlist and (if the performance will be bad) materializing it?
http://ss64.com/ora/syntax-views.html

Related

Nested json jbuilder for photo feed having n+1 major issues

I am having performance issues on one of my api views so I ran the Bullet gem and found some major N+1 issues with the view.
The api is being consumed so the format has to remain identical.
Bullet N+1 output:
localhost:3000/api/v1/games/1/game_feed N+1 Query detected
CompletedQuest => [:comments] Add to your finder: :include =>
[:comments] N+1 Query method call stack
/app/views/api/v1/games/game_feed.json.jbuilder:3:in block in
_b3b681b668d1c2a5691a5b3f7c15bb8e' /app/views/api/v1/games/game_feed.json.jbuilder:1:in
_b3b681b668d1c2a5691a5b3f7c15bb8e'
But I don't know how to accomplish the fix. Here are the relevant parts.
View:
json.game_feed(#game_photos) do |f|
json.extract! f, :id, :user_id, :username, :image_url_original, :comments_count, :likes_count
json.comments f.comments do |comment|
json.(comment, :username, :comment)
end
json.likes f.likes do |like|
json.(like, :id, :user_id, :username)
end
end
Controller:
#game_photos = CompletedQuest.game_photos(#game.id)
Model:
def self.game_photos(game_id)
where("completed_quests.game_id = ?", game_id).order("completed_quests.id DESC").just_photos
end
scope :just_photos, -> { where.not( image_file_name: nil ) }
Model relationships:
# CompletedQuests:
belongs_to :user
has_many :comments, dependent: :destroy
has_many :likes, dependent: :destroy
# Comments:
belongs_to :completed_quest, counter_cache: true
belongs_to :user
So basically for each photo in the feed, it then grabs every comment & likes for ever record - obviously this is bad and I see why, but I can't figure out how to fix it with my current structure.
Any help would be appreciated - but one thing is the structure of the JSON must remain identical.
You could include the associated comments in the query as follows:
# app/models/completed_quest.rb
def self.game_photos(game_id)
includes(:comments).where("completed_quests.game_id = ?", game_id).order("completed_quests.id DESC").just_photos
end
This will include all the associated comments in the result, so when you do f.comments in your view, there won't be a database query for comments of each f instance.

rails spree has_many through association

'm trying to tweek Spree to my needs and I've hit a bump. I'll try to explain as best as I can without pasting too much code, 'cause Spree has quite a lot of it...
For starters. I've extended OptionType like this
Spree::OptionType.class_eval do
PRODUCT_TYPES = SuperMap.new( [:default, 0],
[:vehicle_bonnet, 1],
[:vehicle_images, 2])
super_mapped_attr :product_type, PRODUCT_TYPES
attr_accessible :product_type
end
I want to check in the show view of the ProductsController what sort of product_type I'm dealing with, like this:
<% if #product.option_types.product_type_key == :vehicle_bonnet %>
...
<% end %>
and I get undefined method ``product_type_key'. This method should be available thanks to SuperMap.
Now, the Product and OptionType has a bit strange association (for me at least).
module Spree
class Product < ActiveRecord::Base
has_many :product_option_types, :dependent => :destroy
has_many :option_types, :through => :product_option_types
end
end
so far seems to be ok.
module Spree
class ProductOptionType < ActiveRecord::Base
belongs_to :product
belongs_to :option_type
acts_as_list :scope => :product
end
end
and now comes the weird part:
module Spree
class OptionType < ActiveRecord::Base
has_many :option_values, :order => :position, :dependent => :destroy
has_many :product_option_types, :dependent => :destroy
has_and_belongs_to_many :prototypes, :join_table => 'spree_option_types_prototypes'
attr_accessible :name, :presentation, :option_values_attributes
validates :name, :presentation, :presence => true
default_scope :order => "#{self.table_name}.position"
accepts_nested_attributes_for :option_values, :reject_if => lambda { |ov| ov[:name].blank? || ov[:presentation].blank? }, :allow_destroy => true
end
end
this is actually the whole OptionType model. What suprises me it doesn't have has_many :products, through: :product_option_types. I've tried to add this line to my extension, but didn't help. I've tried few other thing of course, but it just made such a mess I went back to this version, since I think it shows best what I'm trying to achieve. What am I missing?
EDIT (solved, I should hope so...):
Turned out that in the Product model there was an alias alias :options :product_option_types, which was (imho) in the wrong place, because somewhere in the middle of a quite a long model (I should think it should have been somewhere closer to the has_many, but that's just me).
Further more, the options turned out to be an array, no that shocking come to think of it. So, what I did is extended the product model, added a method, where I'm checking the product_type_key, like so:
def type_of_product?(type)
options.each do |opts|
if opts.option_type.product_type_key == type
return true
end
end
false
end
Not the prettiest method, but it works as I expect it to...

Rails, get object association when using build (object still not in database), the DRY

Im building a report system which uses a sort of meta question model. Questions are previusly saved in the database, and then depending of the type of report some questions are taken from the database.
Wanting to keep things DRY, i'm trying to figure out a way to pass the information of the Variable model to my report_header with no avail.
In the new action i have:
reportBody = #report_head.report_bodies.build(:variable_id => a.id)
#report_head.variables #modified, thx.
all i need is to pass the attributes from the Variable to report_head in a DRY way.
If you need to know my models:
class Variable < ActiveRecord::Base
attr_accessible :id,:break_point, :description, :name, :time_frequency, :v_type
has_many :report_bodies
has_many :report_heads, :through => :report_bodies
end
class ReportHead < ActiveRecord::Base
attr_accessible :email, :name , :report_bodies_attributes, :report_bodies, :variables_attributes
has_many :report_bodies
has_many :variables, :through => :report_bodies
accepts_nested_attributes_for :report_bodies
end
class ReportBody < ActiveRecord::Base
attr_accessible :report_head_id, :variable_value, :variable_id, :variables_attributes, :report_heads
belongs_to :report_head
belongs_to :variable
end
Update
I updated the model as suggested, and modified the way to call the variables. However im still confused about how to use it in the view, if i do something like:
<%= f.fields_for :variables do |variable| %>
<%= variable.text_field :name, :value => :name, :class => 'text_field' %>
<% end %>
it prints a string instead of the actual name.
You have define wrong name association, your association of ReportBody should be:
belongs_to :report_head
belongs_to :variable
This is not correct:
#report_head.report_bodies.build(:variable_id => a.id,:report_head_id =>#report_head.id)
chang it to:
#report_head.variables.build(:variable_id => a.id)
it's better, you don't have to set report_head_id. And this is wrong:
#report_head.report_bodies.variables
If you want to get all variables belong to #report_head, you just need using:
#report_head.variables

How to call a scope across a has_many / through relationship

I have the following (on RoR 3.1 and MySQL 5.1):
class Menu < ActiveRecord::Base
has_many :menu_headers
has_many :menu_items, :through => :menu_headers
belongs_to :location
end
class MenuHeader < ActiveRecord::Base
acts_as_tree :parent_id
has_many :menu_items
belongs_to :menu
end
class MenuItem < ActiveRecord::Base
scope :is_enabled, where(:is_enabled => true)
belongs_to :menu_header
end
I'd like to be able to call the scope across the relationship; something like this:
# call the scope :is_enabled here
Menu.find(12).(where menu_items.is_enabled)
but not sure how to do this.
I'd like the behavior for:
Menu.find(12)
to continue to pull menu_items where is_enabled=false
Any ideas on how to do this?
thx
edit #1
added the act_as_tree and location associations as these also need to be working.
Something like this Scope with join on :has_many :through association might work but seems a little bit ugly
This should do the trick:
Menu.find(12).menu_items.is_enabled
It will return all enabled menuitem associated with the menu with id 12.

How do i create an object if it has more than one belongs_to?

I have the following:
class Org < ActiveRecord::Base
has_many :users
has_many :entries
end
class Entry < ActiveRecord::Base
belongs_to :org
belongs_to :user
validates_presence_of :entry_text
end
class User < ActiveRecord::Base
belongs_to :org
has_many :entries
validates_uniqueness_of :user_name
validates_presence_of :user_name, :length => { :minimum => 3 }
end
I can Create Orgs and Users... How do i create an entry if there are two belongs_to? and what is this pattern called?
Double nested resources are tricky. The trick with users usually is to keep it out of your desired entry path.
Your question is kind of broad, but if you specify more information, people would be able to help you better. Also, I would recommend using the gem Devise for your user management system. Since you're using 'users' I would assume you want users from orgs to create entries. The entry created would be a part of org and the user would be the session's current user. Sorry if I am wrong to assume this.
Your routes.rb file can look something like this (assuming rails 3):
resources :orgs do
resources :entries
end
Then the create of your entry controller would look like:
#entry = #org.entries.new(params[:topic])
#entry.user = current_user #or however you are managing the current user's session.
And you'd want to set the org for the entire class by making a method that loads your current org and do a before_filter :loadOrg
def loadOrg
#org = Org.find(params[:id])
end
This is of course assuming your path is something like: /org/(id)/entry/(entry_id)
and not
/org/(id)/user/(user_id)/entry/(entry_id)
which in my opinion is unnecessary and can lead to more problems. You can always create a userpage model that calls all entries by users, but the default route doesn't necessarily have to include users in the path.
I don't see any problem.
#entry = Entry.create(:entry_text => "Hello World!")
Now questions to clarify what do you need:
Can #entry belongs both org and user at the same time? Or it can belongs to only one of them?
Should #entry belongs to at least one of them?
If #entry supposed to belong only one of them, so you should use Polymorphism
http://railscasts.com/episodes/154-polymorphic-association
class Entry < ActiveRecord::Base
belongs_to :textable, :polymorphic => true
validates_presence_of :entry_text
end
class Org < ActiveRecord::Base
has_many :users
has_many :entries, :as => :textable
end
class User < ActiveRecord::Base
belongs_to :org
has_many :entries, :as => :textable
validates_uniqueness_of :user_name
validates_presence_of :user_name, :length => { :minimum => 3 }
end