How do I use different partials and layouts in subfolders? - ruby-on-rails

I need to use a single partial for anything inside my views/admin folder. My setup:
/app/views/
+ admin
+ accounts
+ users
+ layouts
- application.html.slim
+ application
- _header.html.slim
+ users
+ accounts
I have a partial called _header.html.slim in /views/application/. The partial is rendered from /views/layouts/application.html.slim.
I want to render a different _header.html.slim partial for anything under the /views/admin dir. I can create a new _header.html.slim and add it to /views/admin/accounts and /views/admin/users, but I don't want to repeat my self. I want a single partial for everything under admin.
How can I so this? I tried adding /views/admin/application and /views/admin/layouts folders hoping they would override the ones in the /view dir, but no luck.

layouts/application.html.slim:
- if controller.controller_name == "admin"
== render :partial => "admin/header"
- else
== render :partial => "application/header"

The structure of the folders themselves don't control which ones get run or applied - the folder structure is really just to help you organize it in a way that makes sense.
You could accomplish what you're looking for in a couple of ways, depending on your needs:
One way is to specify the layout (which includes the desired partials) in the controller, using the render :layout => 'some_layout_name' option as outlined here (skip to the heading "2.2.11.2 The :layout Option" for the specifics).
Another way is to set a variable in your action that contains, say, the name of the layout(s) or partial(s) you want to render, and in your view do something like:
<% if #custom_partial == "slim" %>
<%= render :partial => 'header.html.slim' %>
<% end %>
So, either specify a custom layout (if you want the whole layout including partials to be custom), or set a flag variable that controls which partials get rendered at which time, and use that variable to control the flow of rendering in your view. Which of those options is right for you depends really on which is cleaner, most reliable, and makes sense for your project; that is, it's up to you to decide.

Related

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/

Rails implicit render in templates from multiple controllers

I have code like render #posts to render my posts collection in an index template which the PostsController renders.
Now I have an Admin::PostsController that also should render the collection but when my posts controller renders #posts it looks for the admin/posts/_post.html.erb partial. Do I now have to write the partial path explicity? Is this feature by design or a bug? It doesn't seem to make sense.
Yes, you need to supply the path explicitly. And yes, this is by design.
It actually makes sense because Rails is a MVC framework and if you create a controller under a different namespace one would expect separate views for that controller too. Think about convenience, if you wanted to quickly bootstrap an application with a few simple commands, an application where there's a public view of posts and an admin view where all of the admin goodies for editing are, you would EXPECT to have a different directory to store all that admin views.
render #posts is a shortcut for a longer method signature.
In case of PostsController, it is a short cut for render :partial => "post", :collection => #posts; the partial is _post.html.erb and it is expected to be in app/views/posts folder.
In case of Admin::PostsController, it is a short cut for render :partial => "admin#post/post", :collection => #posts; the partial is _post.html.erb, and it is expected to be in app/views/admin/posts folder.
If you want a different partial to be used, you should specify it explicitly.
See the Rendering Collections section of Rails Guides page on Layouts & Rendering for detailed explanation.

I want to render controller/action inside a div in Rails

Lets say I have a 300X300px block called "Statistics" or "Friends" this block requires some database objects and logical operations that traditionally should be placed inside a controller, which prepares some instance variables that will be displayed nicely in it's view/partial.
This block could be called from many pages in my website, crossing various controllers and actions.
How can I go about this problem without repeating code inside controllers, nor placing logic inside views.
A bad solution would be to place SQL queries inside views, or using iframes where a single controller/action generates the expected HTML.
Any ideas will be greatly appreciated.
I think this is the kind of thing you should do in a helper method. It might be a good idea to use locals for you partial instead of instance variables so as not to pollute the namespace of your views:
module ApplicatonHelper
def friends_block
friends = Friend.where(...)
# Assuming the 'friends_block' partial is in a directory 'app/views/shared'
render :partial => "shared/friends_block", :locals => {:friends => friends}
end
end
The friends_block partial would use the local variable:
<div id="friends_block">
<% friends.each do |s| %>
...
<% end %>
</div>
And other views would just use the helper method without having to worry about anything else:
<%= friends_block %>
Have a look at the Cells gem:
https://github.com/apotonick/cells
I'd put the block into a partial and ensure that the required instance variables for that block are loaded in a before_filter on the relevant controllers and actions.

