51

Using Rails 3, is there a way to use link_to helper, or any helper for that matter, inside model?

Krule
  • 6,468
  • 3
  • 34
  • 56

6 Answers6

89

There are some reasons that you may need link_to in a model. Yes, @andy, it's a violation of MVC, but that doesn't mean you should get points for not answering the question.

@schwabsauce, it's easier than that. The first line isn't even strictly necessary if you do it in an initializer or something. Same thing works for .sanitize and .raw and a whole load of other awesome functions.

ActionView::Base.send(:include, Rails.application.routes.url_helpers)
ActionController::Base.helpers.link_to(whatever)

If you want to use autopaths you may have to do this inside your link_to:

Rails.application.routes.url_helpers.page_path(@page)
Chuck Vose
  • 4,560
  • 24
  • 31
  • This question was asked a while ago and at that moment @andy's advice helped me resolve this problem. However, you are right in a sense that is does not answer actual question. – Krule Jan 19 '12 at 09:09
32

Be very careful following the advice outlined in Chuck's post if you're doing this in Rails 3.2.1 . It would seem as though that approach is not a safe way to go about including the link_to helper in non-view classes in Rails 3.2.1. There is a safer way (that works for us in any case) outlined below.

When we used the approach in Chuck's post in one of our classes, it ended up having very troubling and difficult to debug consequences. It ended up causing side effects / bugs that only turned up in very specific (and rare) circumstances.

The problem, as far as we can tell, is that this line:

ActionView::Base.send(:include, Rails.application.routes.url_helpers)

Is telling ActionView::Base to include the Rails.application.routes.url_helpers, which ActionView::Base apparently already does on its own. Having it include the url_helpers a second time, seems to cause re-initialization of the routes state (@_routes in classes that have included the ActionDispatch::Routing::UrlFor module).

This leads to seemingly random and unexplained "undefined method 'url_for' for nil:NilClass" exceptions in views that attempt to call, directly or indirectly, the url_for method after ActionView::Base has included the url_helpers the second time.

The solution that worked for us was instead of telling ActionView::Base to include the url_helpers again, just include the UrlHelper module yourself wherever you might need it.

Then when you need to use link_to and have access to the path you can simply do this (assuming login_path is valid for your app):

include ActionView::Helpers::UrlHelper
...
link = link_to('here', Rails.application.routes.url_helpers.login_path)

It took us a very long time and quite a lot of head scratching to track down the bugs caused by the double-include and I just wanted to warn others to be careful when tweaking the behavior of the Rails base classes.

  • Thank you for sharing that! I'm glad you found a better way! – Chuck Vose Apr 07 '15 at 01:32
  • 2
    One-liner: `include ActionView::Helpers::UrlHelper` – karlingen Sep 14 '15 at 08:12
  • @karlingen - omitting the reference to `Rails.application.routes...` can lead to the message "arguments passed to url_for can't be handled. Please require routes or provide your own implementation" – Del Aug 23 '16 at 21:58
5

I got this to work with the following inclusions:

include ActionView::Helpers::UrlHelper
include ActionController::UrlFor
include Rails.application.routes.url_helpers

cattr_accessor :controller
def controller; self.class.controller; end
def request; controller.request; end

Then in my controller I populated the attribute (creating a controller from scratch requires a significant amount of data in the arguments hash).

Lead.controller = self
schwabsauce
  • 399
  • 5
  • 16
  • 1
    fwiw, Rails documentation does lead the way to this solution. The docs for ActionDispatch::Routing::UrlFor point out that "Tip: If you need to generate URLs from your models or some other place, then ActionController::UrlFor is what you’re looking for." Then when you include that file it offers as an error message: raise "In order to use #url_for, you must include routing helpers explicitly. " "For instance, `include Rails.application.routes.url_helpers" So I think this is acceptable by Rails conventions - sometimes this is the best way/best place to generate a link :) – schwabsauce May 19 '11 at 15:41
  • Yes, for instance, if your model is handling payment authorization to a third party, offsite gateway, and needs to generate return/cancel URLs back to the app. – hoffmanc Dec 05 '12 at 12:52
3

link_to helper, MVC violation

What Andy said,

if you're generating HTML in the model you probably need to take a big long look at what you're doing and why.

URL helpers

URLs on the other hand often come in handy outside of view-controller code, in all kinds of service/form/api/... classes for example, even in models if you have to.

Yes, Rails.application.routes.url_helpers is a module, but that doesn't mean you should just include it wherever or funny stuff will start happening as Gary put it:

https://www.destroyallsoftware.com/blog/2011/one-base-class-to-rule-them-all

What you can do is:

    delegate :url_helpers, :to => 'Rails.application.routes'

and then use, for example

    url_helpers.home_url
bbozo
  • 7,075
  • 3
  • 30
  • 56
0

If you want to use any module stuff anywhere and don't mind going against all Ruby norms, throw this hackery into a service:

module View
  extend self

  class HelperIncluder
    include ActionView::Helpers::UrlHelper
  end

  def link_to(*args)
    HelperIncluder.new.link_to(*args)
  end
end

And now:

View.link_to('a', 'b') => "<a href=\"b\">a</a>"
Jacquen
  • 986
  • 8
  • 18
-8

Not without hackery.

If you think you need link_to in a model, you're likely violating some principle of the Model-View-Controller architecture.

A model should be a place for data and business logic, but generating links is almost certainly a job for the controller or view (or, Rails specifically, in a helper class).

Andy Lindeman
  • 12,087
  • 4
  • 35
  • 36
  • 2
    Thank you. Moved things to view helper. All fits right in now. – Krule Jan 17 '11 at 14:11
  • 25
    There are good reasons to use helpers in models. But more to the point, if you're going to offer an opinion at least offer a solution in case they have thought it through. It's possible that someone with 2k rep has their head on straight. – Chuck Vose Jan 18 '12 at 19:31