Rails Helpers with iterators - ruby-on-rails

I have the following helper method:
def tile_for( photo, tileClass = nil )
div_for( photo, :class => tileClass ) do
link_to( image_tag( photo.image_url(:sixth) ), photo )
content_tag( :div, photo.location(:min), :class => 'caption' )
end
end
The problem is, it returns this kind of output:
<div id="photo_25" class="photo">
<div class="caption" style="display: none;">Berlin</div>
</div>
As you can see the link_to is not being output. I guess this is because only the returned value of the block is being included, rather than each executed line? I don't really understand why this kind of code works perfectly in views but doesn't work the same at all in helper methods. Can anyone enlighten me on what's going on and why it works the way it works? How would you build an loop helper method like this?

It works in views since ERB is really just a big concatenation engine. You need to "manually" do this work in your helper, since the code will not be interpreted by ERB:
def tile_for( photo, tileClass = nil )
div_for( photo, :class => tileClass ) do
  link_to(image_tag( photo.image_url(:sixth)), photo)
+ # <- Add this so the block returns the whole string
    content_tag(:div, photo.location(:min), :class => 'caption')
  end
end
div_for also supports arrays, which will be collected into one continuous string. So you can also do like so:
[link_to(image_tag(photo.image_url(:sixth)), photo),
content_tag(:div, photo.location(:min), :class => 'caption')]

Any of the other answers would do. Now, an explanation on why it does works in ERB.
In an ERB template, when a <%= is found, it gets translated to "#{#insert_cmd}((#{content}).to_s)". A simple example: <%= "a" %> gets translated to print "a".
You can take a look at line 631 in erb.rb but skim the previous code for a shake of context (related to the content).

Only the last value of a block is returned. You'll need to make sure your block returns a single value:
def tile_for( photo, tileClass = nil )
div_for( photo, :class => tileClass ) do
link_to( image_tag( photo.image_url(:sixth) ), photo ) +
content_tag( :div, photo.location(:min), :class => 'caption' )
end
end

Related

Using image_tag with srcset attribute?

I try to use an srcset attribute inside an image_tag but I can not make it work.
Im not sure if it is a syntax error or it generally does not work in an image_tag.
Is it possible to use a srcset attribute in an image_tag?
If yes, how?, and if not why not and is there a workaround?
<%= link_to(image_tag("logo.png", alt: "logo", :id => "logo"), root_path) %>
Instead of adding the image_tag to the link_to "name" option you can use open up block and pass your image there.
If you want to use a srcset attribute you could extend the functionality of image_tag by creating a helper:
def image_set_tag(source, srcset = {}, options = {})
srcset = srcset.map { |src, size| "#{path_to_image(src)} #{size}" }.join(', ')
image_tag(source, options.merge(srcset: srcset))
end
It joins each size by comma, so then you can do:
<%= link_to root_path do %>
<%= image_set_tag 'logo.jpg', {
'logo_640.jpg' => '640w',
'logo_1024.jpg' => '1024w',
'logo_1980.jpg' => '1980w'
}, sizes: '100vw', alt: 'logo', id: 'logo' %>
<% end %>
As you can see, the changes introduced in ActionView::Helpers::AssetTagHelper#image_tag in the 5.2.1 Rails version allows you to pass the srcset option, with a hash or an array of 2D arrays containing the different responsive versions of your image:
image_tag("icon.png", srcset: { "icon_2x.png" => "2x", "icon_4x.png" => "4x" })
# => <img src="/assets/icon.png" srcset="/assets/icon_2x.png 2x, /assets/icon_4x.png 4x">
image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw")
# => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw">
So, if you're using a more recent Rails version, you can just use image_tag instead of writing your own implementation.

Ruby on Rails helper prints out odd stuff

