How do I implement Section-specific navigation in Ruby on Rails? - ruby-on-rails

I have a Ruby/Rails app that has two or three main "sections". When a user visits that section, I wish to display some sub-navigation. All three sections use the same layout, so I can't "hard code" the navigation into the layout.
I can think of a few different methods to do this. I guess in order to help people vote I'll put them as answers.
Any other ideas? Or what do you vote for?

You can easily do this using partials, assuming each section has it's own controller.
Let's say you have three sections called Posts, Users and Admin, each with it's own controller: PostsController, UsersController and AdminController.
In each corresponding views directory, you declare a _subnav.html.erb partial:
/app/views/users/_subnav.html.erb
/app/views/posts/_subnav.html.erb
/app/views/admin/_subnav.html.erb
In each of these subnav partials you declare the options specific to that section, so /users/_subnav.html.erb might contain:
<ul id="subnav">
<li><%= link_to 'All Users', users_path %></li>
<li><%= link_to 'New User', new_user_path %></li>
</ul>
Whilst /posts/_subnav.html.erb might contain:
<ul id="subnav">
<li><%= link_to 'All Posts', posts_path %></li>
<li><%= link_to 'New Post', new_post_path %></li>
</ul>
Finally, once you've done this, you just need to include the subnav partial in the layout:
<div id="header">...</div>
<%= render :partial => "subnav" %>
<div id="content"><%= yield %></div>
<div id="footer">...</div>

Partial render. This is very similar to the helper method except perhaps the layout would have some if statements, or pass that off to a helper...

As for the content of your submenus, you can go at it in a declarative manner in each controller.
class PostsController < ApplicationController
#...
protected
helper_method :menu_items
def menu_items
[
['Submenu 1', url_for(me)],
['Submenu 2', url_for(you)]
]
end
end
Now whenever you call menu_items from a view, you'll have the right list to iterate over for the specific controller.
This strikes me as a cleaner solution than putting this logic inside view templates.
Note that you may also want to declare a default (empty?) menu_items inside ApplicationController as well.

Warning: Advanced Tricks ahead!
Render them all. Hide the ones that you don't need using CSS/Javascript, which can be trivially initialized in any number of ways. (Javascript can read the URL used, query parameters, something in a cookie, etc etc.) This has the advantage of potentially playing much better with your cache (why cache three views and then have to expire them all simultaneously when you can cache one?), and can be used to present a better user experience.
For example, let's pretend you have a common tab bar interface with sub navigation. If you render the content of all three tabs (i.e. its written in the HTML) and hide two of them, switching between two tabs is trivial Javascript and doesn't even hit your server. Big win! No latency for the user. No server load for you.
Want another big win? You can use a variation on this technique to cheat on pages which might but 99% common across users but still contain user state. For example, you might have a front page of a site which is relatively common across all users but say "Hiya Bob" when they're logged in. Put the non-common part ("Hiya, Bob") in a cookie. Have that part of the page be read in via Javascript reading the cookie. Cache the entire page for all users regardless of login status in page caching. This is literally capable of slicing 70% of the accesses off from the entire Rails stack on some sites.
Who cares if Rails can scale or not when your site is really Nginx serving static assets with new HTML pages occasionally getting delivered by some Ruby running on every thousandth access or so ;)

You could use something like the navigation plugin at http://rpheath.com/posts/309-rails-plugin-navigation-helper
It doesn't do sub-section navigation out of the box, but with a little tweaking you could probably set it up to do something similar.

I suggest you use partials. There are a few ways you can go about it. When I create partials that are a bit picky in that they need specific variables, I also create a helper method for it.
module RenderHelper
#options: a nested array of menu names and their corresponding url
def render_submenu(menu_items=[[]])
render :partial => 'shared/submenu', :locals => {:menu_items => menu_items}
end
end
Now the partial has a local variable named menu_items over which you can iterate to create your submenu. Note that I suggest a nested array instead of a hash because a hash's order is unpredictable.
Note that the logic deciding what items should be displayed in the menu could also be inside render_submenu if that makes more sense to you.

I asked pretty much the same question myself: Need advice: Structure of Rails views for submenus? The best solution was probably to use partials.

There is another possible way to do this: Nested Layouts
i don't remember where i found this code so apologies to the original author.
create a file called nested_layouts.rb in your lib folder and include the following code:
module NestedLayouts
def render(options = nil, &block)
if options
if options[:layout].is_a?(Array)
layouts = options.delete(:layout)
options[:layout] = layouts.pop
inner_layout = layouts.shift
options[:text] = layouts.inject(render_to_string(options.merge({:layout=>inner_layout}))) do |output,layout|
render_to_string(options.merge({:text => output, :layout => layout}))
end
end
end
super
end
end
then, create your various layouts in the layouts folder, (for example 'admin.rhtml' and 'application.rhtml').
Now in your controllers add this just inside the class:
include NestedLayouts
And finally at the end of your actions do this:
def show
...
render :layout => ['admin','application']
end
the order of the layouts in the array is important. The admin layout will be rendered inside the application layout wherever the 'yeild' is.
this method can work really well depending on the design of the site and how the various elements are organized. for instance one of the included layouts could just contain a series of divs that contain the content that needs to be shown for a particular action, and the CSS on a higher layout could control where they are positioned.

