n+1 query in rails, need solution - ruby-on-rails

Below given are the model in my application. I am trying to find all the companies that matches the keyskills. For example. If I type java in my search-box it should get me all the companies that are matching and keyskills.
class User < ActiveRecord::Base
has_one :company
end
class Company < ActiveRecord::Base
has_many :jobs
belongs_to :user
end
class Job < ActiveRecord::Base
belongs_to :company
has_many :key_skills, dependent: :destroy
accepts_nested_attributes_for :key_skills, reject_if: :all_blank, allow_destroy: true
end
class KeySkill < ActiveRecord::Base
belongs_to :job
end
The steps that I am following are,
step1: Find all the keyskills matching the entered word. (ex: java)
#matched_keyskills = KeySkill.where('name like ?','java')
Since I have association between jobs and keyskills, that is jobs has_many key_skills and key_skills belongs_to job. I can iterate over
#matched_keyskill.each do |k|
k.job.company
end
and get the company records. But, when I tried this method it results in n+1 query also the company name is getting repeated.
Is there a way through which I can get only the company name shown on show page then by clicking on company it shows the jobs associate to it.
also kindly let me know is the db model and association are correct inorder to achieve it.

You can use "joins" and "inclue" to remove the n+1 query:
Try this, it will give you the list of all companies as You required.
Company.joins(key_skill: :job).where('key_skill.name like ?','java')
you can also use eager loading.
http://railscasts.com/episodes/22-eager-loading

Related

Matching the records in ruby on rails

I have a model company, which has association, has many candidates, and belongs to company.
And I have another model key_skill, which has association, has many key_skills, and belongs to candidate.
Another model is candidate, which belongs to company, and has many key skills association.
I am trying to get the candidate whose key skills are matched to the required skill and, it should search and get the candidate who belongs to the particular company.
How can I write a query in the model for this situation?
These are the associations
company.rb
class Company < ActiveRecord::Base
has_many :candidates
end
candidate.rb
class Candidate < ActiveRecord::Base
belongs_to :company
has_many :key_skills, dependent: :destroy
accepts_nested_attributes_for :key_skills, reject_if: :all_blank,
allow_destroy: true
end
key_skill.rb
class KeySkill < ActiveRecord::Base
belongs_to :candidate
end
I think your current association condition is like this:
class Company < ApplicationRecord
has_many :candidates
end
class Candidate < ApplicationRecord
belongs_to :company
has_many :key_skills
end
class KeySkill < ApplicationRecord
belongs_to :candidate
end
For example to fetch all candidates with key_skills with ids 1,2,3 run the following query
Candidate.joins(:company, :key_skills).where("key_skills.id in (?)", [1,2,3])
Try the below:
I am assuming that key_skills table have a field skill and you want to perform search on it.
candidate = Candidate.includes(:company, :key_skills).where("key_skills.skill like ?", "%#{params[:skill]}%")
company = candidate.company

Ruby on Rails 4 - How to get a has_many association for a group of users?

I have the following setup in my database. We have users. Each user has many entries. Users also belong to organizations, through a table called organization_users.
class User < ActiveRecord::Base
has_many :entries
has_many :organization_users
has_many :organizations, :through => :organization_users
end
class Entry < ActiveRecord::Base
belongs_to :user
end
class Organization < ActiveRecord::Base
has_many :organization_users
has_many :users, :through => :organization_users
end
class OrganizationUser < ActiveRecord::Base
belongs_to :user
belongs_to :organization
end
Here's my question: for a given organization, I want to get a list of all the entries for the users in that organization. Is there a nice compact way to accomplish this? I know I can iterate over all users in the organization and get the entries, but I'm not sure if there is a nice rails-y way to do this.
You can do the following assuming you have a foreign key called organization_id in organization_users table as per your Model
Entry.joins(:user => :organization_users).where("organization_users.organization_id = ?", org_id)
Where org_id is the id of the given organization. This will give you all entries of all users in an organization
Try something like that :
my_org = Organization.find(id)
my_org.users.eager_load(:entries)
First you get the organization you want to query. Then via :through association you can directly retrieve all the users for that organization. And last using eager_load, in one query you get all the entries. The result will be an ActiveRecord::Relation.

Correct use of has_many_through

