Is it possible to override Rails path helper methods? [duplicate] - ruby-on-rails

This question already has answers here:
Override route helper methods
(2 answers)
Closed 8 years ago.
Current verbose rails path helpers
I'm constantly writing code to get URLs like:
link_to #applicant.name, company_job_applicant_path(#company, #job, #applicant)
However this code looks more like this (redundant) piece:
link_to #applicant.name, company_job_applicant_path(#applicant.job.company, #applicant.job, #applicant)
This is silly.
Required 'pert' path helpers
The other parameters can clearly be derived from the #job. All I should really need to type is:
link_to #applicant.name, applicant_quick_path #applicant
where there is a definition somewhere of:
def applicant_quick_path applicant
company_job_applicant_path(applicant.job.company, applicant.job, applicant)
end
My questions
Is this a reasonable Rails Way to do things
Where should I store this method?
I can currently access these helpers in the console using app.company_path. How would I access my new helper methods from the console?

Yes, DRY is the "Rails way" to do things. If you're repeating this method over and over again, it makes sense to create a view helper for it. Instead of modifying the path helpers, I'd simply wrap rails link_to method.
You can do something quick and easy like this:
# app/helpers/application_helper.rb
def link_to_applicant(applicant)
link_to applicant.name, company_job_applicant_path(applicant.job.company, applicant.job, applicant)
end
# link_to(#applicant)
#=> Peter Nixey
Alternatively, you can roll in some extra support for the link_to method
def link_to_applicant(applicant, html_options={})
link_to applicant.name, company_job_applicant_path(applicant.job.company, applicant.job, applicant), html_options
end
# link_to_applicant(#applicant, :id=>"applicant-#{#applicant.id}")
#=> <a id="applicant-123" href="companies/jobs/applicants/123">Peter Nixey</a>
If you want to fully support all the features provided by link_to, you can see how they permit for multiple function signatures here
# rails link_to source code
def link_to(*args, &block)
if block_given?
options = args.first || {}
html_options = args.second
link_to(capture(&block), options, html_options)
else
name = args[0]
options = args[1] || {}
html_options = args[2]
html_options = convert_options_to_data_attributes(options, html_options)
url = url_for(options)
href = html_options['href']
tag_options = tag_options(html_options)
href_attr = "href=\"#{html_escape(url)}\"" unless href
"<a #{href_attr}#{tag_options}>#{html_escape(name || url)}</a>".html_safe
end
end
RSpec notes
If you'd like to write tests for your view helpers in RSpec, follow this guide:
https://www.relishapp.com/rspec/rspec-rails/docs/helper-specs/helper-spec

You're describing a very typical Rails helper.
They go in app/helpers.
By default that directory will have an ApplicationHelper module, or you can add your own if you want to organize them differently.

Related

Wrong number of arguments after defining a path in rails

I manually define a path in a rails helper as I had some conflicts when using a slug for the url. It's been working for a while quite well but now I suddenly keep getting a "wrong number of arguments (1 for 0)" error for it.
In the articles helper:
def edit_article_path()
"/articles/#{#article.id}/edit"
end
Any help will be much appreciated.
You should not rely on the #article variable because your method is a helper, which can be accessed everywhere in your views, and some (most?) views might not have the #article variable set.
I recommend you to use this implementation instead:
def edit_article_path(article)
"/articles/#{article.try(:id) || article}/edit"
end
Use it like this:
# view
link_to article.title, edit_article_path(article)
# or
link_to article.title, edit_article_path(article.id)

To extend rails' `link_to`, should I use `alias_method_chain` or mixins + inheritance?

