form_with produces first record as nil - ruby-on-rails

comment controller
class CommentsController < ApplicationController
before_action :load_commentable
before_action :checked_logged_in, only: [ :create]
def new
#comment = #commentabl.comments.new
end
def create
#comment = #commentable.comments.new(comment_params)
#comment.user_id = current_user.id
#comment.commenter = current_user.username
if #comment.blank? || #comment.save
flash[:success] = "Commented was created"
ActionCable.server.broadcast 'comment_channel',
commenter: current_user.username,
comment: #comment.content
redirect_to #commentable
else
flash[:danger] = render_to_string(:partial => 'shared/error_form_messaging',
:locals => {obj: #comment},
format: :html)
redirect_to #commentable
end
end
private
def comment_params
params.require(:comment).permit(:content, :commenter, :user_id)
end
def load_commentable
resource, id = request.path.split('/')[1,2]
#commentable = resource.singularize.classify.constantize.find(id)
end
def checked_logged_in
unless logged_in?
flash[:danger] = 'please log in to be able to comment'
redirect_to login_path
end
end
end
my form for creating a comment:
<%= form_with model:[commentable, commentable.comments.new], :html => {class: "form-horizontal", role:"form"} , local: true do |form| %>
<div class="form-group">
<div class="control-label col-sm-2">
<%= form.label :content, 'Comment' %>
</div>
<div class="col-sm-8">
<%= form.text_field :content , class: 'form-control', placeholder: "enter your comment here", autofocus: true %>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<%= form.submit 'Comment' , class: ' btn btn-primary' %>
</div>
</div>
<% end %>
the form is called in show.html.erb
<h2 class="text-center">
Title: <%= #article.title %>
</h2>
<div class="well col-xs-8 col-xs-offset-2">
<div id="user-info-showpage" align="center">
Created by: <%= render 'shared/user-info', obj: #article.user %>
</div>
<h4 class="text-center">
<strong>Description:</strong>
</h4>
<hr />
<%= simple_format(#article.description) %>
<% if #article.categories.any? %>
<p>Categories: <%= render #article.categories %></p>
<% end %>
<div class="article-actions">
<% if logged_in? && (current_user == #article.user || current_user.admin?) %>
<%= link_to "Delete", article_path(#article), method: :delete,
data: {confirm: "Are you sure you want to delete the article?"},
class: 'btn btn-xs btn-danger' %>
<%= link_to "Edit", edit_article_path(#article), class: 'btn btn-xs btn-success'%>
<%end%>
<%= link_to "View All Articles", articles_path , class: 'btn btn-xs btn-primary'%>
</div>
</div>
<% if logged_in? %>
<div class="col-xs-8 col-xs-offset-2">
<%#= render partial: 'comments/form', :locals => {commentable: #article} %>
</div>
<%end%>
<div class="col-xs-8 col-xs-offset-2">
<div id="comments"></div>
<%= #article.comments.inspect %>
<% #article.comments.each do |c| %>
<div class="well">
<%= c.content %> by
<%= c.commenter %>
</div>
<%end%>
<div id="comments"></div>
</div>
my result is in view is
Please if more info needed, ask me so I can provide
Note: I am not sure this empty record is owing to commentable.comments to be nil or I miss something
I commented render form in show page and now the empty record is gone, so my issue must be related to form_with

From my understanding, you
Expect:
in your articles#show page to not show the empty by _________ <div> HTML because the comment is still built (still in-memory), and not yet saved (not yet in DB).
Solution 1:
app/views/articles/show.html.erb
...
<div class="col-xs-8 col-xs-offset-2">
<div id="comments"></div>
<% #article.comments.each do |c| %>
<!-- ADD THIS LINE -->
<% if c.persisted? %>
<div class="well">
<%= c.content %> by
<%= c.commenter %>
</div>
<% end %>
<%end%>
<div id="comments"></div>
</div>
...
Solution 2 (better but still is a workaround):
app/views/comments/_form.html.erb
<%= form_with model:[commentable, Comment.new(commentable: commentable)], :html => {class: "form-horizontal", role:"form"} , local: true do |form| %>
Explanation:
The reason the page is displaying an empty by _________ <div> is that because you "built" a new comment before .each is called. Because they are sharing same memory space, the build basically also adds it to the array in-memory. See the following:
# rails console
article = Article.create!
comment1 = Comment.create!(commentable: article)
# from here, comment1 is then saved already in the DB
# now let's see what happens when you use "build" or "new"
# They have differences, it seem: for details: https://stackoverflow.com/questions/1253426/what-is-the-difference-between-build-and-new-on-rails/1253462
# redefine (to simulate your #article = Article.find(params[:id])
article = Article.find(article.id)
comment2 = article.comments.build
puts article.comments.count
# SQL: Select count(*) FROM ...
# => 1
puts article.comments.size
# => 2
# notice how `count` and `size` are different. `count` value is "DB-based" while `size` is "memory-based". This is because `count` is an `ActiveRecord` method while `size` is a delegated `Array` method.
# now let's simulate your actual problem in the view, where you were looping...
article.comments.each do |comment|
puts comment
end
# => <Comment id: 1>
# => <Comment id: nil>
# notice that you got 2 comments:
# one is that which is already persisted in DB
# and the other is the "built" one
# the behaviour above is to be expected because `.each` is a delegated `Array` method
# which is agnostic to where its items come from (DB or not)
This is the reason why in your page, the "built" comment is shown in the page because you are calling
<%= render partial: 'comments/form', :locals => {commentable: #article} %>
... which calls commentable.comments.build
BEFORE the <% "article.comments.each do |c| %>
If this is not clear enough yet, try putting
<%= render partial: 'comments/form', :locals => {commentable: #article} %>
... which calls commentable.comments.build
AFTER the <% "article.comments.each do |c| %> ... <% end %>
... and the by _________ <div> should already not show up.

Related

How does my index page need to be written in order to display data?

I am developing a form that is dropdown in nature for the first time. With the help of some of you I have been successful thus far, but at this point I am creating the app/index.html.erb page:
<% #jobs.each do |job| %>
<h2><%= #job.category %></h2>
<p><%= #job.poster %></p>
<% end %>
The code above is rendering the following error:
NoMethodError in Jobs#index
undefined method `category' for nil:NilClass
I thought the code was telling me that I had nil in category which I did and so I updated category as well as others in rails console, but I continue to receive this error and I am not sure why nor how to fix it.
In my app/show.html.erb the code is similar:
<h1><%= #job.category %></h1>
<p><%= #job.poster %></p>
<p><%= #job.location %></p>
<p><%= #job.description %></p>
<%= link_to "Home", root_path %>
and it works just fine.
This is my form partial:
<%= simple_form_for(#job, html: {class: 'form-horizontal'}) do |f| %>
<div class="control-group">
<%= f.label "Poster:", class: 'control-label' %>
<div class="controls">
<%= f.select(:poster, options_for_select([['Nick Maloney','Nick Maloney'],
['Peter Brown','Peter Brown'],['Jen Morris','Jen Morris']])) %>
</div>
</div>
<div class="control-group">
<%= f.label "Category:", class: 'control-label' %>
<div class="controls">
<%= f.select(:category, options_for_select([['Landscaping','Landscaping'],
['Babysitting','Babysitting'],['Tree planting','Tree planting']])) %>
</div>
</div>
<div class="control-group">
<%= f.label "Location:", class: 'control-label' %>
<div class="controls">
<%= f.select(:location, options_for_select([['Dorchester','Dorchester'],
['Roxbury','Roxbury'],['Mattapan','Mattapan']])) %>
</div>
</div>
<div class="control-group">
<%= f.label "Status:", class: 'control-label' %>
<div class="controls">
<%= f.select(:status, options_for_select([['New','New'],
['Pending','Pending'],['Complete','Complete']])) %>
</div>
</div>
<%= f.input :description, label: "Job Description" %>
<%= f.submit 'Add Job', class: 'btn btn-default' %>
<% end %>
and this is my jobs_controller.rb:
class JobsController < ApplicationController
before_action :find_job, only: [:show, :edit, :update, :destroy]
def index
#jobs = Job.all.order("created_at DESC")
end
def show
end
def new
#job = Job.new
end
def create
#job = Job.new(jobs_params)
if #job.save
redirect_to #job, notice: 'Your job was successfully added'
else
render "New"
end
end
def edit
end
def update
end
def destroy
end
private
def jobs_params
params.require(:job).permit(:poster, :category, :location, :status, :description)
end
def find_job
#job = Job.find(params[:id])
end
end
It depends on what value has #job, note that when you iterate over jobs assign a block variable called job, but then inside you want to get the category attribute from #job, the instance variable, and Rails will try to know where does that variable come from and what's the value.
Try job.attribute:
<% #jobs.each do |job| %>
<h2><%= job.category %></h2>
<p><%= job.poster %></p>
<% end %>
#job isn't available in your index view, because your index method has a #jobs variable with all the Job records. And the reason you can access #job.category in your show method is because you have a #job variable created in the private method find_job which is available in such method thanks the before_action callback setted in the second line.

undefined method `comments'

I'm working on ROR project and created the partial form for the comments but i get the error in show.html.rb "undefined method `comments'".I have tried to find what's long but no luck.The highlight of the error is on this image
Image
here is my _form.html.erb
<%= simple_form_for ([#message, #message.comments.build]) do |f| %>
<%= f.input :content , label: "Comments" %>
<%= f.button :submit, :class => "btn-custom" %>
<% end %>
class CommentsController < ApplicationController
def create
#message = Message.find(params[:message_id])
#comment = #message.comments.create(comment_params)
#comment.user_id = current_user.user_id
if #comment.save
redirect_to message_path(#message)
else
render 'new'
end
end
private
def comment_params
params.require(:comment).permit(:content)
end
end
And this is my show.html.rb
<div class="col-md-10 col-md-offset-1">
<div class="message-show">
<h2><%=#message.title %></h2>
<p class="message-posted-by"><%= time_ago_in_words(#message.created_at) %>
ago </p>
<p class="message-desc"><%= #message.description %></p>
<h3 class="comment-section-header">Discussion:</h3>
<p><%= render #message.comments %></p>
<h3 class="reply-to-msg">Reply</h3>
<%= render 'comments/form' %>
<div class="links btn-group">
<%= link_to "Back", root_path, class: "btn btn-default" %>
<%= link_to "Edit", edit_message_path, class: "btn btn-primary" %>
<%= link_to "Delete",message_path(#message), method: :delete,data: {confirm:"Are you sure?"} , class: "btn btn-danger" %>
</div>
</div>
</div>
Seems like your Message model is missing an association to comments:
# in app/models/message.rb
has_many :comments

How can I render a partial with a new instance of my object from the create action of that object's controller in Rails?

I have a series of hard-coded questions in my app. A SaleQualifier picks up each question, and depending on the Answer (SaleQualifier has_one Answer) the app decides which subsequent question to present to the User next.
The SaleQualifier belongs_to SalesOpportunity - and the question and answer field are both rendered inside a partial on my SalesOpportunity show page. This works fine when I'm reloading the entire page every time the question is answered, but I want to use a remote: true link and create.js.erb file in the SaleQualifier view to render the next partial without a full page refresh.
Sales Opportunity Controller:
# GET /sales_opportunities/1
# GET /sales_opportunities/1.json
def show
#sales_opportunity = SalesOpportunity.includes(:company, :user, :timeline_events, :sale_contacts, :swots, :sale_competitors).find(params[:id])
#sale_qualifier = SaleQualifier.new(sales_opportunity_id: params[#sales_opportunity.id])
#answer = #sale_qualifier.build_answer
#question = find_current_question
end
As you can see I'm setting up a new SaleQualifier here, building an answer for it, and finding the relevant question using the method below:
def find_current_question
question = Question.find(#sales_opportunity.next_question)
return question
end
The partial is as follows (I've tried passing locals, but this isn't working as I think the issue is in the create action of the SaleQualifier controller):
<div class="panel panel-default">
<%= form_for(#sale_qualifier, :html => {role: :form, 'data-model' => 'sale_qualifier'}, remote: true) do |f| %>
<% if #sale_qualifier.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#sale_qualifier.errors.count, "error") %> prohibited this answer from being saved:</h2>
<ul>
<% #sale_qualifier.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="panel-body">
<div class="col-sm-6">
<h2><%= #question.question_text %></h2>
</div>
<div class="col-sm-6">
<div class="form-group">
<%= f.hidden_field :sales_opportunity_id, :value => #sales_opportunity.id %>
</div>
<div class="form-group">
<%= f.hidden_field :question_id, :value => #question.id %>
</div>
<%= f.fields_for :answer do |answer| %>
<% if #question.answer_type == 'Text Field' %>
<%= answer.text_area :answer_text, :placeholder => "Enter your answer" %>
<% end %>
<% if #question.answer_type == 'Datetime' %>
<div class='input-group date' id='datetimepicker' data-date-format="YY.MM.DD">
<%= answer.text_field :answer_text, class: "form-control", data: { date_format: 'YYYY/MM/DD' }, :placeholder => "YYYY/MM/DD" %>
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
<% end %>
<% if #question.answer_type == 'Boolean' %>
<%= answer.select :answer_text, [['Yes', true], ['No', false]] %>
<% end %>
<% if #question.answer_type == 'Update' %>
<%= answer.hidden_field :answer_text, :value => "Updated" %>
<% end %>
<% end %>
<% if #question.answer_type == 'Update' %>
<div class="actions">
<%= f.submit "Done", class: "btn btn-large btn-success"%>
</div>
<% else %>
<div class="actions">
<%= f.submit "Submit", class: "btn btn-large btn-success"%>
</div>
<% end %>
<% end %>
</div>
</div>
</div>
Upon submission of this a bunch of code runs behind the scenes to update the SalesOpportunity so the correct next question is rendered.
The create action of my SaleQualifier Controller is as-follows:
def create
#sale_qualifier = SaleQualifier.new(sale_qualifier_params)
#sales_opportunity = #sale_qualifier.sales_opportunity
respond_to do |format|
if #sale_qualifier.save
format.html { redirect_to #sales_opportunity, notice: 'Sale qualifier was successfully created.' }
format.json { render :show, status: :created, location: #sale_qualifier }
format.js
else
format.html { render :new }
format.json { render json: #sale_qualifier.errors, status: :unprocessable_entity }
end
end
end
I believe my issue stems here - because the partial is expecting to see #sale_qualifier as per the definitions set up in my SalesOpportunity show action, but instead #sale_qualifier here is referring to the newly saved instance of that model. As such when I try to use this with remote: true set, I get the following error:
ActionView::Template::Error (undefined method `question_text' for nil:NilClass):
12: <% end %>
13: <div class="panel-body">
14: <div class="col-sm-6">
15: <h2><%= #question.question_text %></h2>
16: </div>
17: <div class="col-sm-6">
18: <div class="form-group">
app/views/sale_qualifiers/_qualify.html.erb:15:in `block in _app_views_sale_qualifiers__qualify_html_erb__3308770490925429645_70100598754380'
app/views/sale_qualifiers/_qualify.html.erb:2:in `_app_views_sale_qualifiers__qualify_html_erb__3308770490925429645_70100598754380'
app/views/sale_qualifiers/create.js.erb:2:in `_app_views_sale_qualifiers_create_js_erb__2206693572750561934_70100598786040'
app/controllers/sale_qualifiers_controller.rb:32:in `create'
The create.js.erb code:
//update the sale qualifier div with the next question
$('#sale_qualifier_div').html("<%= escape_javascript(render :partial => 'sale_qualifiers/qualify', :locals => { :sale_qualifier => #sale_qualifier, :answer => #answer, :question => #question} )%>");
Is there a way I can pass these variables to this partial?
I've managed to solve this (but would appreciate anyone who can tell me why this is a bad idea/give me a better option) through using a block passed to the format.js action where I reset these instance variables:
format.js {
#sales_opportunity = SalesOpportunity.find_by(id: session[:sales_opportunity_id])
#sale_qualifier = SaleQualifier.new(sales_opportunity_id: params[#sales_opportunity.id])
#answer = #sale_qualifier.build_answer
#question = Question.find(#sales_opportunity.next_question)
}
This works, but it feels ugly/smelly.

Rails: Two different NoMethodError's when trying to display files

I'm making a basic application and I made it so a user can attach a file to a form post. That all works perfectly fine, but now I'm trying to display a link to the file and it doesn't seem to work.
I'm getting two errors. One if I attach a file and another if I don't attach a file. They both say undefined method 'doc=' for nil:NilClass but are on different lines of code.
If I don't upload a file this is what I get: NoMethodError in Projects#index on this line of code <% if #project.doc %>.
If I do upload a file this is what I get: NoMethodError in ProjectsController#create on this line of code #project.doc = uploaded_io.original_filename
projects_controller.rb
class ProjectsController < ApplicationController
def index
#projects = Project.all
end
def show
end
def new
#projects = Project.new
end
def create #no view
#projects = Project.new(project_params)
uploaded_io = params[:doc]
if uploaded_io.present?
File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'wb') do |file|
file.write(uploaded_io.read)
#project.doc = uploaded_io.original_filename
end
end
if #projects.save
redirect_to projects_path, :notice => "Your project was sent!"
else
render "new"
end
end
def edit
#projects = Project.find(params[:id])
end
def update #no view
#projects = Project.find(params[:id])
if #projects.update_attributes(project_params)
redirect_to projects_path, :notice => "Your project has been updated."
else
render "edit"
end
end
def destroy #no view
#projects = Project.find(params[:id])
#projects.destroy
redirect_to projects_path, :notice => "Your project has been deleted."
end
private
def project_params
params.require(:project).permit(:title, :description)
end
end
index.html.erb
<div class="container">
<div class="page-header">
<h1>Projects<small> Here are all of your projects.</small></h1>
</div>
</div>
<% #projects.each do |project| %>
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<%= project.title %>
</div>
</div>
<div class="panel-body">
<p>
<%= project.description %>
</p>
<br>
<%= link_to "Discuss", new_project_discussion_path(project) %> |
<%= link_to "Tasks", new_project_task_path(project) %> |
<%= link_to "Edit", edit_project_path(project) %> |
<%= link_to "Delete", project, :method => :delete %>
<% if #project.doc %>
<p>Document: <%= link_to #project.doc, "/uploads/#{#project.doc}", :target => "_blank" %></p>
<% end %>
</div>
</div>
<%end%>
<br>
<br>
<div class="container">
<p><a class="btn btn-primary btn-lg" href="/projects/new" role="button">Create project</a></p>
</div>
_form.html.erb
<%= form_for(#projects, :html => { :multipart => true}) do |f| %>
<% if #projects.errors.any? %>
<ul>
<% #projects.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
<% end %>
<div class="container">
<div class="form-group">
<%= f.label :title %>
<%= f.text_field :title, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :description %>
<%= f.text_area :description, class: "form-control" %>
</div>
<%= label_tag :doc, 'Files (optional)' %>
<%= file_field_tag :doc %>
<br>
<div class="form-group">
<%= f.submit "Submit Project", class: "btn btn-primary" %>
</div>
<% end %>
Updated :
You have many errors, here are a few that I found :
uploaded_io = params[:doc]
Should be
uploaded_io = params[:project][:doc]
Also delete this line
#project.doc = uploaded_io.original_filename
You don't need that.
Finally, in your views, you should have project.doc instead of #project.doc

How do I re-populate form fields when validation fails?

This is the erb template:
<div id='recipe-form'>
<% if #recipe.errors %>
<div id='errors'>
<% #recipe.errors.messages.each do |field, messages| %>
<div class='error'>
<div class=field'><%= field %></div>
<div class='messages'>
<ul>
<% messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
</div>
<% end %>
</div>
<% end %>
<%= form_for #recipe, :html => {:multipart => true}, :url => '/recipes' do |f| %>
<%= f.label :title, 'title' %>
<%= f.text_field :title %>
<div id="photo-upload">
<%= file_field :photo0, :image, :id => 0 %>
</div>
<div id='existing-photos'>
<% recipe.photos.each do |photo| %>
<div id='<%= photo.id %>'>
<img src='<%= photo.image.url(:thumb) %>' />
<ul>
<li>
<%= link_to 'delete',
recipe_photo_url(
:recipe_id => #recipe.slug,
:id => photo.id
),
:method => :delete,
:remote => true
%>
</li>
</ul>
</div>
<% end %>
</div>
<%= f.label :body, 'body' %>
<%= f.cktext_area :body, :ckeditor => {:width => "500"} %>
<%= f.label :tags, 'tags (comma separated)' %>
<%= text_field_tag :tags %>
<%= submit_tag 'submit' %>
<% end %>
</div>
This is the create action:
def create
#recipe = Recipe.new(params[:recipe])
photo_keys = params.keys.select{|k|k.match(/^photo/)}
#photos = []
photo_keys.each do |photo_key|
#photos << Photo.new(params[photo_key])
end
#recipe.tags = Tag.parse(params[:tags])
#recipe.author = current_user
if #recipe.save &&
#photos.all?{|photo|photo.save}
#photos.each do |photo|
photo.recipe_id = #recipe.id
photo.save
end
flash[:notice] = 'Recipe was successfully created.'
redirect_to recipe_url(#recipe.slug)
else
flash[:error] = 'Could not create recipe. '
flash[:error] += 'Please correct any mistakes below.'
render :action => :new
end
end
And this is the new action:
def new
#recipe = Recipe.new
end
I read that if I use form_for as I am using above, the fields will be re-populated automatically.
When I inspect #recipe.errors from within the erb template, I can see that the errors generated by create are also available when the new action is rendered, but the fields do not re-populate.
I'm actually not sure about what render action: does but what I do and works is: Instead of rendering the action just render the template using render :new.
You need to set the same instance variables (those with #), which you already in your create action.

Resources