Ruby on Rails: User helper method to read attribute - ruby-on-rails

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

Related

Elegant way to show corresponding text_field_tag value in Rails 4

So my problem is showing something that a model has in a nice and simpler way.
So what currently works?
In my viewer this works fine:
<%= text_field_tag(:first_name, (current_user.present? ? current_user : '').first_name.present? ? current_user.first_name : '') %>
However this is too long and really hard to maintain, especially when I have several more fields.
So to avoid that I made this in my controller
def user_vals(value)
if(current_user.present?)
current_user.value.present? ? current_user.value : ''
end
return ''
end
Within this controller I can call user_vals(:first_name) but get undefined methodvalue'` error. Furthermore I cannot just call
<%= text_field_tag(:first_name, #user_vals(:first_name)) %>
As I am getting some syntax error with brackets but that's not the real issue.
So my ultimate goal is to have something like this:
<%= text_field_tag(:first_name, #user_vals(:first_name)) %>
Rather than the first code I've given above. How can I achieve that?
You can use try in this case. Just write:
<%= text_field_tag(:first_name, current_user.try(:first_name)) %>
See: http://apidock.com/rails/Object/try
I would recommend to take a step back and try to use Duck Typing to solve this...
What you have right now is a current_user method. This method can return whatever object it wants if the user is not logged in, and that object can respond to whatever it wants. Here's a simplified example:
def current_user
#user || OpenStruct.new(first_name: "")
end
Note: I'm assuming #user holds the currently-signed in user... but this may be a call to super instead, or whatever else depending on your implementation.
So now, instead of branching based on what type of object is coming back from the current_user method, you can now just use the returned object without regard.
current_user.first_name # => Either the User object's first name or `""`
You can go further with this by creating e.g. a GuestUser class and having GuestUser.new returned instead of the OpenStruct above. Guestuser would be a model that is not data-base backed and could respond to any number of methods as needed.
This idea has been represented by many others as well. And using a class to prevent repeated code switching based on nil actually has a name: The Special Case Pattern. As a quick, free example, see the Guest User RailsCast. Or, if you subscribe to Ruby Tapas, be sure to check out episode 112: Special Case. This topic, and many others, are also covered in depth in Avdi Grimm's excellent book: Confident Ruby, which I highly recommend.

Can't call instance method from index view

I'm trying to call method (instance method) which I have defined in the controller from the index.html.erb view.
records_controller.rb:
def calc_cell_balance
4
end
index.html.erb:
<% #records.each do |r| %>
<%= r.calc_cell_balance %><br>
<% end %>
I get this error:
undefined method `calc_cell_balance' for #<Record:0x35d18d8>
I don't want to make it a class method because it's bad design.
If I put the method definition in record.rb (the model), it's working.
I'm not sure what I'm doing wrong, since it's wrong to access the model from the view, but it's the only thing working.
How can I resolve this?
Thanks.
You put an instance method in your controller, RecordsController, but you are trying to call the method on an instance of the Record class. This doesn't make sense at all. Your #records are all Record instances. You would have to do something like:
RecordsController.new.calc_cell_balance
BUT DON'T DO THAT! Your controller is there to just direct what needs to be done, and shouldn't have methods that are called outside of the controller instance itself.
Your method probably belongs in the Record model, or maybe in a helper. It is not at all wrong to access the model from the view. That's the main thing that people do. If you really wanted to not be calling any methods from the view, you could try to gather up all the information in the controller like this:
#records = Record.all
#records_calc_cell_balance = #records.collect(&:calc_cell_balance)
And then you have parallel arrays of data, but that's just silly. Calling model methods from the view is fine. Or, if you feel the method is too view-centric (like maybe you want a method to tell you what CSS class to use), put that in a view helper, which is what it's for.

Rails returning full object instead of integer

Rails's pluralize method was not working like I wanted (words not in english) so I set out to try my own solution. I started out simple with this method in ApplicationController:
def inflect(number, word)
if number.to_i > 1
word = word + "s"
end
return "#{number} #{word}"
end
And called it as such in my view:
<% #articles.each do |article| %>
<%= inflect(article.word_count, "word") %>
<%= inflect(article.paragraph_count, "paragraph") %>
...
<% end %>
But this got me:
undefined method `inflect' for #<#<Class:0x3ea79f8>:0x3b07498>
I found it weird that it referenced a full-fledged object when I thought it was supposed to be just an integer, so I tested it on the console:
article = Article.first
=> (object hash)
article.word_count
=> 10
article.word_count.is_a?(Integer)
=> true
So I threw in a quick words = article.word_count.to_i, but it doesn't throw a TypeError, it actually doesn't do anything, and still returns the same error: undefined method ``inflect' for #<#<Class:0x3ea79f8>:0x3b07498> in reference to the `inflect(article.word_count, "word") line.
Then I thought maybe inflect was already a Rails method and it was some sort of naming conflict, but doesn't matter what I change the method's name to, it keeps giving me the same error: undefined method ``whatever' for #<#<Class:0x3ea79f8>:0x3b07498>
I then tested it on the console and it worked fine. What's going on?
Put your inflect method in ApplicationHelper, not ApplicationController
by default all code in your helpers are mixed into the views
the view is its own entity, it is not part of the controller, when a view instance gets created (automatically when your controller action executes) it gets passed any instance variables you define in your controller action, but does not have access to controller methods directly
NOTE: you can define methods in your controller to expose them to your views by using the helper_method macro - see this post for more info on that - Controller helper_method
but in general you would define the view helper methods in the helpers classes and not in the controller

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].

Rails Test helper method accepting ActionView::Helpers::FormBuilder object

I'm trying to write a test for a helper method that accepts a form helper object, is there a way to create a form object within the test?
/app/views/blahs/edit.html.erb
<% form_for :blahs do |blah| %>
<%= my_helper_method(blah) %>
<% end %>
/app/helpers/blahs_helper.rb
def my_helper_method(blah)
#
# blah is ActionView::Helpers::FormBuilder
# do something with the form object here
#
end
So in my test case how do I create the form object? I'm still on Rails 2.3.9.
Thanks in advance
try using it with a partial first, then get fancy with a helper maybe? Unless you are doing some fudged up "Model" type stuff, a partial is a great way to reuse form code and easier to test.
helpers are for "View logic" in a way. partials are for displaying form fields, even ones that repeat.
It could help to see what you are trying to do in a helper that you can't in a partial.
In theory you could require 'action_view/helpers/form_helper' (which lives in actionpack) in your helper spec which will make the method form_for available.
However I am with pjammer that you should question whether the logic you're putting in your helper_method really belongs there. If you tell us what you're trying to accomplish in helper_method we may be able to give you alternatives that don't require to pass the form object, which is probably a better approach.

Resources