Rails pattern for conditionally displaying fields - ruby-on-rails

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!

Related

Controller, Model, View. Confusion with passing params

I have a simple self method defined in my model.
def self.search(name, type)
#handle name
#handle type
end
My confusion is with regards to the view & controller. Passing the correct values (or using the correct syntax).
In the view, a simple form.
<%= form_tag(index_path, method: :get) do %>
<%= label_tag :type, 'Type' %>
<% type_array = ["Foo", "Foo_One", "Foo_Two", "Foo_Three"] %>
<%= select_tag :type, options_for_select(type_array, selected: params[:type]), include_blank: true %>
<%= label_tag :name, 'Name' %>
<% name_array = ["Foo", "Foo_One", "Foo_Two", "Foo_Three"] %>
<%= select_tag :name, options_for_select(name_array, selected: params[:name]), include_blank: true %>
<%= submit_tag "Filter" %>
<% end %>
Confusion One
Under options_for_select, should it be select_tag :search or select_tag :type? Should it be selected: params[:search] instead??
In the controller
def index
#foo = Foo.all
#variation 1 that i tried
#foo = #foo.search(params[:search]) if search(params[:search]).present?
#variation 2 that i tried
#foo = #foo.search(params[:name,:type]) if search(params[:name,:type]).present?
#variation 3 that i tried
#foo = #foo.search(params[:name][:type]) if search(params[:name][:type]).present?
end
Confusion Two
Variation 3 kind of makes the most sense to me. But i dont see the self.search getting called. Also it throws an error (no implicit conversion of Symbol into Integer).
I'm not sure if the error is with my forms too?
Clearly i'm not very proficient with knowing where to pass the params and "collect" them. I've tried reading the ruby documentation but had a hard time understanding it.
I think the bigger picture would be, whats the proper syntax (or way to collect arguments) in a form for a method?
Confusion 1: It doesn't matter what the select_tags are called in your case as you don't seem to have it tied to an actual model attribute, but 'name' and 'type' are quite confusing simply as there are HTML attributes name and type on input fields. Still, it will still work the way you have done it and the 'selected' options look just fine.
Confusion 2: You need to access them individually from the params hash:
#foo.search(params[:name], params[:type]) if params[:name].present? || params[:type].present?
However, you have defined it as a self method as so:
class FooClass
def self.search(name, type)
# blah
end
This means you can't access it on an instance of the class, i.e #foo.search, you would call it from the class itself:
FooClass.search(params[:name], params[:type]) if params[:name].present? || params[:type].present?
If on the other hand you didn't have the 'self', as so:
class FooClass
def search(name, type)
# blah
end
The you could do:
#foo = FooClass.new
#my_var = #foo.search(params[:name], params[:type])
First off change self.search to just search because your index method in your controller is using an instance of your Foo model. when you do self.method that is a class level method and essentially works without having to create an instance of your class, which in this case is your Foo class.

Adding a class to a helper method call

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") %>

Rails write variable in view without using <%= %>

Let's say I've got the variable #var. Usually I would use <%= #var %> to write it into the view. Now I want to call a module method from within my view which internally decides to write the content of #var or not.
Inside the module I can't use <%= %>. How can I print the content of #var from within the module? The method would be called like this: <% my_method %>. Thanks
Update
Thanks for the answers so far. Maybe I should say more about my initial problem to be more clear. Sorry if I wasn't clear enough.
At first I used the <%= %> tag like this:
def print_if_present(var)
var ? var : ""
end
<%= print_if_present var %>
But then, when the var was nil, I got "" as output, which took space in the view. How can I prevent this behavior?
I assume that your module is actualy the view helper. If is that so, simply return var.
def my_method
if my_condition
#var
else # else clause is optional
#other_var
end
end
Note that the else clause is optional. If you want to write something or nothing, you can simply use the if. This is so because if the if is not executed and there is no else, it will return nil, that will be casted to an empty string in your template. Just to ilustrate,
if true
1
end
=> 1 #return if last value
if false
1
end
=> nil # return nil because there is no else block
Since you still want to print the return of your method on your template, you need to keep the equal sign:
<%= my_method %>
The best way to do this is to have your method return the string and use <%= ... %> as in fotanus’ answer, but in Rails if you really need to write output directly from a helper you could use the concat method:
The preferred method of outputting text in your views is to use the <%= “text” %> eRuby syntax. The regular puts and print methods do not operate as expected in an eRuby code block. If you absolutely must output text within a non-output code block (i.e., <% %>), you can use the concat method.
So you can define a helper like this:
def my_method
if some_condition
concat "Something or other"
else
concat "Something else"
end
end
And then use it in a non-output block:
<% my_method %>

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

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

Resources