I'm creating an app using twitter bootstrap. I'm using Font Awesome to add icons to various places, very often links. So far I've been using a global helper. Here's the simplified version:
# app/helpers/link_to_with_icon.rb
def link_to_with_icon(text, path, options={})
options = options.clone
icon = options.delete(:icon)
text = "<i class='#{icon}'></i> #{text}" if icon.present?
link_to(text, path, options)
end
This worked, but I had to remember changing link_to to link_to_with_icon every time I needed to add an icon to a new link (the app is new, so that's kindof in constant flux). So I decided to stop using link_to completely, and replaced it with link_to_with_icon (since it is compatible).
But then I realized that since I'm not using link_to any more, I might as well modify link_to in the first place. Surely I could add some monkeypatching on the lib folder to make it understand the :icon option.
# lib/extensions/url_helper_extensions.rb
module ActionView
module Helpers
module UrlHelper
# do some magic here
end
end
end
I've done things similar to this before, a couple years ago. In that time alias_method_chain helper was the right tool for the job. In rails 3.x it seems to be deprecated in favor of modules and inheritance.
But if I'm understanding the examples in that page correctly, I would need the link_to method to be provided by some kind of Base module - otherwise you can't add a "Pre-Extension" to it.
Hence my question: Can I extend link_to using modules? Or must I use alias_method_chain?
In particular, a working implementation of the :icon option would be greatly appreciated.
I'd simply do:
# app/helpers/my_helper.rb
module MyHelper
def link_to(text, path, options={})
options = options.clone
icon = options.delete(:icon)
text = "<i class='#{icon}'></i> #{text}" if icon.present?
super(text, path, options)
end
end
But watch out if ever you use link_to with block.
I would either add this in a separate helper, or add it into ApplicationHelper
def link_to(text, path, options = {}, &block)
icon = options.delete(:icon)
text = content_tag(:i, text, :class => icon) if icon
super
end
And you don't want to clone the options Hash because you don't want the icon option to be sent to the original link_to method.

Ruby on Rails. link_to doesn't raise an exception

Why does Rails create the path to the current page in the href atribute of the anchor element instead of raising an exception if I pass to the link_to method an instance variable which isn't associated with any resource (and equals nil)?
Here's an example:
Routes
# app/config/routes.rb
Example::Application.routes.draw do
resource :example
end
HAML
-# app/views/examples/show.html.haml
%div
= link_to 'Non-existent resource', #ne_resource
HTML
<!-- http://localhost/example -->
<div>
Non-existent resource
Thanks.
Debian GNU/Linux 6.0.1;
Ruby 1.9.2;
Ruby on Rails 3.0.6.
If you take a look at the link_to method it links to the url using the url_for method.
def link_to(*args, &block)
if block_given?
options = args.first || {}
html_options = args.second
link_to(capture(&block), options, html_options)
else
name = args[0]
options = args[1] || {}
html_options = args[2]
html_options = convert_options_to_data_attributes(options, html_options)
url = url_for(options) #THIS GETS CALLED
href = html_options['href']
tag_options = tag_options(html_options)
href_attr = "href=\"#{html_escape(url)}\"" unless href
"<a #{href_attr}#{tag_options}>#{html_escape(name || url)}</a>".html_safe
end
end
URL for
def url_for(options = {})
options ||= {}
url = case options
when String
options
when Hash #THIS CASE IS TRUE
options = options.symbolize_keys.reverse_merge!(:only_path => options[:host].nil?)
super
when :back
controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
else
polymorphic_path(options)
end
url
end
From the above, you can see that url_for is valid without options or with a nilClass, it is not designed to raise an exception. If you want errors when using link_to, then make sure to use the dynamic "path" helper methods, in the above case, new_example_path.
I asked DHH (the creator of Rails) a closely related question about link_to's behavior when passed nil, nil arguments. I was hoping it would just not render an tag at all, instead of making me check for nils before calling it. He graciously replied:
https://twitter.com/dhh/status/198487578352156672
The essence of this answer applies to your question. It needs to do something when it is handed a nil. Gazler points out what's technically happening, but DHH's response shows a bit of the higher level "why?" The url_for method is fine with taking a nil argument, with the current page a sensible default.

Override route helper methods

Question has many Comments.
A URL "questions/123" shows a question.
A URL:
"questions/123#answer-345"
shows a question and highlights an answer. 345 - is id of Answer model, "answer-345" is id attribute of HTML element.
I need to override "answer_path(a)" method to get
"questions/123#answer-345"
instead of
"answers/345"
How to do it ?
All url and path helper methods accept optional arguments.
What you're looking for is the anchor argument:
question_path(123, :anchor => "answer-345")
It's documented in the URLHelper#link_to examples.
Using this argument, you should be able to create an answer_path helper via:
module ApplicationHelper
def answer_path(answer)
question_path(answer.question, :anchor => "answer-#{answer.id}")
end
end
Offering a solution which covers more areas (works not only in views but also in controller/console)
module CustomUrlHelper
def answer_path(answer, options = {})
options.merge!(anchor: "answer-#{answer.id}")
question_path(answer.question, options)
end
end
# Works at Rails 4.2.6, for earliers versions see http://stackoverflow.com/a/31957323/474597
Rails.application.routes.named_routes.url_helpers_module.send(:include, CustomUrlHelper)

Automatically append parameters to *_url or *_path methods (Rails)

I have a particular set of views relating to one of my controllers, whereby I want any call to *_path or *_url to append a set of parameters.
Is there some magic method I can override that will let me do this? I have no idea where in the Rails code the *_path or *_url methods are even handled.
Edit for clarity: I'm looking for a way to do this such that I don't have to modify every link in every view where this needs to occur. I don't want every coder who touches this set of views to have to remember to append a parameter to every link they add to the page. The same parameter should be appended automatically. I consider a change to the *_url or *_path call a failure. Similarly, having to override every *_url or *_path call is considered a failure since a new method would have to be added/removed whenever a new link is added/removed.
You can do this by overriding url_for since all the routing methods call it.
module ApplicationHelper
def url_for(options = {})
options.reverse_merge!(#extra_url_for_options) if #extra_url_for_options
super
end
end
Now all you need to do is use a before_filter to set #extra_url_for_options to a hash to force all urls.
class MyController < ApplicationController
before_filter do { #extra_url_for_options = { :format => 'html' } }
end
Note that this will force all links to use the extra options.
Thanks to Samuel's answer, I was able to create a final working solution via a new helper, which I've included below.
module ExampleHelper
def url_for(options={})
options = case options
when String
uri = Addressable::URI.new
uri.query_values = #hash_of_additional_params
options + (options.index('?').nil? ? '?' : '&') + uri.query
when Hash
options.reverse_merge(#hash_of_additional_params)
else
options
end
super
end
end
You can try to use the with_options method. In your view you can do something like
<% with_options :my_param => "my_value" do |append| -%>
<%= append.users_path(1) %>
<% end %>
Assuming you have the users_path of course. my_param=value will be appended to the url
You could make a helper method:
def my_path(p)
"#{p}_path all the parameters I want to append"
end
and in the view use
<%= eval(my_path(whatever)) %>
Eval with give you dynamic scope, so every variable available in your view can be used in the helper. If your parameters are constant you can get rid of eval calls.

Resources