Best way to do has_one and belongs_to_many? - sql

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.

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') }

How to implement has_many :through relationship in rails with this example

i've been searching through similar questions but i still don't get how implement this relationship. I have of course three models :
class Recetum < ActiveRecord::Base
attr_accessible :name, :desc, :duration, :prep, :photo, :topic_id
has_many :manifests
has_many :ingredients, :through => :manifests
end
class Ingredient < ActiveRecord::Base
attr_accessible :kcal, :name, :use, :unity
has_many :manifests
has_many :recetum, :through => :manifests
end
class Manifest < ActiveRecord::Base
attr_accessible :ingredient_id, :quantity, :receta_id
belongs_to :recetum
accepts_nested_attributes_for :ingredient
belongs_to :ingredient
end
Recetum would be a recipe (typo when scaffolding), this recipe may have one or more ingredients (already on the db). So when i create a new Recetum, i need the new recetum to be created and one record inserted in manifest for each ingredient entered by the user.
I would need some help now with views and controllers, how do i create the form for recetum with fields for the ingredients and more important what do i have to modify recetum controller.
Any suggestions or help would be very much appreciated as this part is crucial for my project, thanks in advance.
You have a couple options, and mainly they depend on what you want to do in your view. Do you want to display a set number of max_ingredients or do you want it to be completely dynamic? The dynamic case looks better for the user for sure, but it does make for some more complicated code.
Here is a good RailsCast which explains how to do it dynamically via JavaScript:
http://railscasts.com/episodes/74-complex-forms-part-2
Unfortunately, not everyone runs with JavaScript enabled so you may want to consider doing it the static way.
Firstly, I don't think you need accepts_nested_attributes_for in your Manifest model. However, I do think you need it in your Recetum model. If you're going the static route, you'll probably want to set a reject_if option too.
accepts_nested_attributes_for :manifests, reject_if: :all_blank
Once you do this, you'll need to add manifests_attributes to your attr_accessible.
With the static route, you'll need to prebuild some of the manifests. In your new controller you'll want something like this:
max_ingredients.times do
#recetum.manifests.build
end
In your edit and the error paths of your create and update, you may want:
(max_ingredients - #recetum.manifests.count).times do
#recetum.manifests.build
end
Finally, your view will need some way to set the ingredient. I'll assume a select box for now.
f.fields_for :manifests do |mf|
mf.label :ingredient_id, "Ingredient"
mf.collection_select :ingredient_id, Ingredient.all, :id, :name
You'll want to add some sort of formatting through a list or table probably.
Hopefully, that's enough to get you started.

Rails 3.2 basic relationship has_many

I've reread the rails guide to associations but still am hitting my head up against the wall. I've seen some advanced questions on Stack Overflow and am not able to determine if they shed light on my problem. Perhaps I've haven't gotten my mind wrapped around this. Please show me what I'm not seeing.
I have a School.rb which has_many Events, has_many Venues. Each of the Events and Venues belongs_to School. I'm trying to link up the Venue to an Event. They are tied to the school because they have a matching school_id. The name of the school is easily applied in Event#show and Venue#show as expected. The trick is how do I craft the Event controller to use the school_id to pull the Venue's addy in the Event#show page?
My attempts keep missing so I got to thinking maybe I have to make the Event belongs_to Venue and Venue has_many Events. Is that the right thing to do?
I attempt <%= #event.venue.address %> but that fails with 'undefined method `address' for nil:NilClass'
Maybe I'm overthinking it but as I mention above, I don't know enough to ask the right question. If I was to put my query in English terms it would be "Grab the instance of Venue whose school_id matches the school_id of the current/active Event." Does that make sense? I've attempted to find something close to that in the rails guides and attempted this:
#venues = Venue.where(:school_id => #school_id)
undefined method `address' for []:ActiveRecord::Relation. The venue address is in the venue model.
Here's my school.rb:
class School < ActiveRecord::Base
has_many :events, :dependent => :destroy
has_many :venues, :dependent => :destroy
has_and_belongs_to_many :users
belongs_to :event belongs_to :venue
def self.fetch_for_name(_name)
school = self.new(:name => _name)
end
end
Here's my event.rb:
class Event < ActiveRecord::Base
resourcify
belongs_to :school
belongs_to :venue
end
Here's my Venue.rb:
class Venue < ActiveRecord::Base
resourcify
belongs_to :school
has_many :events
end
Please help me get over this baby step, sam
You need to look at this part of the guide and ensure you have has_many through relationship between venue and events here as explained in the link...
To do the exact thing you are asking, you'd create a method in Venue to query for a given event.
class Venue < ActiveRecord::Base
resourcify
belongs_to :school
has_many :events
def self.venue_for_event(event)
where("school_id = ?", event.school_id)
end
end
However, there are some questions to be asked about your models. Why does a school have many events and venues yet also belong to one event and venue? What is the problem you are trying to solve with these models? If school is the glue that holds venues and events together, than consider making it a join model in a has_many, :through relationshuip

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.

Filter based on model attribute has_many relationship through, rails 3?

I have a simple question, but can't seem to find any solution, though I have found things that are similar, but just not exactly what I am looking for.
I have an application where a User has many Assets through the class UserAsset. I want to be able to do current_user.user_assets , but I only want to return records that have an Asset with a specified field value of "active".
This post is similar but I need to use the main model not the join model as a filter.
class UserAsset < ActiveRecord::Base
belongs_to :asset
belongs_to :user
end
class Asset < ActiveRecord::Base
has_many :user_assets
has_many :users, :through => :user_assets
end
class User < ActiveRecord::Base
has_many :user_assets
has_many :assets, :through => :user_assets
end
I tried setting the default scope on Asset, and also some conditions on the has many (user_assets) relationship, but rails is failing to consider the join on the Assets table. ie Unknown column 'asset.live' in 'where clause'. Trying to achieve the following:
#active_user_assets = current_user.user_assets #only where assets.active = true
So how do I use conditions or scopes to achieve this? I need the user_asset object because it contains info about the relationship that is relevant.
Thanks in advance!
You want current_user.assets, then your scopes should work.
Oh, but you want the user_assets. Hmm. I think you need the :include clause to find() but where to put it, I can't be arsed to think of right now.
Perhaps
current_user.user_assets.find(:all, :include => :assets).where('asset.live=?', true)
(I'm not on Rails 3 yet, so that's going to be mangled)
Are you using :through when you really want a HABTM?