Ruby on Rails. link_to doesn't raise an exception - ruby-on-rails

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.

Related

Rails+Slim: How to use link_to tag with controller & action name; and nested content in the anchor tag

This is what I have right now:
li
= link_to {:controller => "controllername", :action => "actionname"}
span.glyphicon.sidebar-icon.glyphicon-ok-circle
span WhoHas
However this won't work as link_to expects it's first argument to be the content inside the anchor tag. The contents I'd like inside the anchor tag are the following span blocks.
Any help would be much appreciated!
You said this:
However this won't work as link_to expects it's first argument to be the content inside the anchor tag.
This is not true. link_to can also accept a block instead, and it will use the content of the block instead:
link_to("/path/to/thing") do
"My link"
end
And, as such, does not require the first argument to be used as the text for the link. The documentation list the following signatures:
link_to(body, url, html_options = {})
# url is a String; you can use URL helpers like
# posts_path
link_to(body, url_options = {}, html_options = {})
# url_options, except :method, is passed to url_for
link_to(options = {}, html_options = {}) do
# name
end
link_to(url, html_options = {}) do
# name
end
The last two options are what you're looking for.
However, the real reason your code doesn't work is because you're using braces without parentheses - basically, method { :hello => "world" } doesn't do what you think it does. the method { } syntax is reserved for blocks, so it's expecting the contents of a block - which it doesn't find. Rather, it finds a key-value association, which is reserved for hashes. Essentially, Ruby tries to interpret it as a block, but fails. So your options are to either encapsulate it with parentheses, like so: method({ :hello => "world" }), or drop the braces, like so: method :hello => "world". You'll also need to add a do at the end of your link_to to have the content be a block, so your code looks like this now:
li
= link_to :controller => "controllername", :action => "actionname" do
span.glyphicon.sidebar-icon.glyphicon-ok-circle
span WhoHas
Hope that helps.

Passing in options param for content_for method

I found that you can flush content_for looking at the Rails source. https://github.com/rails/rails/blob/master/actionpack/lib/action_view/helpers/capture_helper.rb
Rails Source:
def content_for(name, content = nil, options = {}, &block)
if content || block_given?
if block_given?
options = content if content
content = capture(&block)
end
if content
options[:flush] ? #view_flow.set(name, content) : #view_flow.append(name, content)
end
nil
else
#view_flow.get(name)
end
end
I am trying to set options[:flush] = true, but am having some trouble. The options[:flush] is not evaluating to true in my code below.
My code:
content_for(affiliate_link_pos.to_sym, {:flush=>true}) do
render page
end
Edit:
I have also tried passing a 3rd params (content), but I get wrong number of argument error (3 for 2).
content_for(affiliate_link_pos.to_sym, "true", {:flush=>true}) do
Try this:
content_for(affiliate_link_pos.to_sym, null, :flush=>true) do
render page
end
Looks like the source quoted in the OP's question is actually from Rails 4. Even in Rails 3.2.14, content_for does not accept any options.
content_for(name, content = nil, &block)
Calling #content_for stores a block of markup in an identifier for later use. You can make subsequent calls to the stored content in other templates, helper modules or the layout by passing the identifier as an argument to content_for.

link/button formatting for a post method in rails 3.2.4+

