how to model a many to many relationship - ruby-on-rails

Here is the scenario,
Articles have many Comments
Users can write many Comments for many Articles
The comments table contains both
user_id
article_id
as foreign keys
My models are set up like so
class User < ActiveRecord::Base
has_many :comments
has_many :articles, :through => :comments
class Article < ActiveRecord::Base
has_many :comments
has_many :users, :through => :comments
class Comment < ActiveRecord::Base
belongs_to :users
belongs_to :articles
My routes.rb has the following code
map.resources :articles, :has_many => :comments
map.resources :users, :has_many => :comments
which produces the following routes
new_article_comment
edit_article_comment
new_user_comment
edit_user_comment
etc...
This is not what I want (atleast not what I think I want), since comments must always be related to users and article, how can I get a route like so
new_user_article_comment
edit_user_article_comment
Then I could just do
new_user_article_comment_path([#user, #article])
to create a new comment

You could probably do this using nested routes. This article has a good example of how to do that:
http://weblog.jamisbuck.org/2007/2/5/nesting-resources
Note that the article is basically saying "don't do it":
"Rule of thumb: resources should never
be nested more than 1 level deep. A
collection may need to be scoped by
its parent, but a specific member can
always be accessed directly by an id,
and shouldn’t need scoping (unless the
id is not unique, for some reason)."
However, the above quote doesn't really apply to your action, as well as other cases where the unique id is not (yet) available, since you don't yet have a unique id for the comment, and the user and article ids are absolutely necessary. A comment on that same page offers this qualification:
Thijs van der Vossen said...
"We’ve invented a second rule of thumb;
resources should only be nested for
actions that really need the parent id
(like ‘index’, ‘new’, and ‘create’).
We started using this for an
application with a data model that’s
almost entirely hierarchical except
where it’s not and it seems to work
very nicely. Why keep the parent
resource in the url when the structure
is not strictly hierarchical?"

You have created a circular database schema. Not really recommended.
The data model should look like this:
class User < ActiveRecord::Base
has_many :comments
class Article < ActiveRecord::Base
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :article
And the basic routing:
map.resources :articles do |articles|
articles.resources :comments
end
And then decide what else do you need to make it fit your requirements.

Related

Conditions on has_many in Rails 4.2?

I have a Post model that has_many :authors and has_many :tags through :tag_posts, an Author model that has_many :posts and has_many :tags through :posts, and a Tag model that has_many :posts through :tag_posts.
Posts have to pass moderation (stored as an enum Post.status) before they're visible on the site. Moderators can see unmoderated posts by looking for ones with a different Post.status, but regular users should never be able to see them.
Is there any way I can filter all these associations to make sure they only return the posts that moderators have approved?
For example: if I call Author#tags now, that returns all the tags on all the posts that author has written, but I only want it to return the tags on the approved posts that the author has written. Say the author has two posts, the first is approved and is tagged 'octopus', and the second hasn't been approved yet and is tagged 'squid': calling #tags on that author should only return the octopus tag, not the squid tag.
I have a Post model that has_many :authors and has_many :tags through :tag_posts, an Author model that has_many :posts and has_many :tags through :posts, and a Tag model that has_many :posts through :tag_posts.
First thing's first, let's tidy up those relations. I believe this is what you really want:
class Post < ActiveRecord::Base
belongs_to :author # NOT has_many
has_and_belongs_to_many :tags # use an implicit join table
end
class Author < ActiveRecord::Base
has_many :posts
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts # use an implicit join table
end
Is there any way I can filter all these associations to make sure they only return the posts that moderators have approved?
Yes. Without going into to much detail, the key technique is to define a scope on the Post model; something like this:
class Post < ActiveRecord::Base
scope :approved -> { where(status: Post.statuses['approved']) }
end
You can then show only the approved posts via Post.approved; or only the approved posts for a certain user via Post.where(user: foo).approved.
In order the display a list of tags, you could then, for example, just do:
Post.where(user: foo).approved.map(&:tags).uniq
This will only give you an Array, not an ActiveRecord::Collection. But that's probably good enough, for your purposes.
You can scope has_many itself.
Eg.
class Author
has_many :moderated_posts, -> { moderated }, class_name: 'Post'
end
class Post
scope :moderated, -> { where(status: approved_statuses) }
end

Various belongs_to many associations

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).

Model association between Company, Employee, and department

I am working in Ruby on Rails 3. And trying to map out three models which mimic the data of a Company its employees and their respective departments.
In arrived at the following solution:
class Company < ActiveRecord::Base
has_many :departments
has_many :employees, through => :departments
end
class Department < ActiveRecord::Base
belongs_to :company
has_many :employees
has_one :department_description
end
class DepartmentDescription < ActiveRecord::Base
belongs_to :department
end
class Employee < ActiveRecord::Base
belongs_to :department
end
Is this the 'correct' way to associate these models?
I think your last response may explain why you are struggling to find a correct way to associate these models.
It seems that you see your Department merely as a join_table, and that may be due to the fact that you don't fully understand the has_many => :through construction and that it actually allows your Department to be a proper model with many attributes and methods in it, hence also a 'description' attribute.
To create a separate DepartmentDescription model is actually a waste of resource. Chad Fowler has a few good examples for :has_many => through and nested resources in his Rails Recipes... so check that out.

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