I have users, posts and comments. User can post only one comment to each post.
class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
class Post < ActiveRecord::Base
has_many :comments
belongs_to :user
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
On userpage (http://host/users/1 for example) I want to show all posts where the given user has commented. Each post then will have all other comments.
I can do something like this in my User controller:
def show
#user = User.find(params[:user_id])
#posts = []
user.comments.each {|comment| #posts << comment.post}
end
This way I will find User, then all his comments, then corresponding post to each comment, and then (in my view) for each post I will render post.comments. I'm totally new in Rails, so I can do this =) But I think it's somehow bad and there is a better way to do this, maybe I should use scopes or named_scopes (don't know yet what this is, but looks scary).
So can you point me out to the right direction here?
You could define an association which retrieves all the posts with comments in a single query. Keeping it in the model reduces the complexity of your controllers, enables you to reuse the association and makes it easier to unit test.
class User < ActiveRecord::Base
has_many :posts_with_comments, :through => :comments, :source => :post
# ...
end
:through is an option for has_many to specify a join table through which to perform the query. We need to specify the :source as Rails wouldn't be able to infer the source from :post_with_comments.
Lastly, update your controller to use the association.
def show
#user = User.find(params[:user_id])
#posts = #user.posts_with_comments
end
To understand more about :through and :source take a look at the documentation.
When you got the user, you have the associations to his posts and each post has his comments.
You could write:
(I don't know the names of your table fields, so i named the text text)
# In Controller
#user = User.find(params[:user_id]).include([:posts, :comments])
# In View
#user.posts.each do |post|
post.text
# Comments to the Post
post.comments.each do |comment|
comment.text
end
end
I haven't tested the code, so there could be some errors.
Related
I have an article with a many relationship to users and vice versa. When I create an article signed in as a user, the article is created with the relationship to user. So the relationship is working. I want other users to be able to join this article, so essentially, I would like a button to push the current_user to the array/list of many users.
I am at a complete loss at how to go about this process... Any help is appreciated
So users can have many articles and each article can belong to several users? Sounds like a has_and_belongs_to_many relationship. Have a look at the relevant Rails documentation:
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
In short you'd have a articles_users table, where each row consists of article_id and user_id. When adding a new users to an article, you just create another record in that table.
Alternatively you could look at has_many :through if you believe you'll work with that relationship as a separate entity. I.e. article has_many :users, through: authors.
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
To help you decide, the guide offers some advice:
http://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many
#app/models/user.rb
class User < ActiveRecord::Base
has_many :written_articles, class_name: "Article", foreign_key: :user_id
has_and_belongs_to_many :articles
end
#app/models/article.rb
class Article < ActiveRecord::Base
belongs_to :user #-> for the original owner
has_and_belongs_to_many :users
end
The above is a has_and_belongs_to_many association, which gives you the ability to add users to the article:
#config/routes.rb
resources :articles do
match "users/:id", to: :users, via: [:post, :delete] #-> url.com/articles/:article_id/users/:id
end
#app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def users
#article = Article.find params[:article_id]
#user = User.find params[:id]
if request.post?
#article.users << #user
elsif request.delete?
#article.users.delete #user
end
#redirect somewhere
end
end
This will allow you to use:
<%= link_to "Add User", article_user_path(#article, #user), method: :post %>
<%= link_to "remove User", article_user_path(#article, #user), method: :delete %>
I'm learning Rails (4.2 installed) and working on a social network simulation application.
I have setup an one to many relation between Users and Posts and now I'm trying to add also Comments to posts. After multiple tries and following the documentation on rubyonrails.org I ended up with the following setup:
User Model
has_many :posts, dependent: :destroy
has_many :comments, through: :posts
Post Model
belongs_to :user
has_many :comments
Comment Model
belongs_to :user
The comment is initiated from the Post show page, so the
Post Controller has:
def show
#comment = Comment.new
end
Now the question is: in Comments Controller , what is the correct way to create a new record.
I tried the below and many others, but without success.
def create
#comment = current_user.posts.comment.new(comment_params)
#comment.save
redirect_to users_path
end
(current_user is from Devise)
Also, afterwards, how can I select the post corresponding to a comment?
Thank you
You'll want to create a relation on Post, letting each Comment know "which Post it relates to." In your case, you'll likely want to create a foreign key on Comment for post_id, then each Comment will belong_to a specific Post. So, you'd add belongs_to :post on your Comment model.
So, your models become:
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
has_many :comments, through: :posts
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
end
class Comments < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
Then, to create a Comment, you would want to do one of two things in your controller:
Load the Post corresponding to the Comment you are creating via the URI as a parameter.
Pass the Post ID along in the form in which calls the create method on Comment.
I personally prefer loading the Post from a parameter in the URI, as you'll have less checking to do as far as authorization for can the Comment in question be added to the Post - e.g. think of someone hacking the form and changing the ID for the Post that the form originally sets.
Then, your create method in the CommentsController would look like this:
def create
#post = Post.find(post_id_param)
# You'll want authorization logic here for
# "can the current_user create a Comment
# for this Post", and perhaps "is there a current_user?"
#comment = #post.comments.build(comment_params)
if #comment.save
redirect_to posts_path(#post)
else
# Display errors and return to the comment
#errors = #comment.errors
render :new
end
end
private
def post_id_param
params.require(:post_id)
end
def comment_params
params.require(:comment).permit(...) # permit acceptable Comment params here
end
Change your Comment model into:
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user, through: :post
end
Pass the post_id from view template:
<%= hidden_field_tag 'post_id', #post.id %>
Then change your create action:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(comment_params)
#comment.user = current_user
redirect_to post_path(#post)
end
I have a discussion forum where users can see a list of unread posts. The way I'm doing this is to use a Look, User and Post model:
class Look < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts, through: :looks
has_many :looks
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :looks
has_many :users, through: :looks
end
So the way this works is that there is a list of all post IDs a user has viewed. It's created through the 'show' method:
def show
if current_user
viewer = current_user
view_ids = viewer.posts.pluck(:id).uniq
not_viewed = Post.where("id not in (?)", view_ids)
not_viewed_ids = not_viewed.pluck(:id)
unless Post.find(params[:id]).in?(not_viewed_ids)
Look.create(user: current_user, post: #post, viewstamp: Time.now)
end
end
end
This all works fine so far. The problem is I want to create a Look for all posts, so that I can essentially 'mark all as read'. This line works fine for creating a Look for the current post:
unless Post.find(params[:id]).in?(not_viewed_ids)
Look.create(user: current_user, post: #post, viewstamp: Time.now)
end
...but how do I make one that creates a Look for every post? Like this:
Look.create(user: current_user, post: [NEED ARRAY OF POSTS HERE], viewstamp: Time.now)
The reason I want to do this is so a user can mark all posts as read.
You can create the Look automatically just by adding the users to the posts.
Post.all.each { |p| p.users << current_user; p.save }
I'm trying to add a 'Collections' model to group Posts so that any user can add any Post they like to any Collection they've created. The Posts will have already been created by a different user. We are just letting other users group these posts in their own Collections. Basically like bookmarking.
What is the cleanest, and most Rails-ey-way of doing this?
I've created the model and run through the migration and what not. Also I've already created proper views for Collection.
rails g model Collection title:string user_id:integer
collections_controller.rb
class CollectionsController < ApplicationController
def index
#collections = current_user.collections.all
end
def show
#collection = Collection.all
end
def new
#collection = Collection.new
end
def create
#collection = current_user.collections.build(collection_params)
if #collection.save
redirect_to #collection, notice: 'saved'
else
render action: 'new'
end
end
def update
end
private
def collection_params
params.require(:collection).permit(:title)
end
end
collection.rb
class Collection < ActiveRecord::Base
belongs_to :user
has_many :posts
validates :title, presence: true
end
post.rb
has_many :collections
It seems like has_many or has_and_belongs_to_many associations are not correct? Should I be creating another model to act as an intermediary to then use
has_many :collections :through :collectionList?
If my association is wrong, can you explain what I need to change to make this work?
Also the next part in this is since this is not being created when the Post or Collection is created, I'm not sure the best way to handle this in the view. What is the best way to handle this, keeping my view/controller as clean as possible? I just want to be able to have a button on the Post#Show page that when clicked, allows users to add that post to a Collection of their own.
In such case you should use or has_and_belongs_to_many or has_many :through association. The second one is recommended, because it allows more flexibility. So now you should:
Create new model PostsCollections
rails g model PostsCollections post_id:integer collection_id:integer
and migrate it
Set correct model associations:
Something like:
class Post < ActiveRecord::Base
has_many :posts_collections
has_many :categories, through: :posts_collections
end
class Collection < ActiveRecord::Base
has_many :posts_collections
has_many :posts, through: :posts_collections
end
class PostsCollections < ActiveRecord::Base
belongs_to :post
belongs_to :collection
end
Then you'll be able to use
#collection.first.posts << #post
And it will add #post to #collection's posts
To add a post to a collection from view
Add a new route to your routes.rb, something like:
resources :collections do # you should have this part already
post :add_post, on: :member
end
In your Collections controller add:
def add_post
#post = Post.find(params[:post_id])
#collection = Collection.find(params[:id])
#collection.posts << #post
respond_to do |format|
format.js
end
end
As for views, you'll have to create a form to show a collection select and button to add it. That form should make POST method request to add_post_collection_path(#collection) with :post_id parameter.
You can read more explanations of how rails associations work in Michael Hartl's tutorial, because that subject is very wide, and can't be explained with short answer.
In Rails 3.2 I have been looking for a way to traverse the associations of an object within the before_add callback.
So basically my use case is:
class User < ActiveRecord::Base
has_and_belongs_to_many :meetings
end
class Meeting < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :comments, :before_add => :set_owner_id
end
class Comment < ActiveRecord::Base
belongs_to :meeting
end
def set_owner_id(child)
child.owner_id = <<<THE USER ID for #user >>>
end
and I am creating a comment within the context of a user:
#user.meetings.first.comments.create
How do I traverse the associations from within the before_add callback to discover the id of #user? I want to set this at model level. I have been looking at proxy_association, but I may be missing something. Any ideas?
You should probably create the comment in the context of the meeting, no? Either way, you should handle this in the controller since you'll have no access to #user in your model.
#comment = Meeting.find(id).comments.create(owner_id: #user, ... )
But if you insist on your way, do this:
#comment = #user.meetings.first.comments.create(owner_id: #user.id)