How do I use content_for - ruby-on-rails

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}" %>

Related

content_for are not rendering in partial views

I have a rails app which uses a layout
The simplified version looks like this:
<html>
<head>
<%= render 'layouts/head' # renders the "layouts/_head.html.erb" partial correctly
# the most head (css/js) content gets implemented here %>
</head>
<body>
<%= yield # renders the current action %>
<!-- implement alls scripts which should be exectued on the bottom of the page -->
<%= content_for :scripts_bottom %>
</body>
</html>
in my `layouts/_head.html.erb' I use
<%= content_for :scripts_head %>
<!-- it seems `content_for` instead of `yield` appends content -->
In my partials I place the following snippets to append them :scripts_head. (some of my partials should put javaScripts
<% content_for :scripts_head do %>
<%= javascript_include_tag 'some_script' %>
<% end %>
The content_for in the `layouts/head' renders nothing
How can I resolve that?
It looks like that partials are not able to append their content_for content when the content_for :blah do is placed BEHIND the echoing content_for / yield tag.
If I try try content_for :scripts_bottom it will get rendered at the bottom of the page.
Thanks in advance
Rails 3.2.14
ruby 2.0.0p247
instead of provide, try <%= content_for :scripts_head %>
If you want to use content_for then you need to yield it in your head instead of render.
So your header would look like:
<%= yield :scripts_head %>
Alternatively you can remove the content_for in your partial and just have the JS by itself like this:
<%= javascript_include_tag 'some_script' %>
Then you wouldn't have to change your layout file.
In your layouts/head partial, use yield :scripts_head not content_for

Communication between view and layout

I'm new on RoR and I'm trying to understand how the communication between the view and the layout works.
I found some documentation and I get I need to use provide or content_for methods but it doesn't really explain how it gets accomplished.
Also, why do I need to use yield in my layout to print the value?
Example:
home.html.erb (view)
<% provide(:title, 'Home') %>
application.html.erb (layout)
<title>Great App | <%= yield(:title) %></title>
In your sub-view, you can use either of these methods by passing a block with text content (which can be useful for long content passages such as those that include HTML tags):
<% content_for :title do %>
<h1>The Title</h1>
<% end %>
Or you can simply pass a string directly to #content_view as the second argument:
<% content_for :title, "The Title" %>
There's some better documentation for these methods in the ContentHelper module.
Layouts just wrap other views, and subviews can be thought of as "blocks" that are passed to the layout. If you think of them that way, its natural that the yield keyword is used to invoke the subview like a block.

how to extend child template to parent in rails

I am new to rails. I am having difficulty in understanding template inheritance. Earlier I have worked in django and seen template inheritence there. There I saw child is told about parent using "extends" command. Can anyone explain how it works here. I have gone through guidelines of ruby but it was not clear.
Thanks
It's quite simple to do in Rails.
You simply tell the template you are currently rendering to render another template.
For example layouts/application.html.erb contains something like this:
<% content_for :navigation do %>
<nav>...</nav>
<% end %>
<% content_for :content do %>
<%= yield %>
<% end %>
<%= render :template => 'layouts/main_application' %>
The important part is the render :template part that then delegates this template to also render the layouts/main_application.html.erb that in my case looks something like this:
<header>
...
</header>
<body>
<%= yield :nav %>
<%= content_for?(:content) ? yield(:content) : yield %>
</body>
What I am doing here is having a main template that does not contain the navigation (for things like login etc) and the application.html.erb adds that navigation to the :nav content placeholder.

In RoR, is there an easy way to prevent the view from outputting <p> tags?

I'm new to Ruby and Rails and I have a simple controller that shows an item from the database in a default view. When it is displaying in HTML it is outputting <p> tags along with the text content. Is there a way to prevent this from happening? I suppose if there isn't, is there at least a way to set the default css class for the same output in a statement such as this:
<% #Items.each do |i| %>
<%= i.itemname %>
<div class="menu_body">
Link-1
</div>
<% end %>
So the problem is with the <%= i.itemname %> part. Is there a way to stop it from wrapping it in its own <p> tags? Or set the css class for the output?
Thanks!
You need to enclose it with the HTML tag of your choice. Also if required you can escape bad code by using <%=h i.itemname %> Example:
<% #Items.each do |i| %>
<div><%=h i.itemname %></div>
<div class="menu_body">
Link-1
</div>
<% end %>
Edit: Ryan Bigg is right. Rails doesn't output a <p> tag. Sorry for the wrong info.
You canchange the public/stylesheets/scaffold.css if you want.
Or if you want to change it for a single page say items/index.html.erb
<style>
p{
/* your style here *?
}
</style>

Rails check if yield :area is defined in content_for

I want to do a conditional rendering at the layout level based on the actual template has defined content_for(:an__area), any idea how to get this done?
#content_for_whatever is deprecated.
Use content_for? instead, like this:
<% if content_for?(:whatever) %>
<div><%= yield(:whatever) %></div>
<% end %>
not really necessary to create a helper method:
<% if #content_for_sidebar %>
<div id="sidebar">
<%= yield :sidebar %>
</div>
<% end %>
then of course in your view:
<% content_for :sidebar do %>
...
<% end %>
I use this all the time to conditionally go between a one column and two column layout
<%if content_for?(:content)%>
<%= yield(:content) %>
<%end%>
Can create a helper:
def content_defined?(var)
content_var_name="#content_for_#{var}"
!instance_variable_get(content_var_name).nil?
end
And use this in your layout:
<% if content_defined?(:an__area) %>
<h1>An area is defined: <%= yield :an__area %></h1>
<% end %>
Ok I am going to shamelessly do a self reply as no one has answered and I have already found the answer :)
Define this as a helper method either in application_helper.rb or anywhere you found convenient.
def content_defined?(symbol)
content_var_name="#content_for_" +
if symbol.kind_of? Symbol
symbol.to_s
elsif symbol.kind_of? String
symbol
else
raise "Parameter symbol must be string or symbol"
end
!instance_variable_get(content_var_name).nil?
end
I'm not sure of the performance implications of calling yield twice, but this will do regardless of the internal implementation of yield (#content_for_xyz is deprecated) and without any extra code or helper methods:
<% if yield :sidebar %>
<div id="sidebar">
<%= yield :sidebar %>
</div>
<% end %>
I use #view_flow and value of the content method before checking if the content is present in the view like this:
#view_flow.content[:header_left_or_whatever_the_name_of_your_block_is].present?
Recently stumbled upon it when showing all local, global and instance variables of self in the console with byebug. I’m a fan using this because it’s straight from Rails, won’t throw an error, won’t hide anything w “Rails magic”, returns a definite true or false, + only checks the content in the current context of the view being rendered.
#view_flow is an instance attribute of ActionView::Context and because Action View contexts are supplied to Action Controller to render a template it will be available to any view that has been rendered by Rails. Although it checks for content, the content_for block will not be yielded if it isn’t there. So it’s been my perfect solution in similar situations.

Resources