this code is throwing an error during the page render because it isn't finding the route for this link:
<li class="link">
<%=link_to "Save current options to a cluster", division_clusters_path(#current_dvision, courses: #cluster_courses ), :method => :post unless #cluster_courses.empty? %>
</li>
It worked in an older 3.2 copy, but upon upgrading past 3.2.4 it starts throwing an error because it is looking for a GET route when only a POST route exists (it is meant to go to a standard create method).
I switched it from a link_to to a button_to and tried to explicitly hash out the URL options from the HTML options to prevent ambiguity but I am still screwing something up.
I am assuming if the button_to is bombing, that I am doing something incredibly wrong. I am just not sure what.
The fact that it's falling back to a GET request suggests that the :method => :post is not making its way into the options argument:
# File actionpack/lib/action_view/helpers/url_helper.rb, line 231
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]
.....
end
end
Since the precedence of the unless operator is greater than method invocation, that leaves division_clusters_path(#current_dvision, courses: #cluster_courses ) as a possible culprit. If that is returning something that is swallowed by args[1], then args[2] would get the rest of the options (which are meant for args[1]). By the way, it looks like there is a typo there: #current_dvision.

Is it possible to override Rails path helper methods? [duplicate]

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.

Easy breadcrumbs for RESTful rails application

Is there any helper method (Other than default rails breadcrumb) that generates bread crumb navigation dynamically for a particular page without having to pass trivial parameters in RESTful application? That is, something that figures out automatically where the user is based on the REST url she is visiting?
For above mentioned implementation, we need to pass parameters like
REST
<% add_crumb(‘Profile’, user_profile_path) %>
Current page
<% add_crumb(“My Incoming Messages”, request.path) %>
There must be a way to generalize the code so that no parameter passing is required and should work for all RESTful apps with minimal configuration.
Developed a simple hack. The method however assumes that there exists a method 'name' for every model object corresponding to each resource in the RESTful url. Whatever that the method 'name' returns is shown as breadcrumb name. If it is not found, it is shown as it is without making it link to anything. Separator used is '->' You may change it to suit your requirement.
def get_bread_crumb(url)
begin
breadcrumb = ''
sofar = '/'
elements = url.split('/')
for i in 1...elements.size
sofar += elements[i] + '/'
if i%2 == 0
begin
breadcrumb += "<a href='#{sofar}'>" + eval("#{elements[i - 1].singularize.camelize}.find(#{elements[i]}).name").to_s + '</a>'
rescue
breadcrumb += elements[i]
end
else
breadcrumb += "<a href='#{sofar}'>#{elements[i].pluralize}</a>"
end
breadcrumb += ' -> ' if i != elements.size - 1
end
breadcrumb
rescue
'Not available'
end
end
The method generally accepts request.url (Which given url of the current page) as the parameter. The method purposefully accepts the url for customization purposes. To generate the breadcrumb, simply add following code in your view -
<%= get_bread_crumb(request.url) %>
For the url /ideabox/2/idea/1, the bread crumb looks like
alt text http://www.imagechicken.com/uploads/1234855404069992300.png
Excuse me if code quality is not that great. I'm sure this code can be re-factored but I'm also sure you would be able to do that before using it.
Thanks.
The solution provided by chirantan is great. If you need breabcrumbs for namespaced controller and need to change the breadcrumbs depending on the namespace as well then try this. This is not perfect but refactor it as you need. It works for my project.
Define a new helper: navigation_helper.rb
module NavigationHelper
def navigation_add(title, url, namespace)
if defined? ##namespace and !##namespace.nil? and ##namespace == namespace
##navigation ||= []
else
##navigation = []
end
##navigation << {title: title, url: url} unless title == "Home"
new_nav = []
##navigation.each do |hash|
new_nav.push hash
if hash[:title].to_s == title.to_s
break
end
end
##navigation = new_nav
##navigation.uniq!
##namespace = namespace
end
def render_navigation
if (request.path_parameters[:controller].sub('/', '::_').camelize + 'Controller').classify.constantize.action_methods.to_a.include? 'index'
navigation_add controller_name.camelize.to_s, request.path_parameters.merge({action: 'index'}).except(:id), params[:controller].include?('/') ? params[:controller].split("/").first : nil
end
if defined? ##navigation
render partial: 'navigation/navigation', locals: { navs: ##navigation, namespace: ##namespace }
else
render text: ''
end
end
end
Then define a view for this helper _navigation.haml
- unless navs.blank?
%ol.breadcrumb
- navs.each_with_index do |nav, index|
- if index == 0
%li=link_to fa_icon('arrow-left', text: 'Go Back'), :back
- unless namespace.nil?
%li
%h4.inline= request.fullpath.split('/')[1].gsub('-', '_').camelize
= fa_icon('angle-double-right')
%li= link_to_unless (nav[:title] == controller_name.camelize and action_name == 'index'), fa_icon(nav[:title].downcase.singularize, text: nav[:title]), nav[:url]

Resources