Rails: Elegant way to handle navigation? - ruby-on-rails

Right now I have a navigation partial that looks like this (x10 buttons)...
<% if current_controller == "territories" %>
<li><%= link_to "Territories", {:controller => 'territories'}, :class => 'active' %></li>
<% else %>
<li><%= link_to "Territories", {:controller => 'territories'} %></li>
<% end %>
<% if current_controller == "contacts" %>
<li><%= link_to "Contacts", {:controller => 'Contacts'}, :class => 'active' %></li>
<% else %>
<li><%= link_to "Contacts", {:controller => 'Contacts'} %></li>
<% end %>
Is there a more elegant/DRY solution for doing this?

In a similar vein to what Chuck said:
<% TARGETS.each do |target| %>
<li>
<%= link_to target.humanize,
{ :controller => target },
class => ('active' if current_controller == target)) %>
</li>
<% end %>

It's pretty easy to see where the repetition is in there. It's all of the general form:
<% if current_controller == XXXXX %>
<li><%= link_to XXXXX, {:controller => XXXXX}, CLASS %></li>
<% else %>
[do the same stuff minus ":class => 'active'"]
<% end %>
So we want XXXXX and CLASS to be variables (since those are the only things that change) and the rest can be a simple template.
So, we could do something like this:
%w(Contacts Territories).each |place|
<% class_hash = current_controller == place ? {:class => 'active'} : {}
<li><%= link_to place, {:controller => place}, class_hash)</li>

Check out rails-widgets on github. It provides a ton of convenience helpers for rails UI stuff (tabnavs, tooltips, tableizers, show hide toggle, simple css progressbar) in addition to navigation.
Here are the docs

Check out link_to_unless_current. Not exactly what you asked for, but it's close.
Also, you could put this kind of logic in a helper to abstract it out of the view.

Check out the simple-navigation plugin. It's an 'easy to use' rails plugin for creating navigations for your rails apps.

A slightly different version w/ link_to_unless_current:
<ul>
<% links.each do |link| -%>
<li><%= link_to_unless_current link.humanize, { :controller => target } %></li>
<% end -%>
</ul>
A good resource for stuff like this are the rails docs.

Related

Error nested link in Rails

I have a little problem of link.
I have 2 models nested, one Faqcategoryand Faq.
The route is
resources :faqcategories, :path => 'faqs' do
resources :faqs, :path => 'question'
end
I can display all the "faqcategories" at http://localhost:3000/faqs/
and all the faqcategory as "questions" at http://localhost:3000/faqs/8
But when I want to go on the show of the question at http://localhost:3000/faqs/8/question/1 , it sends me at http://localhost:3000/faqs/1/question/8
I have set up the view like that:
<% #faqs.each do |question| %>
<%= link_to question.title, faqcategory_faq_path(question), class: "btn btn-rose btn-round" %>
<% end %>
In the FaqcateroriesController the "show" is set up like that:
def show
#faqs = #faqcategory.faqs
end
How do you think I can solve that?
I have found the solution.
<% #faqcategory.faqs.each do |question| %>
<%= link_to question.title, faqcategory_faq_path(question.faqcategory_id, question), class: "btn btn-rose btn-round" %>
<% end %>
It was missing "question.faqcategory_id"

How build a breadcrumbs in rails 4

