Various belongs_to many associations - ruby-on-rails

I've found good answers here, here, and here but I'm having trouble generalizing that to what I'm after.
I have multiple categories, that will be curated and selectable. So, users will be able to select cat1, cat2, and cat3, but not type a custom category.
A category can have many posts, a post can have many categories.
A post can have many comments.
A user can have many posts, and many comments.
For the post/category relationship, I'm thinking this will work, but the user/post/comment relationship is where I'm scratching my head...
# app/models/category.rb
class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
# app/models/post.rb
class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
belongs_to :user
has_many :comments
end
# app/models/user.rb
class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
# app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
Does this look close? Do I need any foreign keys anywhere to handle all this? Thanks in advance, I'm sure this is simple and I'm missing something obvious in my understanding.
And then I have to worry about how to write the tests for all this! That's for another day though...
EDIT: I should point out, I haven't started this yet. Just trying to map it out before I start, so it should simplify things, fewer migrations, etc.
EDIT AGAIN: Implemented suggested changes so far. Thanks!

why not start with the specs first? is a good practice on rails with all the power you have with rspec
Your Item should be called Post, why Item? is there any reason? if you want to call it "Item" you need to specify that on the associations
belongs_to :post, class_name: 'Item'
but you are better with Post instead of Item
A comment belongs to a user so the the user has_many :comments, you don't need the ", through: :posts" part
has_many :category_posts
has_many :posts, :through => :category_posts #or would has_and_belongs_to_many work better?
this depends on you, you need extra behavior on the CategoriesPosts? (Categories, in plural) if not, just use has_and_belongs_to_many
Really, i would suggest you start with the specs, you will end up with the implementations without thinking it too much and then you already have it tested and then you can add more specs and refactor it. Read something about TDD and BDD, it's hard at first but it's really good when you get it.

The only change I think I would make to this, other than actually naming Item Post, would be on your user model:
# app/models/user.rb
class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
You don't need a through association there. You could add other scoped comments to be something like comments_on_my_posts, through: :posts, class_name: "Comment", but for the above association on comments, it should be direct (commenter <=> comment).

Related

Reason for belongs_to and has_many associations in ruby-on-rails

I don't get why need both these associations?
Surely if you define one, it would naturally imply the other. Therefore making one of them redundant.
Is there ever a situation where you would have one and not the other?
For example if I define:
class Post < ActiveRecord::Base
has_many :comments
end
why do I then need to define:
class Comment < ActiveRecord::Base
belongs_to :post
end
Surely the above could be implied, and therefore use convention over configuration.
I'm just curious what the thinking was behind this! I'm sure there must be a good reason.
It's not that straight forward. For example if you have a belongs_to relation, the other end could represent a has_one or a has_many.
Secondly, sometimes you don't want the inverse association as it clogs up your model on the other end of the association. For example, you have a lot of classes referencing user but you don't need to know that a user has_many backorder_refirbished_car_parts
First for this code it should be belongs_to if i understand right, as has_one will search at Post for comment_id and i don't think its the case for you and comment who has post_id defined on it
class Comment < ActiveRecord::Base
# has_one :post
belongs_to :post
end
Second: if you don't want to define it, then You can do it but you will not be able to say:
Comment.first.post
so if you don't need it don't write it.
Its not like needed. If you want only belongs_to or has_many association you could. There is not a dependency, If you followed convention of foreign key.

Rails featured image association?

I've been trying to figure out how to associate my models on a project I've been working on for a while, and I've come here for help a couple times before, but I never got a satisfactory answer. I have two models: Post and Image. Every post has several images attached to it and posts can share images, so a HABTM relationship made sense for that, like this:
class Post < ActiveRecord::Base
has_and_belongs_to_many :images
end
class Image < ActiveRecord::Base
has_and_belongs_to_many :posts
end
The problem now is that I want each post to have a single 'featured image.' How do I do this? The first thought that comes to mind is a simple has_one :featured_image on the post and belongs_to :post_featured_on on the image, but the problem with that is that the same image can be featured on multiple posts.
So the next idea I came up with is to reverse the relationship: belongs_to :featured_image on the post and has_many :posts_featured_on on the image. The problem with that is that it isn't very semantic and rails doesn't seem to want to let me set a post's featured image from its form, like this in the controller: Post.new(:featured_image => Image.find(params[:image_id]))
So the next idea suggested to me was a second HABTM relationship, like so: has_and_belongs_to_many :featured_images. There's an obvious problem with this, it's plural. I tried putting unique: true on the post_id column in the migration, but that didn't help the fact that I kept having to do this in my code: post.featured_images.first which can be very frustrating.
The last idea I tried was a has_many :posts, through: :attachment and has_one :featured_posts, through: :attachment in place of the original HABTM, but these just seems unnecessarily cumbersome, and rails doesn't seem to want to let me assign the images on the fly this way like Post.new(:featured_image => Image.find(params[:image_id])).
Is there any good way to do this? Have I done something wrong in my previous attempts? Shouldn't this just be a simple foreign key on the post table? Why does it have to be so difficult?
I like your second idea just fine. The full blown approach is to use a transaction model, such as #depa suggested. The transaction model is great when you want to store additional attributes such as when the Image was made featured for a given post (and perhaps when it was not, as well). But, whether you build that transaction object as well or not, you can just cache the featured image on the post object for quick access. Try just doing this:
class Post < ActiveRecord::Base
has_and_belongs_to_many :images
belongs_to :featured_image, class_name: 'Image'
end
class Image < ActiveRecord::Base
has_and_belongs_to_many :posts
# Purposefully not defining an inverse relationship back to Post.
# You can if you need or want it but you may not.
end
Then, in the controller I'd recommend:
#post = Post.find_by_id(params[:id])
#post.featured_image = Image.find(params[:image_id])
#post.save
You probably didn't have success with this before because of not having attr_accessible :featured_image_id on the Post model. And/or because you were using the wrong attribute name. (It should have been Post.new(featured_image_id: Image.find(params[:image_id])).) Either way, it's good to keep the code a little more object-oriented than all that. The way I laid it out above, you don't have to think about the column name in the database, and can just think about the objects you're dealing with. I.e., just assign the Image to the Post's feature_image reference. Keeping this in mind, I prefer to not set foreign keys as attr_accessible when possible.
You can do what you want using a has_one :through association.
class Post < ActiveRecord::Base
has_and_belongs_to_many :images
has_one :featured_image,
:through => :feature,
:class_name => 'Image'
has_one :feature
end
class Image < ActiveRecord::Base
has_and_belongs_to_many :images
has_many :featured_images,
:through => :features,
:class_name => 'Image',
:foreign_key => :featured_image_id
has_many :features
end
class Feature < ActiveRecord::Base
belongs_to :featured_image,
:class_name => 'Image'
belongs_to :post
end

