Adding a class to a helper method call - ruby-on-rails

So I have a helper method that I am trying to apply css to without putting it in a div or any other element. How would I go about applying the css class to this helper in rails?
I tried:
<%= first_letter_content(e.content), :class => "first-letter" %>
and
<%= (first_letter_content(e.content), :class => "first-letter") %>
both resulting in syntax error, unexpected ',', expecting ')'
Helper code:
def first_letter_content(content)
first_letter = content[0]
return first_letter
end
Any suggestions? I have been trying to find the proper syntax, but no luck.

Your helper does not support options (extra args) but you are trying to give a HTML class to the element.
You should wrap the content of first_letter_content inside a div/span (depending on what you want, block or inline) and apply the class on this HTML element:
<div class='first-letter'>
<%= first_letter_content(e.content) %>
</div>
Or you can directly wrap the content[0] inside a div in the helper method:
def first_letter_content(content, options = {})
content_tag(:div, content[0], options)
end
And use it like this:
first_letter_content(content, class: 'first-letter')
first_letter_content(content, class: 'first-letter', id: 'something')
first_letter_content(content)
Also, you can refactor your helper method to this:
def first_letter_content(content)
content[0]
end
It is a minor improvement but in Ruby the "last thing" used in a method will be returned by this method.
Examples:
def something
a = 2
b = 3
a
end
# => returns `2`
def something_else
a = 2
b = 3
end
# => returns `3`
def whatever
a = 12
nil
end
# => returns `nil`

I am trying to apply css to without putting it in a div or any other element
Css classes are for DOM elements, so you should wrap this content into some element/node.
For example:
def first_letter_content(content, css_class)
content_tag(:div, content[0], class: css_class)
end
Call:
<%= first_letter_content(e.content, "first-letter") %>

Related

Rails pattern for conditionally displaying fields

