Indent entire yield output 2 spaces in Ruby on Rails? - ruby-on-rails

For the sake of good looking code, is there some way to add a tab of spacing across the entire layout <%= yield %> in Ruby on Rails? Here's what I mean:
THIS:
# layout.html.erb
<body>
<%= yield %>
</body>
PLUS THIS:
# page.html.erb
<h1>Test</h1>
<p>Hello, world!</p>
OUTPUTS:
<body>
<h1>Test</h1>
<p>Hello, world!</p>
</body>
WHAT I REALLY WANT TO OUTPUT:
<body>
<h1>Test</h1>
<p>Hello, World!</p>
</body>
I did some research and found that using a minus sign like <%= yield -%> removes indentation, but I couldn't find a way to add it. Any ideas?

What about this?
# layout.html.erb
<body>
<%= yield.gsub(/^/, " ") %>
</body>
Actually, I have a method String#indent in my own library such as:
class String
def indent s = "\t"; gsub(/^/, s) end
end
Using this, you can reuse it in various places.
# layout.html.erb
<body>
<%= yield.indent %>
</body>

Expanding upon Sawa's answer, I just found a slightly more flexible approach to indenting content. Whilst Sawa's method above works just fine, it doesn't push out your yield code enough spaces if you're dealing with multiple block levels before your <%= yield %>.
Here's a slight improvement that can be customized to specific needs:
class String
def indent(spaces)
num = (" " * spaces)
gsub(/^/, num)
end
end
Now you can specify how many spaces of indentation you need right from your layouts like so:
# layout.html.erb
<body>
<div class="content">
<%= yield.indent(4) -%>
</div>
</body>
The above example will apply 4 spaces of indentation to every line of your yield. If there was another level, you would make change it to 6 and so on...

Related

How do I use content_for

