Limiting Comments on Posts Index in Rails App - ruby-on-rails

I am new to rails and trying to limit the number of comments displayed on my Posts Index Page to 2.
Below is my posts_controller:
class PostsController < ApplicationController
before_filter :authorize, :except => [:index, :show]
# GET /posts
# GET /posts.xml
def index
#posts = Post.all(:include => :comments, :order => "created_at DESC")
#comments = Comment.find(:all, :order => "created_at DESC", :limit => 1)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #posts }
end
end
# GET /posts/1
# GET /posts/1.xml
def show
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #post }
end
end
# GET /posts/new
# GET /posts/new.xml
def new
#post = Post.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #post }
end
end
# GET /posts/1/edit
def edit
#post = Post.find(params[:id])
end
# POST /posts
# POST /posts.xml
def create
#post = Post.new(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to(#post, :notice => 'Post was successfully created.') }
format.xml { render :xml => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
# PUT /posts/1
# PUT /posts/1.xml
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to(#post, :notice => 'Post was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.xml
def destroy
#post = Post.find(params[:id])
#post.destroy
respond_to do |format|
format.html { redirect_to(posts_url) }
format.xml { head :ok }
end
end
end
Below is my Post model
class Post < ActiveRecord::Base
has_attached_file :photo, :styles
=> { :medium => "600x600>", :thumb => "100x100>" },
:storage => :s3,
:s3_credentials => "#{RAILS_ROOT}/config/s3.yml",
:path => "/:attachment/:id/:style/:filename"
has_many :comments
validates :name, :presence => true
validates :title, :presence => true,
:length => { :minimum => 5 }
end
Below is my posts index view
<table>
<tr>
<th>BoxScore</th>
<th>Content</th>
</tr>
</table>
<% #posts.each do |post| %>
<%= image_tag post.photo.url(:medium), :class =>"floatleft" %>
<p>
<%= post.content %>
</p>
<div class="comments center">
<h3>Comments:</h3>
<%= render :partial => post.comments.reverse %>
<div class="links">
<% if admin? %>
<%= link_to "New Post", new_post_path %>
<%= link_to 'Edit', edit_post_path(post) %>
<%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %>
<% end %>
<%= link_to 'Comment on the Game', post %>
</div>
</div>
<% end %>
</div>
Comments Partial Below
<% div_for comment do %>
<p>
<strong>Posted <%= time_ago_in_words(comment.created_at) %> ago
</strong>
by
<br/>
<%= h(comment.commenter) %>
<br/>
<%= h(comment.body) %>
<br/>
<%= link_to 'More Comments', comment.post %>
</p>
<% end %>
I am not getting an error message, I just don't know how to limit the amount of comments we are rendering on Posts index page. Thanks

You can't specify conditions on eager loaded associations, unfortunately. Likewise, you can't limit the number of rows returned based on a condition (to my knowledge, though there are a lot of things I don't know about SQL functions).
So you're stuck with either:
Load all of the comments for the posts you're displaying in a query, and limit the number shown in your application.
Load only 2 comments for each of the posts you're displaying individually.
The optimal solution depends on your use case. If you're expecting to show only 5 posts and have thousands of comments on each, option 1 might not be very performant and option 2 might be a good solution. If you are expecting to show more posts per page and have only a handful of comments on any one (the more likely scenario), the first option is going to be your best bet.
Option 1
# controller
#posts = Post.limit(20).all
#comments = Comment.find(#posts.collect &:id).group_by &:post_id
# view
<% #comments[#post.id].first(2).each do |comment| %>
...
<% end %>
Option 2
# controller
#posts = Post.limit(5).all
# view
<% post.comments.limit(2).each do |comment| %>

Related

Rails unwanted duplication with the nested_form gem

I'm not sure what is happening here. I've checked everything here but I'm getting this double file upload field:
Even if I use only one field it still renders two images.
_form.html.erb:
<%= nested_form_for #home, :html=>{:multipart => true } do |f| %>
<% if #home.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#home.errors.count, "error") %> prohibited this blog from being saved:</h2>
<ul>
<% #home.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :body %><br />
<%= f.text_area :body %>
</p>
<p>
<%= f.fields_for :attachments do |attachment_form| %>
<%= attachment_form.file_field :file %>
<br /><br />
<%= attachment_form.link_to_remove "Remove this attachment" %>
<% for attachment in #home.attachments %>
<div class="show_image">
<%= image_tag attachment.file_url(:thumb).to_s %>
</div>
<% end %>
<% end %>
<br /><br />
<%= f.link_to_add "Add attachmnt", :attachments %>
</p>
<br /><br />
<p><%= f.submit %></p>
<br /><br />
<% end %>
Models:
attachment.rb:
class Attachment < ActiveRecord::Base
attr_accessible :description, :file
belongs_to :attachable, :polymorphic => true
mount_uploader :file, FileUploader
end
home.rb:
class Home < ActiveRecord::Base
attr_accessible :body, :attachments_attributes
has_many :attachments, :as => :attachable
accepts_nested_attributes_for :attachments, :allow_destroy => true
end
homes_controller.rb:
class HomesController < ApplicationController
# GET /homes
# GET /homes.json
def index
#homes = Home.all
respond_to do |format|
format.html # index.html.erb
format.json { render :json => #homes }
end
end
# GET /homes/1
# GET /homes/1.json
def show
#home = Home.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render :json => #home }
end
end
# GET /homes/new
# GET /homes/new.json
def new
#home = Home.new
respond_to do |format|
format.html # new.html.erb
format.json { render :json => #home }
end
end
# GET /homes/1/edit
def edit
#home = Home.find(params[:id])
end
# POST /homes
# POST /homes.json
def create
#home = Home.new(params[:home])
respond_to do |format|
if #home.save
format.html { redirect_to #home, :notice => 'Home was successfully created.' }
format.json { render :json => #home, :status => :created, :location => #home }
else
format.html { render :action => "new" }
format.json { render :json => #home.errors, :status => :unprocessable_entity }
end
end
end
# PUT /homes/1
# PUT /homes/1.json
def update
#home = Home.find(params[:id])
respond_to do |format|
if #home.update_attributes(params[:home])
format.html { redirect_to #home, :notice => 'Home was successfully updated.' }
format.json { head :no_content }
else
format.html { render :action => "edit" }
format.json { render :json => #home.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /homes/1
# DELETE /homes/1.json
def destroy
#home = Home.find(params[:id])
#home.destroy
respond_to do |format|
format.html { redirect_to homes_url }
format.json { head :no_content }
end
end
end
How do I stop this from happening?
Any help with this would be great, I've been looking at this for hours!
My first guess would be bad data created while getting this set up. Maybe there are previous attachments on the Home object that are not being displayed correctly, but still exist in the database. Does the same problem happen if you create a new Home and attempt to add attachments?
The fields_for block is creating a button and a display of each photo for each attachment it's being passed. I think that's why you're seeing the duplication. The fields_for is an iterator on attachments with a duplicate iterator inside it (for attachment in #home.attachments).

Rails 3 - Nested Forms and Default Values

I am trying to create form that contains another model in rails. I have accomplished this with using accepts_nested_attibutes and it is working great. The problem is I have an additional field in that table that records the User Name for each comment and I am not sure on how to insert that information when a new comment is being created. The username is being supplied by the Application Controller using the "current_user" method.
Regards,
Kyle
Comment Model
class Comment < ActiveRecord::Base
belongs_to :post
before_save :set_username
private
def set_username
self.created_by = current_user
end
end
Application Controller (This is just a Sandbox app so I just put a string in the method)
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
def current_user
"FName LName"
end
end
Show View
<p id="notice"><%= notice %></p>
<p>
<b>Title:</b>
<%= #post.title %>
</p>
<div id="show_comments"><%= render 'comments' %></div>
<div id="add_comments">
Add Comment
<%= form_for #post, :url => {:action => 'update', :id => #post.id}, :html => { :'data-type' => 'html', :id => 'create_comment_form' } do |f| %>
<%= f.fields_for :comments, #new_comment do |comment_fields| %>
<%= comment_fields.text_area :content %>
<%end%>
<div class="validation-error"></div>
<%= f.submit %>
<% end %>
</div>
Post Controller
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
#comments = #post.comments.all
format.html { redirect_to({:action => :show, :id => #post.id}, :notice => 'Post was successfully created.') }
format.xml { render :xml => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
I was originally thinking you could just set it as a default or a before_save in the model. But models don't have access to current_user. So it's probably best to just set the current user in the controller. It's not as DRY as putting it in the model but it's less hackey and potentially problematic this way.
def update
#post = Post.find(params[:id])
#post.attributes = params[:post]
#post.comments.each do |comment|
comment.created_by = current_user if comment.new_record?
end
respond_to do |format|
if #post.save
#comments = #post.comments.all
format.html { redirect_to({:action => :show, :id => #post.id}, :notice => 'Post was successfully created.') }
format.xml { render :xml => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
Just want to point out that it is possible to access current_user in the model scope. In this case I don think it is necessary, as the solution from #aNoble should work. So if is possible to set the current_user from the controller, I would prefer that.
In short, we add a method to the User class
class User < ActiveRecord::Base
cattr_accessor :current_user
def self.current_user
#current_user ||= User.new("dummy-user")
end
...
end
and in your application controllor, we add a before_filter that sets it. Make sure to call this filter after your authentication is done.
class ApplicationController < ActionController::Base
before_filter { |c| User.current_user = current_user }
end
And, then inside your Comment-model you could just do something like
class Comment
before_create :set_user
def set_user
created_by = User.current_user unless created_by
end
end
(so I only set the created_by if it was not yet set, and only upon creation of a new comment).

Accessing a referenced field using mongoid/mongodb/rails

If I have a model...
class Post
include Mongoid::Document
field :link
field :title
field :synopsis
field :added_on, :type => Date
validates_presence_of :link
embeds_many :replies
references_one :topic
end
and
class Topic
include Mongoid::Document
field :category
referenced_in :post
end
What would I need to code in index.html.erb to access data in topic or to add a topic to post.
I tried post.topic but I get an undefined method error.
Thank you very much.
Edit:
Here is the index.html code
<div id="post">
<% #posts.each do |post| %>
<div class="title_container">
<%= link_to post.title, post.link %> || <%= link_to 'Edit', edit_post_path(post) %> || <%= post.topic %>
</div>
<% end %>
<br />
<h2>Topics<h2>
<% for topic in #post.topics %>
<h3><%= topic.category %></h3>
<% end %>
</div>
here is the posts_controller
class PostsController < ApplicationController
# GET /posts
# GET /posts.xml
def index
#posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #posts }
end
end
# GET /posts/1
# GET /posts/1.xml
def show
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #post }
end
end
# GET /posts/new
# GET /posts/new.xml
def new
#post = Post.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #post }
end
end
# GET /posts/1/edit
def edit
#post = Post.find(params[:id])
end
# POST /posts
# POST /posts.xml
def create
#post = Post.new(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to(#post, :notice => 'Post was successfully created.') }
format.xml { render :xml => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
# PUT /posts/1
# PUT /posts/1.xml
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to(#post, :notice => 'Post was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.xml
def destroy
#post = Post.find(params[:id])
#post.destroy
respond_to do |format|
format.html { redirect_to(posts_url) }
format.xml { head :ok }
end
end
end
Edit:
I am also adding the relevant _form.html.erb code.
<div class="field">
<%= f.label :topic_id %>
<%= f.collection_select :topic, Post.topic, :id, :category, :prompt => "Select a Topic" %>
</div>
Edit:
Updated to 2.0.0.rc.7 still can't get it.
Tried the key method in the railscast video (http://railscasts.com/episodes/238-mongoid) just for fun. I get a "BSON::InvalidObjectId in PostsController#update" error.
I'm guessing a topic has many posts? If you want a referenced association you need to change it to this.
class Post
#...
referenced_in :topic
end
class Topic
#...
references_many :posts
end
Then try changing your collection_select line to this:
<%= f.collection_select :topic_id, Topic.all, :id, :category, :prompt => "Select a Topic" %>
Your post.rb file has a references_one :topic, but in your index view, you're doing for topic in #post.topics, implying that a post can have many topics. Either you need to change your model to say references_many :topics or change your view to work with only having one topic per post.
#user593120
Did you tried this in your index.html.erb
<div id="post">
<% #posts.each do |post| %>
<div class="title_container">
<%= link_to post.title, post.link %> || <%= link_to 'Edit', edit_post_path(post) %> || <%= post.topic %>
</div>
<% end %>
<br />
<h2>Topics<h2>
<% #posts.each do |post| %>
<h3><%= post.topic.category if post.topic %></h3>
<% end %>
</div>

PUT-ing a form to update a row, but I can't find the id. Where is it?

How should I be passing in the ID?
Error:
Couldn't find Product without an ID
Form:
<% form_for :product, #product, :url => { :action => :update } do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :names %><br />
<%= f.text_field :names %>
</p>
<p>
<%= f.submit 'Update' %>
</p>
<% end %>
Controller (for /products/edit/1 view):
def edit
#product = Product.find(params[:id])
end
Controller (to change the db):
def update
#product = Product.find(params[:id])
respond_to do |format|
if #product.update_attributes(params[:product])
format.html { redirect_to(#product, :notice => 'Product was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #product.errors, :status => :unprocessable_entity }
end
end
end
Try removing the :product from the form_for statement
Edit: Try removing the url option also. The issue is that the URL which you are posting to does not have an id attribute.
This form tag should be sufficent:
<% form_for #product do |f| %>
Make sure you have this line in your config/routes.rb:
map.resources :products
For extra credit, you can simplify loading the #product in your controller with a before_filter:
class ProductsController < ApplicationController
before_filter :load_product, :only => [:show, :edit, :update, :destroy]
def load_product
#product = Product.find(params[:id])
end
end

Adding my comments to the index view...Ruby on Rails

Ok...I am new to rails so this may be a stupid question but need help. I just watched and implemented Ryan Bates screencaset about setting up a blog. You can see it here http://media.rubyonrails.org/video/rails_blog_2.mov. Here is the skinny:
Two tables: Posts and Comments. Everything works great including the addition of comments via AJAX. The default development of this blog gives you the index.html.erb view where you can view all the posts
def index
#posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #posts }
format.json { render :json => #posts }
format.atom
end
end
The comments are only viewed via the show.html.erb page and is displayed via this code in that file:
<%= render :partial => #post %>
<p>
<%= link_to 'Edit', edit_post_path(#post) %> |
<%= link_to 'Destroy', #post, :method => :delete, :confirm => "Are You Sure" %> |
<%= link_to 'See All Posts', posts_path %>
</p>
<h2>Comments</h2>
<div id="comments">
<%= render :partial => #post.comments %>
</div>
<% remote_form_for [#post, Comment.new] do |f| %>
<p>
<%= f.label :body, "New Comment" %><br/>
<%= f.text_area :body %>
</p>
<p><%= f.submit "Add Comment"%></p>
<% end %>
What I am trying to do is to get similair representation of the comments functionality to exist in the index.html.erb view (which I will hide with javascript). Which currently just looks like this:
<h1>Listing posts</h1>
<%= render :partial => #posts %>
<%= link_to 'New post', new_post_path %>
My initial thought was just to put this exact same code that is in the show.html.erb file in the index.html.erb file but that doesn't work. I have tried a bunch of things here but I am not familiar enough with Rails (or coding for that matter) yet to do this in a timely manner. I get two main errors. Either that I passed a nil.comments error or an undefined object/method (can't remember).
My question is what do I need to included in the post_controller, the comments_controller and the index.html.erb file to accomplish this. To be complete I have included the code in each below.
POSTS_CONTROLLER
class PostsController < ApplicationController
before_filter :authenticate, :except => [:index, :show]
# GET /posts
# GET /posts.xml
def index
#posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #posts }
format.json { render :json => #posts }
format.atom
end
end
# GET /posts/1
# GET /posts/1.xml
def show
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #post }
end
end
# GET /posts/new
# GET /posts/new.xml
def new
#post = Post.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #post }
end
end
# GET /posts/1/edit
def edit
#post = Post.find(params[:id])
end
# POST /posts
# POST /posts.xml
def create
#post = Post.new(params[:post])
respond_to do |format|
if #post.save
flash[:notice] = 'Post was successfully created.'
format.html { redirect_to(#post) }
format.xml { render :xml => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
# PUT /posts/1
# PUT /posts/1.xml
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
flash[:notice] = 'Post was successfully updated.'
format.html { redirect_to(#post) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.xml
def destroy
#post = Post.find(params[:id])
#post.destroy
respond_to do |format|
format.html { redirect_to(posts_url) }
format.xml { head :ok }
end
end
private
def authenticate
authenticate_or_request_with_http_basic do |name, password|
name == "admin" && password == "secret"
end
end
end
COMMENTS_CONTROLLER
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create!(params[:comment])
respond_to do |format|
format.html { redirect_to #post}
format.js
end
end
end
INDEX.HTML.ERB
<h1>Listing posts</h1>
<%= render :partial => #posts %>
<%= link_to 'New post', new_post_path %>
Here's one simple solution. Step 1, edit your index action to include all the comments belonging to each post.
def index
#posts = Post.all(:include => :comments)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #posts }
format.json { render :json => #posts }
format.atom
end
end
Step 2, edit your index view to display each post, followed by its comments:
<h1>Listing posts</h1>
<% #posts.each do |post| %>
<%= render :partial => post %>
<%= render :partial => post.comments %>
<% end %>
<%= link_to 'New post', new_post_path %>
Edit: I'm not 100% sure of the best way to include the comment creation form also. What I'd try first is this (in index.html.erb):
Try changing your index view to:
<h1>Listing posts</h1>
<% #posts.each do |post| %>
<%= render :partial => post %>
<%= render :partial => post.comments %>
<% remote_form_for [post, Comment.new] do |f| %>
<p>
<%= f.label :body, "New Comment" %><br/>
<%= f.text_area :body %>
</p>
<p><%= f.submit "Add Comment"%></p>
<% end %>
<% end %>
<%= link_to 'New post', new_post_path %>
This should render the "New comment" form for a given post under the comments for that post, but I'm not sure (without actually trying it out) whether the form submission AJAX will successfully update and refresh the index page.

Resources