What is the erb equivalent of macros in jinja2? - ruby

In jinja2 I can specify frequently used template code in macros, essentially like template functions:
{% macro input(name, value='', type='text', size=20) -%}
<input type="{{ type }}" name="{{ name }}" value="{{value|e }}" size="{{ size }}">
{%- endmacro %}
and then use it like so:
{% input("hello") %}
Is there any way to accomplish something similar with erb templates?
Thanks in advance!

If you use lambdas/procs instead of methods then you can:
require 'erb'
doc = <<ERB
<% input = lambda do |name, value='', type='text', size=20| %>
<input type="<%= type %>" name="<%= name %>" value="<%= value || 'e' %>" size="<%= size %>">
<% end %>
<% input["hello"] %>
<% input["HELLO", 123, 'select', 50] %>
ERB
puts ERB.new(doc, 0, '>').result
# >> <input type="text" name="hello" value="" size="20">
# >> <input type="select" name="HELLO" value="123" size="50">
The problem is that erb (and erubis) create these strings by parsing the document and creating another piece of code in which the values inside and outside the tags are inverted. Then the template between the tags is just appended to some local variable. (there are actually ways around this, but you have to go somewhat deep, I've had to extend erubis before to get the alternative behaviour I wanted). When you create methods, that changes scope and the local variable can no longer be seen, so it gives you some obscure error NameError: undefined local variable or method ‘_erbout’ for main:Object When you use closures, the environment is captured, including the local variable, so you can then write to it.

Related

Linking to search results with Ruby

I'm a complete novice in Ruby and Nanoc, but a project has been put in my lap. Basically, the code for the page brings back individual URLs for each item linking them to the manual. I'm trying to create a URL that will list all of the manuals in one search. Any help is appreciated.
Here's the code:
<div>
<%
manuals = #items.find_all('/manuals/autos/*')
.select {|item| item[:tag] == 'suv' }
.sort_by {|item| item[:search] }
manuals.each_slice((manuals.size / 4.0).ceil).each do |manuals_column|
%>
<div>
<% manual_column.each do |manual| %>
<div>
<a href="<%= app_url "/SearchManual/\"#{manual[:search]}\"" %>">
<%= manual[:search] %>
</a>
</div>
<% end %>
</div>
<% end %>
</div>
As you didn't specify what items is returning, I did an general example:
require 'uri'
# let suppose that your items query has the follow output
manuals = ["Chevy", "GMC", "BMW"]
# build the url base
url = "www.mycars.com/search/?list_of_cars="
# build the parameter that will be passed by the url
manuals.each do |car|
url += car + ","
end
# remove the last added comma
url.slice!(-1)
your_new_url = URI::encode(url)
# www.mycars.com/?list_of_cars=Chevy,GMC,BMW
# In your controller, you will be able to get the parameter with
# URI::decode(params[:list_of_cars]) and it will be a string:
# "Chevy,GMC,BMW".split(',') method to get each value.
Some considerations:
I don't know if you are gonna use this on view or controller, if will be in view, than wrap the code with the <% %> syntax.
About the URL format, you can find more choices of how to build it in:
Passing array through URLs
When writing question on SO, please, put more work on that. You will help us find a quick answer to your question, and you, for wait less for an answer.
If you need something more specific, just ask and I can see if I can answer.

How to reuse ERB templating code in function?

I have this piece of code inside ERB template to render all articles in list.
<% #list.each do |a| %>
<article class="my-panel">
<header>
<h2><%= a[:name] %></h2>
<time datetime="<%= to_datetime(a[:time]) %>"><%= time_description(a[:time]) %></time>
</header>
... more stuff cut out
</article>
<% end %>
Now I am going to have to change it to something like this:
<% #list[content_splitter.before_ad_range].each do |a| %>
<%= render_article(a) %>
<% end %>
<%= AdCreator.between_content_ad %>
<% #list[content_splitter.after_ad_range].each do |a| %>
<%= render_article(a) %>
<% end %>
I thought it would be nice to have render_article defined in template as opposed to having html clutter my ruby code. But when I move that code inside function I get an error.
This is the function:
<% def render_article(a) %>
<article class="my-panel">
<header>
<h2><%= a[:name] %></h2>
<time datetime="<%= to_datetime(a[:time]) %>"><%= time_description(a[:time]) %></time>
</header>
<div class="image">
<img alt="" src="<%= rel_url_to a[:img_url_1x] %>" srcset="<%= rel_url_to a[:img_url_2x] %> 2x, <%= rel_url_to a[:img_url_3x] %> 3x">
</div>
<div class="text">
<%= a[:article_text] %>
</div>
</article>
<% end %>
This is the error:
undefined local variable or method `_erbout' for #<Html::FrontPage:0x0055fb94005c68>
Line of code producing this error is:
self.class.instance_variable_get(:#renderer).result(binding)
Why is this happening? How to find more informative error?
How to fix this? Can I avoid moving this obviously html dominant code into ruby helper file?
PS. I suspect problem is that functions to_datetime and time_description can't be accessed from inside ERB function.
I know that function render_article does get called because if I change it's signature to remove parameter I get an error
wrong number of arguments (given 1, expected 0)
# (erb):45:in `render_article'
What you are trying to do is not technically impossible, but rather difficult and not recommended as can be seen by reading comments and answers to this question.
As your error message points out your def won't quite happen in the context you "probably" expect and you certainly won't be able to "freely use erb features" while defining the method (since your context is completely different from what you expect).
The "railsy" way to do this is adding a helper or using a partial, but both come with their drawbacks. .erb files (as most templating-languages) do not "factor" well. If you want to factor things somewhat more freely you should look at the fortitude gem which provides what is basically a ruby-DSL for html which factors pretty easily. However, that is a rather drastic change from what you are probably used to.
If you really want to define a method inside an .erb-file then you would have to do it entirely within a single pair of <% ... %> brackets where you will have only access to your params, not your context. You would have to return what is basically a String in order to be able to use it in <%= ... %> and pay a hell of a lot of attention to escaping rules for everything to make it through. This is most probably more trouble than it is worth (but easy enough to do in fortitude :-).

Divide loop iteration in an html.erb file over different sections for enhancing performance

This is part of my code in an html.erb file
<div class="list_carousel">
<ul id="products-carousel" class="carousel">
<% #posts.select{ |post| post.categories.include?(#categories.find_by_name("Productos")) }.each do |post|%>
[...]
<% end %>
</ul>
</div>
[...]
<div class="modals">
<% #posts.select{ |post| post.categories.include?(#categories.find_by_name("Productos")) }.each do |post|%>
[...]
<% end %>
</div>
The problem here is that I'm using several database queries to perform this loop and I would like to re-use the first loop in other parts of this files in order to enhance performance.
For example I would like to do something like each do |post| do something in this first section, don't do nothing in this second section and continue in the third one. This way I could re-use the instance of the selected postd over which I'm iterating.
In your controller, you can do this:
#posts = Post.all
#productos_category = #categories.where(name: "Productos").first
#productos = #posts.where(category_id: #productos_category.id)
Note that I changed the way you find the associated #productos to use the ActiveRecord query interface, as opposed to using pure ruby select and include? methods. This will allow for the query to be much more efficient. I implied some things, such as the child key for the categories being category_id. Change this where appropriate.
EDIT 1
I believe this will work, although my experience with many-to-many relationships is somewhat limited:
#productos_category = #categories.where(name: "Productos").first
#productos_posts = Post.joins(:join_table).where(join_table: {category_id: #productos_category.id})
Where :join_table is the name of the table that is used in the has_many association. If it is a HABTM relationship, this will probably be called categories_posts.
Then your view would change to this:
<div class="list_carousel">
<ul id="products-carousel" class="carousel">
<% #productos_posts.each do |post|%>
[...]
<% end %>
</ul>
</div>
[...]
<div class="modals">
<% #productos_posts.each do |post|%>
[...]
<% end %>
</div>

Middleman variables visibility in config.rb and pages

I'm missing something about visibility of variables.
In my config.rb i'm using a data structure to generate dynamic pages:
#pages = [
{
id: "cookies",
title: "Happy Chocolate Chip Cookies",
quote: "These cute cookies are full of sweet chocolate and ready to give you energy!",
content: "Orecchini a monachella. Realizzati in fimo, dipinti a mano e rivestiti con vernice lucida."
},
....]
#pages.each do |p|
page "/creations/#{p[:id]}.html", :proxy => "item-template.html", :ignore => true do
#tile = p
end
end
The pages generation goes well, no problem with that. But..
How can i access this data structure also in order to provide dynamic links to the generated pages? I would like to be able to create an index page (let's call it creations.html) with the following code:
<ul>
<% #pages.each do |tile| %>
<li><a href="creations/<%= tile[:id]%>.html">
<%= tile[:title] %>
</a></li>
<% end %>
</ul>
No need to create a custom helper, you can use a yaml data file to populate your template and generate the list of links. Let me explain.
On the same level as source and build directories make sure you create a data directory.
ie:
build
source
data
Inside this create a file called "pages.yml" (for example).
This file needs to be specifically formatted so be careful (or use a yaml parser to make sure you don't have any errors - like a missing comma or escaped quote).
Using the data in your config.rb file, an example would be something like:
- id: "cookies"
title: "Happy Chocolate Chip Cookies"
quote: "These cute cookies are full of sweet chocolate and ready to give you energy!"
content: "Orecchini a monachella. Realizzati in fimo, dipinti a mano e rivestiti con vernice lucida."
- id: "bacon"
title: "Smoked bacon bits"
quote: "everything tastes better with bacon!"
content: "blah"
etc...
Now, in your config.rb file replace #pages.each do |p| with data.pages.each do |p|
data.pages.each loops through each item in the newly created yaml file
You can then simply reference the same file in your index file (creations.html) like so:
<ul>
<% data.pages.each do |tile| %>
<li><a href="creations/<%= tile[:id]%>.html">
<%= tile[:title] %>
</a></li>
<% end %>
</ul>
I had a similar problem around dynamic pages which you can refer to here
Hope this helps. Good luck!
Perhaps add a helper that returns the #pages data structure in your file creations.erb. I.e. in your config.rb file add:
helpers do
def dynamic_pages()
#pages
end
end
and then in your creations.erb have:
<ul>
<% dynamic_pages.each do |tile| %>
<li><a href="creations/<%= tile[:id]%>.html">
<%= tile[:title] %>
</a></li>
<% end %>
</ul>
And, if you want to reference dynamic pages in your dynamic pages(!), a helper could generate that html and you could call the ... nah, never mind!

How to make a value available in all Liquid templates

I'm using Liquid with Sinatra and would like to make a certain value (Sinatra::Application.environment, specifically) available in all templates without defining it as a local in every get/post. Like so:
In app.rb (my main application file):
# nothing in here about the variable
get '/some/route' do
# or here
liquid :my_template
end
In app.rb--my main application file, or something I can require/include:
some_awesome_technique do
def app_env
Sinatra::Application.environment
end
end
In any template:
<p>
{% if environment == :development %}
Never see this in production
{% end %}
</p>
<!-- or even -->
<p>
{% if dev_mode %}
Or this...
{% endif %}
</p>
I don't really care about the implementation as long as I don't have to put redundant code in every route. Thanks in advance!
Something like this will work
before do
#env = Sinatra::Application.environment
end
then in your template:
{% if #env == :development %}
Boo!
{% endif %}

Resources