There are few approaches to this problem.
You might want to use different layouts for each section.
You might want to use a partial included by all views in a given directory.
You might want to use content_for that is filled by either a view or a partial, and called in the global layout, if you have one.
Personally I believe that you should avoid more abstraction in this case.

Related

Where is the best place to store layout logic in Rails?

In my Rails app, I have several controllers that makeup a site for my client. With end users and Admins using virtually all of the controllers.
I have created a partial for including navigation links. My problem is that I don't want them to appear inside of the admin section. The part that makes this tricky is that while the navigation should appear for things like pages/home, /galleries or /galleries/5 they should not appear for things like /pages/new or /galleries/5/edit.
Up until now I have been adding logic to application.html.erb, but things are beginning to get out of hand, there are so many rules in my if statement that I feel dirty about it. Here is an example:
snippet from application.html.erb
<% unless current_page?('/') or current_page?('/admin') or current_page?({action: :new}) %>
<%= render "layouts/top_links" %>
<% end %>
This is all I have written so far, but it will almost certainly get even longer if I continue this way. So here are my questions:
Where should I put the logic for this and how should I call it?
Is current_page? the best way to lay this out?
You can put such dirty logic into helper to make it cleaner
# layouts/application.html.erb
nav_links
# application_helper
def nav_links
return "" if action_name.in?['new', 'edit'] || controller_path == 'admin'
render partial: 'layouts/nav'
end
Done.
You could advance with a role based approach, as it is the most widely used method for access control in rails. Indeed, all those if-else loops in application.html.erb would be a little bit of code smell anyway.
Check Ryan Bates CanCan gem for this purpose.
One possibility is to make use of before_filter, which you can set up in your ApplicationController. What you can do
Apply your own flavour to this, but a goofy example might be:
class ApplicationController < ActionController::Base
before_filter :set_navigation_visibility
def set_navigation_visibility
# Your custom logic
end
That method can also be overridden in the subcontrollers if necessary, or modified using the :only and :except options.
Another possibility is making use of a role-based access-control system. There are many, one such library I have used is Simple-Navigation. A google search for role-based access control for rails seems to yield a number of results that might be helpful.

What is the difference between render and yield in Rails

