more rails partials on the same page - ruby-on-rails

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.

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.

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

Rails: Will_paginate does not work properly after AJAX call

Sorry about the repetition, I could not find the answers to my questions after a lot of searches. The will_paginate does not work after the Ajax call.
After delete action, It does not display the pages numbers at all (it simply returns all results).
After search action, the page number does not update, it shows more pages than what should actually be there.
Here is the controller code.
class CitiesController < ApplicationController
def index
#cities = City.get_cities(params[:city_name],params[:city_population]).paginate(:page => params[:page])
respond_to do |format|
format.html
format.js
end
end
def create
#city = City.new(params[:city])
if #city.save
flash[:success] = "City information saved successfully"
redirect_to cities_path
else
render 'index'
end
end
def new
#city = City.new
end
def destroy
#city = City.find(params[:id]).destroy
#city.destroy
#cities = City.all
respond_to do |format|
format.html {redirect_to cities_path}
format.js
end
end
end
Here is the index view:
<div class="row">
<h1>Search Cities or <%= link_to "Create New City", new_city_path %></h1>
<h3>99 random city information are generated in the database </h2>
<h3>Simply type any letter or city population between 0 and 10 to filter out</h3>
<%= form_tag "/cities/index", method: :get, remote: true do %>
<%= label_tag(:q, "City Name:") %>
<%= text_field_tag 'city_name' %>
<%= label_tag(:q, "City Population greater than, in units of 1 million:") %>
<%= text_field_tag 'city_population' %>
<label></label>
<%= button_tag("Search", :class => "btn") %>
<% end %>
<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>"><%= value %></div>
<% end %>
<div id='table' class="table table-striped">
<%= render 'table' %>
</div>
<%=will_paginate #cities %>
</div>
Here is the partial view of the table:
<div class="row">
<h1>Enter City descriptions or <%= link_to "Search Cities", cities_path %></h1>
<%= form_for #city, html: {multipart: true} do |f| %>
<%= f.label :city_name %>
<%= f.text_field :city_name %>
<%= f.label :city_description %>
<%= f.text_field :city_description %>
<%= f.label :city_population_in_units_of_millions %>
<%= f.text_field :city_population %>
<label></label>
<%= f.file_field :image%>
<label></label>
<%= f.submit "Create new City", :class => "btn"%>
<% end %>
</div>
Finally two js.erb codes associated with index and delete actions:
index.js.erb:
$('#table').html('<%= escape_javascript(render('table')) %>');
destroy.js.erb:
$('#table').html('<%= escape_javascript(render('table')) %>');
Found the solution by myself.
add <%= will_paginate #cities %> into partial file
and change #cities = City.all to
#cities = City.all.paginate(page: params[:page])
because will_paginate does not expect an array of objects.
write in your controller
def index
#cities = City.get_cities(params[:city_name],params[:city_population]).paginate(:page => params[:page])
respond_to do |format|
format.html
format.js
end
end
in your view folder create index.js.erb and write
$('id or class').html('<%=escape_javascript render :partial => which part you update %>')

Create and edit object using modal in 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

Resources