Can't suppress output in nested block helper in rails 3 - ruby-on-rails

This one is sort of twisting my noodle.
I have something resembling this (in a rails 3 engine if that matters)
class Builder
def initialize
#foos = []
end
def foo(&block)
#foos << helper.capture(&block) #helper being a class that is including ActionView::Helpers
end
def to_html
#foos.join "\n"
end
end
module ApplicationHelper
def widget
b = Builder.new
yield b
b.to_html
end
end
#in a view somewhere
<%= widget do |b| %>
<% b.foo do %>
static content
<% end %>
<% end %>
Everything is working out great, but that nested static content is getting output twice -- once where I want it, and once where widget was called.
From what I have read, capture is supposed to deal with this exact problem. I am pretty sure the problem stems from how I am calling capture (from a dummy proxy class that includes ActionView::Helpers), but the problem is that b.foo call is calling a method on a class instance, not from the context of something that will be mixed into the template.
Is there any way to get around this problem? Or am I approaching this from the wrong direction. I am trying to model something fairly involved and am really happy with the api, just can't seem to get passed this problem.

If you modify the helper method to pass in self, which would be the current view instance, and then use this to capture, you might not have this issue. Substitute your use of helper for the provided view instance.

Related

Create html tag wrapper in Rails application

I'm new to Rails and I'd like to create a helper function that consume whatever I pass and returns wrapped element. I was trying to use something like content_tag / tag however it won't cover all of my use cases. It should create div and wrap it over element that I pass as an argument. In most cases it would be just nested HTML. I'm looking for something like code below that would consume anything, it would be nice if it would work with render method as well.
Helper method:
def helper_method(content)
content_tag(:div, content)
end
in ERB file:
helper_method('<span><p>Something</p></span>')
In React I would just pass "children". How should I handle that in Rails?
Capture the block, and pass it to content_tag like so:
def something(css_class, &block)
content_tag(:div, class: css_class, &block)
end
Use it in a view like so:
<%= something('my-css-class') do %>
...your content here...
<% end %>
This avoids the security issues inherent with .html_safe.
You'll use exactly how you described it. You can return HTML safe strings from helpers by setting the string as html_safe
Example:
def helper_method content
content_tag(:div, content.html_safe)
end
Note that, this can potentially introduce security issues. You don't want to call html_safe on user entered strings because scripts will execute. It's not really safe as the name suggests ;)
Read more:
stay-safe-while-using-html-safe-in-rails-9e368836fac1
everything-you-know-about-html_safe-is-wrong
proper use of html_safe
For ideal approach, use sanitize:
def helper_method content
content_tag(:div, sanitize(content))
end

Ruby on Rails: User helper method to read attribute

