Ruby on Rails: Can't insert content into yield block - ruby-on-rails

I have a partial called _main_nav.html.erb in views/layout folder, this is content file:
<% content_for :main_navigation do %>
<h1>Something must here</h1>
<% end %>
In my _header.html.erb file, i used yield to insert content:
...
<%= yield :main_navigation %>
<nav id="utility" class="nav-collapse">
...
But when i visit home page, it does'n appear h1 element, i have wrong somewhere?

The issue is that you are trying to yield content from a view that isn't currently being rendered.
You would need to do:
_header.html.erb
...
<%= render "layouts/main_nav" %>
<%= yield :main_navigation %>
<nav id="utility" class="nav-collapse">
...
_main_nav.html.erb
<% content_for :main_navigation do %>
<h1>Something must here</h1>
<% end %>
http://guides.rubyonrails.org/layouts_and_rendering.html#understanding-yield

Related

How to use yield with dashboard sidebar to show contents on same page

I have a rails app with a functionality quite similar to yelp with a dashboard for customers where they can edit their place but also change their acccount settings, etc. A customer can only have one place for now.
I've created a dashboard controller, views with a partial for the sidebar and everything is working so far but my problem is that when clicking on a link in the sidebar it yields to the application.html.erb. I want to yield everything from the sidebar to the main part of the dashboard/index.html.erb
So my question is how do I yield the things I click in my sidebar to the part on the right next to the sidebar on the page. Basically the functionality is like a navbar on top (only for logged in customers) but I get confused with two yields. I tried "content_for" and <%= yield :sidebar %> but didn't figure out how to get it working yet. Also I am using devise with a user and customer model which share the views and have the functionality for the customer to edit his user account in the dashboard sidebar which might cause a problem with "content_for"?
Please note that I am still learning ruby on rails and am very happy for any kind of input!
dashboard_controller.rb
def index
#place = Place.where(customer_id: current_customer.id).first
end
dashboard/index.html.erb
<div class="content">
<div class="sidebar">
<%= render 'dashboard/sidebar' %>
</div>
<div class="main">
<%= yield %>
</div>
</div>
_sidebar.html.erb
<li class="nav-link">
<%= link_to "<span class='fa fa-cog'></span> Edit".html_safe, edit_place_path(#place) %>
</li>
<li class="nav-link">
<%= link_to "<span class='fa fa-cog'</span> Settings".html_safe, edit_customer_registration_path %>
</li>
<li class="nav-link">
<%= link_to "<span class='fa fa-sign-out'></span> Log Out".html_safe, destroy_customer_session_path, method: :delete %>
</li>
application.html.erb
<body>
<%= render 'layouts/shared/header' unless #disable_navbar %>
<%= yield %>
<%= render 'layouts/shared/footer' unless #disable_footer %>
</body>
Within the context of a layout, yield identifies a section where content
from the view should be inserted.
http://guides.rubyonrails.org/layouts_and_rendering.html#understanding-yield
So no - yield will not "yield" to your dashboard/index.html.erb. Rather if you want it to have a different layout you should create a layout.
Lets look at an example:
<% # app/views/layouts/application.html.erb %>
<body>
<nav id="top-menu">
<h1><%= link_to 'MyApp', root_path %></h1>
<ul>
<%= yield :nav_links %>
<%# lets provide some default content for nav_links %>
<% content_for :nav_links do %>
<li><%= link_to 'Products', products_path %></li>
<% end %>
</ul>
</nav>
<div id="main">
<%= yield %>
</div>
</body>
Here we create a named yield in the layout called :nav_links and also add some "default" content with content_for :nav_links.
So lets look at what happens when we render /products/index.html.erb:
<div class="products">
<% if #products.any? %>
<%= render #products %>
<% else %>
<p>No products are available at this time</p>
<% end %>
</div>
<%# we also want to add a contextual navigation link %>
<% content_for(:nav_links) do %>
<li><%= link_to 'Sales', sales_path %></li>
<% end %>
The rendered result is:
<body>
<nav id="top-menu">
<h1>MyApp</h1>
<ul>
<li>Products</li>
<li>Sales</li>
</ul>
</nav>
<div id="main">
<div class="products">
<p>No products are available at this time</p>
</div>
</div>
</body>
Rails inserts the content from the view into the "main" layout and the content from content_for(:nav_links) is concatenated into the buffer.
Also I am using devise with a user and customer model which share the
views and have the functionality for the customer to edit his user
account in the dashboard sidebar which might cause a problem with
"content_for"?
The only problem you may have is a "namespace collision" - if you are using yield :sidebar and a gem for example is also using the same name for a yield you may have unexpected results.

Rendering A Partial / Layout With Multiple Blocks

I have a very simple requirement - I have a layout comprising of a header and body. It is a sub-layout of the page, not for the page itself.
This layout is repeated throughout multiple pages, and it is possible the structure around it will change. So I want to be able to separate the content of the header and the content of the body from the structure that contains it.
My first attempt was to use render a partial as a layout that used named yields to render a header and body:
<header class="Resource-header">
<%= yield :resource_header %>
</header>
<div class="Resource-body">
<%= yield :resource_body %>
</div>
Then render it from my templates like this:
<%= render layout: 'admin/resource' do %>
<% content_for :resource_header do %>
<% end %>
<% content_for :resource_body do %>
<% end %>
<% end %>
However, this renders nothing.
I started playing with the order of things, and discovered that if the content_for blocks are declared before the call to the partial, this approach does work:
<% content_for :resource_header do %>
<% end %>
<% content_for :resource_body do %>
<% end %>
<%= render layout: 'admin/resource' do %><% end %>
However this just feels incredibly hacky. It seems that content_for is scoped globally, and there is no association between the content_for block and the partial rendering.
So what is the correct way for me to achieve this?
I just happened to have exactly same problem.
Solution is:
in your partial layout file 'admin/resource' body:
<header class="Resource-header">
<%= yield resource, :resource_header %>
</header>
<div class="Resource-body">
<%= yield resource, :resource_body %>
</div>
in your templates do:
<%= render layout: 'admin/resource' do |resource, section| %>
<% case section %>
<% when :resource_header %>
Resource header shows here.
<% when :resource_body %>
Resource body shows here.
<% end %>
<% end %>
Take a look on rails presenters https://www.ruby-toolbox.com/categories/rails_presenters
Maybe your solution is cells gem.
Eventhough the question is quite old now, I had a similar issue today. I came up with sth. like this. No gem or custom class required, just some fancy block usage ;)
<!-- app/views/layouts/fancy-blocks.html.erb -->
<%
body, footer = nil
yield(
proc {|&blk| body = capture(&blk) },
proc {|&blk| footer = capture(&blk) }
)
%>
<section class="body"><%= body %></section>
<footer><%= footer %></footer>
<!-- app/views/some-other/view.html.erb -->
<%= render 'layout/fancy-blocks' do |body, footer| %>
<% body.call do %>
BODY
<% end %>
<% footer.call do %>
FOOTER
<% end %>
<% end %>

