Empty item displaying from nested resource - ruby-on-rails

I'm new to Rails and am building a todo app.
For some reason an empty Item instance is displaying within all lists by default. Item.count is returning 0, but <%= render #list.items %> always has one "instance" of _item.html.erb.
I have #list assigned to List.find(params[:id]) in the list controller's show method and am calling <%= render #list.items %> in its show view.
I have this link in _item.html.erb
<%= link_to "Delete", [item.list, item],
method: :delete,
data: { confirm: "Are you sure?" }
%>
and it will display the delete link, but link to lists/*/items (as opposed to list/*/items/*) in the empty item, which gives a routing error. The link works fine in actual items though.
Controllers:
lists_controller.rb:
class ListsController < ApplicationController
before_action :set_list, only: [:show, :edit, :update, :destroy]
def index
#lists = policy_scope(List).where(author: current_user)
end
def show
#item = #list.items.build
end
def new
#list = List.new
authorize #list
end
def create
#list = List.new(list_params)
#list.author = current_user
authorize #list
if #list.save
flash[:notice] = "Your list was created."
redirect_to #list
else
flash[:error] = "Your list was not created."
render "new"
end
end
def edit
end
def update
if #list.update(list_params)
flash[:notice] = "Your list was updated."
redirect_to #list
else
flash[:error] = "Your list was not updated."
render "edit"
end
end
def destroy
if #list.destroy
flash[:notice] = "Your list was deleted."
redirect_to root_path
else
flash[:error] = "Your list was not deleted."
redirect_to #list
end
end
private
def list_params
params.require(:list).permit(:title, :description, :author_id)
end
def set_list
#list = List.find(params[:id])
authorize #list
end
end
items_controller.rb:
class ItemsController < ApplicationController
before_action :set_list
def create
#item = #list.items.create(item_params)
authorize #item
if #item.save
flash[:notice] = "Your item was added."
redirect_to #list
else
flash.now[:error] = "Your item was not added."
end
end
def destroy
#item = #list.items.find(params[:id])
authorize #item
if #item.destroy
flash[:notice] = "Your item was deleted."
redirect_to #list
else
flash.now[:error] = "Your item was not deleted."
end
end
private
def item_params
params.require(:item).permit(:name, :list_id)
end
def set_list
#list = List.find(params[:list_id])
end
end
Models:
list.rb:
class List < ActiveRecord::Base
validates :title, presence: true
validates :description, presence: true
belongs_to :author, class_name: "User"
has_many :items, dependent: :destroy
end
item.rb:
class Item < ActiveRecord::Base
validates :name, presence: true
belongs_to :list
end
Routes:
Rails.application.routes.draw do
root "lists#index"
resources :lists do
resources :items
end
devise_for :users
end
Reference migration:
class AddListsToItems < ActiveRecord::Migration
def change
add_reference :items, :list, index: true, foreign_key: true
end
end

The issue was caused by the way I passed the instance variable to the form.
This line in ListsController is building a new item on each page refresh.
def show
#item = #list.items.build
end
I removed that assignment, as well as its passing to the form.
<%= render "items/form", list: #list, item: #item %> becomes <%= render "items/form", list: #list %>
simple_form_for([list, item] becomes simple_form_for([#list, #list.items.build]

#item = #list.items.build is your problem.
The issue is that building a nested item will always yield an "empty" item, especially if it's nested.
Whilst you can use the code in the new action with impunity, using in show will basically just create a blank object that you have to contend with.
--
If you want to create a new item object when viewing your #list, you'll be better building / invoking on the fly (as you're doing already):
<%= form_for [#list, #list.items.new] ...

try
<%= render 'item' %>
and on your _item.html.erb (which is rendered), use the following code:
<%= #list.items %>

Related

Adding a comment model to associate with a post/user model

I finally was able to associate a user model with a post model with:
def create
#post = Post.new(post_params)
#post.user = current_user
Right now I'm trying to create a comment in the Post#Show page but continue to receive an error regarding my form. The two errors I'm running into are:
NoMethodError in Posts#show undefined method `comments_path'
when I have #comment = Comment.new in Post#show. When it's removed I get:
ArgumentError in Posts#show First argument in form cannot contain nil or be empty
What could possibly be wrong with my form? Also if someone can recommend a better way to associate my 3 models (basically have a user_id and a post_id when comment is created I'm open for suggestions). My comment form is going to appear within the Post#Show page. My current code is:
Comment Model
belongs_to :user
belongs_to :post
User Model
has_many :posts
has_many :comments
Post Model
has_many :comments
belongs_to :user
Comment Controller
class CommentsController < ApplicationController
before_action :find_comment, only: [:show, :edit, :update, :destroy]
def index
#comments = Comment.all
end
def new
#comment = Comment.new
end
def create
#post = Post.find(params[:id])
#comment = #post.comments.build(comment_params)
#comment.user = current_user
if #comment.save
flash[:notice] = "Successfully created..."
redirect_to comments_path
else
flash[:alert] = "failed"
redirect_to root_path
end
end
def show
end
def edit
end
def update
if #comment.update
flash[:notice] = "You updated your comment"
else
flash[:alert] = "Failed to update"
end
def destroy
#comment.destroy
redirect_to '/'
end
private
def find_comment
#comment = Comment.find(params[:id])
end
def comment_params
params.require(:comment).permit(:comment)
end
end
Post Controller
class PostsController < ApplicationController
before_action :find_post, only: [:show, :edit, :update, :destroy]
def index
#posts = Post.all
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
#post.user = current_user
if #post.save!
flash[:notice] = "Successfully created..."
redirect_to posts_path
else
flash[:danger] = "failed to add a post"
render 'new'
end
end
def show
end
def edit
end
def update
if #post.update
flash[:notice] = "Successfully updated"
redirect_to post_path
else
flash[:alert] = "Failed to update Post"
redirect_to :back
end
end
def destroy
if #post.destroy
flash[:notice] = "Successfully delete"
redirect_to posts_path
else
flash[:danger] = "Wasn't able to delete Blog post."
redirect_to :back
end
end
private
def find_post
#post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :body, :description)
end
end
Post#Show View
<%= render '/comments/form' %>
Comment#form
<%= form_for #comment do |f| %>
<%= f.label :comment, "Title" %>
<%= f.text_field :comment, placeholder: "Write a comment" %>
<%= f.submit "Submit" %>
<% end %>
If there is something missing please ask me to update my post. Thank you all who help me better understand my problem.
Your associations seem fine. The error
NoMethodError in Posts#show undefined method `comments_path'
means that you don't have the route comments_path or Comments#Create. Basically, when you have a form_for with a Comment as the parameter, it assumes you are wanting to go to the create or update route, depending if it's a new record. The easiest thing to do would be to add
resources :comments
to your routes file. However, since you want a comment associated with a post, you want to modify your routes file to have:
resources :posts do
resources :comments
end
Then, change your form to look like this:
<%= form_for [#post, #comment] do |f| %>
So when you submit a form, you will have a params[:post_id] and a params[:id] to play with. Find the Post with the params[:post_id]. Ignore the params[:id] when you're creating a comment, but use it when you're updating a comment.
Edit: Here is a link to some help regarding Nested Resourcing.

Rails Couldn't find Part with 'id'=27 [WHERE "parts"."active" = ?] RecordNotFound in PartsController#Destroy

I am trying to create a way for users on my website to delete a comment. I have set up my view like this
<li><%= comment.content%> by: <%= comment.user.first_name %> </li>
<% if logged_in? %>
<%=link_to 'delete', part_comment_path(#part, comment), data: {confirm: "Are you sure you want to delete this comment?"}, method: 'DELETE'%>
<%end%>
When I click the delete link I get the error "Couldn't find Part with 'id'=27 [WHERE "parts"."active" = ?]" which is coming from the parts controller. Currently my Comments controller is set up like this.
class CommentsController < ApplicationController
def create
part = Part.find(params[:part_id])
#comment = part.comments.create(comment_params.merge(user: current_user))
respond_to do |format|
format.html {redirect_to part}
format.js{}
end
end
def destroy
#part = Part.find(params[:part_id])
#comment = #part.comments.find(params[:id])
#comment.destroy
format.html {redirect_to part}
format.js{}
end
private
def comment_params
params.require(:comment).permit(:content)
end
end
and my Parts controller is set up like this
class PartsController < ApplicationController
before_filter :authorize, :except => [:index, :show]
def index
#parts = Part.all
#categories = Category.all
#parts = #parts.search(params[:search]) if params[:search].present?
end
def new
#part = Part.new
end
def show
#part = Part.find(params[:id])
end
def create
#part = Part.new(part_params)
if #part.save
redirect_to part_path(#part)
end
end
def edit
#part = Part.find(params[:id])
end
def update
#part = Part.find(params[:id])
if #part.update_attributes(part_params)
redirect_to #part
end
end
def destroy
#part = Part.find(params[:id])
#part.destroy
redirect_to parts_path
end
private
def part_params
params.require(:part).permit(:description, :name, :price, :active, :avatar, :discount, :category_id)
end
end
my routes for comments are nested
resources :parts do
resources :comments, only: [:create, :destroy]
end
My models are arranged so that a Comment belongs_to a Part and a Part has_many Comments. Thanks for the help with this one
I think delete method is not working .Try using this <%=link_to 'delete', part_comment_path(#part, comment), data: {confirm: "Are you sure you want to delete this comment?"}, method: :delete>.And check for any other comment

Undefined Method in 'comments_path' using "Commentable" form_for Polymorphic association

I'm getting an error raised of "undefined method 'comments_path' for..."
At this code in app/views/comments/_form.html.erb (line 1).
I recently tried to implement nestable comments via polymorphic associations on a website I'm building; however, I'm running into a problem that's not allowing me to move forward.
I'm trying to implement nested comments on a 'commentable' model (ie the blog in this case) and then when show is clicked, all the nested comments are shown and the individual can comment on the blog or reply to a comment (and hence result in nested comments) without leaving the page.
I've included a few other files to show the setup, but if I've missed one that's necessary, please let me know and I'll promptly add it. Any help is much appreciated. I've been stumped for several hours and I'm sure its something simple.
<%= form_for [#commentable, #comment] do |f| %>
<%= f.hidden_field :parent_id %></br>
<%= f.label :content, "New Comment" %></br>
<%= f.text_area :body, :rows => 4 %></br>
<%= f.submit "Submit Comment" %>
<% end %>
app/views/blogs/show.html.erb
<div class="content">
<div class="large-9 columns" role="content">
<h2>Comments</h2>
<div id="comments">
<%= nested_comments #comments %>
<%= render "comments/form" %>
</div>
</div>
</div>
app/controllers/comments_controller
class CommentsController < ApplicationController
def new
#parent_id = params.delete(:parent_id)
#commentable = find_commentable
#comment = Comment.new( :parent_id => #parent_id,
:commentable_id => #commentable.id,
:commentable_type => #commentable.class.to_s)
end
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
if #comment.save
flash[:notice] = "Successfully created comment."
redirect_to #commentable
else
flash[:error] = "Error adding comment."
end
end
private
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
def comment_params
params.require(:comment).permit(:parent_id, :body, :commentable_type, :commentable_id)
end
end
app/controller/blogs_controller.rb
class BlogsController < ApplicationController
before_filter :authenticate, :except => [ :index, :show ]
before_action :set_blog, only: [:show, :edit, :update, :destroy]
include MyModules::Commentable
# GET /blogs
# GET /blogs.json
def index
#blogs = Blog.all.order('created_at DESC')
respond_to do |format|
format.html
format.json
format.atom
end
end
# GET /blogs/1
# GET /blogs/1.json
def show
#blog = Blog.find(params[:id])
end
# GET /blogs/new
def new
#blog = Blog.new
end
# GET /blogs/1/edit
def edit
#blog = Blog.find(params[:id])
end
# POST /blogs
# POST /blogs.json
def create
#blog = Blog.new(blog_params)
respond_to do |format|
if #blog.save
flash[:success] = "Blog was sucessfuly created"
format.html { redirect_to #blog }
format.json { render :show, status: :created, location: #blog }
else
format.html { render :new }
format.json { render json: #blog.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /blogs/1
# PATCH/PUT /blogs/1.json
def update
respond_to do |format|
if #blog.update(blog_params)
flash[:success] = "Blog was successfully updated."
format.html { redirect_to #blog }
format.json { render :show, status: :ok, location: #blog }
else
format.html { render :edit }
format.json { render json: #blog.errors, status: :unprocessable_entity }
end
end
end
# DELETE /blogs/1
# DELETE /blogs/1.json
def destroy
#blog.destroy
respond_to do |format|
flash[:success] = "Blog was successfully deleted."
format.html { redirect_to blogs_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_blog
#blog = Blog.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def blog_params
params.require(:blog).permit(:title, :body, :image, :image_cache, :remote_image_url)
end
private
def authenticate
authenticate_or_request_with_http_basic do |name, password|
name == "admin" && password == "runfast"
end
end
end
my routes file looks like this...
Rails.application.routes.draw do
resources :blogs do
resources :comments
end
resources :applications
resources :reviews
resources :properties
root to: 'blogs#index'
end
the blog and comment models...
class Blog < ActiveRecord::Base
validates_presence_of :body, :title
has_many :comments, :as => :commentable, :dependent => :destroy
mount_uploader :image, ImageUploader
end
class Comment < ActiveRecord::Base
has_ancestry
belongs_to :commentable, :polymorphic => true
validates_presence_of :body
end
and finally the commentable module in lib/my_modules/commentable.rb
require 'active_support/concern'
module MyModules::Commentable
extend ActiveSupport::Concern
included do
before_filter :comments, :only => [:show]
end
def comments
#Commentable = find_commentable
#comments = #Commentable.comments.arrange(:order => :created_at)
#comment = Comment.new
end
private
def find_commentable
return params[:controller].singularize.classify.constantize.find(params[:id])
end
end
#blog
#<Blog id: 8, title: "New Blog Post about Databases.... Again", body: "RDM, the database management system, was designed ...", created_at: "2015-03-01 22:28:07", updated_at: "2015-03-03 00:11:07", image: "IMG_2210.JPG">
#commentable
#<Blog id: 8, title: "New Blog Post about Databases.... Again", body: "RDM, the database management system, was designed ...", created_at: "2015-03-01 22:28:07", updated_at: "2015-03-03 00:11:07", image: "IMG_2210.JPG">
app/helpers/comments_helper.rb
module CommentsHelper
def nested_comments(comments)
comments.map do |comment, sub_comments|
content_tag(:div, render(comment), :class => "media")
end.join.html_safe
end
end
#_params instance variable in better errors
{"utf8"=>"✓", "authenticity_token"=>"OH2tDdI5Kp54hf5J78wXHe//Zsu+0jyeXuG27v1REqjdAec7yBdlrVPLTZKEbLZxgR2L7rGwUwz5BlGTnPcLWg==", "comment"=>{"parent_id"=>"", "body"=>"Hello!\r\n"}, "commit"=>"Submit Comment", "controller"=>"comments", "action"=>"create", "blog_id"=>"8"}
You're getting the error in this view: app/views/blogs/show.html.erb.
The data for this view has been prepared in blogs#show. So in your view, you should have <%= form_for [#blog, #comment] do |f| %>, since you set #blog, not #commentable.
You should also do #comment = Comment.new. Not sure where you set this one...
Do <% raise #commentable.inspect %> in your view (app/views/blogs/show.html.erb). If it's nil, then that's why you're getting the error.

Associating nested attributes to user

I'm trying to build a small expense tracking app using Rails 4.1. Using devise for authorization. Expense and it's nested attribute, comments belong to a user. The associations are set up in the model and expenses are getting associated with the user. Here's the Expense controller:
class ExpensesController < ApplicationController
def new
#expense = Expense.new
#item = #expense.items.build
##comment = #expense.comments.build
end
def index
#expenses = Expense.all
##items = Item.where(:expense_id => #expense.id)
end
def show
#expense = Expense.find(params[:id])
#items = Item.where(:expense_id => #expense.id)
end
def create
#expense = current_user.expenses.new(expense_params)
respond_to do |format|
if #expense.save
ExpenseMailer.expense_submission(#expense).deliver
format.html { redirect_to #expense, notice: 'Expense Report Submitted.' }
format.json { render :show, status: :created, location: #expense }
else
format.html { render :new }
format.json { render json: #expense.errors, status: :unprocessable_entity }
end
end
end
def edit
#expense = Expense.find(params[:id])
end
def update
#expense = Expense.find(params[:id])
##comment = #expense.comments.build
if #expense.update(expense_params)
#if #comment.save
#ExpenseMailer.comments_added(#expense).deliver
flash[:notice] = "Expense Report Updated"
redirect_to expenses_path
#else
# flash[:notice] = "Expense Report Updated"
#redirect_to expenses_path
##end
else
render 'edit'
end
end
The form from where the comment attributes are built looks like:
<%= nested_form_for (#expense) do |f| %>
<div class="form-group">
<%= f.label :state %><br />
<%= f.select :state, Expense.states, :include_blank => false, class: "form-control" %>
</div>
<%= f.fields_for :comments, #expense.comments.build do |comment| %>
<div class="form-group">
<%= comment.label :comment%>
<%= comment.text_area :comment, class: "form-control" %>
</div>
<%= comment.hidden_field :commenter %>
<% end %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
</div>
The #comment.commenter = current_user isn't adding the current user id to the database. Should I include it in the expense controller somewhere?
You have to add:
#comment.commenter = current_user
below that if statement. Like this:
def create
#article = Expense.find(params[:expense_id])
if #comment = #expense.comments.create(comment_params)
#comment.commenter = current_user
#comment.save
ExpenseMailer.comments_added(#expense).deliver
redirect_to expenses_path
end
end
And then save the comment again. In your current code you're overwriting the #comment object with the newly created object by doing:
#comment = #expense.comments.create(comment_params)
but you haven't set the commenter on that new object anywhere yet.
Model
I just tried to create better code for your strong params, but I couldn't work out how to include the param in your nested attributes
I would therefore recommend using the inverse_of: method in your Comment model to get it sorted properly:
#app/models/expense.rb
Class Expense < ActiveRecord::Base
belongs_to :user
has_many :comments, inverse_of: :expense
accepts_nested_attributes_for :comments
end
#app/models/comment.rb
Class Comment < ActiveRecord::Base
belongs_to :expense, inverse_of: :comments
before_create :populate_expense, on: :create
private
def populate_expense
self.commenter_id = self.expense.user_id
end
end
This should work if you're populating the comments from the accepts_nested_attributes_for directive
Comments
I don't understand why you've created two create actions for both your expenses and comments controllers - the controller action is meant to be independent of the Model
What I'm trying to say is that if you think the comments#create controller action will be invoked by your nested attribute creation, you'd be mistaken - it is only invoked when you send a request to it through the Rails router :)
If you're creating Comments and Expenses separately, you'll be able to use these two different actions; but they won't be invoked by each other. Only Model methods can be invoked by the controller (you shouldn't be calling other controller methods)
If you wanted to create a Comment from the expenses#show page, here's how you'd set it up:
#config/routes.rb
resources :expenses do
resources :comments #-> domain.com/expenses/:expense_id/comments/new
end
#app/controllers/expenses_controller.rb
Class CommentsController < ApplicationController
def new
#expense = Expense.find params[:expense_id]
#comment = #expense.comments.new
end
def create
#expense = Expense.find params[:expense_id]
#comment = #expense.comments.new(comment_params)
#comment.save
end
private
def comment_params
params.require(:comment).permit(:comment, :params).merge(commenter_id: current_user.id)
end
end
This will work if you wanted to create a comment from the expenses#show page. If you do this, you need to ensure you are calling the comments#new / comments#create actions, rather than those of the expenses controller

Comments for microposts in Rails 4

After successfully completeling a micropost system. Logged in users can post through the form and create a post. I begun to add to this but allowing any user to comment on a post as well.
I get a multitude of errors. And for the most part I don't know what the problem is so would like some advise on how to get this finished. Right now the error is:
NameError in StaticPages#home
undefined local variable or method `micropost'
StaticPages#home is the page that displays everyones microposts, and thus the comments form as well. users#show is the public profile of users and displays the same as statispages (have yet to work out how to make posts to a users wall, eventually main page will be a general feed and user page will be a user-to-user feed).
Comments model
create_table "comments", force: true do |t|
t.string "content"
t.integer "user_id"
t.integer "micropost_id"
t.datetime "created_at"
t.datetime "updated_at"
end
User.rb
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :comments
micropost.rb
belongs_to :user
has_many :comments, dependent: :destroy
comment.rb
belongs_to :user
belongs_to :micropost
comments_controller.rb
class CommentsController < ApplicationController
before_filter :signed_in_user, only: [:create, :destroy]
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = Comment.new(params[:comment])
#comment.micropost = #micropost
#comment.user = current_user
if #comment.save
flash[:success] = "Comment created!"
redirect_to current_user
else
render 'shared/_comment_form'
end
end
end
microposts_controller.rb
class MicropostsController < ApplicationController
before_action :authenticate_user!
before_action :correct_user, only: :destroy
def index
#microposts = Micropost.all
#comment = #micropost.comments.build(params[:comment])
#comment.user = current_user
end
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
else
#feed_items = []
render root_url
end
end
def destroy
#micropost.destroy
redirect_to root_url
end
private
def micropost_params
params.require(:micropost).permit(:content)
end
def correct_user
#micropost = current_user.microposts.find_by(id: params[:id])
redirect_to root_url if #micropost.nil?
end
end
users_controller.rb
def show
#user = User.find(params[:id])
#microposts = #user.microposts
#comment = Comment.new
if user_signed_in?
#micropost = current_user.microposts.build
#feed_items = current_user.feed
end
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #user }
end
end
Anyplace calling the comment form
<%= render 'shared/comment_form', micropost: micropost %>
_comment_form.html.erb
<%= form_for([micropost, #comment]) do |f| %> #Here's the current error#
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, :placeholder => "Leave a comment" %>
</div>
<button class="btn" type="submit">
Create
</button>
<% end %>
Schema shows all the correct fields of the model. All DB migrations have been done. I've seen it say that in form_for([micropost, #comment]) the first field cannot be nil. I'm assuming this means that the comments aren't correctly filling micropost_id with the id of the micropost they attach to.

Resources