url_for in controller - ruby-on-rails

in a rails 2 controller i get some data from a model
#company = Company.first
and generate the url in the view
<%= url_for #company %>
Of course this works fine. But when i try to use this in a script
include ActionController::UrlWriter
default_url_options[:host] = 'www.example.com'
#company = Company.first
puts url_for(#company)
it fails with
/gems/actionpack-2.3.8/lib/action_controller/url_rewriter.rb:127:in `merge': can't convert Company into Hash (TypeError)
Any ideas?

I think the issue might be that the url_for method that you're used to calling in your views (and defined on ActionView as a helper) is not the same url_for method that gets called when you're in a controller.
ActionController::Base has its own, similar (but not the same) method called url_for method. In the scope of your controller, the method defined on ActionController::Base is the one being called. http://apidock.com/rails/ActionController
The link to ActionController docs above technically points to the Rails3 version of the API, but it hasn't really changed. If you absolutely need or want the Rails 2.3 docs, you can download them here.

Those are not the same methods.
In your view, you're calling ActionView::Helpers::UrlHelper#url_for. That method has several checks in it to decide what to do based on the type of data that you passed in. If you pass in a model object you end up in the method ActionController::PolymorphicRoutes#polymorphic_path which figures out which named route it's supposed to be using.
The url_for that you're calling in your script doesn't know how to do any of that. However, it can still do quite a bit and I would suggest that you read the comments in that file for ideas on how to use it. The error message that you got will point you right to it.
/gems/actionpack-2.3.8/lib/action_controller/url_rewriter.rb
(Note: actionpack 2.3.14 is available. You might want to upgrade while you're at it.)

Related

Rails Model method that builds a string of links

I have a method on a model called Photo. I have it finding a selection of things from elsewhere in my app. All I need it to do at the end is to create a string of links that I can then output later on when the method is called on an instance.
My code is:
cars.map { |c| link_to(c.name, c) }.join(" AND ")
But i'm hitting this error:
undefined method `link_to' for #<Photo
Any ideas how to fix this?
link_to is a view helper which means it's only available in Rails views by default because it's a router / request concern.
If you specifically want to use link_to you have to include it or reference it directly.
See this SO answer
include ActionView::Helpers::UrlHelper
...
cars.map { |c| link_to(c.name, c) }.join(" AND ")
There are other ways of getting paths than using link_to that I would recommend you consider:
It's arguable that the Rails team would tell you to use UrlFor as the tip in that link suggests:
Tip: If you need to generate URLs from your models or some other place, then ActionController::UrlFor is what you're looking for. Read on for an introduction. In general, this module should not be included on its own, as it is usually included by url_helpers (as in Rails.application.routes.url_helpers).
UrlFor also allows one to access methods that have been auto-generated from named routes.
class User < ActiveRecord::Base
include Rails.application.routes.url_helpers
def base_uri
# named_route method that uses UrlFor
user_path(self)
end
end
User.find(1).base_uri # => "/users/1"
create your own concern to bring in route helpers via ActionMailer as this article suggests
As you may see if you scroll through other SO questions about including view helpers in models, there is pushback on using router and request -based methods outside of controllers and views because it violates the principles of MVC.
I think your use case can give you some peace of mind about this, but it's worth knowing the water is murky and some people may advise you otherwise.
The traditional Rails wisdom (and what I'm about to give you here) is that models should not be creating HTML. They also shouldn't have methods that return HTML. Creating HTML <a> tags should be done much closer to the user interface: in a view template or maybe in a view helper. One reason is that the particular way the hyperlink should be generated is a concern of the view. (Does it need a nofollow attribute? class attributes? This will change, even from one view to another.) And the model should not have any knowledge of these details.
When you do generate links in the views, then you have access to all the helpers such as link_to.
Instead, as I understand it, a model should be responsible for returning its own data. Maybe in your case that'd be an array of dicts of :label, :url. I.e., pure data that'd be easy to pass to link_to.
Hope that helps!

How to access params hash in lib directory rails 6

In my rails app, I am using Kramdown to parse Markdown. I want to extend the functionality of the convert_a method in the HTML converter. Part of this involves accessing the database, but it is dependent on a parameter in the URL. Because I am not directly calling the method that I am overriding I cannot simply pass the method the params hash. Is there a way to access this hash, or even just get the current URL in a module in the lib directory?
to give a bit more context, the method call is in a helper method here:
# in app/helpers/myhelper.rb
def to_html(text)
Kramdown::Document.new(text, parse_block_html: true).to_custom_html
end
and here is the file in which I override the convert_a:
# in lib/custom_html.rb
class CustomHtml < Kramdown::Converter::Html
def convert_a(el, indent)
# use params[:foo] to make query
format_as_span_html(el.type, el.attr, inner(el, indent))
end
end
Edit:
To give a bit more context on where the overrided method is called. I am not extremely familiar with the Kramdown codebase, however it seems that when to_custom_html is called the following bit of code is run inside of Kramdown.rb:
output, warnings = Converter.const_get(name).convert(#root, #options)
which subsequently calls convert_#{el.type} on the internal kramdown elements.
You can pass additional options in Kramdown::Document#new, so just do something like Kramdown::Document.new(text, my_params: params). Then you can use the #options method of the converter to access your params.

In Rails 5, how access cookies method in a model or helper?

How does one access cookies inside a Rails model or helper?
Trying to dry up some controller methods, I am trying to move multiple controller calls to cookies() into application_helper.rb and/or a model.
What doesn't work in application_helper.rb:
cookies[:foo]
ActionDispatch::Cookies.cookies[:foo]
ActionController.cookies[:foo]
ActionDispatch::Cookies::ChainedCookieJars.cookie[:foo]
All of which result in undefined method 'cookies'
Note: Well-meaning answers that simply regurgitate MVC dogma are mis-placed here... I've architected long enough (decades) to know when coloring outside the MVC lines (if possible) is the better route. But the precise syntax eludes me even after digging through Rails source. This question is prompted by a somewhat complex situation related to, among other things, inconsistent browser handling of cookies in cross-domain, ajax environment that sometimes includes local files (for which Chrome declines to manage cookies).
It's not a good idea :) Models are classes, and they shouldn't be aware of what is happening on a web level, and that's why cookies method is implemented in ActionController, but there's no such implementation in ActionModel or ActionHelper. If you need a cookie value in a model, pass a value from a controller then. This is how it should be done.
As #Vasili mentioned, cookies is only available in controllers. But if you want to access cookies in helpers or models, just pass it as an argument, for example:
helper example:
module ApplicationHelper
def some_helper(given_cookies)
given_cookies[:foo] = 'bar'
end
end
In view:
<%= some_helper(cookies) %>

Create a routes to a controller without model with a /id

I have product with a foreign collection_id key
I want to pass an id to a controller.
To do so i have the following routes for my controller :
controller :magasin do
get "magasin" => "magasin#index"
end
The only view in my controller is magasin/index.html.erb
The link to magasin is link_to collection.nom, magasin_path(collection)
This kind of syntax usually works in controllers with models. Here my link is : http://localhost:3000/magasin.2 instead of http://localhost:3000/magasin/2
Later on i will need to call the same view with product_kind_id instead of collection_id and i will add sort by name/price ....
How can i have the ID as a normal argument (/:id)instead of a type(.id)?
A popular URL schema to follow is RESTful routing. Rails has this built-in and will set it up for you if you initialize your resource via rails generate scaffold Magasin nom:string.
If you put resources :magasins in your routing file, it will route /magasins to MagasinsController#index and /magasins/1 to MagasinsController#show with params[:id] set to "1". It will also set up a few other routes that will be useful in the future but for now will just raise an action not found exception.
You don't want to use the dot as an argument delimiter, since Rails places what comes after the dot in the request.format method or just in params[:format] (ordinarily accessed through the respond_to method that comes with the generated scaffolds). Save that dot for later when you are working on delivering alternative display formats like XML and JSON.
I realize I've said a lot in a small space, so feel free to ask any follow up questions once you've consulted the Rails Guide on the issue, and I'll be very glad to help!

View helper link_to in Model class

Using Rails 3, is there a way to use link_to helper, or any helper for that matter, inside model?
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)
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.
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
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
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"
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).

Resources