I'm trying to use a helper method to determine the value of an attribute for several records. Here is the basic function I am trying to get working (from the view):
<% if Baseline.where(subject_id: sub.subject_id).first.crf_status(crf) == 1 %>
<td bgcolor="#98FB98" >
<% else %>
My helper function is crf_status(crf), and it looks like this:
application_helper.rb
def crf_status(crf)
case crf
when Baseline then 'baseline_status'
when FollowUp3Week then 'follow_up_3_week'
...
end
end
So a working example would be if crf_status(Baseline) would return:
<% if Baseline.where(subject_id: sub.subject_id).first.baseline_status == 1 %>
<td bgcolor="#98FB98" >
<% else %>
Right now, the error is 'undefined method 'crf_status' for Baseline'. So based on what I've read, perhaps I have to reference ApplicationHelper in each controller? That doesn't sound right. Please let me know what you think.
Thanks.
edit. I forgot to make this more clear: crf_status(crf) is being passed an object from an array [Baseline, FollowUp3Week...].
The actual line starts with it as well -> if crf.where(subject_id:...
When you do method chaining like .first.crf_status(crf) you don't get a fresh global scope every time. I.e. to get this example to work your crf_status would need to be defined as an instance method on the Baseline model.
From a MVC design perspective, it's frowned upon to do database queries (i.e. where) from your views; you should do it from the controller instead. The choice to use helpers here is totally optional. By putting it in a helper all you're doing is making it inaccessible from code outside your views.
To cut to the chase, here's what you should write in your Baseline model file:
def crf_status(crf)
case crf
when Baseline then baseline_status
when FollowUp3Week then follow_up_3_week
end
end
Note that the baseline_status and follow_up_3_week are actually method calls with the implicit receiver self.
You are calling "crf_status" on an instance of a model, helpers can only be called on views and controllers.
You have to do something like this
<% if crf.where(subject_id: sub.subject_id).first.send(crf_status(crf)) == 1 %>
<td bgcolor="#98FB98" >
<% else %>
Anyway, that looks like a weird code smell (making queries on view is not right and that crf_status looks like something that you should move inside your models)
If you want to return a method that is to be called in the context, use the .send method.
Baseline.where(subject_id: sub.subject_id).first.send(crf_status(crf))
Whatever is returned from your method will be executed. This is a great metaprogramming example. You want to test against the class of the instance, so use the .class method on your case line. You'll want to return symbols not strings though, so do this:
def crf_status(crf)
case crf
when Baseline then :baseline_status
when FollowUp3Week then :follow_up_3_week
else :default
end
end
Edit: Changed case for type comparison

Rails partial locals in helper

Is there a way in RoR to access current partial locals in helper. I want something like
<% render partial: 'foo', locals: { :foo: 'bar' } %>
then to be accessed in lets say ApplicationHelper:
def my_helper_method
...
my_var = ...local_assigns[:foo] # should assign 'bar'
...
end
Other way to describe the problem would be: How do I pass all the locals passed to a partial to my helper method implicitly? If I do it explicitly, there are a lot of boilerplate code, which just pass partial arguments to to a helper method, and I have so many of them.
Is it possible?
Helpers have no knowledge of local variables inside partials. Unless you explicitly pass them a parameter, you can't do what you are proposing. What you can do is take an object-oriented approach using presenters, and avoid using helpers all together.
Either make your own, as outlined in the Railscasts episodes, or use a gem like Draper. Personally, I am in favour of the "roll your own" approach because it's very simple.
Some pseudo-code to get the idea across:
class FooPresenter
def initialize(object, template)
#object, #template = object, template
end
def amazing_foo
#template.content_tag :div, class: 'foo' do
"#{#object.name}: Wow! this is incredible!"
end
end
end
module FooHelper
def present_foo(object)
presenter = FooPresenter.new(object, self)
yield presenter if block_given?
presenter
end
end
Just instantiate that from your view.
= present_foo(foo) do
= amazing_foo
Yay, no need to pass params.
Helpers are just modules floating around in the namespace, and frankly, much of the time they encourage bad coding practices. Presenters offer a clear OOP way of handling complex view logic. Give it a try.
Usually you would pass the parameter into the method from the view, so change your method to be:
def my_helper_method(input_param)
...
my_var = ...foo # should assign 'bar'
...
end
and call this as any other method in the view passing foo as the input_param.
you need to send param to my_helper_method
def my_helper_method(foo)
...
my_var = foo
...
end
in partial
<%= my_helper_method(foo) %>

Rails view and controllers, (if/else statements)

I've just started my own rails project from scratch. I'm trying the view to display hello if the minutes variable is set to a certain value.
The code is in the controller right now, and I want it to display the output in the view. is this possible? or do I write it in the view? Not sure of doing it right.
home_controller.rb
class HomeController < ApplicationController
def index
#minutes = 23
#minutes = params[:minutes]
end
end
index.html.erb
<% if #minutes == 23 %>
<%= "helllo" %>
<% else %>
Anonymous
<% end %>
#minutes = params[:minutes] || 23
It's good enought in this simple case put some logic in the view but usually is better to add helper methods (Read the Getting Started guide is a good idea).
Probably you've already seen the app/helpers directory, in these helper files you can define methods which are available in the view layer, methods related to view layer but do stuff that would be weird or dirty to put in templates files.
For example in your case you could have a /app/helpers/time_helper.rb:
module TimeHelper
# I know the method name sucks a little
def show_hello_if_minutes_is_23(minutes = #minutes)
if minutes==23
"Hello"
else
"Anonymous"
end
end
end
and then use in your index.html.erb template:
<%= show_hello_if_minutes_is_23 %>
As you can see:
You can read the method name and understand what it does (at high level)
Logic is put in a ruby method
The method take a minutes argument but it's optional
And remember: usually repetition is evil (The DRY thing) but in view-land sometimes one time is too much (not in this simple case however).
UPDATE: I've just seen you put set the #minutes variable to 23 and then you overwrite it making the previous assignment useless. I don't know what you're trying to do in your controller but if your question is about having a default value for the minutes variable go with the Yuri's answer and use use the ||= operator: #minutes ||= params[:minutes].

How do I pass html based on ruby code / erb into a method expecting a string in Rails 3?

The following code doesn't work because I can't access the helper inside of a controller:
string << "#{has shared link_to #review.title, #review}"
But within the action (or possibly a method in the model) I still need to pass the html that would be generated from this.
I tried the template instance but doesn't work in Rails 3
Just as a basic example, say you have:
app/messages_controller.rb and
helper/messages_helper.rb
In you messages_helper.rb you may have something like what you've suggested.
#Also you should use = instead of <<
#You can only use << on a variable that has already been initialised
#a = "hello " #=> "hello"
#a << "world" #=> "hello world"
#b << "whatevs" #=> ERROR
def dummy_helper_method
#html_string = "has shared #{link_to #review.title, #review}"
#html_string
end
Then in your messages_controller.rb in any of your methods you can call your new dummy_method and you'll now get access to your #html_string instance variable
def index
dummy_helper_method
#you can now access your #html_string variable from inside this index method
end
Just as a note though, this isn't the right way to do this. You shouldn't call it in your controller unless you're trying to do something fairly specific with it which it doesn't look like you are. If you're trying to get it out into your view so that you can display it, you can actually call your helper method that you've just created in any of your messages' view files (views/messages/anything_in_here.html.erb) rather than calling it in your controller.
For example:
#views/messages/edit.html.erb
<%= dummy_helper_method %>
Anyways, hope it helps

Resources