Eagerload has_one association with a table that has multiple related values - sql

So I have the following models defined:
class Album
has_many :photos
has_one :cover_photo, class_name: "Photo"
end
class Photo
belongs_to :photo
end
What I want to do now is eagerload the cover_photo association when loading albums like this:
Album.where(...).includes(:cover_photo)
But according to ActiveRecord documentation and the observed behavior, limit (which is what the has_one association is defining), is not respected. So I get the following query:
Photo Load (1.0ms) SELECT "photos".* FROM "photos" WHERE "photos"."album_id" IN (5, 4, 2, 1)
Although, album.cover_photo now returns a single object without firing extra queries, clearly ALL photos are being loaded from the database which is not what I want here.
I know I can achieve this with using a JOIN and have access to the photo attributes inside the album object, but I really need to be able to use the Photo model as the knowledge of interpreting the photo attributes lives in there.

This article answers my question exactly and also another question I had regarding how I can manually preload records into an AR association - https://mrbrdo.wordpress.com/2013/09/25/manually-preloading-associations-in-rails-using-custom-scopessql/
Relevant code from the post:
class Album < ActiveRecord::Base
has_many :photos
has_one :display_photo, -> {
self.select_values = ["DISTINCT ON(photos.album_id) photos.*"]
order('photos.album_id')
}, class_name: "Photo"
end

Related

rails multiple has_many relationships between the same models

I'm trying to figure out the best way to model a many-to-one relationship in rails where there are multiple scopes to the relationship.
An example would be a restaurant has-many photos. I want to be able to call
restaurant.lounge_photos
and receive only the lounge photos,
but also be able to call
restaurant.food_photos
and receive just the food photos.
The two methods I can think of are:
to use multiple joins table, and a has_many to has_one relationship for each type of photo.
to add a 'type' attribute to the photo model and write a scoping method.
Both of these seem a bit clunky to me.
Is there a better solution here?
I think you have to go has_many and Single Table Inheritance(STI), as follow.
Make association with restaurant and photo
class Restaurant < ActiveRecord::Base
has_many :photos
end
class Photo < ActiveRecord::Base
belongs_to :restaurant
end
Then you have to use STI in Photo model. The reason is that you have almost all fields are common for lounge_photos and food_photos.
OR
Using scope directly you can differentiate it and achieve your goal.
For more details of use STI you can refer this link.
This is one way, using a type column
has_many :food_photos,
class_name: 'Photo',
foreign_key: :restaurant_id,
-> { where(type: 'food') }
has_many :lounge_photos,
class_name: 'Photo',
foreign_key: :restaurant_id,
-> { where(type: 'lounge') }

Querying relationships of relationships in Rails

I am trying to create a query in Rails but am having some trouble creating the correct one. Below is my models with their relationships.
class User < ActiveRecord::Base
has_and_belongs_to_many :rsvps, class_name: 'Event'
has_many :albums
end
class Event < ActiveRecord::Base
has_many :albums
has_and_belongs_to_many :attendees, class_name: 'User'
end
class Album < ActiveRecord::Base
belongs_to :user
belongs_to :event
end
I need to get all events a user has "rsvp'ed" to that they haven't uploaded an album to yet. I can find out if a user has uploaded an album to a particular event using the following:
u = User.find(1)
e = Event.find(1)
e.albums.where(user_id: u.id)
I want to be able to run this query on each of the user's rsvp'ed albums. I know I could do something like this:
u.rsvps.delete_if { |e| !e.albums.where(user_id: u.id).blank? }
However, I want to do this all in one query instead of getting the rsvps and then iterating over them and deleting them when necessary.
In order to get all events a user has rsvp'ed to but haven't uploaded an album to yet, you can use the following, which (UPDATE) now also works when a user has not uploaded any albums.
#event_ids = Album.where(user_id: u.id).pluck(:event_id))
#event_ids.empty? ? u.rsvps : u.rsvps.where("id not in (?)", #event_ids)
In addition, this query should work as well.
u.rsvps.where.not(id: Album.where(user_id: u.id).pluck(:event_id))

Returning associations for specific model when there is a polymorphic association in Rails 3.2