Can someone explain the difference between "<%= render %>" and "<%= yield %> with <% content_for :partial do %>/<% end %>"? specifically how the routing changes when switching from one to another, the benefits of using one over the other, when is it practical to use one over the other. THIS is the closest explanation I have found, but isn't quite clear enough for me.
I have been trying for several days to wrap my head around this, but it seems that each configuration I try either comes close, or errors out.
If theres are three views, aaa and bbb and ccc, and each has an index.html.erb, but bbb and ccc have a _content.html.erb partial (signified by the underscore) how can you accomplish getting the bbb or ccc partial in aaa using either render or yield?
The following works:
aaa's index.html.erb :
<div">
<%= render 'bbb/content' %>
</div>
and bbbs _content.html/erb :
<p>Content from bbb.</p>
BUT this does NOT:
aaa's index.html.erb :
<div">
<%= yield :container %>
</div>
and bbbs _content.html/erb :
<% content_for :container do %>
<p>Content from bbb.</p> ### viewed in aaa
<% end>
and cccs _content.html.erb would have nothing, or the content_for, but I still dont get aaa's index.html to be populated with content.
If I use the render, I can explicitly place the content in. But I thought that the benefit of using the yield :whatever would allow me to choose what to populate it with, and I can't get it to populate anything as soon as I change it from render to yield. Do I also have to update the routes file? If so, how do I choose which one to populate it with? Does that mean its in the controller? and needs an action?
I also have though that it depends on which file is initially routed to, but like I said, I think I need to understand the difference between the two before I can begin to use the partials to my advantage.
First of all, yield is ruby, render is rails. Usually one uses a common layout for the application whose inner content changes according to action/context. The problem usually lies in defining where our layout ends and context-specific template begins. Take, for instance, the HTML title tag. Let's say you have an application called Cities. In most cases, you want your page title to be "Cities" all the time. But, if you're for instance, inside Amsterdam page, then you would like the have "Amsterdam" as your page title.
# application.html.erb
<html>
<head>
<%= content_for?(:page_title) ? yield(:page_title) : "Cities" %>
......
# city/index.html.erb
<% content_for :page_title do %>
<%= #city.name %>
<% end %>
<div class="bla"...
Within Rails you usually define your application title in your application layout. One strategy for changing the page title would be to use content_for in the specific cities template and change accordingly.
Render, on the other hand, accomplishes different rendering strategies. Straight. When you call render, it renders. content_for/yield doesn't render automatically, it is stored somewhere and then fills up the missing spots in the due places. So, you can think of it as more as a "store/search/replace" in comparison to render, which just plain renders.
Good rule of thumb to use one over the other is: if the template you are writing needs to present different information per context, strongly consider using content_for.
yield
Ruby code (Proc class) and takes your block and does what it is supposed to do with it. Yield is also fast compared with other Ruby based ways of doing the same thing.
I'd assume (and I only) use it in the layouts because it's quick and I mindlessly do what's normal in Rails. yield is also used to pass content to a specific spot in your layout. I often have <%= yield :head %> in the head, just above the head tag, so that I can pass random weirdness that sometimes comes up.
Common Uses:
Mostly just used in layouts
(if you are fancy/inclined to do so in a Model) as a true Ruby Proc
statement.
render
Rails code that you pass arguments to that, as the docs say, "Renders the content that will be returned to the browser as the response body". partials, actions, text, files...etc.
Common Uses:
Used in both views and the controller.
When your controller method exits, it renders the associated file. So the edit controller renders edit.html.erb. It uses the specified layout or application.html.erb if none is specified.
Within your layout file, when you call yield it will fill in the information from your render. If you call yield with a parameter, it will look for a content_for section in your render file matching that parameter. I'm not completely sure, but I don't think you can call yield from outside of your layout file, and I don't think it will fill in any information except that found in your render file.
Anywhere in your layout file or your rendered file, you can render a partial by calling render with the partial name minus the underscore.
I hope that helps.
Edit to answer question in comment:
yield and render perform similar functions however yield only looks in the render file whereas render specifies which file to render. Also, render outputs the entire file, but yield with a parameter can output just a subsection of the file.
Here's a visual to put them both in perspective:
The render method is called at the end of a controller action and orchestrates what block is passed to the method that is actually rendering the application.html.erb by yielding the passed block.
https://richstone.io/debunk/

Using Rails helpers to render partials

From my understanding, helpers are mainly used to clean up views from some view-specific logic.
But on my currently new project (legacy application), I've stumbled upon a lot of helpers that look like this
def itemprepare
render :partial => 'items/itemlist_summary'
end
Is this correct? Rendering a partial to me seems like something you would want to do in the view, as it doesn't include any logic that needs to be abstracted.
Should I just inline all of these helpers?
Rendering a partial doesn't belong in a helper. Helpers should help you to do things that contain logic. Logic doesn't belong in the controller unless it's logic to render partials and decide if something should be displayed or not.
Although you generally shouldn't use helper methods to render partials, I can see how in some situations that might be necessary. For those circumstances, you need to use the concat method:
def itemprepare
concat(render(:partial => 'items/itemlist_summary'))
end
Like Ajedi32 says, partials use belongs to views but sometimes it's useful to use them in helpers. I hope it's useful to show what I've done in my application:
I've been following the excellent article Thinking of Rails Helper to help DRY our view. I'm using Jquery Mobile with a fixed header, nav-bar, navigation panel and a footer.
In every page I need to include the footer and the navigation panel, so usually it would have been:
<div data-role="footer">
<h4>Page Footer</h4>
</div><!-- /footer -->
<%= render "shared/nav_panel" %>
</div><!-- /page -->
at the end of each page.
Then I refactored the render partial into the application helper and now it is:
# app/helpers/application_helper.rb
def page_footer
footer = content_tag :div , :"data-role" => "footer" do
content_tag :h4, "Page Footer"
end
nav_panel = render(:partial => 'shared/nav_panel')
footer + nav_panel
end
and in the view I just call:
<%= page_footer %>
This is just a short example; in reality the app has a footer that changes according to the logged-in status, user language, etc..
We have a couple of helpers like that in our project, but most of them are in our custom gem. Wrapping partial rendering with helper prevents application from knowing how the information is rendered and we can easily extend logic, change partial, or do whatever we want inside this helper as long as it renders requested part of the view. Sometimes these partial require some data that resides inside the gem itself and there is no need to expose it to application. So application calls helper method (sometimes without any parameters at all) that forms required parameters and locals and passes them to partial.
But when you're just rendering partial inside your application and you don't need any extensive logic around that rendering I don't think there's much use from creating new helper for every partial.

New to RoR, confused about where to put markup

