How to improve has_one association activerecord queries - ruby-on-rails

I've a biography model, and it has one to one association with lifestyle model, one with education model, and one to one with location model.
When application loads I need to get all of this information. I'm doing this by:
biography = current_user.biography
lifestyle = biography.lifestyle
education = biography.education
location = biography.location
result = { "biography" => biography, "lifestyle" => lifestyle, "education" => education, "location" => location}
And then sending the json result back:
render :json => result
So I'm executing total of four queries for this get. Is there a way to issue less queries and get the same result back?
I've tried using joins, but it is only returning columns from one table.
n+1 won't really help here as I'm not looping over the results.
Also, includes hasn't given me the desired results also.
Is there a better alternative?
Here are some of the associations:
class Lifestyle < ActiveRecord::Base
belongs_to :biography
end
class Biography < ActiveRecord::Base
has_one :lifestyle, dependent: :destroy
has_one :education, dependent: :destroy
has_one :location, dependent: :destroy
end

biography = Biography.where(user_id: current_user.id).eager_load(:lifestyle, :education, :location).first
:)

This seems to me like a case for as_json. You could overwrite the default method in your Biography model to include the attributes you need for the biography, the associations you want to include, and the attributes for each of those associations (see http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json). Then, to avoid n+1s, you could do something like:
biography = Biography.includes(:lifestyle, :education, :location).first
render json: biography.as_json

Related

Active Record Table Joins

I have been racking my brain all day and can't get this to work. I am very new to ruby and rails so my apologies for any silly errors.
My problem is I am joining 3 tables together to get a #students object. This works but if I call for example #student.name then 'name' doesn't exist.
Below is my code:
Controller
note I have tried using .includes and .join and the same problem happens.
class MyprojectController < ApplicationController
def show
#project = Project.find(params[:id])
#dateformat = '%b %e - %H:%M'
#user = Student.includes("INNER JOIN researchers ON students.researcher_id = researchers.id
INNER JOIN users ON researchers.user_id = users.id").where('user_id = ?', current_user.id)
end
User Model
class User < ApplicationRecord
include EpiCas::DeviseHelper
has_many :event_registrations
has_many :events, through: :event_registrations
belongs_to :project
has_many :researchers
#has_many :students, :through => :researchers
#has_many :supervisors, :through => :researchers
# def self.authenticate(username)
# where(username: username).first
# end
end
Researcher Model
class Researcher < ApplicationRecord
#belongs_to :project
belongs_to :user
has_many :supervisor
has_many :students
end
Student Model
class Student < ApplicationRecord
#Must have the following
validates :name, :email, :surname, :email, :supervisor, :registration_number, presence: true
#ensures unique email addresses
validates :email, uniqueness: true
#assosiations
belongs_to :researcher
end
So every student has a researcher_id and every researcher has a user_id. So the joins should go student->researcher->user and then I want to be able to use all the attributes from all tables in an #user object.
I tried using Student.join(:researcher, :user) but that tried to do a join from the Student table to the researchers table and then tried to join the user table by using a user_id from the student table (but of the user_id is in the researcher table). So i have just done the query myself.
All the data seems to be there but as 'raw attributes'.
Any help would be greatly appreciated!
-James
Rather than try and join things into one return (like you would in sql) use includes so that you can access all your data in fewer queries but you still have access to your data in objects. The point of using an ORM like ActiveRecord is to be able to access your data using objects. The downside of using an ORM is that sometimes it's not as efficient at getting you the exact data you want, because the data is pushing into objects. Using includes provides a sort of middle ground where you can access the data you require in objects and you don't necessarily have to run queries for each association.
Try something like (depending on how you're getting your user id -- I'm assuming from project):
#user = User.includes(researcher: :student).find(project.user_id)
And then you can access things through the normal rails associations:
researcher = #user.researcher
student = researcher.student
I hope that helps and best of luck!

ActiveRecord: Reference attribute from associated model in where statement