I have a polymorphic association in a Rails 3 app where a User may favorite objects of various classes.
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :favoriteable, :polymorphic => true
end
class User < ActiveRecord::Base
has_many :favorites
end
class Image < ActiveRecord::Base
has_many :favorites, :as => :favoriteable
end
class Video < ActiveRecord::Base
has_many :favorites, :as => :favoriteable
end
I would like to be able return a list of just a User's favorite_images for example.
user.favorite_images #returns a list of the images associated with the user via :favoritable
I'm guessing there is a straightforward way of doing this but I haven't been able to figure it out. Let me know if you need anymore info.
Thanks!
===edit====
I know that I could retrieve what I am looking for via
favorite_images = user.favorites.collect{|f| if f.favoriteable_type=='Image'; f.favoriteable; end}
I could define an instance method for the User class and put that inside. I was wondering if there is a way to do it as some sort of has_many association. Really just because going forward it would be easier to have all that in one place.
When you created the table for Favorite you created a couple of columns favoriteable_id and favoriteable_type and you can use this information to restrict your query.
If you do user.favorites you will get all of the favorites and to restrict them to say just the images then you can do user.favorites.where(favoriteable_type: 'image') but that just gives you the favorite records and it sounds like you want the actual images. To get those you can do it by then mapping and pulling the favoriteable out. You'll likely want to include it in the query though so you don't hit the database so much. I would also make this a method on User.
def favorite_images
favorites.includes(:favoriteable).where(favoriteable_type: 'Image').map(&:favoriteable)
end

rails order through count on other table

I'm adding quiz functionality to the twitter app from the Hartl tutorial and have these Models:
User is nearly the same as the tutorial:
class User < ActiveRecord::Base
has_many :followed_users, through: :relationships, source: :followed
has_many :takens, dependent: :destroy
has_many :questions, through: :takens
end
Taken is a table of Question ids to User ids:
class Taken < ActiveRecord::Base
belongs_to :user
belongs_to :question
end
nothing interesting in Question:
class Question < ActiveRecord::Base
attr_accessible :category, :correct, :option1, :option2, :option3, :qn
end
I want to be able to show followed_users and followers in order of the number of tests they have taken. In the console this can be had through:
User.find_by_id(1).question_ids.count
Then I can do something like:
User.find_by_id(1).followers.first.question_ids.count
in the console to get the count for a single follower.
I feel like I'm almost there.
How do I sort the followers and followed_users through their 'takens' count? (I was also looking at cache_count, which at first seemed promising, but might not be what I need...)
Ruby on Rails does not provide an object oriented mechanism to perform this; you have to write the SQL yourself. In your case, I'd say that the following line SHOULD work:
User.find_by_sql("SELECT users.*, COUNT(questions.id)
AS c FROM users, questions WHERE questions.user_id = users.id
GROUP BY users.id ORDER BY c DESC")
I don't have the actual tables in front of me, so I can't be sure that this is actual valid SQL, but hopefully it should work.
EDIT: There were a few syntax errors with my SQL but they've been fixed. Note that I'm assuming that your tables are called users and questions. They may differ for you.

Rails 3 - associations

I print in my view a number that tell me, how many people read my article. It looks something like a:
<%=article.hits.count%>
As is possible to see, I created a simple association.
Now I am trying to get the information, if the user who is log in on my page, so if he is already had read this article. In my table that contains hits is column user_id.
But I can't still find the way, how to get...
I tried something like:
<% if session[:login_user_id].hits.user_id == session[:login_user_id]%>
Have you read it already.
<% end %>
But the example above doesn't work me... Could anyone help me please, how to do?
EDIT: The models:
class Article < ActiveRecord::Base
has_many :hits
end
class Hits < ActiveRecord::Base
belongs_to :article, :class_name => "DataHit", :foreign_key => "article_id"
has_many :users
end
class User < ActiveRecord::Base
belongs_to :hit
end
Thanks in advance
Let's first talk about the model you like to receive. For me, it sounds like:
Every article can be visited / read by many users.
Every user can read / visit many articles.
This is a classical n:m-association which is normally implemented by a has-many-through association.
If this is the intention, it should be implemented like:
class Article < ActiveRecord::Base
has_many :hits
has_many :users, :through => :hits
end
class Hits < ActiveRecord::Base
belongs_to :article, :class_name => "DataHit", :foreign_key => "article_id"
belongs_to :user
end
class User < ActiveRecord::Base
has_many :hits
has_many :articles, :through => :hits
end
Of course, you have to add migrations that ensure that the final DB model is like that:
Hit has article_id and user_id to ensure that users may find the articles they have read
If you have that model implemented, it should be more easy. Then you have operations available like: #article.users.contains(User.find(user_id)). Have a look at the tutorial at Ruby on Rails Guides which explain what the has-many-through relation is and which advantages they have.
It would be helpful if you try the things first in the console of Rails. To do that, start with:
Start the rails console in the root directory of your application: rails c
Enter there e.g.: art = Article.find(1) to get the article with the id.
Try which methods are available: art.methods.sort to see all methods that could be used. If there is no method users, you have did something wrong with the assocication.
Try the call: us = art.users and look at the result. It should be a rails specific object, an object that behaves like a collection and understands how to add and remove users to that collection (with the whole life cycle of rails). The error your currently have could mean different things:
Your database model does not match your associations defined in Rails (I suspect that).
Some minor tweak (misspelling somewhere) which hinders Rails.
I hope this gives you some clues what to do next, I don't think that we can fix the problem here once and for all times.