Using multiple yields to insert content

I am trying to insert content on my page with yield but every time action removes whole content from the page. I have one main yield which is working fine:
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
</div>
</body>
But inside that new content which is displayed on one page I have another yield:
<div class="container">
<%= render 'admins/menu' %>
<%= yield :admin %>
</div>
When user clicks on the menu which is rendered, new content should be displayed below that menu.
admins/_menu.html.erb
<div class="navbar">
<div class="navbar-inner">
<div class="container">
<ul class="nav">
<li><%= link_to "Users", :controller => "admins", :action => "test" %></li>
<li><%= link_to "1", ... %></li>
<li><%= link_to "2", ... %></li>
<li><%= link_to "3", ... %></li>
</ul>
</div>
</div>
</div>
Controller:
class AdminsController < ApplicationController
def index
end
def test
#users = User.paginate(page: params[:page])
end
end
test.html.erb
<% content_for :admin do %>
<h1>All users</h1>
...
<% end %>
When I click on the option 'Users' from menu, page refreshes, menu disappears and nothing is displayed inside `body'. I want the content to be displayed below menu. How to use that second yield and accomplish this functionality?
I hope the question is not confusing. If question is confusing, please write me in comments and I will edit it immediately.
Thank you :)
So, when you go to the index page you will get the piece of html that will be placed in the main layout, and this piece of html look like this:
<div class="container">
<%= render 'admins/menu' %>
<%= yield :admin %>
</div>
This code will yield :admin properly.
When you go to the test page you do not have this html code anymore (since it only belongs to the index method). So, anything you put in the content_for(:admin) block will be ignored since no-one is printing it.
What you probably want to do is creating a shared layout for all your admin pages. Follow this guide and you'll have your solution.
Solution
Edit the application.html.erb layout using this:
<%= content_for?(:content) ? yield(:content) : yield %>
instead of
<%= yield %>
Then create an admins.html.erb file inside the layouts folder to handle your admin pages' layout. Something like this:
<% content_for :content do %>
  <div class="container">
<%= render 'admins/menu' %>
<%= yield %>
</div>
<% end %>
<%= render template: "layouts/application" %>
Will do fine. Then in the index.html.erb and test.html.erb just place regular HTML content, without using the content_for(:admin) block. Everything should work fine and you'll have your custom admin template, with a slightly different look from regular pages.
Calling yield doesn't work in helper modules, while content_for does, so you should replace your yield calls in the helper files.
Also noteworthy: using provide is recommended over content_for when you're only using the method in 1 place instead of multiple places. You'll get better performance since it won't leave the buffer open while looking for more content, and your intent will be clearer to other developers that may see your code. (see http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html#method-i-provide)
I found that you have to add an yield (without actually outputing) before the namespaced tags.
<div>
<% yield %>
<div class="mt-3">
<div class="text-2xl tracking-wide font-bold text-gray-900">
heading
<%= yield :heading %>
</div>
</div>
<div class="relative bg-white rounded-xl shadow-xl mb-8 min-h-28">
<%= yield %>
</div>
...

Ruby on rails: Yielding specific views in a specific places in the layout

If I have one <%= yield %> tag then all my views render in the same place in the layout. Can I have different <%= yield %> tags for different views? Is so how do I do this? Thanks
Look into ActionView::Helpers::CaptureHelper. You can do something like this in your views:
<% content_for :sidebar do %>
<!-- sidebar content specific to this page -->
<% end %>
This will run the template inside the content_for block, but will not output as part of the regular template yield buffer, it will be stored in a separate buffer for later. Then later on, including in the layout, you can use yield :content_name to output the content:
<div class="content">
<%= yield %>
</div>
<div class="sidebar">
<%= yield :sidebar %>
</div>
So in a sense you can have different yields for different views, you just have to give the differing content a name with content_for in the views, and yield it with that same name in the layout.
Consider your case, where you want different views in different places. Let's say you have three panels, panel1, panel2, and panel3. You can do this in your layout:
<div id="panel1"><%= yield :panel1 %></div>
<div id="panel2"><%= yield :panel2 %></div>
<div id="panel3"><%= yield :panel3 %></div>
You don't even need to include a plain <%= yield %> if you don't want to. Then in your views, you can choose which panel to display the content in by surrounding the entire view with the appropriate content_for. For example, one of your views might be changed like this:
<% content_for :panel2 do %>
<!-- Your View -->
<% end %>
To show in panel 2. Another one might be intended for panel 3, like this:
<% content_for :panel3 do %>
<!-- Your View -->
<% end %>
Yes, you can have multiple <%= yield %> tags. You can specify each yield tag with names like these in the base view.
<%= yield :head %>
<%= yield :footer %>
Then use the content_for tag in your individual views.
<% content_for :head do %>
<%= stylesheet_link_tag 'custom' %>
<% end %>
You can use yield and content for:
For example:
<%= yield :head %>
<% content_for :head do %>
<title>A simple page</title>
<% end %>
Refer :layout and rendering guide.

Two-part Rails layouts

My web pages consist of two parts, let's say top and bottom (except header and footer -- those are consistent across pages). What is the best practice to dynamically generate those parts depending on the action?
One approach I have come up with is to have view for the top and partial for the bottom; in the layout call yield for the top and render partial for the bottom. The name of the partial is dynamically substituted depending on the action.
Not sure it is the best way to do it.
I think your idea is fine. In your views you could do:
<%- content_for :top do -%>
[…]
<%- end -%>
<%- content_for :bottom do -%>
<%= render #partial_name %>
<%- end -%>
Of course you should check whether the partial exist and provide some default behavior. But I think you're aware of that anyway.
And then in your layout:
<div id="top">
<%= yield :top %>
</div>
<div id="bottom">
<%= yield :bottom %>
</div>
Here is a very simplified version of a view DSL I've used in the past. Worked well for us. In reality we parameterized the the helper methods so we could choose from many layout partials on the fly (to have pages with sidebars, multiple columns, etc).
# app/views/shared/_screen.erb
<div id="screen">
<div class="screen_header">
<%= yield :screen_header %>
</div>
<div class="screen_body">
<%= yield :screen_body
</div>
<div class="bottom">
<%= yield :footer %>
</div>
</div>
# app/helpers/screen_helper.rb
module ScreenHelper
def screen(&block)
yield block
concat(render :partial => 'shared/screen')
end
def screen_header
content_for :screen_header do
yield
end
end
def screen_body
content_for :screen_body do
yield
end
end
def footer
content_for :footer do
yield
end
end
end
# app/views/layouts/application.erb
# only showing the body tag
<body>
<%= yield :layout
<body>
# Example of a page
# any of the sections below (except screen) may be used or omitted as needed.
# app/views/users/index.html.erb
<% screen do %>
<% screen_header do %>
Add all html and/or partial renders for the header here.
<%end%>
<% screen_body do %>
Add all html and/or partial renders for the main content here.
<% end %>
<% footer do %>
Add all the html and/or partial renders for the footer content here.
<% end %>
<% end %>

Resources