I've got two models with a has_many / has_many relationship. I have a variable exp_ids which is an array of integers representing the id's of some ExperienceLevel records. I need to write a query that will select all JobDescriptions that have an ExperienceLevel with one of those ids.
The query must work on an existing ActiveRelation object called job_descriptions, which is being passed through some flow controls in my controller to filter the results based on my params.
I've tried these queries below and some other variations, but with little success. As far as I can tell, ActiveRecord thinks that experience_levels is an attribute, which is causing it to fail.
job_descriptions.where(experience_levels: exp_ids)
job_descriptions.joins(:experience_levels).where(experience_levels.id: exp_ids)
job_descriptions.joins(:experience_levels).where(experience_levels: exp_ids)
job_descriptions.joins(:experience_levels).where("experience_levels.id IN exp_ids")
job_descriptions.includes(:experience_levels).where("experience_levels.id = ?", exp_ids).references(:experience_levels)
Here are my models:
class JobDescription < ActiveRecord::Base
has_many :job_description_experience_levels
has_many :experience_levels, through: :job_description_experience_levels
end
class JobDescriptionExperienceLevel < ActiveRecord::Base
belongs_to :job_description
belongs_to :experience_level
end
class ExperienceLevel < ActiveRecord::Base
has_many :job_description_experience_levels
has_many :job_descriptions, through: :job_description_experience_levels
end
I'm not sure if what I want to do is even possible. I've used a similar approach for another job_description filter where I selected the company_id, but in the case, company_id was an attribute of JobDescription.
Try this:
job_descriptions.joins(:job_description_experience_levels).where(job_description_experience_levels: { experience_level_id: exp_ids })
job_descriptions.joins(:experience_levels).where(experience_levels: {id: exp_ids})
Try this one. Note the lack of plural on the experience level.id
job_descriptions.includes(:experience_levels).where("experience_level.id = ?", exp_ids).references(:experience_levels)

Flatten a has_many association and get an attribute sum

I would like to understand the most efficient way to flatten some has_many associations, and subsequently get the sum of some attribute on the child associations. Suppose you have the following data structures:
class Restaurant < ActiveRecord::Base
has_one :address
has_many :menu_items
end
class Address < ActiveRecord::Base
belongs_to :restaurant
end
class MenuItem < ActiveRecord::Base
belongs_to :restaurant
end
Say that MenuItem has the attribute "cost", and Address has the attribute "zip_code". What I would like to do is maybe find all restaurants in the zipcode '10101' and get the sum of their menu item's cost attributes. Say that I want to be able to show the average cost of restaurants' offerings in an area.
I think there are quite a few brute force-y ways to do this, but I know there should be something better. For example, in C#/LINQ if I had a similar set of data structures it would be easy to write:
var sum=restaurants.Where(r => r.zip_code==10101).SelectMany(r => r.MenuItems).Sum(mi => mi.Cost);
The best I have come up with, but that feels wrong to me, is:
def thing_I_dont_know_how_to_do
find_zip=Address.where(:zip_code => zip_code)
restaurants=Restaurant.joins(:address, :menu_items).merge(find_zip)
restaurant_ids=restaurants.map(&:id)
sum=MenuItems.sum(:cost, :conditions => {:restaurant_ids => venue_ids})
end
Can anyone help me improve upon this?
You can do it with a single SQL request which selects all the menu items of restaurants in zip code 10101, and sums all their cost:
MenuItem.joins(restaurant: :address).where(addresses: { zip_code: '10101' }).sum('cost')

Thinking Sphinx and searching multiple models

