How can I make rails helpers more object-oriented
-
21-08-2019 - |
Question
I'd like to, for all helpers, across my entire Rails application, replace this syntax:
time_ago_in_words(@from_time)
with this:
@from_time.time_ago_in_words
Ideally this change would also make the helpers available anywhere in my application (the same way, say, 5.times
is).
Does anyone know of a plugin that does this? Would it be difficult to roll my own?
Solution
If you create a new model you can make it inherit the methods from an existing class.
Example (app/models/mykindofstring.rb):
class Mykindofstring < String
def all_caps_and_reverse
self.upcase.reverse
end
end
Then from the controller:
@my_string = Mykindofstring.new('This is my string')
and finally calling it from the view:
<%= @my_string %>
displays as This is my string, while:
<%= @my_string.all_caps_and_reverse %>
displays as GNIRTS YM SI SIHT.
The class inherits from the string class, so all the methods that apply to a string also apply to a mykindofstring object. The method in it is just a new name for a combination of two existing string methods, but you can make it do whatever you want, such as a method to convert a time into a formatted string.
Basically it's just extending a current Ruby class with your own methods instead of using functions/helpers.
Major Edit
Based on Ians comment to this answer and his own answer to the above question, I played around a bit in Rails and came up with adding this to the application_helper.rb:
module ApplicationHelper
String.class_eval do
def all_caps_and_reverse
self.upcase.reverse
end
end
end
Now just call the method on any string in the app:
@string = 'This is a string.'
@string.all_caps_and_reverse
.gnirts a si sihT
OTHER TIPS
You won't find any easy way to do this across your whole application for all (or even most) helpers.
Helpers are structured as modules meant to be included in view rendering classes. To leverage them, you'd have to keep a copy of an ActionView around (not a HUGE deal I suppose).
You can always open up the classes and specify each helper you want, though:
class Time
def time_ago_in_words
ActionView::Base.new.time_ago_in_words self
end
end
>> t = Time.now
=> Tue May 12 10:54:07 -0400 2009
>> t.time_ago_in_words
=> "less than a minute"
(wait a minute)
>> t.time_ago_in_words
=> "1 minute"
If you take that approach, I recommend caching the instance of ActionView::Base that you use. Or, if you don't want to go so heavyweight, you can create your own class to just include the helpers you want (you don't want to include the helpers directly in each Time, Date, String, etc, class, though, as that will clutter up the method namespace pretty fierce -- and conflict with the natural names you want).
If you are using Ruby 2.1 you can use Refinements. They are an alternative to monkey-patching Ruby's classes. to borrow @Jarrod's monkey-patch example:
module MyString
refine String do
def all_caps_and_reverse
upcase.reverse
end
end
end
You can now use this in any object you want:
class Post
using MyString
end
@post.title.all_caps_and_reverse
You can read more about Refinements here.
Make a model, TimeAgo or something similar. Implement following this idea here: a code snippet for "wordifying" numbers
Then, in your controller, create the instance variables using this class. Then, in your view, call @from_time.in_words