Rails - Pass options into a helper method that accepts a block? - ruby-on-rails

I have a helper that creates mailer template rows (html). I want to be able to pass in styles to the row (optionally), like a background color.
module MailHelper
def module_row(&block)
h << "<table border='0' cellpadding='0' cellspacing='0' width='100%'>"
# more table html here
h << capture(&block)
# more table html here
h << "</table>"
raw h
end
end
I want to be able to optionally pass in a background color, but I can't seem to figure out how to do that while passing in the '&block'. Is this possible in Ruby?

You sure can!
module MailHelper
def module_row(options={}, &block)
...
if options[:foo]
do_foo_stuff
end
end
end
<% module_row(foo: true) do |x| %>
...
<% end %>
Common practice is to define defaults like this:
def module_row(options={}, &block)
opts = {
foo: true,
background_color: 'black'
}.merge!(options)
if opts[:foo]
do_foo_stuff
end
end

You can pass options as a Hash well, like:
module MailHelper
def module_row(**opts, &block)
bgcolor = opts[:bgcolor] || '#FFFFFF'
...
h << "<table border='0' cellpadding='0' cellspacing='0' width='100%'>"
# more table html here
h << capture(&block)
# more table html here
h << "</table>"
raw h
end
end
Then you can call:
module_row(bgcolor: '#AAAAAA', &my_block)
or:
module_row(bgcolor: '#AAAAAA') { block content }

Related

Ruby on Rails Custom Helper outputting HTML nested list

as in title I'm trying to create helper that does that but I'm struggling. I'm getting errors or simply empty list like this:
And I want to achieve this:
There is to much logic to simply put this code in view. A results is a hash where the key is a website id and value is either an array of bookmarks ids or just bookmark id.
My code:
module WebsitesHelper
def present_search_results(results)
content_tag(:ul, class: "websites-list") do
results.each do |key, value|
website = Website.find(key)
concat(content_tag(:li, website.url, class: "website-#{key}") do
bookmarks = website.bookmarks.select do |b|
if value.is_a?(Array)
value.include?(b.id)
else
value = b.id
end
end
content_tag(:ul, nil, id: "website-#{key}") do
bookmarks.each do |b|
content_tag(:li, b.title)
end
end
end)
end
end
end
end
If you want to stick with helpers, then something like this could help:
def present_search_results(results)
content_tag(:ul, class: "websites-list") do
results.map do |website_id, bookmarks|
bookmarks = [bookmarks] unless bookmarks.is_a?(Array)
content_tag(:li, class: "website-#{website_id}") do
website = Website.find(website_id)
concat(website.url)
concat(
content_tag(:ul, class: "bookmarks-list") do
bookmarks.map do |bookmark_id|
bookmark = Bookmark.find(bookmark_id)
content_tag(:li, bookmark.title)
end.reduce(:+)
end
)
end
end.reduce(:+)
end
end
But, in my opinion, that code is not easy to read, so you could use plain html instead, like this:
def present_search_results(results)
list = "<ul class='websites-list'>"
results.each do |(website_id, bookmarks)|
bookmarks = [bookmarks] unless bookmarks.is_a?(Array)
website = Website.find(website_id)
list += "<li class='website-#{website_id}'>#{website}"
list += "<ul class='bookmarks-list'>"
bookmarks.each do |bookmark_id|
bookmark = Bookmark.find(bookmark_id)
list += "<li>#{bookmark.title}</li>"
end
list += "</ul></li>"
end
list += "</ul>"
list.html_safe
end
I like this one better, since it is easier to read. But both with output the list you want.

How to present list of names in Presenter?