I'm looking for a way to perform a search against multiple models (see this post), and got a couple of answers saying that Thinking Sphinx would be a good match for this kind of thing.
Indeed, it looks sweet, and it seems the application-wide search capability (ThinkingSphinx.search) is close to what I want. But the docs state this will return various kinds of model objects, depending on where a match was found.
I have a models somewhat like this:
Employee
Company
Municipality
County
Where employees are linked to County only though Company, which in turn is linked to a Municipality, which in turn is linked to the actual County.
Now as a result from my search, I really only want Employee objects. For example a search for the string "joe tulsa" should return all Employees where both words can be found somewhere in the named models. I'll get some false positives, but at least I should get every employee named "Joe" in Tulsa county.
Is this something that can be achieved with built in functionality of Thinking Sphinx?
I think what you should do in this case is define associations for your Employee model (which you probably have already), e.g.:
class Employee < ActiveRecord::Base
...
belongs_to :company
has_one :municipality, :through => :company
has_one :county, :through => :company
...
end
class Company < ActiveRecord::Base
...
belongs_to :municipality
has_many :employees
has_one :county, :through => :municipality
...
end
class Municipality < ActiveRecord::Base
...
belongs_to :county
has_many :companies
...
end
class County < ActiveRecord::Base
...
has_many :municipalities
...
end
Edit: I tested the multi-level has_one relationship, and it doesn't work like that. Seems to be fairly complex to model these 4 layers without denormalizing. I'll update if I come up with something. In any case, if you denormalize (i.e. add redundant foreign IDs to all models to your employees table), the associations are straightforward and you massively increase your index generation time. At the same time, it may involve more work to insure consistency.
Once the associations are set up, you can define the Sphinx index in your Employee model, like this:
define_index do
indexes :name, :sortable => :true
indexes company(:name), :as => :company
indexes municipality(:name), :as => :municipality
indexes county(:name), :as => :county
...
end
That way the columns in your associations are indexed as well, and Sphinx will automatically join all those tables together when building the index.

Trouble with Rails 3 has_many :through association and views/forms

I have previously used has_and_belongs_to_many associations in my older Rails apps but am moving to using has_many, :through for new and current apps. However, I believe I am missing something central to the has_many, :through association in Rails 3.
Currently, I am building an app for our town's volunteer fire department. When we have a meeting, we want to check off whether or not a fire fighter is present, excused, or absent.
My models:
#FireFighter Model
class FireFighter < ActiveRecord::Base
has_many :attendance_meetings
has_many :meetings, :through => :attendance_meetings
accepts_nested_attributes_for :meeting
#Meeting Model
class Meeting < ActiveRecord::Base
has_many :attendance_meetings
has_many :fire_fighters, :through => :attendance_meetings
accepts_nested_attributes_for :fire_fighter
#AttendanceMeeting Model
class AttendanceMeeting < ActiveRecord::Base
attr_accessor :status # this is the added property on the join model that we need populated
belongs_to :fire_fighter
belongs_to :meeting
The problem I'm having is how to set this up in the view. Currently, I have:
= hidden_field_tag "meeting[fire_fighter_ids][]"
-#meeting.fire_fighters.each do |fire_fighter|
= fire_fighter.fire_fighter_name
%span.radio_btn= radio_button_tag :fire_figher_ids, fire_fighter.id, "present"
present
%span.radio_btn= radio_button_tag :fire_figher_ids, fire_fighter.id, "excused"
excused
%span.radio_btn= radio_button_tag :fire_figher_ids, fire_fighter.id, "absent"
absent
%br
This will spit out each fire fighter and three radio buttons for each fire fighter (with options for present, excused, or absent). However, the name of the resulting radio buttons are all the same, so you can only pick one for all fire fighters.
As I noted above, I feel certain I am missing something basic but I am stumped. I've read through tons of SO questions and The Rails 3 Way book's sections on ActiveRecord. Any suggestions or directions would be most appreciated. Thank you!
It should be something like:
class Meeting < ActiveRecord::Base
has_many :attendance_meetings
has_many :fire_fighters, :through => :attendance_meetings
accepts_nested_attributes_for :attendance_meetings
# view
= form_for #meeting do |f|
= f.fields_for :attendance_meetings do |f_a_m|
= f_a_m.object.fire_fighter.name
= f_a_m.check_box :status, 'present'
= f_a_m.check_box :status, 'excused'
= f_a_m.check_box :status, 'absent'
For the approach you're taking you need to build the association for each firefighter with the meeting. Something like:
#meeting.firefighters << Firefighter.all
This doesn't seem particularly optimal however. I would form the join with a boolean :status for those who are absent (t) or excused (f) with present not included, imagine it like meeting_absentees. Seems backward but in this way your table will have far fewer rows.

Resources