I'm trying to create a dynamic page title. Is more then just #project.title
The project title, in the page title contains many elements, like the project name, the project category and the project city.
I've tried this
<%= content_for :page_title #project.name, #project.category, #project.city %>
<%= content_for :meta_description, #project.description %>
But I get an error like this one
syntax error, unexpected keyword_ensure, expecting ')'
I've also tried
<%= content_for (:page_title #project.name, #project.category, #project.city) %>
which resulted in the same error: syntax error, unexpected keyword_ensure, expecting ')'
Basically it's just a syntax error. If you want to store a block you use <% content_for :page_title, "My title" %>. Only if you want to reuse this block, you should add a = after the opening tag.
According to the docs you can only pass one content parameter. So you have to do the concatenation by yourself.
<% content_for :page_title, "#{#project.name} #{#project.category} #{#project.city}" %>
The documentation for the content_for helper can be found here.
quick answer
Maybe, just try this in your Project view
<% content_for :page_title, "#{#project.name} #{#project.category} #{#project.city}" %>
tl;dr
content_for stores a bit of code in an identifier. In order to access this stored content later you have to pass the identifier as an argument to content_for.
Note: yield can still be used to retrieve the stored content.
Or in other words, the content_for method allows you to insert content into a named yield block in your layout.
for example
If your current layout contains a yield(:title) ...
<!-- app/views/layouts/application.html.erb -->
<html>
<head>
</head>
<body>
<div id="title"><%= content_for?(:title) ? yield(:title) : "No title block!" %></div>
<div id="main"><%= yield %></div>
</body>
</html>
Then you can insert a title from a view like this
<!-- app/views/projects/index.html.erb -->
<% content_for :head do %>
"#{#project.name} #{#project.category} #{#project.city}"
<% end %>
See: https://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html#method-i-content_for
and https://rubyplus.com/articles/3081-Layouts-and-Content-For-Tag-in-Rails-5
To answer your question. The error messages you receive tell you that your Ruby code is syntactically incorrect. In idiomatic ruby, it's always wrong to call functions the way you've shown us. Parameters must always be called individually, separated by a comma:
# wrong code, wrong syntax
<%= content_for :page_title #project.name, #project.category, #project.city %>
# wrong code, right *right syntax*
<%= content_for :page_title, #project.name, #project.category, #project.city %>
Also as pointed out in Robin's answer, the docs for content_for show that you can only pass one content parameter. So you have to do the concatenation by yourself.
<% content_for :page_title, "#{#project.name} #{#project.category} #{#project.city}" %>

Collect data in views and output in head

I have a helper that collects data from multiple views:
# application_helper.rb
def data
#data ||= []
end
In my HTML, I want to add data to this helper. This should be possible from within the layout, views and partials:
# layout.html.erb
<html>
<head></head>
<body>
<section><% data << "section data" %></section>
<%= yield %>
<footer><% data << "footer data" %></footer>
</body>
</html>
# my_view.html.erb (which is rendered with yield)
<h1>My View</h1>
<% data << "view data" %>
The problem is, that the data should be rendered as a data attribute on the body tag, e.g.
<body data-test="<%= data.to_json %>">
Of course, this would render an empty array, because at this point of time, the array is not filled yet.
I tried it with
<body data-test="<%= yield(:data) %>">
...
<% content_for(:data) do %>
<%= data.to_json %>
<% end %>
</body>
but this also doesn't work. It only works, when I put the content_for call inside a view. And furthermore, I only get view data but not section data or footer data. So it seems that only data that is added within my view is rendered, not the data that I add within my layout.
How would something like this be done? Rendering data at the top of an html page, that is collected later in the document?
<html>
<head></head>
<body data-test="<%= content_for(:data) %>">
<section></section>
<%= yield %>
<footer>></footer>
</body>
</html>
content_for can be used to both store content for later use and provide dynamic blocks since subsequent calls to content_for concatenate.
Lets say we are rendering users/show:
<h1><%= #user.name %></h1>
<% content_for(:data) { #user.name } %>
<% content_for(:data) { ", " + #user.username } %>
This would give
<body data-test="john doe, john_doe">
However its use for passing data structures is questionable since its just a string buffer.
A better alternative is to gather the data beforehand and use JSON to pass the data into javascript.

Rails what does this "yield" do?

What does the yield do in this snippet?
<body data-spy="scroll" data-target=".sidebar">
<!-- Your timezone is <%= Time.zone %> -->
<!-- <%= "Ruby Version is #{RUBY_VERSION}" if Rails.env =~ /test|development/ %> -->
<%= render partial:'shared/account_status' %>
<%= render partial:"shared/session_timeout" %>
<div class="container">
<%= render partial:"shared/branding" %>
<%= render partial:"shared/nav", locals:{icons:icons, actionable_urls:actionable_urls, top_level_items:MenuItem.top_level_items_with_access_rights_for_user(current_user).sort{|a, b| a.sequence <=> b.sequence}, current_item:current_navigation_item} %>
<div style="clear:both"></div>
<div id="content">
<%= render partial:"shared/flash", object:flash %>
<%= yield %>
</div>
</div>
<%= render partial:"shared/ldap_user_menu" if signed_in_as_ldap_user? %>
</body>
SOLUTION
see #Christian_Rolle 's answer below
The reserved Ruby key word yield is for processing closures (like a Proc or lamda). That said it is some kind of placeholder for processing some logic.
In Ruby on Rails view templating it is used for merging in partials. In the case of a layout file like the application.html.erb it merges in controller response templates like index.html.erb or show.html.erb.
Think of it as a placeholder for your controller response HTML in a global layout environment.
Read more at: Understanding yield
or about Ruby closures:
Do the Proc! ... a Ruby closure and
Have a lambda! ... a Ruby closure
okay ... lets talk about it in easy way , yield is like a placeholder or like a container . And while you make different parts of view and want to show it on any specific layout file , then you can just call that part on the yield section . And this is all it does .

switch layout columns based on controller

so basically my application looks like this
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<div id="content">
<div id="content_left">
// Some Stuff
</div>
<div id="content_right">
<%= yield %>
</div>
</div>
</body>
</html>
now i want to switch between a two column layout and a single column layout based on the controller (at best: also based on the method i'm using).
between the body and the content and also in there head there is way too much stuff for simply creating a second layout and adding this as a layoutcall in my controller without too much code duplication.
what i would love to do is something like this:
all should use this
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
// much stuff
<%= yield %>
</body>
</html>
and now i can switch between two layouts, f.e
single_col
<div id="content">
<%= yield %>
</div>
or two_column
<div id="content">
<div id="content_left">
// Some Stuff
</div>
<div id="content_right">
<%= yield %>
</div>
</div>
and then in the last yield there should be the view that is related to my controller and method.
is there an easy way to achieve this?
thanks for all hints.
PLEASE leave a comment if something is unclear.
Put the different parts of the layouts in partials! Then you render the partials based on what is in
params[:controller] and params[:action]. For example:
<% if params[:controller] == "controller_name" %>
<%= render 'partial_name1' %>
<% else %>
<%= render 'partial_name2' %>
<% end %>
The params[:controller] and params[:action] are always available! This is an example to show you how it works. Of cause there shjouldnt be any logic in the view aswell!
You could use two layouts that would both render header and footer partials containing what's common to both.
OR, you use a <%= yield :sidebar %> and then inject something
<% content_for(:sidebar) do %>
some stuff here
<% end %>
Take a look at Rails guide section about those here.
I think you looking for a way to use a shared layout with two sub layouts inside, here you go:
add this to your application_helper.rb
# Allows easy using nested layouts
def inside_layout(layout = 'application', &block)
render :inline => capture_haml(&block), :layout => "layouts/#{layout}"
end
layouts/application.html.haml
!!!
%html
%head
-# your header content
%body
.content
= yield
layouts/single_column.html.haml
= inside_layout do
.middle
= yield
layouts/two_column.html.haml
= inside_layout do
.left
-# your shared left content
.right
= yield
the column layouts can now be used like normal layouts, so you can just set them in your controller
layout "single_column"
Note: All markup is in HAML a gem which I can highly suggest.
hope it helps :)
You can use multiple layouts in a rails project. You could have a 2 column and a single column one.
To specify the layout used, provide it in the method in the controller.
def index
respond_to do |format|
if current_user
format.html {render 'pages/index', :layout => 'home'}# index.html.erb
else
format.html {render 'pages/landingpage', :layout => 'landingpage'}# index.html.erb
end
end
end
For example the controller above, will render the the views index.html.erb and landingpage.html.erb based on the value of current_user. You can also only provide the layout parameter to render like render :layout => 'home'
You could also have a look at nested layouts but I have never worked with those.
You could use sub_layouts
Class IndexController < ApplicationController
def index
def sub_layout
"left"
end
end
end
Add sub layouts to /app/view/shared/layouts/sub/_mysublayout.haml or .erb
in /app/views/layouts/application.haml/.erb * your main layout file*
= render :partial=>"layouts/sub/#{controller.sub_layout}"
With some checking for nils with rescue nil you can make this sub template load the layout for say a right or a left col, use this with great benefit in my apps. Its a big time saver and gives you the extra flexibility to set the layout on an action base.
Im assuming that in your single column layout you have no information for your content_left.
And in two column layout you have information for both content_left and content_right.
There are lots of ways but im recommending something completely controlled by CSS.
CSS classes will be like following
#content_left{
float: left;
background-color: red;
}
#content_right{
background-color: green;
}
#content {
margin: 0 auto;
width: 80%;
}
Now notice... if content_left div is empty then the content_right div will expand to full width. And you are ready with single column layout. And if you have data in content_left it will be showing accordingly.
I am using helper method to set Dynamically sidebar,you can use as per your requirement.
<div id="main_content" class="<%= main_content_css_class %>">
<%= yield %>
</div>
<div id="sidebar" class="<%= sidebar_css_class %>">
<%= yield :sidebar %>
</div>
In Helper
def sidebar_enabled?
current_page = "#{controller.controller_name}.#{controller.action_name}"
current_controller = controller.controller_name
pages = %w(home)
return pages.include?(current_page) || pages.include?(current_controller)
end
def main_content_css_class
sidebar_enabled? ? "grid_12" : "grid_16"
end
# Returns the CSS class for the 'sidebar' div depending on sidebar requirement
def sidebar_css_class
sidebar_enabled? ? "grid_4" : "dont-show"
end
Using your code is also cleaner and maintainable.
I usually put classes on the body element for the controller and action name, as it comes in handy in many ways. Here's an example based on that strategy:
...
<body class="<%= controller_name %> <%= action_name %>">
<div id="content">
<div id="sidebar">
// Some Stuff
</div>
<div id="main">
<%= yield %>
</div>
</div>
</body>
...
then it's a matter of getting the right CSS. If you had a 'posts' controller, the sidebar is usually visible, and you want to hide it:
body.posts { #sidebar { display: none; } };
If you want it to only show up sometimes, you could invert the logic so it's usually hidden, then override with a more specific scope to show it. Or, you could have one column on all 'index' actions, two columns for everything else, etc.
The downside to this, and it's not insignificant, is that your views are coupled to the name of the controller. I.e. this strategy violates the "tell, don't ask" principle. I still think it's worth it, since I haven't been bit by a nasty refactoring involving this code yet. Usually it's a simple matter of changing a couple instances of the controller name in ex. "posts.css.scss" when renaming the file; no big deal. Just something to weigh.
Also, note that I picked more descriptive class names. "Left" and "right" are ephemeral, as your example demonstrates. "Sidebar" and "main" might not be what you have, but I'd make an attempt to describe what goes in them if possible.

'spaceless' equivalent of Django templates in Rails

Notice the readability and balance of:
<li class='aclass anotherclass <%= maybeaconditionalclass %>'>
<a href="<%= some stuff %>">
<%= some other stuff %>
</a>
</li>
which unfortunately produces trailing whitespace inside the link resulting in a ugly trailing underline. Now although less readable I can live with this:
<li class='apossibleclass anotherclass <%= maybeaconditionalclass %>'>
<%= some other stuff %>
</li>
Still, the same problem remains if I now consider this kind of thing:
li.apossibleclass:after {
content: "/";
}
as the whitespace between the closing A and LI gets in the way of what should be sticking to my list item's end. I could only produce that ugly mess as a workaround:
<li class='apossibleclass anotherclass <%= maybeaconditionalclass %>'>
<%= some other stuff %></li>
Django came up with a nice solution: {% spaceless %}, so I'm looking for the equivalent of the {% spaceless %} tag in Rails erb templates.
Yes, that would be an useful feature, and as far as I know, there's nothing like it in Rails. So I've coded it.
# Strip all whitespace between the HTML tags in the passed block, and
# on its start and end.
def spaceless(&block)
contents = capture(&block)
# Note that string returned by +capture+ is implicitly HTML-safe,
# and this mangling does not introduce unsafe changes, so I'm just
# resetting the flag.
contents.strip.gsub(/>\s+</, '><').html_safe
end
This is a helper you can place in your application_helper.rb, and then use like this:
<%= spaceless do %>
<p>
Foo
</p>
<% end %>
... which will result in the output string like
<p> Foo </p>
Sadly, this only works in Rails 3. Rails 2 support for a feature like this will require some dirty hacks to ERb.

Resources