I have my custom presenter
class ShiftPresenter
def initialize(shift, template)
#shift = shift
#template = template
end
def h
#template
end
def users_list
logs = ShiftLog.by_shift(#shift)
names = logs.map do |log|
log.cardiologist.name
end
h.content_tag :div, names unless names.empty?
end
end
and #index view
- present shift do |shift_presenter|
= shift_presenter.user_list
How to present users names using li instead of ['tom', 'jerry']
You can add this to your presenter method:
def users_list
logs = ShiftLog.by_shift(#shift)
names = logs.map(&:cardiologist).map(&:name)#.compact.uniq # you can add this if you want
h.content_tag :div do
h.content_tag :ul do
ul_content = ''.html_safe
names.each do |name|
ul_content << h.content_tag :li, name
end
ul_content
end
end
The thing is it works as block with the return statement: the last used/returned object will be put inside the content_tag.
Try to wrap each element of names in users_list method into <li> tag and join them in a string. To do this you need to change this line:
h.content_tag :div, names unless names.empty?
into this:
h.content_tag :div, names.map{|str| '<li>' + str + '</li>'}.join unless names.empty?

Rails helper method: Nested hash to nested HTML list

I am trying to write a Rails helper method to convert a nested hash into a nested HTML list.
For example:
{
:parent => "foo",
:children => [
{
:parent => "bar",
:children => [
{
:parent => "baz",
:children => []
}
]
}
]
}
should become:
<ul>
<li>foo</li>
<ul>
<li>bar</li>
<ul>
<li>baz</li>
</ul>
</ul>
</ul>
The hash may have any number of levels, and any number of parents per level.
What is the best way to achieve this please?
You can make a recursive method to render to hash to a nested set of lists. Place this in your relevant helper:
def hash_list_tag(hash)
html = content_tag(:ul) {
ul_contents = ""
ul_contents << content_tag(:li, hash[:parent])
hash[:children].each do |child|
ul_contents << hash_list_tag(child)
end
ul_contents.html_safe
}.html_safe
end
Zach Kemp's answer very effectively addresses the question. If you are looking for something a bit more generic (a nested hash for which you will not know the key names), as I was, the following module may be helpful (also at https://github.com/sjohnson/auto_hash_display with more details):
module HashFormatHelper
# These methods add classes to the HTML structure that are defined in Bootstrap (and can be defined for other CSS frameworks)
def format_hash(hash, html = '')
hash.each do |key, value|
next if value.blank?
if value.is_a?(String) || value.is_a?(Numeric)
html += content_tag(:ul, class: 'list-group') {
ul_contents = ''
ul_contents << content_tag(:li, content_tag(:h3, key.to_s.underscore.humanize.titleize), class: 'list-group-item')
ul_contents << content_tag(:li, value, class: 'list-group-item')
ul_contents.html_safe
}
elsif value.is_a?(Hash)
html += content_tag(:ul, class: 'list-group') {
ul_contents = ''
ul_contents << content_tag(:li, content_tag(:h3, key.to_s.underscore.humanize.titleize), class: 'list-group-item')
inner = content_tag(:li, format_hash(value), class: 'list-group-item')
ul_contents << inner
ul_contents.html_safe
}
elsif value.is_a?(Array)
html += format_array(value)
else
Rails.logger.info "Unexpected value in format_hash: #{value.inspect}"
Rails.logger.info "value type: #{value.class.name}"
end
end
html.html_safe
end
def format_array(array, html = '')
array.each do |value|
if value.is_a?(String)
html += content_tag(:div, value).html_safe
elsif value.is_a?(Hash)
html += format_hash(value)
elsif value.is_a?(Array)
html += format_array(value)
else
Rails.logger.info "Unexpected value in format_array: #{value.inspect}"
Rails.logger.info "value type: #{value.class.name}"
end
end
html
end
end
This code can also be used to display XML by setting the hash value equal to Hash.from_xml(your_xml_data) and then passing that in to format_hash(hash).
Please note that the from_xml method may strip off XML tag attributes, so it works best for XML that doesn't have attributes.

Rails: add field to all forms

Is it possible to add hidden field to all form tags?
I'm trying to do it in following way:
module ActionView::Helpers::FormTagHelper
def form_tag(url_for_options = {}, options = {}, &block)
html_options = html_options_for_form(url_for_options, options)
if block_given?
f = form_tag_in_block(html_options, &block)
else
f = form_tag_html(html_options)
end
hidden_f = ActiveSupport::SafeBuffer.new "<input name='n' type='hidden' value='v' /><\/form>"
f.gsub!(/<\/form>/, hidden_f)
f
end
end
But server shows the error:
ActionView::Template::Error (Could not concatenate to the buffer because it is not html safe.):
How should i do it?
It might be simpler to redefine the extra_tags_for_form method, which is used to add the _method, utf8, and authenticity_token hidden fields. Something like this could work:
module ActionView::Helpers::FormTagHelper
alias_method :orig_extra_tags_for_form, :extra_tags_for_form
def extra_tags_for_form(html_options)
orig_tags = orig_extra_tags_for_form(html_options)
orig_tags << "<input name='n' type='hidden' value='v' /><\/form>".html_safe
end
end
Since this advice involves redefining a private method, you will need to be sure to test it carefully any time you upgrade Rails.
Try with
module ActionView::Helpers::FormTagHelper
def form_tag(url_for_options = {}, options = {}, &block)
html_options = html_options_for_form(url_for_options, options)
if block_given?
f = form_tag_in_block(html_options, &block)
else
f = form_tag_html(html_options)
end
hidden_f = ActiveSupport::SafeBuffer.new "<input name='n' type='hidden' value='v' /><\/form>"
f.gsub!(/<\/form>/, hidden_f)
f.html_safe
end
end
gsub! taints your string with HTML unsafeness.

blocks in views and refactor in rails

I have notes attribute in Product model with text "something, something else".
In views I wanted see:
<div>
<span>Something</span>
<span>Something else</span>
</div>
Also I have working code, but I want refactor with decorator(draper) or maybe use helpers.
%div
- product.notes.split(/,/).each do |e|
%span= e.strip.capitalize
In decorator:
def notes_list
model.notes.split(/,/).each do |e|
h.content_tag(:span, e.strip.capitalize)
end
end
In views:
%div
= product.notes_list
(or analog in helpers:
def notes_list(product)
product.notes.split(/,/).each do |element|
content_tag(:span, element.strip.capitalize)
end
end
call:
%div
= notes_list(product)
)
But this returns
<div>
"
["something", " something else"]
"
</div>
What is wrong?
your notes_list is returning product.notes.split(/,/)
Try
def notes_list(product)
result = product.notes.split(/,/).inject([]) do |result, element|
result << content_tag(:span, element.strip.mb_chars.capitalize)
end
result.join("\n")
end

Resources