I find myself repeating this type of code over and over and over again.
<% if !#model.property.blank? %>
<label>Property</label>
<div><%= #model.property %></div>
<% end %>
The goal being to only output a label and a property value if and only if the value is present. I find that repeating this code many times makes it hard to scan the source code. Can this be reduced and made more concise? What pattern can be applied to this to make it easier to code?
You can create a helper for you, that will deal with theses tests automatically:
# application helper
def display_if_exists(instance, attribute)
return nil if instance.blank? || attribute.blank?
label_tag = content_tag :label do
instance.class.human_attribute_name attribute.to_sym
end
div_tag = content_tag :div do
instance.try(attribute.to_sym)
end
return (label_tag + div_tag).html_safe
end
And use it this way:
# view
display_if_exists(#user, :username)
A little improvement, with options:
def display_if_exists(instance, attribute, options = {})
return nil if instance.blank? || attribute.blank?
label_options = options.delete(:label)
div_options = options.delete(:div)
label_tag = content_tag :label, label_options do
instance.class.human_attribute_name attribute.to_sym
end
div_tag = content_tag :div, div_options do
instance.try(attribute.to_sym)
end
return (label_tag + div_tag).html_safe
end
And use the options like this:
display_if_exists(#user, :username, { label: { class: 'html-class' }, div: { style: 'margin-top: 2px;' } })
An other option is the Rails Presenter Pattern. It is very interesting, but might be too deep for what you are trying to achieve:
http://eewang.github.io/blog/2013/09/26/presenting-the-rails-presenter-pattern/
http://fr.slideshare.net/mdesjardins/presenters-in-rails
http://fr.slideshare.net/thaichor/presenter-and-decorator-in-rails
Ruby on Rails patterns - decorator vs presenter
May be you would like to extract this into a helper method where you can put the existing logic and call that helper.
def print_property_if_present(model)
"<label>Property</label><div>#{model.property}</div>" if model.property.present?
end
Don't forget to call html_safe to render the output in an HTML printable format.
Hope this helps!

How to extend a core Rails FormBuilder field

I am using Bootstrap 3 with Rails 4, and I wanted to create a custom FormBuilder to handle some of Bootstrap's unique HTML syntax. Specifically, I needed a custom helper that would create the form-group div wrapper around a form field, since Bootstrap applies error state to this wrapper, and not the field itself...
<div class="form-group has-error">
<label class="col-md-3 control-label" for="user_email">Email</label>
<div class='col-md-9'>
<input class="form-control required-input" id="user_email" name="user[email]" placeholder="peter#example.com" type="email" value="someone#example.com" />
</div>
</div>
Note the extra class has-error in the outer div...
Anyway, I wrote that helper, and it works great!
def form_group(method, options={})
class_def = 'form-group'
class_def << ' has-error' unless #object.errors[method].blank?
class_def << " #{options[:class]}" if options[:class].present?
options[:class] = class_def
#template.content_tag(:div, options) { yield }
end
# Here's a HAML sample...
= f.form_group :email do
= f.label :email, nil, class: 'col-md-3 control-label'
.col-md-9
= f.email_field :email, class: 'form-control required-input', placeholder: t('sample.email')
Now I want to utilize Bootstrap's form help text in order to display error messages. This requires me to extend Rails native helpers (such as text_field in the example above) and then call them within the the block of f.form_group.
The solution seemed simple enough: call the parent, and append my span block onto the end...
def text_field(method, options={})
#template.text_field(method, options)
if !#object.errors[method].blank?
#template.content_tag(:span, #object.errors.full_messages_for(method), class: 'help-block')
end
end
Only it wouldn't output any HTML, the div would simply show up empty. I've tried a bunch of diff syntax approaches:
super vs text_field vs text_field_tag
concat-ing the results -- #template.concat(#template.content_tag( [...] ))
dynamic vars, e.g. def text_field(method, *args) and then options = args.extract_options!.symbolize_keys!
I only ever get weird syntax errors, or an empty div. In some instances, the input field would appear, but the help text span wouldn't, or vice verse.
I'm sure I'm screwing up something simple, I just don't see it.
Took a few days, but I ultimately stumbled onto the proper syntax. Hopefully it saves someone else's sanity!
Ruby's return automagic, combined with Rails at-times complex scoping, had me off kilter. Specifically, #template.text_field draws the content, but it must be returned by the helper method in order to appear inside the calling block. However we have to return the results of two calls...
def text_field(method, options={})
field_errors = object.errors[method].join(', ') if !#object.errors[method].blank?
content = super
content << (#template.content_tag(:span, #object.errors.full_messages_for(method), class: 'help-block') if field_errors)
return content
end
We must return the results of both the parent method (via super) plus our custom #template.content_tag(:span, injection. We can shorten this up a bit using Ruby's plus + operator, which concatenates return results.
def text_field(method, options={})
field_errors = object.errors[method].join(', ') if !#object.errors[method].blank?
super + (#template.content_tag(:span, #object.errors.full_messages_for(method), class: 'help-block') if field_errors)
end
Note: the form was initiated with an ActiveModel object, which is why we have access to #object. Implementing form_for without associating it with a model would require you to extend text_field_tag instead.
Here's my completed custom FormBuilder
class BootstrapFormBuilder < ActionView::Helpers::FormBuilder
def form_group(method, options={})
class_def = 'form-group'
class_def << ' has-error' unless #object.errors[method].blank?
class_def << " #{options[:class]}" if options[:class].present?
options[:class] = class_def
#template.content_tag(:div, options) { yield }
end
def text_field(method, options={})
field_errors = object.errors[method].join(', ') if !#object.errors[method].blank?
super + (#template.content_tag(:span, #object.errors.full_messages_for(method), class: 'help-block') if field_errors)
end
end
Don't forget to tell form_for!
form_for(:user, :builder => BootstrapFormBuilder [, ...])
Edit: Here's a number of useful links that helped me along the road to enlightenment. Link-juice kudos to the authors!
Writing a custom FormBuilder in Rails 4.0.x
Formatting Rails Errors for Twitter Bootstrap
Very Custom Form Builders in Rails
SO: Nesting content tags in rails
SO: Rails nested content_tag
SO: Rails 3 Custom FormBuilder Parameters
SO: Trying to extend ActionView::Helpers::FormBuilder
RailsGuides: Form Helpers
Real world sample from treebook tutorial code

sending multiple parameters to another function in ruby

I'm trying to create a function that adds some functionality to the link_to function in rails. What I'd like it to do is simply to add a class to it. What I have so far:
#application_helper.rb
def button_link(*args)
link_to(*args.push(class: 'btn'))
end
Problem is that if I now add another class to the button_link function it doesn't work.
Example:
<td class='button'>
<%= button_link "Show", category_path(item), class: "btn-primary" %>
</td>
I get the following error: wrong number of arguments (4 for 3). How can I do this correctly?
link_to has 4 method signatures.. This is the one used most often.
Below we check to see if a class was already sent in -- and because of how HTML classes work, we want to have multiple classes, which are space-separated values.
def button_link(body, url, html_options={})
html_options[:class] ||= ""
html_options[:class] << " btn"
link_to body, url, html_options
end
The other method signatures can be viewed http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
Try changing your helper method to this, trying to maintain the link_to form:
def button_link(name, url_options, html_options = {})
if html_options.has_key?(:class)
css_options = html_options.fetch(:class)
css_options << ' current'
html_options.merge!( { :class => css_options } )
else
html_options.merge!( { :class => ' btn' } )
end
link_to(name, url_options, html_options)
end

Render ERB as HTML and ERB from a Rails View

I'm making a style guide where I output the code on the right that is displayed on the left.
I know that adding %% escapes ERB
I have written a helper that takes the contents of a block and renders the code in two places one showing the html and I want the other to show the source ERB that created the html.
The problem is I get back HTML where I wanted ERB.
The View Code
<%= display_code do %>
<%= link_to "Button", "/style_guide, class: "btn" %>
<% end %>
The Helper Code
module StyleGuideHelper
def display_code(&block)
content = with_output_buffer(&block)
html = ""
html << content_tag(:div, content, class: "rendered-code")
html << content_tag(:div, escape_erb(content), class: "source-code-preview")
html.html_safe
end
def escape_erb(code)
code = code.gsub("%=", "%%=")
end
end
Expected Result
Button <%= link_to "Button", "/style_guide, class: "btn" %>
Actual Result
Button Button
Cheers
The issue is that this helper runs the block (link_to "Button", ...) -- it never sees the source code inside the block, just its output. You could replace escape_erb with h to capture the resulting HTML, but that won't pop back up to the ERB that generated it.
As I see it, your options are:
Break out examples into partials, then make a helper that a) renders the partial and b) displays the underlying file.
Specify your ERB fragments as strings (heredocs?), pass the string into the helper, and have the helper a) evaluate it via ERB.new(string).result(binding) to render the result and b) display the string.
Make the helper determine what part of the view invoked it, then parse the .erb well enough to find the block. Catch is, the precise format of what you see in callers is subject to change without notice due to the way views are compiled.
Make a helper that uses crazy metaprogramming juju to evaluate the block in both an ERB context as well as your own special context that intercepts the code being evaluated and turns it back into markup.
...sorted in approximate order of complexity and odds of success.
This code below will allow you to retrieve the code for a given block.
class ERBSource
ERB = ::ActionView::Template::Handlers::ERB
def self.for(block)
new(block).source
end
attr_reader :block, :file, :line_number
def initialize(block)
#block = block
#file, #line_number = *block.source_location
end
def source
lines = File.readlines(file)
relevant_lines = lines[(line_number - 1)..-1] || []
extract_first_expression(relevant_lines)
end
private
def extract_first_expression(lines)
code = lines.slice[0,1].join # add the first two lines so it has to iterate less
lines.each do |line|
code << line
return code if correct_syntax?(compile_erb(code))
end
raise SyntaxError, "unexpected $end"
end
def correct_syntax?(code)
stderr = $stderr
$stderr.reopen(IO::NULL)
RubyVM::InstructionSequence.compile(code)
$stderr.reopen(stderr)
true
rescue Exception
$stderr.reopen(stderr)
false
end
def compile_erb(code)
ERB.erb_implementation.new(
code,
:escape => false,
:trim => (ERB.erb_trim_mode == "-")
).src
end
end
This is what the helper looks like
module StyleGuideHelper
def render_example(name, &block)
code = ERBSource.for(block)
content_tag(:h2, name) +
content_tag(:div, &block) +
content_tag(:pre, content_tag(:code, code))
end
end

Custom form helpers

Is there a way that I can create a custom form helper so that instead of:
special_field_tag :object, :method
I can achieve something like:
form.special_field :method
Yes, you can add to the FormBuilder class and get access to the object passed into the form_for. I've done this for a lot of things: dates, times, measurements, etc. Heres an example:
class ActionView::Helpers::FormBuilder
include ActionView::Helpers::TagHelper
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::FormOptionsHelper
include ActionView::Helpers::CaptureHelper
include ActionView::Helpers::AssetTagHelper
# Accepts an int and displays a smiley based on >, <, or = 0
def smile_tag(method, options = {})
value = #object.nil? ? 0 : #object.send(method).to_i
options[:id] = field_id(method,options[:index])
smiley = ":-|"
if value > 0
smiley = ":-)"
elsif smiley < 0
smiley = ":-("
end
return text_field_tag(field_name(method,options[:index]),options) + smiley
end
def field_name(label,index=nil)
output = index ? "[#{index}]" : ''
return #object_name + output + "[#{label}]"
end
def field_id(label,index=nil)
output = index ? "_#{index}" : ''
return #object_name + output + "_#{label}"
end
end
Which you can use like this:
<% form_for #quiz do |f| %>
<%= f.smile_tag(:score) %>
<% end %>
There are some instance variables created by Rails that you can access in these helper methods:
#object - the model object specified by the form
#object_name - the class name of the object
#template - I think its an instance of the ActionView, you can possibly bypass all the includes I added by calling methods on the template. Haven't tried that yet.
#options - options passed to the FormBuilder when its created by the form_for call
I wrote the field_id and field_name methods to create these attributes on the HTML input elements the same way the regular helpers do, I'm sure there is a way to tie into the same methods that Rails uses, but I haven't found it yet.
The sky is the limit on what you can do with these helper methods, they simply return strings. You can create entire HTML tables or pages in one, but you better have a good reason to.
This file should be added in the app/helpers folder
#Tilendor, thanks so much for the pointers. Here is an example of an enum_select form tag helper that uses Rails 4.1 enums to automatically populate the options of a select tag:
# helpers/application_helper.rb
module ApplicationHelper
class ActionView::Helpers::FormBuilder
# http://stackoverflow.com/a/2625727/1935918
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::FormOptionsHelper
def enum_select(name, options = {})
# select_tag "company[time_zone]", options_for_select(Company.time_zones
# .map { |value| [value[0].titleize, value[0]] }, selected: company.time_zone)
select_tag #object_name + "[#{name}]", options_for_select(#object.class.send(name.to_s.pluralize)
.map { |value| [value[0].titleize, value[0]] }, selected: #object.send(name))
end
end
end
The trickiest construct is #object.class.send(name.to_s.pluralize) which produces a hash of available values (e.g., Company.time_zones). Putting it in helpers/application_helper.rb makes it automatically available. It is used like:
<%= f.label :billing_status %>:
<%= f.enum_select :billing_status %><br />
Our app was displaying phone numbers in text fields, and we wanted to omit the country code for domestic numbers. I was using form helpers. After reading this and rails source a bit, i came to this solution:
class ActionView::Helpers::FormBuilder
def phone_text_field name, options = {}
value = object.public_send(name).to_s
if value.start_with? "9" # national number
value = value[1..-1]
options["value"] = value
end
text_field name, options
end
end
I put this on app/helpers/application_helper.rb and use it like i use text_field() helper. Hope this helps someone.

Resources