Where to put partials shared by the whole application in Rails?

Where would I go about placing partial files shared by more than one model?
I have a page called crop.html.erb that is used for one model - Photo.
Now I would like to use it for another model called User as well.
I could copy and paste the code but that's not very DRY, so I figured I would move it into a partial.
Since it's shared between two models - where would I place that partial?
Thanks!
The Rails convention is to put shared partials in /app/views/shared.
Update
Layout inheritance is now in the guides under layout and rendering
Template inheritance works similarly.
Rails 3.1 and following versions implement template inheritance, so I think the correct place for shared partials is now /app/views/application/, say you are in products#index you can do the following:
-# products#index
= render #products.presence || 'empty'
-# /app/views/application/_empty.html.haml
There are no items
btw it's application because the connection is the controller inheritance, so this assumes ProductsController < ApplicationController
This way if you implement /app/views/products/_empty.html.haml that will be taken, the above is a fallback for all the missing partials, and I can't check right now, but I think even for the template itself...
Railscast: template inheritance!
TL;DR
Rails 3.1, Rails 4, Rails 5 and whatever comes next
app/views/application
The engine searches this path automatically if the view is not found in the controller path.
Rails 3 and prior
app/views/shared
The engine does NOT search this path automatically.
Long story
Rails 3 (and prior version) have no default location for storing shared views.
The unofficial convention is to store shared views in app/views/shared. Wherever you'd end up storing them though, you have to specify the path
# render app/views/shared/menu.html.erb
<%= render :partial => "shared/menu" %>
This suggestion was popularized by Agile Web Development with Rails.
Rails 3.1 introduces an official standard for where to store shared views:
app/views/application
Thanks to this standard, the engine now automatically looks for templates in app/views/application. As a result, you don't have to use the full path anymore.
Those curious can follow here the thought process behind this decision.
Old syntax
# render app/views/application/menu.html.erb
# unless menu.html.erb is found in appp/views/my_controller
<%= render :partial => "menu" %>
New syntax
# render app/views/application/menu.html.erb
# unless menu.html.erb is found in appp/views/my_controller
<%= render partial: "menu" %>
Of course, you can still place your shared views wherever you want and reference them by path
<%= render :partial => "my_own_special_shared_folder/menu" %>
Unless you have a very good reason to do this though, please stick to the new standard and store your shared views in app/views/application.
The Rails View uses app/views/layouts for shared partials like header and footer, but the Ruby on Rails Guide uses app/views/shared in an example. I guess it comes down to personal preference. I would use layouts for high-level stuff like a main nav or a footer, but shared for more narrow controller-level stuff like a form.
I general have a shared folder in my views that contains commonly used partials.
I arrived here in 2021 (rails 6) and got confused by the answers (many different ways).
I asked some senior rails developers what they'd do and they also gave me 2 different answers.
But TL;DR
Create a folder called 'shared' or 'application' inside your views folder (e.g. app/views/shared or app/views/application.
Then simply move the partial there, and access it with either
<%= render partial: 'shared/socials' %>
# or
<%= render partial: 'application/socials' %>
or even simpler
<%= render 'shared/socials' %>
# or
<%= render 'application/socials' %>
It doesn't matter where you put them. You can render any partial at any arbitrary location by providing the file's path to render - it doesn't need to be associated with the controller that's rendering it. I use a directory simply called partials under the view directory, and call partials in it like this:
render :partial => 'partials/mypartial'

How do I implement Section-specific navigation in 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.

Resources