Complex Active Record Association - ruby-on-rails

User
first_name: "Johnny"
age: 15
Car
name: "Mazda"
Car
name: "BMW"
I would like results that look like
[ {"first_name": "Johnny", "age": 15, cars: [ { "name": "Mazda" }, { "name" : "BMW" } ] } ]
Is it possible to do this in Rails? So far I can get the Users car by typing johnny.cars but I'd like the data structure to also hold the values of the parent.
I was toying with the idea of using named_scopes and transient attributes but no luck so far any ideas/help is greatly appreciated.

You Could actually retrieve all the records from the other table using :includes statement
includes is magically syntax where in you can eager load all the assocaited records of a record.
Example In your case
User has_many cars
so If you do
#users = User.includes(:cars)
Now this would load all the user records and will eagerload all the cars records for each user objects
so if you loop through a user object and do
<% #users.each do |user| %>
<%= user.cars %>
<%end%>
user.cars wont fire a seperate query to fetch the all cars for the users instead it will fetch those record eager loaded using the includes clause
This technique is widely used to avoid N+1 query problem
Google for N+1 to know more on it
Hope this Help

Related

Can i cache a parent in rails?

I have this code:
Business.all.limit(50).each do |business|
card = {name: business.name, logo: business.logo, category: business.category.name}
feed << card
end
In my models, Business belongs to Category, and Category has many Business
My problem is that this will query the DB 50 times, each time I want to retrieve each business' category name.
I have seen Rails cache effectively by using :include, but all examples I have seen are for child records, for example:
Category.all :include => [:businesses]
but in this case I want to cache the parent's data.
Its the same you can do by using singular model name
Business.includes(:category)

rails getting attributes of associated model for many objects in one query

My title might be confusing, I wasn't sure what to write.
In rails I understand how to fetch Many Objects for One parent object
#first_user = User.first
#first_user_posts = #first_user.posts
But how can I fetch Many Objects for Many parent objects and select its attributes in one query?. I am trying to do something like that:
#many_posts = Post.all
#posts_by_user_gender = #many_posts.joins(:user).map(&:gender)
hoping it would give me an array that could look something like this:
#posts_by_user_gender => ["male", nil, "female", nil]
#I know I can do this map technique if I fetch it directly from the User model
# User.all.map(&:gender),
# but I want to start with those that posted in a specific category
# Post.where(:category_id => 1)
and then to count the males I could use the Ruby Array method .count
#males_count_who_posted = #posts_by_user_gender.count("male")
=> 1
I could always do 3 separate queries
#males_count_who_posted = #many_posts.select(:user_id).joins(:user)
.where("gender = ?", "male").count
#females_count_who_posted = ...
but I find that extremely inefficient, especially if I do the same for something like "industry" where you could have more than 3 options.
you can join model via SQL syntax
#posts_by_user_gender = #many_posts.joins("LEFT JOIN Users where users.id=posts.user_id").joins("LEFT JOIN Genders where genders.id=user.gender_id")

How to select from a table that has been joined with same model/class/table?

I'm trying to get a count of how many subcontacts every contact has.
Class Contacts
has_many :subcontacts, class_name: "Contact",foreign_key: "supercontact_id"
belongs_to :supercontact, class_name:"Contact"
And here's the activerecord part i have so far that's roughly what i'm trying to do.
Contact.joins{subcontacts.outer}.select(subcontacts.count as subcontact_count)
I think the problem is that the joins portion is looking for a association name and the select part is looking for a table name. The trouble is that the table name is the same table... What's the best way to do this so that it stays as a relation or using SQL so that we can minimize the number of queries so that it isn't an N+1 problem?
Try using
results = Contact.joins(:subcontacts).select("count(subcontacts.id) as count, contacts.id").group("contacts.id")
and count can be fetched as
results.map do |result|
"Contact ID: #{result.id} - Subcontacts Count: #{result['count']}"
end
Contacts.all.each do |contact|
puts contact.name => contact.subcontacts.count
end
OR
Contacts.all.map{|contact| [contact.name => contact.subcontacts.count]}
The above will provide you the hash like answer{contact_name => subcontacts.count}

How to sort a merged array and hash chronologically?

In a Rails 3.2 app I have a polymorphic ActivtyFeed model.
class ActivityFeed
belongs_to :user
belongs_to :feedable, polymorphic: true
end
I need to aggregate some items in the ActivityFeed index view. For example, instead of rendering separate items for each photo, I want to group photos by a date or event, and display "User uploaded x photos".
My controller looks like this:
#feed_items = #user.activity_feeds.sort_by(&:created_at)
#feed_array = []
photos = #feed_items.find_all{|f|f.feedable_type.eql?("Photo")}
#feed_items.delete_if{|f|f.feedable_type.eql?("Photo")}
#feed_array << #feed_items
grouped_photos = photos.group_by{|p| p.feedable.event_id}
#feed_array << grouped_photos
#feed_array.flatten!
Then in the views I'm using e.g.
if feed_array.class.eql?(Hash)
render grouped photo partial
elsif feed_array.feedable_type.eql?("Post")
render single post partial
etc
I'm having trouble sorting items chronologically, because the array contains a nested hash.
[#<ActivityFeed id: 7, user_id: 2, feedable_type: "Post", feedable_id: 3>, {2=>[#<ActivityFeed id 3, user_id: 4, feedable_type: "Photo", feedable_id: 6>]}]
How can I sort this array?
I've tried #feed_array.sort{|a,b| a.['created_at'] <=> b.['created_at'] } but got comparison of ActivityFeed with Hash failed
Is this the best approach, or is there a better way to go?
you can merge the array and the hash and then sort it
merged.sort {|a,b| a.method <=> b.method }
you only need to tell the sort how to sort the objects
one suggestion, instead of
#user.activity_feeds.sort_by(&:created_at)
do
#user.activity_feeds.order('created_at ASC')
that should be faster, since you already get the activities sorted from the database (if activity_feeds is a relation)
Well you need to do little bit modification to your original design.
What I would suggest is to add event to activity feed and also you need to keep the original photo to the activity feed as well.
Because, by keeping photo as a activity feed you will be able to display a single photo on your activty stream when the photo is not associated with any event. But, if every photo belongs to an event then you can remove the photo from activity feed.
Then every object will be activity feed and can be sorted easily based on created_at.
Also you don't need to extract the photos manually.
Hope this will help!

How can I write this query efficiently in Ruby on Rails?

I have three tables: Accounts, Investments, and Games. An Investment has an account_id, game_id, some statistic counters, and is created the first time an Account participates in a Game.
I want to provide a JSON list of the latest Games along with the user's Investment in that Game, like this:
[{id: 666, name: "Foobar", ..., investment: {tokens: 58, credits: 42, ...}},...]
If they have not yet participated in the game, I still want to include an Investment object with default values, so I overrode the serializable_hash function in my Game model:
# game.rb
has_many :investments
def serializable_hash(options=nil)
options ||= {}
i = investments.find_or_initialize_by_account_id options[:uid]
{:id => id, ..., :investment => i.serializable_hash}
end
However, when I run something like Game.find(list_of_ids).to_json(:uid => current_user.id), Rails does a separate query on the Investments table for each Game. I tried Game.includes(:investments).find(list_of_ids).to_json(:uid => current_user.id) but not only does that load the investments for all users, it still does a separate query for each game to find or initialize the investment object.
In short, given a list of game IDs and an account id, what's a clean way to load the associated Investment objects that exist in one query, and initialize the rest?
You want to give the list of ids to the server in one go. I use the IN operator for this:
Game.includes(:investments).where("games.id IN (?)", list_of_ids)

Resources