Cleanup erb files by abstracting/moving ruby code - ruby-on-rails

I was wondering if it would be possible to cleanup or reorganize my views so that I have less Ruby code. The HTML often becomes cumbersome to work with because it has so much Ruby code.
I thought about moving all the Ruby stuff into helpers and assign each function (links, tags etc.) to methods.
Example. Problem becomes much worse with a more complicated layout.
<div class="sidebar">
<div id="art_nav">
<%= link_to "Previous", art_path(#previous), :remote => true, :class => "prev" unless #previous.nil? %>
<%= link_to "Next", art_path(#next), :remote => true, :class => "next" unless #next.nil? %>
</div>
</div>
Would become:
<div class="sidebar">
<div id="art_nav">
<%= link_to_previous %>
<%= link_to_next %>
</div>
</div>
Helper:
def link_to_previous
link_to "Previous", art_path(#previous), :remote => true, :class => "prev" unless #previous.nil?
end
def link_to_next
link_to "Next", art_path(#next), :remote => true, :class => "next" unless #next.nil?
end
This seems to work with simple examples.. but I am wondering how I should organize stuff when I have to do loops or similar.
UPDATED: Loop example added
<% arts.each do |art| %>
<h3><%= art_title %></h3>
<p><%= art_description %></p>
<div id="comments_<%= art.id %>">
<%= render :partial => "/comments/index", :locals => {:resource => art} %>
</div>
</div>
<% end %>
What would you do?

Here is a pattern you could use in your code, to clean it up further:
def menu(*options, &block)
params = options[0]
active = (params[:active] if params) || false
first = (params[:first] if params) || false
"
<div class=\"menu\">
<div class=\"menu-left #{active ? "active" : "inactive"} #{first ? "first" : "not-first"}\">
</div>
<div class=\"menu-center\">
<div class=\"menu-content #{active ? "active" : "inactive"}\">
#{capture(&block)}
</div>
</div>
<div class=\"menu-right #{active ? "active" : "inactive"}\">
</div>
</div>
".html_safe
end
With this helper, code gets very simple:
<%= menu(:active => #active_menu == :menu1 ? true : false, :first => true) do %>
<%= link_to "Les produits et les prix", menu1_path %>
<% end %>
At the center of this pattern, there is the call
#{capture(&block)}
Sorry this example is not "generic" at all, I did a copy-and-paste from my own code. But I'm sure you get the idea.

Related

Filtering by params works in development but not in production

I have a Rails 3.2.22 app that tracks dispatch calls and each call belongs to a region and a region has many calls. In my views I originally had all calls from all regions, but now I'm filtering by regions using a simple form_tag in the view and passing the region ID as a param to the controller and back to the view.
So locally if I hit the calls index view I will trigger a url like:
http://loopify.xyz:9001/calls?utf8=%E2%9C%93&region=1
Which will then in my views show me all calls with the region id of "1". I can switch to different regions in the views and it will display the proper filtered calls by region.
When I deployed this to my staging server, the params filtering does not work at all and shows all calls even though when I select a region I will get a URL like:
http://staging.example.com/calls?utf8=%E2%9C%93&region=1
Here is the index action of my calls controller:
def index
if params[:region].present?
#region = params[:region]
#assigned = Call.includes(:units, :transferred_from, :transferred_to, :nature, :region, :service_level).where(region_id: params[:region]).assigned_calls.until_end_of_day
#unassigned = Call.includes(:units, :transferred_from, :transferred_to, :nature, :region, :service_level).where(region_id: params[:region]).unassigned_calls.until_end_of_day
else
params[:region] = "1"
#assigned = Call.includes(:units, :transferred_from, :transferred_to, :nature, :region, :service_level).where(region_id: params[:region]).assigned_calls.until_end_of_day
#unassigned = Call.includes(:units, :transferred_from, :transferred_to, :nature, :region, :service_level).where(region_id: params[:region]).unassigned_calls.until_end_of_day
end
#note = Note.new
#units = Unit.active.order("unit_name").map{|unit| unit.calls.where(call_status: "open").empty? ? ["#{unit.unit_name} #{unit.unit_type.unit_type} #{unit.status.unit_status}", unit.id] : ["#{unit.unit_name} (on call) #{unit.unit_type.unit_type} #{unit.status.unit_status}", unit.id] }
end
Here is my index.html.erb view:
<div id="active">
<%= render "assigned_calls" %>
</div>
<div id="inactive">
<%= render "unassigned_calls" %>
</div>
<script>
$(function() {
setInterval(function(){
$.getScript('/calls/?region=<%= params[:region] %>')
}, 20000);
});
</script>
Here is the index.js.erb file to allow ajax refresh and JS
$("#active").html("<%= escape_javascript render("assigned_calls") %>");
$("#inactive").html("<%= escape_javascript render("unassigned_calls") %>");
Here is an excerpt of the assigned partial. Both assigned and unassigned really large to display here in their entirety but will be happy to supply them in a github gist if you need more context.
<div class="page-header well">
<h3><%= pluralize(#assigned.size, "Active Call") %></h3>
</div>
<%= form_tag calls_path, :method => 'get' do %>
<%= select_tag "region", options_from_collection_for_select(Region.order(:area), :id, :area, selected: #region), prompt: "Choose Region" %>
<%= submit_tag "Select", :name => nil, :class => 'btn' %>
<% end %>
<% #assigned.each_with_index do |call, index| %>
<div class="widget">
<div class="widget-header">
<div class="pull-right">
<%= link_to 'View', call, :class => 'btn btn-primary btn-small'%>
<% if dispatch? || admin? || manager? || operations? %>
<%= link_to 'Edit', edit_call_path(call), :class => 'btn btn-info btn-small'%>
<%= link_to "Close", '#close-modal', data: {toggle: "modal", target: "#close-modal#{index}" }, class: 'btn btn-small btn-warning' %>
<%= render 'layouts/close_call_modal', call: call, index: index %>
<%= link_to "Cancel", '#cancel-modal', data: {toggle: "modal", target: "#cancel-modal#{index}" }, class: 'btn btn-small btn-danger' %>
<%= render 'layouts/cancel_call_modal', call: call, index: index %>
<%= link_to "Notes", '#note-modal', data: {toggle: "modal", target: "#note-modal#{index}" }, class: 'btn btn-small btn-primary' %>
<%= render 'layouts/note_modal', call: call, index: index %>
<% end %>
</div>
<i class="icon-phone"></i>
<h3><%= link_to call.incident_number, call %> <span><%= status(call) %></span></h3>
<% if call.wait_return == "yes" && call.parent_call_id == nil %>
<i class="icon-arrow-right dim"></i> <span class="badge badge-important" >Initial Transport</span>
<% end %>
<% if call.wait_return == "yes" && call.parent_call_id.present? %>
<i class="icon-arrow-left dim"></i> <span class="badge badge-important" >Return Transport</span>
<% end %>
<% if call.traffic_type == "Emergency" %>
<span class="badge badge-important"><%= call.traffic_type %></span>
<% else %>
<span class="badge badge-info"><%= call.traffic_type %></span>
<% end %>
<span class="badge badge-primary" ><%= call.region.try(:area) %></span>
<span class="badge badge-info" ><%= call.service_level.try(:level_of_service) %></span>
</div>
<% end %>
I'm really not sure what the problem is here. In development I'm serving the app with Thin, in staging I'm using passenger and nginx.
Is there something in my code that will not work in a production environment that I'm missing?
To recap, the region filtering works perfectly in development but once deployed to staging the filtering does not work.
I dug into the logs and here is the request from both dev (local) and prod (staging) logs:
dev:
Started GET "/calls/?region=1&_=1468278029235" for 127.0.0.1 at 2016-07-11 18:00:29 -0500
Processing by CallsController#index as JS
prod:
Started GET "/calls/?region=1&_=1468278074295" for 192.168.130.1 at 2016-07-11 18:01:14 -0500
Processing by CallsController#index as JS
I'm not sure why this even happens/works but it does. If I turn the log level in production.rb from info to debug the param filtering works fine. I saw a reference to this on an old github issue. Rails core closed it as a non-bug, but obviously there's something to this. Unfortunately 3.2 is not supported anymore so this "hack" will have to do. Now it's time to upgrade the app to 4.2.6.

Rails: 'Load More' button with Ajax & Kaminari

I would like to create a "load more" ajax pagination, with Kaminari.
I'm using this code :
class BienvenueController < ApplicationController
def index
#articles = Admin::Article.page(1).per(2)
respond_to do |format|
format.html
format.js
end
end
end
# Bienvenue#index
<div class="container" style="min-width:<%= #width %>px">
<%= render "shared/articles" %>
<%= link_to_next_page #articles, 'Load More', :remote => true, :id=>"load_more_link" %>
# Shared/articles
<% #articles.each do |a| %>
<article class="<%= a.rubrique.color %>">
<div class="sharing">
<%= image_tag "facebook-32.png" %>
</div>
<p class="color<%= a.rubrique.color %>"><i>Le <%= I18n.localize(a.created_at, :format => :long) %> par David Perrotin</i></p>
<h1><%= a.titre %></h1>
<div class="excerpt">
<%= a.chapo.html_safe %>
</div>
<div class="image">
<%= image_tag a.mainphoto.url(:medium), :width=>"100%" %>
</div>
<div class="contenu">
<%= a.contenu.html_safe %>
</div>
<div class="readmore">
<%= link_to "Continuer la lecture", article_path(a) %>
</div>
</article>
<% end %>
# index.js.erb
$('.container').append("<%= escape_javascript(render 'shared/articles')%>");
$('#load_more_link').replaceWith("<%= escape_javascript(link_to_next_page(#articles, 'Load More', :remote => true, :id=>'load_more_link'))%>");
But the problem is that when I click on "Load More", it always shows the two same articles, the partial is never refreshed with two more articles, like I would like.
I just ran into an issue with this that might help others. Depending on your version of jQuery, don't use replaceWith on the #load_more_link in index.js.erb.
There is a regression (http://bugs.jquery.com/ticket/13401) that an empty replaceWith does nothing, so on the very last page of your set, the link_to_next will be empty, making the line: $('#load_more_link').replaceWith(''); and thus will not replace the last "more" button, so you'll continually load the last page of your data set.
Fixed by updating jQuery version or use empty().html('...') instead of replaceWith.

Link to current post from an array of records

Just trying to get my head around the following, probably basic I know. I am looping through an array of records using .each and would like to view the post i click via an ajax request on the same page
<h2>Recent News</h2>
<ul>
<% #tynewyddpost.reverse.each do |t| %>
<li>
<% single = t.photos.first %>
<a class="photo" href="#"><%= image_tag(single.avatar.url(:thumbnail_news_images)) %></a>
<p><%= link_to t.title, tynewyddnews_path(:type => 'tynewyddnews'), :remote => true %></p>
<p class="date"><%= date_output(t.published_on) %></p>
</li>
<% end %>
</ul>
So when i click the title it will render the same post no matter which record i click.
The partial i render
<div class="post-item">
<% #tynewyddpost.reverse.each do |t| %>
<h2><%= t.title %></h2>
<div id="work-samples">
<% for photo in t.photos %>
<%= image_tag(photo.avatar.url(:news_images), :class => 'work-sample') %>
<% end %>
</div>
<p class="post-description"><%= t.comments.html_safe %></p>
<div class="post-item-panel">
<ul>
<li class="date">
<p><%= date_output(t.published_on) %></p>
</li>
</ul>
</div>
</div>
<% end %>
Controller
def tynewyddnews
#title = 'Ty Newydd News'
tynewyddpost = Post.tynewydd_posts.reverse
tynewyddpost.pop
#tynewyddpost = tynewyddpost
#tynewyddpostlatest = Post.tynewydd_posts.first
end
Scope
scope :tynewydd_posts, :include => :department, :conditions => {"departments.name" => "Ty Newydd"}, :order => "posts.published_on DESC"
My question is how to get the particular post i have clicked. I cant do
<%= #tynewyydpost.title %>
As i get undefined method title for array. Bit of theory here i know but how to get an individual record from an array in this instance
Any help appreciated
You need to pass the id of the post you're clicked on:
<p><%= link_to t.title, tynewyddnews_path(:type => 'tynewyddnews', :post_id => t.id), :remote => true %></p>
so in your controller you can do
#theposticlickedon = Post.find(params[:post_id])
or
#theposticlickedon = Post.tynewydd_posts.find(params[:post_id])
However, you also may want to define a different path to show the individual post, instead of the tynewyddnews_path you have in your link.
You need to specify in every link ID of this post.
For example:
<%= link_to t.title, tynewyddnews_path(:type => 'tynewyddnews'), :post_id=>t.id, :remote => true %>
And than specify that in controller action you're calling , by finding this by id
#tynewyddnews=Post.find(params[:post_id])
Than you're partial instance #tynewyddnews will be clicked post

How do I implement a show all comments feature in ruby on rails?

I'm implementing show/hide feature for users comments.
Discussed here: https://stackoverflow.com/a/10174194/439688
My aim was to:
1. Limit the default shown comments to 2.
2. Have a span with text that states the number of total comments for that particular micropost and when clicked by a user have it expand and show all comments for that micropost. I would be using Jquery/Ajax to hide, show, prepend etc.
The first change was to limit the amount of comments shown to the user and I achieved this by creating a method in my helper called "comments" and here I pass in the id of the micropost the comment belongs to.
def get_comments(micropost_id)
Comment.limit(2).order("created_at DESC").where(:micropost_id => micropost_id)
end
Now the each loop that loops through each comment will only show the 2 most recent comments.
<<% #microposts.each do |m| %>
<% if m.poster_id.nil? %>
<div class="postHolder">
<nav class="micropostOptions">
<ul class="postMenu">
<li class="deletePost"><%= link_to content_tag(:span, "Delete post"), m, :method => :delete, :confirm => "Are you sure?", :title => m.content, :class => "message_delete", :remote => true %>
</li>
<li class="disableCommenting"><%= link_to content_tag(:span, "Pause commenting"), "2" %></li>
<li class="blockCommenter"><%= link_to content_tag(:span, "Block commenter"), "3" %></li>
<li class="openInNewWindow"><%= link_to content_tag(:span, "Open in new window"), "4" %></li>
<li class="reportAbuse"><%= link_to content_tag(:span, "Report abuse"), "5" %></li>
</ul>
</nav>
<%= link_to image_tag(default_photo_for_current_user, :class => "poster_photo"), current_users_username %>
<div class="post_content">
<div class="post_container">
<div class="mainUserNameFontStyle"><%= link_to current_users_username.capitalize, current_users_username %> - <div class="post_time"> <%= time_ago_in_words(m.created_at) %> ago.</div>
</div>
<%= simple_format h(m.content) %> </div>
<div class="commentsCount">
<%= content_tag :span, pluralize(m.comments.count, 'comment'), :class => "view_all_comments" if m.comments.any? %>
</div>
<% if m.comments.any? %>
<% comments(m.id).each do |comment| %>
<div class="comment_container">
<%= link_to image_tag(default_photo_for_commenter(comment), :class => "commenter_photo"), commenter(comment.user_id).username %>
<div class="commenter_content"> <div class="userNameFontStyle"><%= link_to commenter(comment.user_id).username.capitalize, commenter(comment.user_id).username %> - <%= simple_format h(comment.content) %> </div>
</div><div class="comment_post_time"> <%= time_ago_in_words(comment.created_at) %> ago. </div>
</div>
<% end %>
<% end %>
<% if logged_in? %>
<%= form_for #comment, :remote => true do |f| %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= f.hidden_field :micropost_id, :value => m.id %>
<%= f.text_area :content, :placeholder => 'Post a comment...', :class => "comment_box", :rows => 0, :columns => 0 %>
<div class="commentButtons">
<%= f.submit 'Post it', :class => "commentButton", :disable_with => "Post it" %>
<div class="cancelButton"> Cancel </div>
</div>
<% end %>
<% end %>
</div>
</div>
From here this is where it gets confusing for me. I got slightly further using link_to but then decided I'd prefer not to have the url to the comments count show in the browser status bar. This is why I switched to using span.. but now it's not quite easy to do what I wish to do as I can't use the link_to/remote => true now.
How do I make it so when a user clicks the comment count span an ajax call is made pointing to:
def load_comments
#load_comments = Comment.where(:micropost_id => params[:id])
respond_to do |format|
format.js { render :load_comments }
end
end
I thought about putting a click function in users.js but how would I pass the params of the micropost that is in the each loop in the code above into users.js? I don't think it's possible.
All my comment posting is done via ajax but because I used forms for these it was so much easier for me to just add remote => true and create some js templates and do something on success of ajax post.
Not sure if I'm even going about this the right way. I'd appreciate some help/advice from more experienced rails programmers.
Kind regards
Rails partial
#Display all the comments based on local passed to this partial
# Initially pass limit as 2(or whatever you want). then on click of span pass limit as nil. then you can check if limit is nil you can query the model without limit specifier.
<% #comments = Comment.custom_find(#your_params) %>
<% #comments.each do |comment| %>
<%= comment.title %>
<% end %>
javascript/jquery
function load_all_comments(id)
{
new Ajax.Updater('show_comments',
'<%=url_for(:controller => "your_controller", :action => "your_action")%>', {
parameters: {'id':id },
method: 'get',
onSuccess: function(request){
div_comments = document.getElementById("partial_comments_list");
div_comments.innerHTML = request.responseText;
}
});
} // you can call this js function on span click. use jquery if you want.
Controller:
Then inside your_action of your_controller, dont forget to render the partial
render :partial => "show_comments", :layout => false
Edit:
you can even pass locals to your partial
render :partial => "show_comments", :locals => {:post => #post}
Using this every time your partial view will get updated, on the basis of locals you pass.
of course this is just an example not a complete code/solution.
There may be better ways. but this worked fine for me.
Another option is to just output all of the comments and hide the ones you don't want to show first. <div class="hidden_comments" style="display:none;"> a comment </div>
Then just have some javascript to show them when the span is clicked?
<script type="text/javascript">
$("#span_id").click(function() {
$('.hidden_comments').show();
});
</script>
This works great if you do not don't have a ton of comments.
If you really want to do it your way, I have done it before but it gets messy.
Put this in your application.js
$('.comment_span').live('click', function () {
$.get(this.data_url, null, update_row, 'json');
return false;
});
Your span would look like this:
<span class="comment_span" data_url="http://website.com/resource/more_comments">
show all comments
</span>
This example returns the data as json, so I used the update_row function to update replace the comments data.
function update_row(data, status) {
$("#comments-table").append(data.html);
};
Here is what my controller looked like:
def more_comments
#comments = Comments.all
if #comments
respond_to do |format|
format.js {
render :json => {
:html => render_to_string(:partial => "comments"),
}.to_json
}
end
end
end
You should do this via your index action.
Pass a param to it to determine if you want to show all comments or just the current set (I'd use will_paginate to handle this.
Haven't looked too deep into your code as I'm on my phone right now, but something like this:
class CommentsController < ApplicationController
def index
If params[:show_all] == "true"
#comments = Comment.all
else
#comments = Comment.where(foo: bar).paginate(per_page: 2, page: params[:page])
end
end
Then you have it respond to JavaScript and send the page param with your Ajax request

Rails render partial with block

I'm trying to re-use an html component that i've written that provides panel styling. Something like:
<div class="v-panel">
<div class="v-panel-tr"></div>
<h3>Some Title</h3>
<div class="v-panel-c">
.. content goes here
</div>
<div class="v-panel-b"><div class="v-panel-br"></div><div class="v-panel-bl"></div></div>
</div>
So I see that render takes a block. I figured then I could do something like this:
# /shared/_panel.html.erb
<div class="v-panel">
<div class="v-panel-tr"></div>
<h3><%= title %></h3>
<div class="v-panel-c">
<%= yield %>
</div>
<div class="v-panel-b"><div class="v-panel-br"></div><div class="v-panel-bl"></div></div>
</div>
And I want to do something like:
#some html view
<%= render :partial => '/shared/panel', :locals =>{:title => "Some Title"} do %>
<p>Here is some content to be rendered inside the panel</p>
<% end %>
Unfortunately this doesn't work with this error:
ActionView::TemplateError (/Users/bradrobertson/Repos/VeloUltralite/source/trunk/app/views/sessions/new.html.erb:1: , unexpected tRPAREN
old_output_buffer = output_buffer;;#output_buffer = ''; __in_erb_template=true ; #output_buffer.concat(( render :partial => '/shared/panel', :locals => {:title => "Welcome"} do ).to_s)
on line #1 of app/views/sessions/new.html.erb:
1: <%= render :partial => '/shared/panel', :locals => {:title => "Welcome"} do -%>
...
So it doesn't like the = obviously with a block, but if I remove it, then it just doesn't output anything.
Does anyone know how to do what I'm trying to achieve here? I'd like to re-use this panel html in many places on my site.
While both of those answers above work (well the example that tony links to anyway) I ended up finding the most succinct answer in that above post (comment by Kornelis Sietsma)
I guess render :layout does exactly what I was looking for:
# Some View
<%= render :layout => '/shared/panel', :locals => {:title => 'some title'} do %>
<p>Here is some content</p>
<% end %>
combined with:
# /shared/_panel.html.erb
<div class="v-panel">
<div class="v-panel-tr"></div>
<h3><%= title -%></h3>
<div class="v-panel-c">
<%= yield %>
</div>
</div>
Here's an alternative based on previous answers.
Create your partial on shared/_modal.html.erb:
<div class="ui modal form">
<i class="close icon"></i>
<div class="header">
<%= heading %>
</div>
<div class="content">
<%= capture(&block) %>
</div>
<div class="actions">
<div class="ui negative button">Cancel</div>
<div class="ui positive button">Ok</div>
</div>
</div>
Define your method on application_helper.rb:
def modal_for(heading, &block)
render(
partial: 'shared/modal',
locals: { heading: heading, block: block }
)
end
Call it from any view:
<%= modal_for('My Title') do |t| %>
<p>Here is some content to be rendered inside the partial</p>
<% end %>
You can use the capture helper, and even inline in the render call :
<%= render 'my_partial',
:locals => { :title => "Some Title" },
:captured => capture { %>
<p>Here is some content to be rendered inside the partial</p>
<% } %>
and in shared/panel:
<h3><%= title %></h3>
<div class="my-outer-wrapper">
<%= captured %>
</div>
which will produce:
<h3>Some Title</h3>
<div class="my-outer-wrapper">
<p>Here is some content to be rendered inside the partial</p>
</div>
See http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html
Based on the accepted answer this is what worked well for me using Rails 4.
We can render a panel as such:
= render_panel('Non Compliance Reports', type: 'primary') do
%p your content goes here!
This requires a helper method and a shared view:
helper method (ui_helper.rb)
def render_panel(heading, options = {}, &block)
options.reverse_merge!(type: 'default')
options[:panel_classes] = ["panel-#{options[:type]}"]
render layout: '/ui/panel', locals: { heading: heading, options: options } do
capture(&block)
end
end
View (/ui/panel.html.haml)
.panel{ class: options[:panel_classes] }
.panel-heading= heading
.panel-body
= yield
I think it will work (just did quick dirty test) if you assign it to a variable first and then output it.
<% foo = render :partial => '/shared/panel', :locals =>{:title => "Some Title"} do %>
<p>Here is some content to be rendered inside the panel</p>
<% end %>
<%= foo %>

Resources