How can I re-use blocks of haml with arguments? - ruby-on-rails

I've been using HAML for over 10 years, and this simple use case continues to elude me.
Say I have the following haml code
- links.each do |link|
= link_to link.url do
%i.fa.fa-map
= link.text
This block works while looping only, but imagine a situation where I want to reuse the block in multiple loops, or in multiple places in the HAML file. I do not currently know how to do that without creating a separate partial or helper. When the block is big enough, I often will create a partial, but often the block is small and a partial seems tedious overkill. Sometimes I have several blocks like this that I only want re-used in one HAML file.
I tried first to create this with content_for but it doesn't accept arguments and can't be cached.
My second attempt was with capture_haml, but I think that has to be called directly indide of HAML, not a ruby method. The following obviously fails for syntax reasons, but you see what I'm trying to do:
:ruby
def entry_link(url, text)
capture_haml(url, text) do |url, text|
= link_to url do
%i.fa.fa-map
text
end
end
Update:
I was able to get this far, which is functional but ugly AF.
:ruby
def entry_link(url, text)
capture_haml(url, text) do |url, text|
link_to url do
haml_tag :i, {class: 'fa fa-map'}
haml_concat(text)
end
end
end
= entry_link('/', 'My Link')
The following would be a more acceptable syntax, but I get the strange error undefined local variable or method _hamlout'`
- def entry_link(url, text)
- link_to url do
%i.fa.fa-map
text

Related

Create html tag wrapper in Rails application

I'm new to Rails and I'd like to create a helper function that consume whatever I pass and returns wrapped element. I was trying to use something like content_tag / tag however it won't cover all of my use cases. It should create div and wrap it over element that I pass as an argument. In most cases it would be just nested HTML. I'm looking for something like code below that would consume anything, it would be nice if it would work with render method as well.
Helper method:
def helper_method(content)
content_tag(:div, content)
end
in ERB file:
helper_method('<span><p>Something</p></span>')
In React I would just pass "children". How should I handle that in Rails?
Capture the block, and pass it to content_tag like so:
def something(css_class, &block)
content_tag(:div, class: css_class, &block)
end
Use it in a view like so:
<%= something('my-css-class') do %>
...your content here...
<% end %>
This avoids the security issues inherent with .html_safe.
You'll use exactly how you described it. You can return HTML safe strings from helpers by setting the string as html_safe
Example:
def helper_method content
content_tag(:div, content.html_safe)
end
Note that, this can potentially introduce security issues. You don't want to call html_safe on user entered strings because scripts will execute. It's not really safe as the name suggests ;)
Read more:
stay-safe-while-using-html-safe-in-rails-9e368836fac1
everything-you-know-about-html_safe-is-wrong
proper use of html_safe
For ideal approach, use sanitize:
def helper_method content
content_tag(:div, sanitize(content))
end

What is the syntax for named yield with both names and parameters in ruby/rails?

