blocks in views and refactor in rails - ruby-on-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

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.

Appending to yield in content_tag

Lets say I have this helper in application_helper.rb
def my_helper(content = nil, *args, &block)
content_tag(:div, class: :my_wrapper) do
(block_given? ? yield : content) + content_tag(:span, "this is the end", *args)
end
end
And i call it from a view with
my_helper do
content_tag(:div, "this is the beginning")
end
I would expect the result to be something like
<div class="my_wrapper">
<div>
this it the beginning
</div>
<span>
this is the end
</span>
</div>
But in fact, the span with the text "this is the end" will not be appended to the yield.
If i were to use this line in the helper instead:
(block_given? ? content_tag(:div, &block) : content) + content_tag(:span, "this is the end", *args)
I would get both content, but the yield would be wrapped inside another div.
How could I add / append content after a yield, without wrapping the yield in a different content_tag?
You can use capture to achieve this:
def my_helper(content = nil, *args, &block)
content_tag(:div, class: :my_wrapper) do
(block_given? ? capture(&block) : content) + content_tag(:span, "this is the end", *args)
end
end
And be sure to do this in your view:
<%= my_helper do %>
<%= content_tag(:div, "this is the beginning") %>
<%- end %>

Show categories/sub-categories in tree hierarchy inside a dropdown

I have a category table with fields id,name and parent_id. The root categories have parent_id 0. Now i want to show the list of categories in a drop down and a structure like this:
root_category
first_sub_category
sub_sub_category
another_sub_sub_category
second_sub_category
another_root_category
first_sub_category
second_sub_category
Here's my Controller:
def new
#category = Category.new
end
And here's the view:
<%= f.label :parent_category %>
<% categories = Category.all.map{|x| [x.name] + [x.id]} %>
<%= f.select(:parent_id, options_for_select(categories), {}, class: 'form-control') %>
Please Help.
Assuming you can get the children of a given category similar to:
has_many :children, :class_name => 'Category', :foreign_key => 'parent_id'
Create a method for categories to get all children and indent each by the level:
def all_children2(level=0)
children_array = []
level +=1
#must use "all" otherwise ActiveRecord returns a relationship, not the array itself
self.children.all.each do |child|
children_array << " " * level + category.name
children_array << child.all_children2(level)
end
#must flatten otherwise we get an array of arrays. Note last action is returned by default
children_array = children_array.flatten
end
Then in your view:
<select>
<option></option>
<% root_categories.each do |category| %>
<option><%=category.name%></option>
<% category.all_children2.each do |child| %>
<option><%=child.html_safe%></option>
<% end %>
<% end %>
</select>
I haven't 100% tested this but the bits I did suggest it should work...
Solved the problem by adding these functions in application_helper.rb
def subcat_prefix(depth)
(" " * 4 * depth).html_safe
end
def category_options_array(current_id = 0,categories=[], parent_id=0, depth=0)
Category.where('parent_id = ? AND id != ?', parent_id, current_id ).order(:id).each do |category|
categories << [subcat_prefix(depth) + category.name, category.id]
category_options_array(current_id,categories, category.id, depth+1)
end
categories
end
and using them in my view like this
<%= f.select(:parent_id, options_for_select(category_options_array), {}, class: 'form-control') %>

Times loop does not work

I wrote a simple helper for my rails application:
def calendar_build
5.times do
whole_cal
end
end
def whole_cal
content_tag(:div, :class => "row") do
small_cal + big_cal
end
end
def small_cal
content_tag(:div, :class => "col-xs-2 token-text") do
concat(content_tag(:p, "15.20"))
end
end
def big_cal
content_tag(:div, :class => "col-xs-10 weite_cal") do
concat(content_tag(:input,"", class: "form-control input-sm pa"))
end
end
How you can see i try to generate 5 whole_cal:
def calendar_build
5.times do
whole_cal
end
end
But in my view this only displays 5 why? Thanks
The return value of the loop is 5.
1.9.3p194 :056 > 5.times {}
=> 5
You should probably do
def calendar_build
whole_cals = []
5.times do
whole_cals << whole_cal
end
whole_cals
end
And iterate the array in your view
Couldn't you do whole_cal*5? String multiplication works in general.
You have to add the generated html code to a variable like result, then put result as last line in your method to return the concatenated code
something like:
result = ""
5.times do
result += html code
end
result
and depending on your html code, you should replace the last line with result.html_safe

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?

Resources