I have two models: Orders and Items. This is a many to many relationship, but I'm unsure if HMT is correct
Order model:
-user_id
-transaction_cost
-delivery_time
Item model:
-price
-name
Orders should be able to get all items in the order
Items do not need to be able to get all orders
The convention on this is to use the names of both models. A good name might be ItemOrders. Has many through is almost certainly a correct choice here.
class Order < ActiveRecord::Base
has_many :item_orders, dependent: :destroy
has_many :items, through: :item_orders
end
class Item < ActiveRecord::Base
has_many :item_orders, dependent: :destroy
has_many :orders, through: :item_orders
end
class ItemOrder < ActiveRecord::Base
belongs_to :item
belongs_to :order
end
Now you just have another ActiveRecord model, and you can add to it as you'd like. It will also be helpful for debugging. You can even use a model/scaffold generator to generate these:
rails g model item_order order:references item:references
This way you get the migrations correct right away. Nothing needs to be altered on your other models except for the above code.

Can a has and belongs to many association not call a query on the associated object

I have two models Activities and Users
class Activity < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :activities
end
I can load all the activities which are associated with users by calling
User.all.includes(:activities)
But this actually pulls up all my activity objects from the database, which can cause performance problems as the table is quite large. My requirement is only to get the activity_ids associated
I can directly get that by executing the plain sql on the join table
select activity_id from activities_users where user_id in (...)
My question is:
Can I do get the above functionality in a rails friendly way
From has_and_belongs_to_many you could do things like #User.find(1).activity_ids
In terms of including them for a collection to avoid an n+1 query I don't think you can do it any way other than the way you've currently got it.
If you make it a has_many_through it'll have a model:
class Activity < ActiveRecord::Base
has_many :user_activities
has_many :users, through: :user_activities
end
class User < ActiveRecord::Base
has_many :user_activities
has_many :activities, through: :user_activities
end
class UserActivity < ActiveRecord::Base
belongs_to :user
belongs_to :activity
end
Then you can do #users = User.includes(:user_activities).all so when you loop over them and do something like it should avoid the n+1 query because you've already loaded them in.
#users.each do |user|
user.activity_ids
end

What is the best way to setup my tables and relationships for this use case?

1)A user can have many causes and a cause can belong to many users.
2)A user can have many campaigns and campaigns can belong to many users. Campaigns belong to one cause.
I want to be able to assign causes or campaigns to a given user, individually. So a user can be assigned a specific campaign. OR a user could be assigned a cause and all of the campaigns of that cause should then be associated with a user.
Is that possible? And could I set it up so that the relationships could be simplified like so:
User.causes = all causes that belong to a user
User.campaigns = all campaigns that belong to user whether through a cause association or campaign association
This should work.
class User < ActiveRecord::Base
has_many :causes, :through => :cause_users
has_many :campaigns, :through => :campaign_users
# other model stuff
class Cause < ActiveRecord::Base
has_many :users, :through => :cause_users
has-many :campaigns
# other model stuff
class Campaign < ActiveRecord::Base
belongs_to :cause
has_many :users, :through => :campaign_users
# other model stuff
class CampaignUser < ActiveRecord::Base
belongs_to :campaign
belongs_to :user
# other model stuff
class CauseUser < ActiveRecord::Base
belongs_to :cause
belongs_to :user
# other model stuff
has_many :through requires that you create a new model for each of these joins: campaign_users and cause_users, as is shown but it provides more functionality later on than has_and_belongs_to_many.
I would also suggest using better names than :campaign_users and :cause_users so the relationship is more meaningful.
I believe you should use the following:
class User < ActiveRecord::Base
has_and_belongs_to_many :causes
has_and_belongs_to_many :campaigns
end
class Cause < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :campaigns
end
class Campaign < ActiveRecord::Base
has_and_belongs_to_many :users
belongs_to :cause
end
This way you can use
User.causes
User.campaigns
Cause.campaing
Cause.users
Campaign.users
Campaign.cause
You can read here about has_and_belongs_to_many relationship, here about has_one and here about belongs_to.
Let me know if this is what you want :]
Edit:
"I would still need User.campaigns to
be campaigns from a user's causes or
individual campaigns associated with a
user"
You can have a method on users model that returns all campaigns. Something like this:
def all_campaigns
self.campaigns + self.causes.collect{ |c| c.campaigns }
end
You can make :has_many :through associations between users and campaigns using a join model, and also between users and causes using the another join model. The you can make a :has_many :campaigns association in the causes model, putting a :belongs_to :cause in the campaign model.
But you won't be able to fetch all the users campaigns or causes by User.campaigns.orders or User.order.campaigns. You should make an iteration over the User.campaigns collection or User.causes, fetching Campaign.cause or Cause.capaigns. Or even making a custom SQL query, using joins and conditions to filter information in the joins.

Resources