Reputation: 441
does anyone know how it works, and above all how to override it?
I work on a Rails 5.0.0.1 project. For practical reasons we use namespaced routes like
namespace :survey do
resources :questions
resources :answers
end
We have Question
and Answer
models accordingly.
The documentation tells, that the helper link_to
can be used with simply passing the ActiveRecord instance as second parameter, which would mean in my case to simple write the following in the view (haml):
= link_to 'Show question', @question
This works fine if there is no namespace but goes for the named helper method question_path
even if the route is namespaced - instead of survey_question_path
. The point is that I would like to decide from an ActiveRecord instance which named route helper to use.
The interesting thing is that Rails knows that the Question instance will have a named route like question_path, I just cannot figure out how.
What I have figured out this far:
link_to
uses a helper method url_for
, which then chooses the appropriate (or not) named route helper - but how?model_name
, which contains attributes like route_key
(plural) and singular_route_key
- I am not sure if this has anything to do with what I am looking for, or even if it has how to override it.I thought there was a simple method to override in the model like to_partial_path
for render @question
, or self.controller_path
in the case of controllers to render actions from another view directory, but I cannot find it.
Any help would be appreciated.
EDIT
After some more googling I stumbled upon the following paragraph in APIdock (Relying on named routes):
Passing a record (like an
Active Record
) instead of a hash as the options parameter will trigger the named route for that record. The lookup will happen on the name of the class. So passing a Workshop object will attempt to use theworkshop_path
route. If you have a nested route, such asadmin_workshop_path
you’ll have to call that explicitly (it’s impossible forurl_for
to guess that route).
Upvotes: 1
Views: 971
Reputation: 630
As for your questions:
- the second parameter of link_to uses a helper method url_for, which then chooses the appropriate (or not) named route helper - but how?
If you follow the #url_for method you mentioned around the source, we'll be thrown around, from ActionView::RoutingUrlFor to ActionDispatch::Routing::PolymorphicRoutes
In the ActionDispatch::Routing::PolymorphicRoutes#polymorphic_url method, it makes use of an internal class and calls HelperMethodBuilder.polymorphic_method, passing all given options (including your model class as 'record_or_hash_or_array' (yeah, I know...)).
A few lines below, on this method's declaration, you'll get thrown around small methods a bit more and end up on a method like this:
def self.build(action, type)
prefix = action ? "#{action}_" : ""
suffix = type
if action.to_s == "new"
HelperMethodBuilder.singular prefix, suffix
else
HelperMethodBuilder.plural prefix, suffix
end
end
def self.singular(prefix, suffix)
new(->(name) { name.singular_route_key }, prefix, suffix)
end
def self.plural(prefix, suffix)
new(->(name) { name.route_key }, prefix, suffix)
end
As you can see, it'll decide if it's the #new action or not, and call either #singular or #plural, which will, in turn, answer your second question:
- the ActiveRecord instances have the method model_name, which contains attributes like route_key (plural) and singular_route_key - I am not sure if this has anything to do with what I am looking for, or even if it has how to override it.
These two methods get assigned, as we can see above, to a lambda that is the first parameter to this inner class's initializer, which will be stored as @key_strategy. Back in **ActionView#UrlFor", on the first link I showed you, you see this call:
else
builder.handle_model_call(self, options)
Which will pass on to a few methods on the Builder's side:
def handle_model(record)
args = []
model = record.to_model
named_route = if model.persisted?
args << model
get_method_for_string model.model_name.singular_route_key
else
get_method_for_class model
end
[named_route, args]
end
def handle_model_call(target, model)
method, args = handle_model model
target.send(method, *args)
end
private
def get_method_for_class(klass)
name = @key_strategy.call klass.model_name
get_method_for_string name
end
def get_method_for_string(str)
"#{prefix}#{str}_#{suffix}"
end
And, if I haven't missed anything, this last method returns your URL path.
Upvotes: 1
Reputation: 239402
Rails uses your model's model_name
, that's it, and you shouldn't override that method. If you want to turn a Question
into a call to survey_question_path
, then you need to define your own question_path
which returns survey_question_path
...
def question_path(question)
survey_question_path(question)
end
... or just use the full method name yourself.
You can also use arrays of symbols/models, which get passed directly to url_for
:
= link_to 'Show question', [:survey, @question]
Upvotes: 2