views:

623

answers:

3

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?

A: 

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

Terry Lorber
+2  A: 

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

Jarrod
But it's Ruby! Subclassing is "proper" OO, but you don't want to create new objects of another type, you want to use actual God-given strings, so just open up the String class and add your method: class String; def all_caps_and_reverse; self.upcase.reverse; end; end
Ian Terrell
Thanks, good point. I revised the answer accordingly.Btw, this is why I like Stack Overflow; I've only been here a few days and my code is already improving.
Jarrod
Why do it with class_eval rather than just redeclaring the string class?
Horace Loeb
When inside the ApplicationHelper module, class_eval works. When outside, reopening the class works (I just did it in the same file, underneath the ApplicationHelper).It's probably got something to do with defining a class inside a module. When doing some trial and error with it I had to restart the app, so it's may also have something to do with how Rails autoloads things.I'm sure there are other places to put it and other ways to do it, but for a quick addition to the class it seems like better practice to use class_eval inside something that's already in the Rails load path.
Jarrod
+1  A: 

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

Ian Terrell