Newbie here: conflict associations to the same table. has_many:invoices, has_many :invoices, through: user_invoice_viewers

this seems pretty basic stuff here, but actually i'm finding it a bit harsh to define this scenario with Rails...
Perhaps any of you can provide some guidance?
So I have three Tables, Users, Invoices, and User_Invoice_Viewers (these basically map users that have viewer access to an invoice)
Now my models :
User.rb :
has_many :invoices
has_many :user_invoice_viewers
has_many :invoices, through :user_invoice_viewers
Invoice.rb
belongs_to user_invoice_viewers
belongs_to :user
User_Invoice_Viewers.rb
belongs_to :users
belongs_to :invoices
Now this just seems wrong... I repeat has_many :invoices on User model, so i expect conflict when executing : User.invoices ...
What would be the best solution for this? I had thought of putting it all on a user_invoice table, but since i expect to have more owners than viewers, for performance reasons, i decided to build a direct dependency between invoice and its owner...
Thanks
I would consider using the :class_name option on the association, so that the two relationships are named differently. Something like this:
class User < ActiveRecord::Base
has_many :invoices
has_many :user_invoice_viewers
has_many :viewable_invoices, through :user_invoice_viewers, :class_name => "Invoice"
...
end

Rails: Should I use has_many :through?

I'm trying to figure out how to best way to create associations for the following models:
User
Category
Post
Comments
Here are the requirements I'm trying to meet:
A user can have many posts
A post belongs to a user
A post can have many comments
A comment belongs to a post
A post can belong to a category
A user does NOT have many categories.
(The number of categories is fixed and the same for all users)
A category can have many posts
In terms of routing, I'd like to be able to access a post within a certain category for a specific user. For example:
http://domain.com/users/1/categories/1/posts
Since there is no direct relationship between User and Category, I'm not quite sure how to best set up the associations. And I'm totally lost on how to configure the routes.
Here's what I have for my models:
class User < ActiveRecord::Base
has_many :posts
end
class Category < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :metric
has_many :comments
end
class Comments < ActiveRecord::Base
belongs_to :post
end
Is this a case where I should be using has_many :through relationships? Or something more complex like polymorphic associations? Thanks in advance!
Yes, it would be a very good idea to use :
User has_many :comments, :through => :posts
If you like, you can also get categories comments, by :
Category has_many :comments, :through => :posts
Remember that a through association is just a facility that allows you to do things like user.comments directly (and through is the way for the association to find the user comment that is referred to post model).

Ruby on rails model with multiple parents

In my Rails application, I have two models, Articles and Projects, which are both associated with a user. I want to add comments to each of these models. What's the best way to structure this?
Here's my current setup:
class Comment < ActiveRecord::Base
belongs_to :article
belongs_to :project
end
class Article < ActiveRecord::Base
belongs_to :user
has_many :comments
end
class Project < ActiveRecord::Base
belongs_to :user
has_many :comments
end
class User < ActiveRecord::Base
has_many :articles
has_many :projects
has_many :comments, :through => :articles
has_many :comments, :through => :projects
end
Is this the right way to handle the structure? If so, how do I manage the CommentsController to have an article_id if it was created through an Article, and a project_id if it was created through a Project? Are there special routes I should set up?
One final comment: Comments don't always have to have a user. Since this if for my website, I want anonymous viewers to be able to leave comments. Is this a trivial task to handle?
Make Comment a polymorphic model. Then create a polymorphic association.
Here's an example of polymorphic relationship from the Rails wiki and here's a Railscast from the screencast-men Ryan Bates.
You can check out - acts_as_commentable plugin http://github.com/jackdempsey/acts_as_commentable/tree/master
Or you can proceed with polymorphic relation
You could have ArticleComments and ProjectComments with similar structure but stored separately, then create a method that returns both types of comments.

Resources