Rails - print a "recursive" html variable in Slim - ruby-on-rails

I would like to render in my view a "html"-content variable, let me explain:
Somewhere in a helper there is a pseudocode like this
# view helper for an ERB view
def render_something_recursively(i = 1)
html = "<li>"
html << "hi number #{i}"
if i < 1000
html << render_something_recursively(i++)
end
html << "</li>"
end
Sorry for the bad example but I hope that it give you an idea, in facts I would like to iterate on a hierarchy structure (a tree) with flexible depth. For this reason I need a recursive method and I would like to keep it out from the view.
My question is: how can I accomplish to the same result but in Slim (or eventually in HAML)? How can I give the information of "indentation" at the htmlvariable?
Is it possible or I must use an ERB view?
My final goal should be "easily" something like that:
# recursive_list.html.slim
ul class="a_recursive_list"
=render_something_recursively

Use content_tag and it will render whatever templating engine you are using:
def render_something_recursively(i = 1)
content_tag(:li, "hi number #{i}") do
if i < 1000
render_something_recursively(i++)
end
end
end
Additionally, I don't think you should nest <li> elements directly inside each other, you should either render text or a <ul> element nested inside the <li>.

Related

Output slim to variable

I'm using Base64.encode64(val) to convert html to base64.
Example:
- val = link_to 'Link', link_path
= Base64.encode64(val)
But how can I get slim markup to variable? Like so:
.class = link_to 'Link', link_path # <- this output with slim div
Or even multiple lines
div
span
.another_div
There is a way by putting slim code into partial and do this:
- var = render 'partial'
= Base64.encode64(var) # Convert into base64
How to do this without partial?
Slim exposes its templating through the Tilt interface, like so:
# Render a template file:
Slim::Template.new("template.slim", options).render(scope)
# Render a string:
Slim::Template.new(options) { "b slim markup" }.render(scope)
Where options is an optional hash of options for slim and scope is the object in which the template code is executed.
So the following:
slim_markup = <<-SLIM
div
span
.another_div
SLIM
# The options hash and scope have been omitted for the sake of simplicity
html_output = Slim::Template.new { slim_markup }.render
Sets the value of html_output to:
<div></div>
<span></span>
<div class="another_div"></div>
But for your example with the url helper link_path, you must provide slim a scope in which the url helpers are available e.g. a controller.
This is an old question, but I have wondered about this many times, and I always spend a lot of time researching it.
Using Slim 4 you can use capture directly:
- val = capture
div
span
.another_div
This will put the rendered slim into your variable.
Another way from the box using capture method. From the docs:
Using the Binding you can capture to local variables as follows:
module Helpers
def capture_to_local(var, &block)
set_var = block.binding.eval("lambda {|x| #{var} = x }")
# In Rails we have to use capture!
# If we are using Slim without a framework (Plain Tilt),
# you can just yield to get the captured block.
set_var.call(defined?(::Rails) ? capture(&block) : yield)
end
end
The helper can then be used in the Slim template as follows
/ The captured_content variable must be known by the Binding beforehand.
= capture_to_local captured_content=:captured_content
p This will be captured in the variable captured_content
= captured_content
Read more https://github.com/slim-template/slim#capturing-to-local-variables

How do I pass html based on ruby code / erb into a method expecting a string in Rails 3?

The following code doesn't work because I can't access the helper inside of a controller:
string << "#{has shared link_to #review.title, #review}"
But within the action (or possibly a method in the model) I still need to pass the html that would be generated from this.
I tried the template instance but doesn't work in Rails 3
Just as a basic example, say you have:
app/messages_controller.rb and
helper/messages_helper.rb
In you messages_helper.rb you may have something like what you've suggested.
#Also you should use = instead of <<
#You can only use << on a variable that has already been initialised
#a = "hello " #=> "hello"
#a << "world" #=> "hello world"
#b << "whatevs" #=> ERROR
def dummy_helper_method
#html_string = "has shared #{link_to #review.title, #review}"
#html_string
end
Then in your messages_controller.rb in any of your methods you can call your new dummy_method and you'll now get access to your #html_string instance variable
def index
dummy_helper_method
#you can now access your #html_string variable from inside this index method
end
Just as a note though, this isn't the right way to do this. You shouldn't call it in your controller unless you're trying to do something fairly specific with it which it doesn't look like you are. If you're trying to get it out into your view so that you can display it, you can actually call your helper method that you've just created in any of your messages' view files (views/messages/anything_in_here.html.erb) rather than calling it in your controller.
For example:
#views/messages/edit.html.erb
<%= dummy_helper_method %>
Anyways, hope it helps

Rails: Is it possible to write view helpers with HAML syntax?

