A source-code getaway.
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}#{name}>".html_safe
end
Result: "<a href="/photos/< id >">View Photo</a>"