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') }
Related
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
Class User
has_many :universities
end
Class University
belongs_to :user
has_many :courses
end
Class Course
belongs_to :university
end
Now, I want to find the courses of any user.
I can use the following query:
User.find(1).universities.collect{|x| x.courses}
But is there any other simple ways to get this result? Please explain your answer so that I can understand.
Thanks in advance!
Add into User model:
has_many :courses, through: :universities
Now you can fetch all courses of a user via:
User.find(1).courses
From docs (http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association):
A has_many :through association is often used to set up a many-to-many connection with another model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding through a third model.
I have a rails project similar to a blog with posts that have set of images and one featured image. The image set was a pretty straight forward HABTM relationship, as several posts can share the same image and one post can have many images, but the featured image has been a bit more troubling.
Every post should have one and only one featured image and one image can be the featured image on several posts, so my first thought was just to reverse the relationship and let images has_many posts and posts belong_to images, but that seems problematic in a lot of different ways. First, it's not very semantic. Second, the post controller needs extra code to accept a value for image_id, as Post.new didn't seem to want to accept image_id as an attribute.
My second thought --and this is the one I'm going with so far-- was to use a HABTM relationship on both with a limit: 1 specifier on the the post's has_and_belongs_to_many :featured_images and a unique: true on t.belongs_to :post in the migration. This solution works, but it seems hack-ish. Also, it means that I have to access the featured picture like this post.featured_images.first rather than post.featured_image. Worse, I can't help but think that this would hurt database performance as it has to access three tables instead of two and it has to search for the post id in the many-to-many table, rather than identifying immeadiately via the id column.
So, is this the right way to do this or is there something better? Does rails have anything like a has_one, belongs_to_many relationship?
why do not try something like that (without HABTM, just has_many):
class Image < ActiveRecord::Base
belongs_to :post
attr_accessible :featured
after_commit :reset_featured, if: :persisted?
protected
# garant that featured will be only one
def reset_featured
Image.where('id <> ?', self.id).update_all(featured: false) if self.featured
end
end
class Post < ActiveRecord::Base
has_many :images, conditions: { featured: false }
has_one :featured_image, class_name: 'Image', conditions: { featured: true }
end
Since this is a case where you have a "has and belongs to many" relationship but you want to store extra information about the relationship itself (the fact that an image is "featured" for a post), I would try a has_many :through arrangement instead. Something like this:
class Post < ActiveRecord::Base
has_many :post_images, inverse_of: :post
has_many :images, through: :post_images
has_one :featured_post_image, class_name: PostImage,
inverse_of: :post, conditions: { is_featured: true }
has_one :featured_image, through: :featured_post_image
accepts_nested_attributes_for :post_images, allow_destroy: true
attr_accessible :post_images_attributes
end
class PostImage < ActiveRecord::Base
belongs_to :post
belongs_to :image
attr_accessible :image_id
end
class Image < ActiveRecord::Base
has_many :post_images
has_many :posts, through: :post_images
end
Unfortunately, adding validations to ensure that a post can never have more than one featured image is trickier than it looks. You can put a validation on Post, but that won't save you if some other part of your app creates PostImages directly without touching their associated posts. If anyone else reading this has some insight into this problem, I'd love to hear it.
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
I'm pretty new to Rails, and i'm trying to do a polymorphic HABTM relationship. The problem is that I have three models that I want to relate.
The first one is the Event model and then are two kind of attendees: Users and Contacts.
What I want to do is to be able to relate as an attendee both users and contacts. So, what i have right now in my code is:
Event Model
has_and_belongs_to_many :attendees, :polymorphic => true
User Model
has_and_belongs_to_many :events, :as => :attendees
Contact Model
has_and_belongs_to_may :events, :as => :attendees
How the HABTM table migration needs to be? I'm a little confused and i have found no help on that.
Is it going to work?
No, you can't do that, there's no such thing as a polymorphic has_and_belongs_to_many association.
What you can do is create a middle model. It would probably be something like this:
class Subscription < ActiveRecord::Base
belongs_to :attendee, :polymorphic => true
belongs_to :event
end
class Event < ActiveRecord::Base
has_many :subscriptions
end
class User < ActiveRecord::Base
has_many :subscriptions, :as => :attendee
has_many :events, :through => :subscriptions
end
class Contact < ActiveRecord::Base
has_many :subscriptions, :as => :attendee
has_many :events, :through => :subscriptions
end
This way the Subscription model behaves like the link table in a N:N relationship but allows you to have the polymorphic behavior to the Event.
Resolveu parcialmente.
It does solve the problem given the framework that we have at our disposal, but it adds "unnecessary" complexity and code. By creating an intermediary model (which I will call B), and given A -> B -> C being "A has_many B's which has_many C's", we have another AR Model which will load one more AR class implementation into memory once it is loaded, and will instantiate for the sole purpose of reaching C instances. You can always say, if you use the :through association, you don't load the B association, but then you'll be left with an even more obsolete model, which will only be there to see the caravan pass by.
In fact, this might be a feature that is missing from Active Record. I would propose it as a feature to add, since it has been cause of concern for myself (that's how I landed in this post hoping to find a solution :) ).
Cumprimentos