During refactoring it would be quite handy just to copy part of HAML template and paste it to helper's code. Currently in such cases 1) I have to rewrite that part of view from scratch 2) I have to use that verbose syntax like content_tag or haml_tag.
I know that it's possible to define partials with HAML systax that will serve as helper. Though 1) as for me it's inconvinient to create a separate file for each small tiny function 2) invocation syntax for partial is quite verbose.
Ideally i'd like my *_helper class to look like this:
- def some_helper(*its_args)
.some_class
= some_ruby_expression
%some_tag#some_id
- another_expression do
etc
or at least like this:
define_haml_helper :some_helper, [:arg1, :arg2], %{
.some_class
= some_ruby_expression
%some_tag#some_id
- another_expression do
etc
}
Is there a plugin that solves my issue?
Alternatively, maybe you can describe how do you refactor HAML snippets to reusable elements (helpers/functions/partials/builders/etc)?
From the reference:
def render_haml(code)
engine = Haml::Engine.new(code)
engine.render
end
This initiates a new Haml engine and renders it.
If all you are after is a method for small reusable snippets, how about partials with local variables? http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials
Haml now has a capture_haml method that you can use to accomplish this.
def some_helper
capture_haml do
.some_class
= yield
#some-code-after
end
end
some_helper do
%h1 Hello World
end
=> <div class="some_class">
<h1>Hello World</h1>
</div>
<div id="some-code-after"></div>
Here is a link with more info on capture_haml:
http://haml.info/docs/yardoc/Haml/Helpers.html#capture_haml-instance_method
I used heredoc for such purposes:
def view_helper
Haml::Engine.new(<<~HAML).render
.example
#id ID
.desc Description
HAML
end
This way has a lot of issues with a scope of variables, so, as mentioned above, the much more correct way is to use partials for this.
UPD1: here is a solution on how to solve issues with scope:
def view_helper
Haml::Engine.new(<<~HAML).render(self)
.form
= form_tag root_path do
= submit_tag :submit
HAML
end
UPD2: even better solution(founded on the internet):
def render_haml(haml, locals = {})
Haml::Engine.new(haml.strip_heredoc, format: :html5).render(self, locals)
end
def greeting
render_haml <<-HAML
.greeting
Welcome to
%span.greeting--location
= Rails.env
HAML
end

Can't suppress output in nested block helper in rails 3

This one is sort of twisting my noodle.
I have something resembling this (in a rails 3 engine if that matters)
class Builder
def initialize
#foos = []
end
def foo(&block)
#foos << helper.capture(&block) #helper being a class that is including ActionView::Helpers
end
def to_html
#foos.join "\n"
end
end
module ApplicationHelper
def widget
b = Builder.new
yield b
b.to_html
end
end
#in a view somewhere
<%= widget do |b| %>
<% b.foo do %>
static content
<% end %>
<% end %>
Everything is working out great, but that nested static content is getting output twice -- once where I want it, and once where widget was called.
From what I have read, capture is supposed to deal with this exact problem. I am pretty sure the problem stems from how I am calling capture (from a dummy proxy class that includes ActionView::Helpers), but the problem is that b.foo call is calling a method on a class instance, not from the context of something that will be mixed into the template.
Is there any way to get around this problem? Or am I approaching this from the wrong direction. I am trying to model something fairly involved and am really happy with the api, just can't seem to get passed this problem.
If you modify the helper method to pass in self, which would be the current view instance, and then use this to capture, you might not have this issue. Substitute your use of helper for the provided view instance.

Evaluating string templates

I have a string template as shown below
template = '<p class="foo">#{content}</p>'
I want to evaluate the template based on current value of the variable called content.
html = my_eval(template, "Hello World")
This is my current approach for this problem:
def my_eval template, content
"\"#{template.gsub('"', '\"')}\"" # gsub to escape the quotes
end
Is there a better approach to solving this problem?
EDIT
I used HTML fragment in the sample code above to demonstrate my scenario. My real scenario has set of XPATH templates in a configuration file. The bind variables in the template are substituted to get a valid XPATH string.
I have thought about using ERB, but decided against as it might be a overkill.
You can do what you want with String's native method '%':
> template = "<p class='foo'>%s</p>"
> content = 'value of content'
> output = template % content
> puts output
=> "<p class='foo'>value of content</p>"
See http://ruby-doc.org/core/classes/String.html#M000770
You can render a string as if it were an erb template. Seeing that you're using this in a rake task you're better off using Erb.new.
template = '<p class="foo"><%=content%></p>'
html = Erb.new(template).result(binding)
Using the ActionController methods originally suggested, involves instantiating an ActionController::Base object and sending render or render_to_string.
I can't say I really recommend either of these approaches. This is what libraries like erb are for, and they've been throughly tested for all the edge cases you haven't thought of yet. And everyone else who has to touch your code will thank you. However, if you really don't want to use an external library, I've included some recommendations.
The my_eval method you included didn't work for me. Try something like this instead:
template = '<p class="foo">#{content}</p>'
def my_eval( template, content )
eval %Q{"#{template.gsub(/"/, '\"')}"}
end
If you want to generalize this this so you can use templates that have variables other than content, you could expand it to something like this:
def my_eval( template, locals )
locals.each_pair{ |var, value| eval "#{var} = #{value.inspect}" }
eval %Q{"#{template.gsub(/"/, '\"')}"}
end
That method would be called like this
my_eval( '<p class="foo">#{content}</p>', :content => 'value of content' )
But again, I'd advise against rolling your own in this instance.
This is also a nice one:
template = "Price of the %s is Rs. %f."
# %s - string, %f - float and %d - integer
p template % ["apple", 70.00]
# prints Price of the apple is Rs. 70.000000.
more here
To late but I think a better way is like ruby-style-guide:
template = '<p class="foo">%<content>s</p>'
content_text = 'Text inside p'
output = format( template , content: content_text )

Resources