Create and edit object using modal in rails - ruby-on-rails

I've been trying to create and edit an object using a modal view in the index html in Rails. I'm using twitter bootstrap in the project. I m so far successful in creating the object using the modal.
For things to work, I had to create an object called #post = Post.new in my index action.
Since before the edit modal is displayed, the edit object must be ready as #post = Post.find(params[:id]), but it happens in the edit action.
Is their a way using which I can initialize #post for my edit modal view before it is displayed?
Here's my code:
index.html.erb
<div class="page-header">
<h1>Posts</h1>
</div>
<% #posts do |post| %>
<tr>
<td><%= post.name %></td>
<td><%= post.description %></td>
<td><%= link_to 'Edit', edit_post_path(post), :class => 'btn btn-mini btn-min-standard-width', :data => {:toggle => "modal", :target => "#editItemModal"} , :remote => true %>
<%= link_to 'Destroy', post, confirm: 'Are you sure?', method: :delete, :class => 'btn btn-mini btn-danger btn-min-standard-width' %></td>
</tr>
<% end %>
<%= link_to 'New Item', new_post_path,
:class => 'btn btn-primary btn-standard-width' , :data => {:toggle => "modal", :target => "#newItemModal"} , :remote => true %>
<div id="newItemModal" class="modal hide fade" >
<button type="button" class="close" data-dismiss="modal">×</button></h1>
<div class="page-header">
<h1>New item </h1>
</div>
<%= render :partial => 'form' %>
</div>
<div id="editItemModal" class="modal hide fade" >
<button type="button" class="close" data-dismiss="modal">×</button></h1>
<div class="page-header">
<h1>Edit item </h1>
</div>
<%= render :partial => 'form' %>
</div>
_form.html.erb
<%if #post%>
<%= form_for(#post, :html => { :class => 'form-horizontal', :remote => true } ) do |f| %>
<div class="control-group">
<%= f.label :name, :class => 'control-label' %>
<div class="controls">
<%= f.text_field :name %>
</div>
</div>
<div class="control-group">
<%= f.label :description, :class => 'control-label' %>
<div class="controls">
<%= f.text_area :description %>
</div>
</div>
<div class="form-actions">
<%= f.submit nil, :class => 'btn btn-primary' %>
</div>
<% end %>
<% end %>
PostController
class PostsController < ApplicationController
def index
#post = Post.new
#posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
def show
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #post }
end
end
def new
#post = Post.new
end
def edit
#post = Post.find(params[:id])
respond_to do |format|
format.html
format.json { render json: #post }
end
end
............

As long as I understand you want to use instance variable #post in the edit modal view.
Without knowing your code base, what can I say:
Find the corresponding action in the controller that renders the edit modal view, create one if there is no
Initialize #post with the model Post found by id, just simply copy and paste that into the proper action
Call it within the view scope
Hope that answers your question

Related

form_with produces first record as nil

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.

more rails partials on the same page

I have a rails app. I'm displaying more partials (user, task, conversation, message) from different classes on the users/:id/show page. I set all the instance variables (for other classes as well) in the users.controller's def show action.
It seems to be a bit heavy, so is there a better approach than this? (I'm using #task and #message for the AJAX calls.)
def show
#user = User.find(params[:id])
if Task.between(current_user.id, #user.id).present?
#tasks = Task.uncompleted.between(current_user.id, #user.id).order("created_at DESC").includes(:assigner).paginate(page: params[:page], per_page: 12)
#task = Task.new
if Conversation.between(current_user.id, #user.id).present?
#conversation = Conversation.between(current_user.id, #user.id).first
#messages = #conversation.messages.includes(:user)
#message = Message.new
respond_to do |format|
format.html
format.js { render :template => "tasks/update.js.erb", :template => "tasks/destroy.js.erb", layout: false }
end
end
else
redirect_to user_profile_path(#user)
end
end
UPDATED:
users/show:
<%if #conversation%>
<%= render 'conversations/show' %>
<% end %>
<tbody class="newtaskinsert2">
<%= render partial: "tasks/task_between", collection: #tasks, as: :task %>
</tbody>
conversations/_show:
<div class="chatboxcontent">
<% if #messages.any? %>
<%= render #messages %>
<% end %>
</div>
<div class="chatboxinput">
<%= form_for([#conversation, #message], :remote => true, :html => {id: "conversation_form_#{#conversation.id}"}) do |f| %>
<%= f.text_area :body, class: "chatboxtextarea", "data-cid" => #conversation.id %>
<% end %>
<%= form_for([#conversation, #message], html: {class: "refile_form"}, remote: true) do |form| %>
<span class="btn btn-success btn-sm btn-file">Choose file
<%= form.attachment_field :message_attachment, direct: true, presigned: true, class: "choosefile" %></span>
<%= form.submit "Send File", class: "btn btn-primary btn-sm btn-submit-refile", style:"display:none"%>
<% end %>
<span id="progresspercent"></span>
</div>
You can keep only #user instance variable in the controller, and in partials use: #user.tasks instead of #tasks, #user.tasks.new instead of #task etc. Note also, that you can pass parameters to partials (3.4.4 Passing Local Variables)
For sample:
<%= render partial: "your_partial", locals: {tasks: #user.tasks} %>
Update:
With your way (call methods from class instead of objects) you can do something like it:
def show
#user = User.find(params[:id])
if Task.between(current_user.id, #user.id).present?
# #user.tasks.where(another_user_field_name: current_user).present? - looks more like Rails way
#tasks = Task.uncompleted.between(current_user.id, #user.id).order("created_at DESC").includes(:assigner).paginate(page: params[:page], per_page: 12)
#conversation = Conversation.between(current_user.id, #user.id).first
if #conversation
respond_to do |format|
format.html
format.js { render :template => "tasks/update.js.erb", :template => "tasks/destroy.js.erb", layout: false }
end
end
# Do not forget that if #conversation is not exists this code render views by default way
else
redirect_to user_profile_path(#user)
end
end
<%= render 'conversations/show' %>
<tbody class="newtaskinsert2">
<%= render partial: "tasks/task_between"%>
</tbody>
<%if #conversation%>
<div class="chatboxcontent">
<%= render '_your_messages_partial', locals: {messages: #conversation.messages.includes(:user)}%>
</div>
<div class="chatboxinput">
<%= form_for([#conversation, #conversation.messages.new], :remote => true, :html => {id: "conversation_form_#{#conversation.id}"}) do |f| %>
<%= f.text_area :body, class: "chatboxtextarea", "data-cid" => #conversation.id %>
<% end %>
<%= form_for([#conversation, #conversation.messages.new], html: {class: "refile_form"}, remote: true) do |form| %>
<span class="btn btn-success btn-sm btn-file">Choose file
<%= form.attachment_field :message_attachment, direct: true, presigned: true, class: "choosefile" %></span>
<%= form.submit "Send File", class: "btn btn-primary btn-sm btn-submit-refile", style:"display:none"%>
<% end %>
<span id="progresspercent"></span>
</div>
<% end %>
You can make this code more shorter in case of using relations (#user.conversations instead of Conversation.between... etc)
I would suggest to use caching techniques for your views:
http://edgeguides.rubyonrails.org/caching_with_rails.html#fragment-caching
http://edgeguides.rubyonrails.org/caching_with_rails.html#russian-doll-caching
In the extreme scenario where performance is still an issue for you, I would recommend to start denormalising the partials until you re happy with the performance.

How to use ajax on rails 4?

I am trying to create a comment area in rails 4 using ajax. My target is to create comment and its output in same page without page loading. I have added some code. When i click on "add comment" button the data entered to DB, but does not reflect in same page.Please share with me if any one have any idea, thank u.
My codes are below:
In comment controller:
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.create
#comment.body = params[:comment]["body"]
respond_to do |format|
#comment.save
format.html { redirect_to #article }
format.js
end
end
In show.html.erb under views/articles
<div class="col-xs-12 col-sm-12">
<h2 class="text-center"><%= #article.title.html_safe %></h2>
<p><%= #article.body.html_safe %></p>
<h2>Comments</h2>
<div id="comments">
<!-- <p><%= render :partial => #article.comments %></p> -->
<%= render :partial => 'comments/comment', :collection => #article.comments %>
</div>
<%= form_for([#article, Comment.new], :remote => true) do |f| %>
<p>
<%= f.label :body, "New comment" %><br/>
<%= f.text_area :body, type:"text", tabindex: "6", class: "form-control", placeholder: 'your comment', rows: "4", required: true %>
</p>
<p><%= f.submit "Add comment" %></p>
<% end %>
</div>
_comment.html.erb under view/comments
<%= div_for comment do %>
<p>
<b>
Posted <%= time_ago_in_words(comment.created_at) %> ago
</b>
<br/>
<%= comment.body %>
<%= link_to 'Delete', article_comment_path(comment.article_id, comment.id), method: :Delete, :class => 'btn btn-sm btn-warning', tabindex: "3" %>
<%= link_to 'Edit', article_path(comment.article_id, comment.id), :class => 'btn btn-sm btn-warning', tabindex: "3" %>
</p>
<% end %>
create.js.erb under views/comments
page.insert_html :bottom, :comments, :partial => 'comment', :object => #comment
page[:new_comment].reset
config/application.rb
config.action_view.JavaScript_expansions[:defaults] = %w(jquery rails application)
the proper way to handle this case in rails is using a remote form, this way you can interactively insert and remove objects from db.
Here are two explained blog posts about remote forms in rails detailed guide about remote forms and how to partials ajax rails. This way you can submit the form without refresh an then have an handler to generate the created element after the callback from the controller.
Hope this fits your question.
Added something like:
create.js.erb under views/comments
$("#comments").append("<%= escape_javascript(render(:partial => #comment)) %>");
$("#new_comment")[0].reset();
And It works for me.

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.

Rails - AJAX/JQuery not working

For my application, I have projects where users can make comment (class Newcomments) postings. Right now it posts great but on page refresh. I am trying to use AJAX/JQUERY so that it will post with no page refresh. I am following this railscasts tutorial.
So right now, the posts are made to my database and the page does not refresh. But when I refresh, the posts shows up. The page that I render my comments for a specific project is on the projects/_comments.html.erb.
Question: How would I adjust it so that it the new comment made renders?
newcomments_controller.rb
def create
#newcomment = #commentable.newcomments.new(params[:newcomment])
if #newcomment.save
respond_to do |format|
format.html { redirect_to comments_project_path(#commentable) }
format.js
end
else
render :new
end
end
view/newcomments/_form.html.erb
<span class="comment">
<%= form_for [#commentable, #newcomment], remote: true do |f| %>
<%= f.text_area :content, rows: 3, :class => "span8" %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= f.submit "Add Comment", :class => "btn btn-header" %>
<% end %>
</span>
view/newcomments/create.js.erb
$('#newcomment').append('<%= j render(#newcomments) %>');
projects_controller.rb
def comments
#commentable = #project
#newcomments = #commentable.newcomments.newest.page(params[:comments_page]).per_page(10)
#newcomment = Newcomment.new
respond_to do |format|
format.html # show.html.erb
format.json { render json: #project.comments }
end
end
projects/comments.html.erb
<%= render 'comments' %>
projects/_comments.html.erb
<%= render #newcomments %>
view/newcomments/_newcomment.html.erb
<div class="comments">
<%= link_to newcomment.user.name %></strong>
Posted <%= time_ago_in_words(newcomment.created_at) %> ago
<%= newcomment.content %>
</div>
<span class="comment">
<%= form_for [#commentable, #newcomment] do |f| %>
<div class="field">
<%= f.text_area :content, rows: 3, :class => "span8" %>
</div>
<%= f.hidden_field :user_id, :value => current_user.id %>
<div class="actions">
<%= f.submit "Add Comment", :class => "btn btn-header" %>
</div>
<% end %>
<% unless newcomment.newcomments.empty? %>
<%= render #newcomments %>
<% end %>
</span>
Try binding AJAX actions as described here:
http://www.alfajango.com/blog/rails-3-remote-links-and-forms/
Also, consider returning a render comments partial html instead of json comment object, to do so you need to tell in form after :remote directive that :'data-type'=>'html', so that returned html will hit function binded on AJAX success, and then swap html of container div with, for example, jQuery

Resources