One could use yield with a :name in views in rails:
= yield :some_place
so then using then using content_for :some_place do ... to insert a code block only in there where yield :some_place is placed (http://guides.rubyonrails.org/layouts_and_rendering.html#using-the-content-for-method).
Also ruby allows passing parameters in the yiled (http://www.tutorialspoint.com/ruby/ruby_blocks.htm):
def test
yield 5
puts "You are in the method test"
yield 100
end
test {|i| puts "You are in the block #{i}"}
But I didn't find anything about using yield/content_for both with names and parameters in rails views:
= yield :some_place, 5, 6
...
= content_for :some_place do |a,b|
h3 = "Yield provided parameters: #{a} and #{b}"
Is it possible? Where is the official rails or ruby syntax for yield statements and passing blocks?
I heard something about the Proc.new() that could be somehow related to the problem.
content_for(:name) evaluates first, and stores a snip of HTML for later use. yield(:name) only fetches this content. Hence, you can't pass arguments into a method that was already called, and won't be called again.
You probably merely need to cut a partial HTML.erb file, and render it from your target location. Render takes named parameters as a hash.

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

Rails: Print to page, not to console?

I want to write a helper method to print a list of radio buttons. The obvious way to do it is to have the helper method return a string of html code for my view file to render.
But that's just not pretty, appending <li> after <li> to a string, then returning it.
The code would be more readable, of course, if I could just print each line of html in turn. Can I print lines to an output stream and have it end up on my page (not on the server console)?
(I'm using rails 2.3.5, btw.)
You can build up a list in a helper method by calling content_tag and passing in the desired element and a block that renders some HTML (like the radio_button helper), then joining the whole mess into a string, e.g. (air code):
module SomeHelper
def radio_button_list(things)
things.collect do |thing|
content_tag(:li) { radio_button(thing.foo, thing.bar, thing.baz) }
end.join
end
end

In Rails, what exactly is form_for? It seems magical

First of all, I realize I should have tried to fully grasp Ruby before jumping into Rails. However, nothing in Ruby seemed too difficult to quickly grasp (until this!), so I decided to get started while I was still enthusiastic about learning. ,__,
Anyway, here is a super-condensed example of form_for:
<%= form_for(#post) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<% end %>
I understand that methods in Ruby need not be called with parenthesis. However, form_for is being called with parenthesis, and yet somehow, it seems as though the do |f| block is being passed to it!
Is form_for returning a method that takes a block, and then is that method immediately being called (without parenthesis) by passing the do |f| block? What's going on here?
Ruby uses a lot of what is called 'syntactic sugar' in that it is a bit more flexible than other languages in how certain operations are interpreted.
for instance:
model.property = "something"
is actually a function call:
model.property=("something")
your form_for example is a similar case. Blocks are silently passed into Ruby functions as a parameter.
my_block = Proc.new { some code }
my_function(param1, param2, &my_block)
is equivalent to
my_function param1, param2 do
some code
end
and in the function def for my_function you could write:
def my_function(param1, param2, &block)
and you could access the block via the parameter, as well as yield.
So when you use block syntax, it's interpreted as a parameter, but it's really not.
What happens here is simply that the form_for method is invoked with both parameters and a block in the same call.
Here's a basic example of a method taking both a parameter and a block, to illustrate the principle:
def hello(name)
puts "Hello, #{name}!"
yield if block_given?
puts "Goodbye, #{name}!"
end
This method can be called with a single parameter, or with a parameter and a block:
> hello("John")
Hello, John!
Goodbye, John!
> hello("John") do
* puts "Inside the block"
* end
Hello, John!
Inside the block
Goodbye, John!
Regarding the question in your comment:
Why are there parenthesis around only the first parameter? After encountering the open+closed parenthesis after form_for, how does Ruby know that it should "wait" before calling the method?
If I understand your question correctly, you're asking why there's only parentheses around #post in the form_for call, and not around the entire block. That's the syntax for passing a block to a method in Ruby - if a block follows immediately after the method and its regular parameters, the block is passed along to the method together with the parameters.
Here are a few of the most common ways of calling a method in Ruby:
# Calling a method without a block
mymethod(param1, param2)
# Same as above, but leaving out parentheses
mymethod param1, param2
# Calling a method with a block that takes no arguments
# (this works without parentheses too)
mymethod(param1, param2) { do_stuff_in_block() }
# or
# (this works without parentheses too)
mymethod(param1, param2) do
do_stuff_in_block()
end
# Calling a method with a block that takes arguments
# (this works without parentheses too)
mymethod(param1, param2) do |arg1, arg2|
do_stuff_in_block(arg1, arg2)
end
# or
# (this works without parentheses too)
mymethod(param1, param2) { |arg1, arg2| do_stuff_in_block(arg1, arg2) }
Have a look at Blocks and Iterators in Programming Ruby for more details about both how to call methods with blocks and how to write your own methods that accept blocks.
There are 2 separate questions, but I will answer the more pressing one first:
form_for #product do |f|
# stuff here
end
and
form_for(#product) do |f|
# stuff here
end
Are exactly the same thing. form_for is a method that takes block as a parameter, amongst other parameters.
Methods in ruby can be passed arguments (e.g. #post) and a block (e.g. all the stuff inside the do ... end).
If you look at how form_for is defined within rails you will see:
def form_for(record, options = {}, &proc)
# a lot of funky magical rails stuff ...
end
Everything in the do block is captured in proc. The preceding & in the method definition indicates that proc is a block and can be executed. Somewhere along the line, rails will execute proc.
Blocks are a big part of ruby's magical awesomeness. Check this awesome free ruby e-book for more info on blocks. Blocks are a kind of 'closure' and ruby has different flavors of closures which behave slightly differently (the other common closure is lambda). If you want to dig real deep into how closures work in Ruby, check out this great tutorial.

Resources