I want to create a helper which iterates thru the list of user's communities and creates as many thumbnails as the number of comunities available. So I created these 2 helper methods
def print_admined_group_thumbs
#user_groups_hash[:admined_groups].each {
|group_hash|
name = group_hash[:name]
group_id = group_hash[:group_id]
photo = group_hash[:photo]
members_count = group_hash[:members_count].to_i
concat(get_group_thumbnail_html(group_id, name, members_count, photo))
}
end
# creates a small preview for the selected comunity group
def get_group_thumbnail_html(group_id, name, members_count, photo)
content_tag(:div, :class => "col-md-55") do
concat(content_tag( :div, :class => 'thumbnail') do
concat(content_tag(:div, :class => 'image view view-first') do
concat(image_tag(photo, :class => "group_thumb_image"))
concat(content_tag(:div, :class => "mask") do
concat(content_tag :p, "Your text")
concat(content_tag(:div, :class => "tools tools-bottom") do
end)
end)
concat(content_tag(:div, :class => "caption") do
concat(content_tag(:p, name))
end)
end)
end)
end
end #end get_group_thumbnail_html
So I simply add this call to my view
<%= print_admined_group_thumbs %>
It all works almost correctly and creates all thumbnails just like I want, except for one thing. It also prints out the entire contents of "group_hash" variable right after the thumbnails. Maybe I'm just too exhausted today, but I can't seem to figure out why? I'd be greateful if somebody helped me solve this problem and explain what am I doing wrong with it?
#some_hash.each {} automatically returns the hash after it completes. So your function print_admined_group_thumbs() adds your thumbnails to the template and returns the hash.
The problem is here:
<%= print_admined_group_thumbs %>
That = means "take whatever value is returned and add it to the template. So you're accidentally adding the hash to the template after printing the thumbnails to the template. You can easily fix it by removing the =:
<% print_admined_group_thumbs %>
This tells rails to run the function without adding its return value to the template.

Questions on method return values and arguments after do

