Mini-
Source
Island

A source-code getaway.


link_to [Rails]

link_to seems pretty nice, though it takes some big leaps. It looks like the link text is the first argument, then... an object, or a string, or a hash, or a symbol... or a ball, a sandwich... can literally anything go here? Let's find out.

Eric Saxman | 'Andrelton' - 2015.
Find me on GitHub | LinkedIn.

Example:

  <% @photos.each do |photo| %>
     ...
     <%= link_to 'View Photo', photo %>
  <% end %>
        

link_to receives the desired link text as name, along with various other options.

url_for is used to build a url from the provided options, which in this example is a photo model object.

Later, content_tag will return the html element for the link using:
- an anchor tag
- the link text as provided above, or the url, as a default
- the html options (e.g. class, data), including the url generated above

rails/actionview/lib/action_view/helpers/url_helper.rb

def link_to(name = nil, options = nil, html_options = nil, &block)
  html_options, options, name = options, name, block if block_given?
  options ||= {}

  html_options = convert_options_to_data_attributes(options, html_options)

  url = url_for(options)
  html_options['href'] ||= url

  content_tag(:a, name || url, html_options, &block)
end
        

url_for is called in link_to regardless of the option type. Thus, if options is a string, it is assumed to be a url, and it is returned unchanged.

Otherwise, a url is built from the provided options. For example, if options is a model (as in the example, a common usage), handle_model_call is used to construct the url.

The controller instance (self) and the options are passed along as arguments.

rails/actionpack/lib/action_dispatch/routing/url_for.rb

def url_for(options = nil)
  case options
  when nil
    _routes.url_for(url_options.symbolize_keys)
  when Hash
    route_name = options.delete :use_route
    _routes.url_for(options.symbolize_keys.reverse_merge!(url_options),
                   route_name)
  when String
    options
  when Symbol
    HelperMethodBuilder.url.handle_string_call self, options
  when Array
    polymorphic_url(options, options.extract_options!)
  when Class
    HelperMethodBuilder.url.handle_class_call self, options
  else
    HelperMethodBuilder.url.handle_model_call self, options
  end
end
        

In the example above, it would be common to see routing methods like 'photo_path(photo)' in the app's views. With the help of the subsequent three methods, handle_model_call will send a method (photo_path) to the target (the photos controller) with the photo as the argument.

handle_model constructs that 'photo_path' string, which will be sent to the controller.

model_name, called on the photo object, allows get_method_for_string to construct 'photo_path'.

These methods are being called on an instance of HelperMethodBuilder. 'path' is the suffix held as an attribute on that instance.

Upon sending the photos controller the 'photo_path' method, handle_model_call will receive a string like '/photos/< photo's id >'. It passes this back to url_for, which returns it to link_to. link_to now has a url for use in content_tag's construction of the html element for the link.

rails/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb

def handle_model_call(target, model)
  method, args = handle_model model
  target.send(method, *args)
end

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 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
        

content_tag simply prepares the arguments to be sent along to content_tag_string.

rails/actionview/lib/action_view/helpers/tag_helper.rb

def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
  if block_given?
    options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
    content_tag_string(name, capture(&block), options, escape)
  else
    content_tag_string(name, content_or_options_with_block, options, escape)
  end
end
        

content_tag_string returns an html-element string, the final result of link_to.

def content_tag_string(name, content, options, escape = true)
   tag_options = tag_options(options, escape) if options
   content     = ERB::Util.unwrapped_html_escape(content) if escape
   "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}".html_safe
end
        

Result:

    "<a href="/photos/< id >">View Photo</a>"