Seriously, I have no idea where to start. How do I implement a helper breadcrums without using gems?
I tried some gems, but I preffer make a simple helpe. Exist someone or some tutorial? I not found this =/
Thanks!
My solution:
navigation_helper.rb
module NavigationHelper
def ensure_navigation
#navigation ||= [ { :title => 'Home', :url => '/' } ]
end
def navigation_add(title, url)
ensure_navigation << { :title => title, :url => url }
end
def render_navigation
render :partial => 'navigation', :locals => { :nav => ensure_navigation }
end
end
_navigation.html.erb
<ol class="breadcrumb">
<% nav.each do |n| %>
<% unless n.equal? nav.last %>
<li><%= link_to n[:title], n[:url] %></li>
<% else %>
<li><%= n[:title] %></li>
<% end %>
<% end %>
</ol>
application.html.erb
<%= render_navigation %>
And any view:
<% content_for :title, 'My Page Title' %>
<% navigation_add #something.anything, '#' %>
You cant do this.
In your application_helper:
def breadcrumb(&block)
content_tag :ol, { :class => "breadcrumb", :itemprop => "breadcrumb" } do
block.call
end
end
def breadcrumb_item(name = nil, url = nil, html_options = {}, &block)
if name or block
html_options[:class] = "#{html_options[:class]} active" unless url
content_tag :li, html_options do
if block
block.call name, url
else
url ? link_to(name, url) : name
end
end
end
end
Now in views you paste this: (I used index_path and #user.name) - you can paste this code on show view as an example
<%= breadcrumb do %>
<%= breadcrumb_item "index", index_path %>
<%= breadcrumb_item #user.name %>
<% end %>
Now when you need some breadcrumb you can just call this trunck above and change the path and the instance variables #your_variable
I further worked on Elton Santos's solution and decided breadcrumbs should be automatic like history. So I modified some code:
In my application.html.erb
<%= render_navigation %>
In my views, I was already using:
<% content_for :heading do 'User Detail' end %>
So, my navigation_helper.rb look like:
module NavigationHelper
def navigation_add(title, url)
nav_list = session['navigation'].present? ? session['navigation'] : []
nav_list << { 'title' => title, 'url' => url }
# 1. Take last 3 items only (-1 is last, not -0)
nav_list = nav_list[-3..-1] if nav_list.length > 3
# 2. Now, if first is pointing root, remove it
nav_list.shift if nav_list[0]['url'] == '/'
# 3. If last one is same as its predecessor, remove it
nav_list.pop if nav_list.length > 1 && (nav_list[-1]['url'] == nav_list[-2]['url'])
session['navigation'] = nav_list
end
def render_navigation
render partial: 'shared/navigation', locals: { nav_list: session['navigation'] }
end
end
and finally, _navigation.html.erb:
<ol class="breadcrumb">
<li><%= link_to '/' do %>
<i class="fa fa-home"></i> Home <% end %>
</li>
<i class="fa fa-angle-double-right" style="color: #ccc; padding: 0 5px;"></i>
<% nav_list.each_with_index do |nav, i| %>
<% if i != nav_list.length-1 %>
<li><%= link_to nav['title'], nav['url'] %></li>
<% else %>
<li class="active"><%= nav['title'] %></li>
<% end %>
<% end %>
</ol>
So, what's going up here is; I save every page title in session and build breadcrumbs from that. I keep recent three entries only along with hard-coded one for Home and remove duplicate entries when they are not apart.

How do I get this case statement to work?

In my view, I am doing this:
<% case #post
when #post.has_children? %>
<% #post.children.each do |child| %>
<li><%= link_to child.title, post_path(child)%></li>
<% end %>
<% when #post.has_siblings? %>
<% #post.siblings.where.not(id: #post.id).each do |sibling| %>
<li><%= link_to sibling.title, post_path(sibling)%></li>
<% end %>
<% when !#post.parent.nil? %>
<li><%= link_to #post.parent.title, post_path(#post.parent) %></li>
<% else %>
</ul>
</p>
<p>
There are no related posts.
</p>
<% end %>
Basically what I want to do is I want to check #post for a variety of conditions. If it has_children?, if it has_siblings?, etc.
I don't want the statement to exit if any of the above is true or false.
Once the view loads, it should automatically check for all of these statements. If it finds any of the above true, it should execute the command right below the check.
The issue is when I do this, it always defaults to the else. i.e. the case statement doesn't work.
I know I could simply just do a bunch of disjointed if statements, but then the HTML around it gets a bit weird.
Is there a way I can do this with a CASE statement?
Edit 1
The reason the if statement doesn't work properly, is if I have 3 if statements back to back - none of which that interact with each other (that's the only way to cycle through all of the conditions properly), is that the else doesn't trigger properly.
E.g. if the first two conditions are true, but the third is not...it will print out "there are no related posts"...when that's not the case. It is the case that there are no parent posts.
Basically I just want to have a catch-all related posts, so I am simply iterating through all of the various options and checking to see if those relations exist. If they do, I am pulling them out and if they don't then they move on. If none exist, then I don't print "there are no related posts".
The fact that the view is already looking looking complex is a sign that it may be a good idea to refactor the logic out of the view and place it into the Post model where it belongs. Ideally the view(s) should end up looking like this:
<%# posts/show.html.erb %>
<% if #post.has_related_posts? %>
<%= render partial: 'children', collection: #post.children, as: :child %>
<%= render partial: 'siblings', collection: #post.other_siblings, as: :sibling %>
<%= render partial: 'parent', locals: {parent: #post.parent}%>
<% else %>
<p>There are no related posts</p>
<% end %>
The paritals:
<%# posts/_children.html.erb %>
<li><%= link_to child.title, post_path(child)%></li>
<%# posts/_sibling.html.erb %>
<li><%= link_to sibling.title, post_path(sibling)%></li>
<%# posts/_parent.html.erb %>
<% unless parent.nil? %>
<li><%= link_to parent.title, post_path(parent) %></li>
<% end %>
Then the Post model can organize the logic:
class Post < ActiveRecord::Base
def has_related_posts?
!children.to_a.empty? || !other_siblings.to_a.empty? || !parent.nil?
end
def children
self.children || [] # Rails does this automatically, but just for the example
end
def other_siblings
self.siblings.where.not(id: self.id)
end
#...
end
I know this doesn't directly answer your question, however IMHO I think it's a better solution.
You have two options here.
Use IF ELSIF
<% if #post.has_children? %>
<% #post.children.each do |child| %>
<li><%= link_to child.title, post_path(child)%></li>
<% end %>
<% elsif #post.has_siblings? %>
<% #post.siblings.where.not(id: #post.id).each do |sibling| %>
<li><%= link_to sibling.title, post_path(sibling)%></li>
<% end %>
<% elsif !#post.parent.nil? %>
<li><%= link_to #post.parent.title, post_path(#post.parent) %></li>
<% else %>
</ul>
</p>
<p>
There are no related posts.
</p>
<% end %>
Use only case as doz mentioned
<% case
when #post.has_children? %>
<% #post.children.each do |child| %>
<li><%= link_to child.title, post_path(child)%></li>
<% end %>
<% when #post.has_siblings? %>
<% #post.siblings.where.not(id: #post.id).each do |sibling| %>
<li><%= link_to sibling.title, post_path(sibling)%></li>
<% end %>
<% when !#post.parent.nil? %>
<li><%= link_to #post.parent.title, post_path(#post.parent) %></li>
<% else %>
</ul>
</p>
<p>
There are no related posts.
</p>
<% end %>
You can do it with a case statement. Just give case no parameter. Eg instead of case #post just use case
This is the equivalent of an if statement. And should work for what your trying to achieve
Check out ruby case statements for some examples

Making an if conditional depending on what view I'm on (Rails 4)

Need some help with this please.
I have a model Photos that has a column zone_id and category_id.
Photos with categories and Photos with views render the same index view (photos index).
I want to have a sidebar, depending on if you are in a Photo index by views or in a Photo index by category shows one dropdown or another.
So, if you click on Photo by Modern category, I want the sidebar to render a dropdown "Search by Zone"
In the other case, If you are in Photos index view by zone, I want my side bat to render a dropdown "Search by category".
I know this is with an if / unless, but don't know how to tell the conditional "Hey dude, Im in Photos by category, so you need to render Photos Zone dropdown".
Please help!
Photos Controller
def type
#photos = Photo.by_category(params[:category_id]).paginate(:page => params[:page])
render :index
end
def zone
#photos = Photo.by_zone(params[:zone_id]).paginate(:page => params[:page])
render :index
end
# I've got rid of Photo.with_user_avatar ...
def search
#photos = Photo.all
#photos = #photos.where('category_id = ?', params[:category_id]) if params[:category_id]
#photos = #photos.where('zone_id = ?', params[:zone_id]) if params[:zone_id]
#photos = #photos.paginate(:page => params[:page])
render :index
end
Routes
get 'spots/:category_id', to: "photos#type", as: :spots_category
get 'spots/zonas/:zone_id', to: "photos#zone", as: :spots_zone
get 'spots/:category_id/:zone_id', to: "photos#search", as: :spots_category_zone
get 'spots/zonas/:zone_id/:category_id', to: "photos#search", as: :spots_zone_category
Photos Index View
<% if #photos.first.zone %>
<h6 class="bold medium uppercase grey">Seleccionar estilo para: </br>
<span class="green big"><%= (#photos.first.zone.name ? "#{#photos.first.zone.name}": "") %></h6></span>
<!-- One dropdown-->
<ul class="listnone bold lightgrey">
<li><%= link_to "Moderno", spots_category_zone_path(1,#photos.first.zone) %>
<span class="spot_count small right"><%= #photos.count %></span></li>
<li><%= link_to "Clásico", spots_category_zone_path(2,#photos.first.zone) %>
<span class="spot_count small right"><%= #photos.count %></span></li>
<li><%= link_to "Industrial & Loft", spots_category_zone_path(3,#photos.first.zone) %>
<span class="spot_count small right"><%= #photos.count %></span></li>
<li><%= link_to "Minimalista", spots_category_zone_path(4,#photos.first.zone) %>
<span class="spot_count small right"><%= #photos.count %></span></li>
<li><%= link_to "Rústico", spots_category_zone_path(5,#photos.first.zone) %>
<span class="spot_count small right"><%= #photos.count %></span></li>
<li><%= link_to "Contemporáneo", spots_category_zone_path(6,#photos.first.zone) %>
<span class="spot_count small right"><%= #photos.count %></span></li>
<li><%= link_to "Étnico", spots_category_zone_path(7,#photos.first.zone) %>
<span class="spot_count small right"><%= #photos.count %></span></li>
<li><%= link_to "Art Deco", spots_category_zone_path(8,#photos.first.zone) %>
<span class="spot_count small right"><%= #photos.count %></span></li>
<li><%= link_to "Ecléctico", spots_category_zone_path(9,#photos.first.zone) %>
<span class="spot_count small right"><%= #photos.count %></span></li>
</ul>
<% else %>
<!-- The other dropdown -->
</i> Quiero ver spots de...<br>
<ul id="drop1" data-dropdown-content class="f-dropdown" data-options="align:right">
<li><%= link_to "Baños", spots_zone_path(1) %></li>
<li><%= link_to "Cocinas", spots_zone_path(2) %></li>
<li><%= link_to "Cuartos de estar", spots_zone_path(3) %></li>
<li><%= link_to "Dormitorios", spots_zone_path(4) %></li>
<li><%= link_to "Exteriores", spots_zone_path(5) %></li>
<li><%= link_to "Hostelería", spots_zone_path(6) %></li>
<li><%= link_to "Infantil", spots_zone_path(7) %></li>
<li><%= link_to "Oficinas", spots_zone_path(8) %></li>
<li><%= link_to "Salones", spots_zone_path(9) %></li>
</ul>
<% end %>
Thanks a lot!
Here we are again! Two easy ways.
You can set a variable equal to the action:
def type
#photos = Photo.by_category(params[:category_id]).paginate(:page => params[:page])
#action = 'type'
render :index
end
def zone
#photos = Photo.by_zone(params[:zone_id]).paginate(:page => params[:page])
#action = 'zone'
render :index
end
in your view
<% if #action == 'type' %>
<%= render 'sidebar/zone_menu' %>
<% elsif #action == 'zone' %>
<%= render 'sidebar/category_menu' %>
<% end %>
Or you can skip the variable and just call params[:action] in your view:
<% if params[:action] == 'type' %>
<%= render 'sidebar/zone_menu' %>
<% elsif params[:action] == 'zone' %>
<%= render 'sidebar/category_menu' %>
<% end %>
Personally, I'd go with the variable. It just feels like the kind of logic that belongs in the controller. Your mileage may vary.
I'd also put the menus into partials (make a "sidebar" folder, create _zone_menu.html.erb and _category_menu.html.erb respectively) and load them that way but you can certainly just put the full menus in the view instead. If you're going to do partials, I find it a best practice to keep instance variables out of partials by passing locals:
<%= render 'sidebar/category_menu', locals: { photo_count: #photos.count, current_zone: #photos.first.zone } %>
And then in the partial, replace #photos.count and #photos.first.zone with photo_count and current_zone.
As #subvertallchris pointed out you can access the params hash so I would do this in the controller.
before_filter :set_action
private
def set_action
#action = params[:action]
end
then you can use the views stated in #subvertallchris answer

How do I apply an 'active' class to my navigation based on the current_page in a DRY way? - Rails 3

So in my application.html.erb I have my navigational structure that looks something like this:
<div id="navigation">
<ul class="pills">
<% if current_page?(:controller => 'welcome', :action => 'index') %>
<li><%= link_to "Profile", vanity_path(:vname => current_user.username) %></li>
<li><%= link_to "Settings", settings_path %></li>
<li><%= link_to "Sign Out", signout_path %></li>
<% elsif !current_page?(:controller => 'welcome', :action => 'index') %>
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "Profile", vanity_path(:vname => current_user.username) %></li>
<li><%= link_to "Settings", settings_path %></li>
<li><%= link_to "Sign Out", signout_path %></li>
<% end %>
</ul>
</div>
However, what I would like to do, is once they are on any of the pages in the navigation, it applies a class active to the respective link.
So for instance, if the user is on mydomain.com/johnbrown which is the Profile link, the rails helper would look something like this:
link_to "Profile", vanity_path(:vname => current_user.username), :class => "active".
But how do I do that in a programmatic way, so I am not duplicating content? i.e. how do I get that functionality for all the pages in my navigation and write it as DRY as possible?
Thanks.
This is a really great question. I've never really been happy with the solutions I've seen or come up with. Maybe we can get one together here.
Here is what I've tried in the past
I've made a helper that returns a hash with :class defined since I use HAML
def active_tab(path)
request.path.match(/^#{path}/) ? { :class => 'active' } : {}
end
ex usage:
= link_to "Dashboard", dashboard_path, active_tab("#{dashboard_path}$")
Or an alternative along the same lines
def active_class(path)
request.path =~ /#{path}/ ? 'active' : nil
end
ex usage:
= link_to 'Presentations', admin_presentations_path, :class => "#{active_class('presentations')}"
I would love to see some other suggestions on this.
I found this answer for a bootstrap related navbar but you could easily use it with your nav.
Answer taken from here
You can use helper for handle "current_page?", example a method :
module ApplicationHelper
def is_active?(link_path)
if current_page?(link_path)
"active"
else
""
end
end
end
example bootstrap navbar
<div class="navbar">
<div class="navbar-inner">
<a class="brand" href="#">Title</a>
<ul class="nav">
<li class="active">Home</li>
<li>Link</li>
<li>Link</li>
</ul>
</div>
</div>
So, on view looks like
<li class="<%= is_active?(some_path) %>">
<%= link_to "name path", some_path %>
</li>
For Haml
Just simple looks like :
%ul.nav
%li{class: current_page?(some_path) && 'active'}
= link_to "About Us", some_path
You may define a helper method in application_helper.rb
def create_link(text, path)
class_name = current_page?(path) ? 'my_class' : ''
content_tag(:li, class: class_name) do
link_to text, path
end
end
Now you can use like:
create_link 'xyz', any_path which would render as
<li class="my_class">
xyz
</li>
Hope it helps!
Why don't you just take the redundant parts out of the if else block? Also take a look at 'link_to_unless'
<div id="navigation">
<ul class="pills">
<li><%= link_to_unless(current_page?(:controller => 'welcome', :action => 'index'), "Home", root_path %></li>
<li><%= link_to "Profile", vanity_path(:vname => current_user.username) %></li>
<li><%= link_to "Settings", settings_path %></li>
<li><%= link_to "Sign Out", signout_path %></li>
</ul>
</div>
then I would add some jQuery to inject active class into the link that matches the window.location pattern.

Resources