I want to make a helper like the following.
def my_div some_options, &block
# How do I print the result of the block?
end
You should use CaptureHelper.
def my_div(some_options, &block)
# capture the value of the block a string
content = capture(&block)
# concat the value to the output
concat(content)
end
<% my_div([]) do %>
<p>The content</p>
<% end %>
def my_div(some_options, &block)
# capture the value of the block a string
# and returns it. You MUST use <%= in your view.
capture(&block)
end
<%= my_div([]) do %>
<p>The content</p>
<% end %>
Use capture + concat if you need to concat the output.
Use capture if you need to capture and then reuse the content. If your block doesn't explicitely use <%=, then you MUST call concat (preferred way).
This is an example of a method that hides the content if the user it not an admin.
def if_admin(options = {}, &block)
if admin?
concat content_tag(:div, capture(&block), options)
end
end
<% if_admin(:style => "admin") do %>
<p>Super secret content.</p>
<% end %>
so two things that are important:
rails ignores anything that isn't a string in a content_tag (and content_for)
you can't use Array#join (etc.) because it produces unsafe strings, you need to use safe_join and content_tag to have safe strings
I didn't need either capture or concat in my case.
def map_join(objects, &block)
safe_join(objects.map(&block))
end
def list(objects, &block)
if objects.none?
content_tag(:p, "none")
else
content_tag(:ul, class: "disc") do
map_join(objects) do |object|
content_tag(:li) do
block.call(object)
end
end
end
end
end
this can be used like this:
= list(#users) do |user|
=> render user
= link_to "show", user
(this is slim but works fine with erb too)
http://www.rubycentral.com/book/tut_containers.html
The yield statement will return the result of the block passed. So if you wanted to print (console?)
def my_div &block
yield
end
my_div { puts "Something" }
Would output "Something"
But:
What is the idea of your method? Outputting a DIV?
Related
I'm looking to style a flash notice within a helper passing along an array. So something like:
def import
#fruit = Fruit::Import.run(params[:food_id], params[:file]).result
if #fruit.empty?
flash[:notice] = 'Success'
else
flash_message(#fruit)
end
end
I made sure to include in the controller include FoodsHelper at the top of the controller.
So within the helper I have:
def flash_message(fruit)
content_tag(:ul, class: 'alert alert-error') do
fruit.each do |type, msg|
msg.each do |item|
content_tag(:li, item, class: "flash_#{type}")
end
end
end
end
The hope would have a ul and each of the elements within the array from fruit would display as a list item. This however comes up with undefined method `content_tag' did you mean content_type?
Is there a good way to iterate a flash notice with content_tag?
You could use some code from this (I don't really know what you are trying to do here)
The hope would have a ul and each of the elements within the array
from fruit would display as a list item. This however comes up with
undefined method `content_tag' did you mean content_type?
If you want to use content_tag from a controller you need to add this:
include ActionView::Helpers::TagHelper
or in Rails 5 just use helpers.content_tag
helpers.content_tag(:ul, class: 'alert alert-error') do ...
Is there a good way to iterate a flash notice with content_tag?
There's no such a thing as 'iterate a flash[:notice] with content_tag'
Maybe you want to do this:
Controller:
def import
#fruit = Fruit::Import.run(params[:food_id], params[:file]).result
if #fruit.empty?
flash[:notice] = 'Success'
else
flash[:notice] = #fruit
end
end
Helper:
def flash_message
content_tag(:ul, class: 'alert alert-error') do
flash.each do |type, msg|
if msg.is_a? Array
msg.each do |item|
content_tag(:li, item, class: "flash_#{type}")
end
else
content_tag(:li, msg, class: "flash_#{type}")
end
end
end
end
View:
<%= flash_message %>
Current behavior
With simple_form you need to pass an array:
<%= f.input :my_field, collection: [[true,"Yes"],[false,"No"]] %>
Expected behavior
It would be nice to be able to pass a hash, so you do not need to do invert.sort on every hash passed. Is there any way to do this for every input?
<%= f.input :my_field, collection: {"true"=> "yes", "false"=>"No" } %>
Is it possible to pass a hash directly into the input without invert.sort?
You can add your own helper my_simple_form_for to use your own YourFormBuilder
module ApplicationHelper
def my_form_for record, options = {}, &block
options[:builder] = MyFormBuilder
simple_form_for(record, options, &block)
end
end
Or just use it in this way:
<%= simple_form_for #record, builder: MyFormBuilder do |f| %>
In your own builder you can overwrite input:
class YourFormBuilder < SimpleForm::FormBuilder
def input(attribute_name, options = {}, &block)
options[:collection] = options[:collection].invert.sort if options[:collection].present? and options[:collection].kind_of? Hash
super
end
end
Based on our earlier Q&A, you could enhance the Hash extension to include as_select_options:
module DropdownExt
def self.extended(receiver)
receiver.each do |k,v|
define_method(k) do
v.is_a?(Hash) ? v.extend(DropdownExt) : v
end
end
define_method(:as_select_options) do
unless receiver.values.map{|v|v.class}.include?(ActiveSupport::HashWithIndifferentAccess)
receiver.invert.sort
else
[]
end
end
end
end
class Dropdowns
class << self
private
def dropdowns_spec
YAML.load_file("#{path}").with_indifferent_access
end
def path
Rails.root.join("spec/so/dropdowns/dropdowns.yaml") # <== you'll need to change this
end
end
dropdowns_spec[:dropdown].each do |k,v|
define_singleton_method k do
v.extend(DropdownExt)
end
end
%i(
truck_model
bike_model
).each do |to_alias|
singleton_class.send(:alias_method, to_alias, :car_model)
end
end
Which would let you do something like:
Dropdowns.car_model.field1.as_select_options
=> [["false", "no"], ["true", "yes"]]
Or, I suppose:
<%= f.input :my_field, collection: Dropdowns.car_model.field1.as_select_options %>
It doesn't avoid invert.sort. But, it does bury it a little bit and wrap it up in a convenient as_select_options method.
I've got a presenter which I would like to make the each_with_index method available in. I've added include Enumerable in my base presenter however, I'm still getting a no method error. My current code is below:
index.erb
<% #bids.each_with_index do |bid, index| %>
<% present bid do |bid_presenter| %>
<div class="large-4 medium-4 columns <%= bid_presenter.last_column %>"></div>
<% end %>
<% end %>
bid_presenter.rb
class BidPresenter < BasePresenter
presents :bid
def last_column
if index == bid.size - 1
'end'
end
end
end
base_presenter.rb
class BasePresenter
include Enumerable
def initialize(object, template)
#object = object
#template = template
end
private
def self.presents(name)
define_method(name) do
#object
end
end
# h method returns the template object
def h
#template
end
# if h is missing fallback to template
def method_missing(*args, &block)
#template.send(*args, &block)
end
end
bids_controller.erb
# GET /bids
# GET /bids.json
def index
#bids = current_user.bids
end
You are forwarding all not explicitly implemented methods to #template. Whatever #template is doesn't seem to properly implement each. You need to make #template respond properly to each.
By default, Rails 3 escapes strings you output directly – e.g., <%= '<h1>' %> renders as <h1>
Because of this I have to annoyingly do this a lot:
<%= sanitize #post.body %>
Is there any way I can make this the default? I.e., I want this:
<%= #post.body %>
to be equivalent to:
<%= sanitize #post.body %>
instead of:
<%= h #post.body %>
as it is by default
class ActiveSupport::SafeBuffer
def concat(value)
super(ERB::Util.h(value))
end
alias << concat
def dirty?
false
end
end
Have fun being XSS'd. Do not use in production. This does disable XSS protection entirely and you can't even explicitly tell a piece of data is unsafe. I'd rather do
class Post
def body_with_raw
body_without_raw.html_safe
end
alias_method_chain :body, :raw
end
or even
class ActiveRecord::Base
def self.html_safe(*attributes)
attributes.each do |attribute|
name = attribute + "with_raw"
before = attribute + "without_raw"
define_method name do
before.html_safe
end
alias_method_chain attribute, "raw"
end
end
end
so you can
class Post
html_safe :body
end
Based on Tass' answer, I feel like this might work (but I'm not sure):
class ActiveSupport::SafeBuffer
def concat(value)
if dirty? || value.html_safe?
super(value)
else
# super(ERB::Util.h(value)) # this is what Rails does by default
super(ActionController::Base.helpers.sanitize(value))
end
end
end
I am making a view helper to render set of data in a format. I made these classes
require 'app/data_list/helper'
module App
module DataList
autoload :Builder, 'app/data_list/builder'
##data_list_tag = :ol
##list_tag = :ul
end
end
ActionView::Base.send :include, App::DataList::Helper
helper is
module App
module DataList
module Helper
def data_list_for(object, html_options={}, &block)
builder = App::DataList::Builder
arr_content = []
object.each do |o|
arr_content << capture(builder.new(o, self), &block)
end
content_tag(:ol, arr_content.join(" ").html_safe, html_options).html_safe
end
end
end
end
builder is
require 'app/data_list/column'
module App
module DataList
class Builder
include App::DataList::Column
include ActionView::Helpers::TagHelper
include ActionView::Helpers::AssetTagHelper
attr_reader :object, :template
def initialize(object, template)
#object, #template = object, template
end
protected
def wrap_list_item(name, value, options, &block)
content_tag(:li, value).html_safe
end
end
end
end
column module is
module App
module DataList
module Column
def column(attribute_name, options={}, &block)
collection_block, block = block, nil if block_given?
puts attribute_name
value = if block
block
elsif #object.respond_to?(:"human_#{attribute_name}")
#object.send :"human_#{attribute_name}"
else
#object.send(attribute_name)
end
wrap_list_item(attribute_name, value, options, &collection_block)
end
end
end
end
Now i write code to test it
<%= data_list_for #contracts do |l| %>
<%= l.column :age %>
<%= l.column :contact do |c| %>
<%= c.column :phones %>
<% end %>
<%= l.column :company %>
<% end %>
Every thing is working fine , age , contact , company is working fine. But phones for the contact is not showing.
Does any one have an idea, i know i have missed something in the code. Looking for your help.
Updated question with complete source is enter link description here
There are two issues I can see in the column module.
1) If a block is provided you're setting it to nil - so if block is always returning false. 2) Even if block wasn't nil you're just returning the block as the value, not actually passing control to the block. You should be calling block.call or yielding. Implicit blocks execute faster, so I think your column module should look more like this:
module DataList
module Column
def column(attribute_name, options={})
value = begin
if block_given?
yield self.class.new(#object.send(attribute_name), #template)
elsif #object.respond_to?(:"human_#{attribute_name}")
#object.send :"human_#{attribute_name}"
else
#object.send(attribute_name)
end
end
wrap_list_item(attribute_name, value, options)
end
end
end
The solution is now posted in the discussion.