sorry for this noob question I am just not able to understand ruby/rails grammer,
In rails 2.x, I read the following code,
def localized_input_form_for(record_or_name_or_array, *args, &proc)
options = args.extract_options!
args << options.merge(:builder=>LocalizedFormBuilder)
concat('<div class="inputbox">')
form_for(record_or_name_or_array, *args, &proc)
concat('</div>')
end
What does the above function return? Shouldn't it be the return value of the last line statement concat('</div>')?
In the views, I have,
<% localized_input_form_for(#customer) do |f| %>
What is the f in the above code, is it the same f as form_for(xx) do |f|?
The following code works without problem,
<%= f.text_field :name, :required => true, :size => 30,:class =>'ime_on' %>
In rails 4, I made the following modification,
def localized_input_form_for(record_or_name_or_array, *args, &proc)
options = args.extract_options!
args << options.merge(:builder=>LocalizedFormBuilder)
concat('<div class="inputbox">'.html_safe)
concat(form_for(record_or_name_or_array, *args, &proc))
concat('</div>'.html_safe)
end
Without adding concat out of form_for, and without adding html_safe, the original code just doesnt work.
Now, everything still works, the
<% localized_input_form_for(#customer) do |f| %>
works without problem, the form is shown exactly as before. So what is the return value of this function now? and what is f above?
The only difference is, the original options in
<%= f.text_field :name, :required => true, :size => 30,:class =>'ime_on' %>
which are, required: true, size: 30, and class:'ime_on' don't show in the final html!
It generates the following,
<input type="text" value="abc" name="customer[name]" id="customer_name">
without size, class etc options. The html generated by rails 2.x do have these options showing up.
I am just confused about the difference. And I also don't understand why the original rails 2.x and rails 4 both worked (the |f| reflects the form generated by form_for, so f.text_field will get the right value from database).
Yes, your method will return the last line. In your case this is concat("</div>") which evaluates to just "</div>".
The problem is, that concat is not acting as you expect, because it's not occurring within a text buffer and so there's nothing to "concat" to.
To fix this, wrap your helper in a capture block like so:
def some_html
capture do
# ...
concat('<div class="inputbox">')
# ...
concat('</div>>
end
end
More on the capture method: http://apidock.com/rails/ActionView/Helpers/CaptureHelper/capture

Rails: Setting class and data-tag of an HTML attribute with a single rails method

I'm currently working on a tour interface that guides new users around my site. I have a Tour model that has many TourStops, each of which contains information about a section of the site.
Basically, I'd like to write a function for the Tour model that -- when passed the number of a TourStop -- generates the correct class and data attribute for the HTML element it's attatched to. For example, I'd like
<%= link_to image_tag("new_button.png", tour.stop_data(1), :title => 'Add new asset'), new_asset_path %>
to call a function and return something like
def stop_data(order)
" :class => '#{tour_stops.find_by_order(order).name}',
:data => '{:order => order}'"
end
creating a link_to tag like:
<%= link_to image_tag("new_button.png", :class => 'tour_stop_1',
:data => {:order => 1}, :title => 'Add new asset'), new_asset_path %>
The above code doesn't work. Is something like this even possible? If not, what's a better approach I might take?
The image_tag accepts two parameters. A source, and a options Hash.
What you are trying to do is squeezing your return value from stop_data into this options Hash.
In order to get this to work, you first, need to return a Hash from stop_data, and second, make sure you pass only two arguments to image_tag - the source, and the options.
First:
def stop_data(order)
{
:class => tour_stops.find_by_order(order).name,
:data => { :order => order } # you may need order.to_json
}
end
Second:
link_to image_tag("new_button.png", tour.stop_data(1), :title => "Add new asset"), new_asset_path
This looks like it will work, but it won't, since your'e passing three parameters to image_tag.
When you do the following:
image_tag("new_button.png", :class => "tour_stop_1", :data => { :order => 1 }, :title => "Add new asset")
It looks like you're passing even 4 parameters to image_tag, but in fact they are only two. In Ruby, when the last parameter of a method is a Hash, you don't need to wrap the Hash key/value pairs in curly braces ({}), so the example above is essentially the same as
image_tag("new_button.png", { :class => "tour_stop_1", :data => { :order => 1 }, :title => "Add new asset" })
Now, to get your helper to work with image_tag, you need to merge the options, so they become only one Hash.
link_to image_tag("new_button.png", tour.stop_data(1).merge(:title => "Add new asset")), new_asset_path
Again, we're omitting the curly braces when calling merge, because it's only (and therefore last) parameter is a Hash. The outcome is the same as:
tour.stop_data(1).merge({ :title => "Add new asset" })

Generic way of using content_tag for multiple elements of the same kind

I want to generate html like,
<label for='field'>
Label Text
<span class='span1'> Some Text 1 </span>
<span class='span2'> Some Text 2 </span>
...
</label>
I want a call a helper such as,
label_for 'field', :label => 'Label Text', :type1 => 'Some Text 1', :type2 => 'Some Text 2'
For which I tried to do something like,
content_tag(:label, opts[:label], :for => field_name) do
['span1', 'span2'].map { |i|
content_tag(:span, opts[i], :class => i) if opts[i] }.compact.joins('+').html_safe
}
end
But this does not work (of course).
['span1', 'span2'] array is fixed and the user has the option of choosing to display as many spans as needed.
How can I solve this problem?
Why not something like this?
def special_label_for(field_name, label_text, span_array)
content_tag "label", :for => field_name do
concat label_text
span_array.each_with_index do |span_content, index|
concat content_tag("span", span_content, :class => "span" + index.to_s)
end
end
end
special_label_for :user_name, "User Name", ["This is the first Span", "And this is the second Span", "Can also have a third span", "Or as many spans as you like"]
Haven't tested this code, may need to add/remove the concat or an html_safe to get it rendering properly in your view.
Similar thing I need mutliple html element in Label. I solved here
http://apidock.com/rails/ActionView/Helpers/FormTagHelper/label_tag#1428-Html-inside-Lable-tag
You can pass parameters to make dynamic
You can even do something like this:
if the given array is list_of_items
list_of_items.map{ |item| content_tag(:span, item) }.join('').html_safe

Resources