I'm very new to RoR, moving from PHP. I have what I hope is a simple question.
Say I have a Model called Article, stored in the table articles.
If I want to display a list of articles on several pages in my site, I might have some simple markup such as;
<% Article.find_each do |article| %>
<div class="article_list_wrapper">
<h1><%= article.title %></h1>
<p><%= article.body %></p>
</div>
<% end %>
Where would I put this markup? I mean in what file; if I put it in a view, then I will have to copy it if I want a similar list in a different part of the site.
Putting it in the Model itself seems intuitive to me, the Article object should know how to render itself, right? But that seems totally against the CMV idea.
And what if I wanted to have several ways of rendering the same object? Like a list view, a full page view etc.
Many thanks, just trying to get into the correct habits from the start.
You would put that portion of HTML markup into what is known as a partial -- something that can be rendered as a small piece of a larger layout. You're right, you wouldn't want to store the format in the model, because you might wish to render that specific HTML for web browsers and render very similar XML or JSON for clients using an API and render some other very similar content into ASN.1 or YAML for further other clients. The "view" really should stay associated with the views.
With this specific example, you would probably go one further and provide the articles to render via a variable rather than letting the view find the articles itself:
Controller:
def flubber:
#articles = Article.find_each(some_criteria)
end
View:
<div class="article_list_wrapper">
<%= render :partial => "article_list", :collection => #articles %>
</div>
And then provide a View for the corresponding controller with the name _article_list:
<h1><%= article.title %></h1>
<p><%= article.body %></p>
If the <div class="..."> is going to be identical in every single use of this partial, you could either pass a :template to the partial, or put it into a view helper, or put the entire render with the :template argument in a view helper. (Either the first or last approach here is what I'd do -- the <div class="..."> doesn't seem so important to require its own file, but if it were more complicated, it might -- and then I wouldn't want to give the partial :layout argument every time I needed it.)
MVC (Model-View-Controller):
The Model has (primarily) the database piece and business logic.
The Controller controls application flow.
The View shows the info, e.g. the browser view.
So the markup you show which has a bunch of HTML goes in the view.
That is app/models/views/article.rb
The Controller is the part that gets the info.
You should actually have #article = Article.all in your controller and then just use #article.each instead of Article.find_each in your view.
If you have conditions you can apply them directly on the Controller find but the better approach is to learn about how you can use model finders with scopes and then use those scopes from the controller. Always try and push things up from View to Controller to View and follow the 'fat Model, thin Controller' approach when practical.
For reuse partials can help but as better understanding of rails will help more.
See railscasts by Ryan Bates. Incredible tutorials.
You might also like my own bookmarks for rails at my (rails) linker app.
You can use partials for this.

Rendering polymorphic object in Rails

For people that only want the question, here it is :
Is there a way to specify the folder to look in when you call render on an object? I don't want to specify the view, only the folder to look in.
And for people that want context :
I am working on an activity stream system (something that looks like google+/facebook).
I have "Activities", which are exactly like google+ feeds (or facebook, or whatever!). So, I have a simple loop that display each activities, which are bound to one of the following object (polymorphic) : User, Group, Comment, Note.
In my view that render an activity (views/activities/_activity.html.erb), I have
<%= render activity.object %>
where activity.object is a reference to the bound object (User, Group, Note, Comment). If it's a user, it goes to views/users/_user.html.erb and renders it. For a group, views/groups/_group.html.erb.
That works just fine. However, I come to the point where the rendering of a group in my activities should not be the same rendering as in the group list page. Is there a way to specify the folder to look in when you call render on an object? So that my :
<%= render activity.object %>
would become :
<%= render activity.object, :folder => 'views/activities/' %>
Note that I don't want to specify which view directly, as I don't want to do a case for each of the possible types of objects (User, Group, Note, Comment) in the activity. I want to to have the same behaviour as of right now, which means if it finds a views/activities/_user.html.erb, it would load any user in the activities with that view instead of the one in the views/users/_user.html.erb.
Thanks
I'm not aware of any folder type option, but when I do this I usually do:
<%= render "activities/#{activity.object.class.name.underscore}" %>
That would give you similar behaviour.
EDIT A good point below by Dominic, if your classes are nested in namespaces, you will have to include the appropriate structure.
i.e.
module Foo
class Bar < ActiveRecord::Base
end
end
# class.name is Foo::Bar, underscored is 'foo/bar'
<%= render "activities/#{activity.object_type.underscore}" %>
# will be in
activities/foo/_bar.html
In current Rails (3.2.9), you can define a to_partial_path method on your model to tell it where to look.
For example, you can do:
class User
def to_partial_path; "user"; end
end
Rails will then look for _user.html.erb relative to the view from which you're calling render.
My own approach, which I extracted in polymorphic_render gem - to add suffixes for special partials, but store that partial in resource folders.
In your case views/users/_user.html.erb will have very common user representation (which probably used in users list rendering), but views/users/_user_activity.html.erb will render special partial for user activity.
And inserting that partials is very simple, just <%= polymorphic_render activity.object, :activity %>

Resources