#132
Oct 20, 2008

Helpers Outside Views

Have you ever wanted to access helper methods outside of the view layer? In this episode I show you a couple ways to do just that.
Download (10.4 MB, 6:14)
alternative download for iPod & Apple TV (8 MB, 6:14)

Full Episode Source Code

# models/category.rb
def description
  "This category has #{helpers.pluralize(products.count, 'product')}."
end

def helpers
  ActionController::Base.helpers
end

# products_controller.rb
def create
  @product = Product.new(params[:product])
  if @product.save
    flash[:notice] = "Successfully created #{@template.link_to('product', @product)}."
    redirect_to products_url
  else
    render :action => 'new'
  end
end

RSS Feed for Episode Comments 27 comments

1. Ben Oct 20, 2008 at 00:41

I don't know why, but in my controller, I simply can do this:

flash[:notice] = "Successfully created #{link_to('product', @product)}."

and as far as I know i din't do anything fancy.

But I have another question: What about url_helpers in classes inside an observer? I didn't get it to work...

Ben


2. Ben Oct 20, 2008 at 00:43

"url_helpers in classes inside an observer" should be "url_helpers inside an observer" :)


3. Per Velschow Oct 20, 2008 at 03:46

Being a bit cheeky one could argue that if a helper method is useful and safe to use outside of ActionView, then it should probably be refactored out into its own module - perhaps ActiveSupport.

Seeing ActionView or ActionController in the model code should generally make you shiver, I think. :)


4. Ryan Bates Oct 20, 2008 at 07:23

@Ben, interesting, link_to does not work directly in a controller for me. What version of Rails are you using? And have you installed any additional plugins which might enable this?

Observers, like models, exist outside the scope of the current request. Perhaps you need a sweeper instead? That's like an observer but has access to URL helpers and other request related things.

@Per Velschow, good point. I could see the pluralize helper method being moved into ActiveSupport, but the interface would likely need to change (maybe being callable on integer or string) and I'm not entirely certain these kind of methods really belong on those classes.

But until that happens, I'm okay with referencing ActionController::Base in a model if it's simply to get the helper methods that work well in a model. Unless of course you're building a gem of some kind that needs to be used outside of the traditional Rails structure.


5. Mark Wilden Oct 20, 2008 at 09:44

Interesting stuff. However, there's a good reason why you have to jump through hoops to use helper methods in non-view code: because helper methods are for presentation. MVC and all that. That may be a worthwhile tradeoff, but it's important to keep in mind.


6. aprendizbasico Oct 20, 2008 at 09:54

Would be better to have the reference in a class base of our models ??

class BaseModel
  def helpers
    ActionController::Base.helpers
  end
end

class Category < BaseModel
 ...

or is there a better way ?

Thanks


7. Ol.keene Oct 20, 2008 at 11:02

also, view's methods can be deligated:

delegate :number_with_precision, :to => 'ActionController::Base.helpers'

@Ryan: link_to is in actionview's namespace, so w/o extending it's not available in controller


8. Bob Walsh Oct 20, 2008 at 11:21

Great as always Ryan!
One question: what would you do if you want to use a custom helper from application_helper.rb in a controller or model?


9. AC Oct 20, 2008 at 13:40

Nice hint. Cheers.


10. James Oct 20, 2008 at 14:04

@Ryan-
Is there a way to access the "current_user" from the model and/or from an observer? Or is that just a terrible practice? I find I'm often wanting to get the current_user's id in various models, and ALWAYS need it for event observers (where an event is logging the who, what, when of an action).


11. Darryl Oct 20, 2008 at 15:14

Is the 'ActionController::Base.helpers' method only available in 2.1?

We're using Rails 2.0.2 and get a 'method not found' trying to use it.


12. taelor Oct 20, 2008 at 16:32

I have never seen that @template variable before. Never knew you could do that. Hopefully that will help when trying to render a link_to_remote inside a controller. I have pretty much quit a project cause I couldn't get it to work.

Thanks again Ryan.


13. vtron Oct 21, 2008 at 06:13

@James
user is a notion that exist only in the context of a request, therefore it is within controller scope.
To observe a model in this context you have to use a sweeper.

For audit trails use the fantastic acts_as_audited plugin
http://github.com/collectiveidea/acts_as_audited/tree/master

You will find the exact scenario of logging user action on models.

Another technique I used is to introduce instance variables on
retrieved model objects and assign current_user or action to them in the controller. This captures the idea that this is not the peristent object, but the object manipulated via an interface.
Once that is done, you can even implement audit or access control within the model class via callbacks and validations, respectively. Some of your validations can be conditioned on on whether these 'controller-related' instance variables are nonnil. If nonnil, then the model is manipulated via a controller (rather than, say a daemon/script/console).

 


14. Lopre Oct 21, 2008 at 07:31

Very good cast!


15. James Edward Gray II Oct 22, 2008 at 14:16

Well, technically pluralize() is available through ActiveSupport today, though it doesn't switch strategies based on a count. That's pretty easy to add though:

"product".send(products.count != 1 ? :pluralize : :to_s)


16. Anlek Oct 23, 2008 at 07:22

How would you use your app helpers in a mailer body. Assuming I had a signup email, and I wanted to use a helper from my users_helper.rb inside my signup.html.erb. The docs say I can use action pack helpers but make no reference to using application helpers.

Thanks for the great podcast!


17. Anlek Oct 23, 2008 at 08:53

Figured out my own question, see this link: http://www.railsonwave.com/railsonwave/2007/8/13/use-helper-functions-in-you-email-view


18. Forrest Zeisler Oct 28, 2008 at 10:28

I found this cast extremely useful. There have been a number of times when I had to call a helper from the controller. Up till now I've been loading the helpers directly in, which is not ideal. I'm definitely going to be modifying my approach in the future.

Thanks for the great advice and keep up the good work.


19. Forrest Zeisler Oct 28, 2008 at 10:32

@Anlek I hit the same problem with loading helpers into emails. After some googling it turns out that you can load additional helpers into action_mailer and ar_mailer. You just have to add a:

helper :name_of_helper

to the top of you email model class.


20. Guest Apr 26, 2009 at 02:33

What if I want to create a Custom class in lib/ and have this class access the concat and capture View helpers. So that I can use this class in my views.
Example:

lib/my_class.rb
class MyClass
  def some_method
     concat 'something'
  end
end

views/[...]/index.html.erb
[...]
<% MyClass.some_method %>

Thanks.


21. Guest Apr 26, 2009 at 02:36

Oups, forgot a "self." in the Class method definition:
  def self.some_method
  [...]


22. tiffany jewelry May 06, 2009 at 22:37

Nice hint.


23. grosser May 30, 2009 at 01:25

I used the same trick to extend string with all ActionView helpers http://pragmatig.wordpress.com/2009/05/30/all-actionview-helpers-on-strings/


24. Michael Sheakoski Jun 11, 2009 at 09:11

Instead of using @template.link_to() in your controller, you can use self.class.helpers.link_to() which is probably a bit safer than accessing the private API.

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(use pastie or gist for code)

sponsored by:
if you want to help:
required:
Get Quicktime Player