[RUBY] I tried to understand how the rails method "link_to" is defined

I learned from an elite engineer in the workplace that reading a good code will be a catalyst for growth as an engineer, so I put it into practice immediately! I will share it by the way.

By learning good code, you will be able to write code that is highly readable and has few bugs, so please refer to it as well.

Start with link_to.

definition of link_to

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

From Ruby on Rails Official API, to name (when block = do ~ end is not used) The character string to be displayed, url information in options, and the value you want to set in the HTML a tag such as class and rel are passed in html_options.

It is assumed that options and html_options are passed in hash format.

Immediately, I will add interpretation line by line.

def link_to(name = nil, options = nil, html_options = nil, &block)

The description of = nil in the argument definition means that when no value is assigned, nil is assigned to the argument. & Block in the 4th argument is a description to receive "block". If you read this article, a block is used when passing a chunk of code, not a value as an argument ***. *** It seems that the chunk of code passed is called a block ***.

When writing link_to do ~ end, ~ is substituted here.

Next line

html_options, options, name = options, name, block if block_given?

block_given? (Ruby Reference Manual) From the reference manual, block_given? becomes true when the block is passed to the link_to method, and the contents of the if statement are executed. In other words, Multiple assignment (Ruby reference).

For example, when using block, from Ruby on Rails Official API

link_to(options = {}, html_options = {}) do
  # name
end

Will be described as In this case, options (url information) will be passed to name, which is originally the display name, and html_options, which defines class and id, will be passed to options, which is url information, from the first line of the link_to definition.

Therefore, the role of this line is to reassign as defined by the method so that it works as the method user intended. (For example, the value of name to which url information has been assigned is reassigned to options)

Next

options ||= {}

||=Is a description that the value of the right side is assigned when the left side is false or is not defined. In the first line, when no value is passed to the argument, nil is assigned to options, so the content of options is nil, that is, false. At this time, {} (empty hash) is assigned to optins.

html_options = convert_options_to_data_attributes(options, html_options)

A method called convert_options_to_data_attributes is called.

Reading the convert_options_to_data_attributes method

The definition of this method is link here

def convert_options_to_data_attributes(options, html_options)
  if html_options
    html_options = html_options.stringify_keys
    html_options["data-remote"] = "true".freeze if link_to_remote_options?(options) || link_to_remote_options?(html_options)

    method = html_options.delete("method".freeze)

    add_method_to_attributes!(html_options, method) if method

    html_options
  else
    link_to_remote_options?(options) ? { "data-remote" => "true".freeze } : {}
  end
end

Let's read convert_options_to_data_attributes.

If html_options exists, the third line will be executed.

html_options = html_options.stringify_keys

stringfy_keys is a rails method that converts a hash from symbol format to string format.

4th line

html_options["data-remote"] = "true".freeze if link_to_remote_options?(options) || link_to_remote_options?(html_options)

In short, if options or html_options has a setting of remote: true, true will be assigned to the key data-remote in string format.

You'll understand why this happens if you understand the contents of the rails method link_to_remote_options ?. I will explain in detail in the interpretation of the else statement, so please refer to that first.

5th line

method = html_options.delete("method".freeze)

Delete for hash deletes the key that matches the argument value → returns the deleted key value Masu The argument is method ".freeze. freeze does not delete the first function of delete, the second function. , That is, returns only the value of method.

Therefore, the value of the key method of html_options is assigned to the variable method. (HTML method setting value) This way of writing is interesting.

6th line

add_method_to_attributes!(html_options, method) if method

In addition, if the method value is set, call the add_method_to_attributes! Method. Since it has a! Mark, it is a destructive method.

add_method_to_attributes! method

The add_method_to_attributes! method is defined as follows.

def add_method_to_attributes!(html_options, method)
  if method_not_get_method?(method) && html_options["rel"] !~ /nofollow/
    if html_options["rel"].blank?
      html_options["rel"] = "nofollow"
    else
      html_options["rel"] = "#{html_options["rel"]} nofollow"
    end
  end
  html_options["data-method"] = method
end

Add_method_to_attributes! Method reading comprehension

add_method_to_attributes! Method 2nd line.

The conditional expression of if is an AND condition. One item of AND condition is the return of method_not_get_method? Method value.

The definition expression of this method is omitted, but as the name suggests, true is returned for methods other than get (such as delete). (It's the naming method that is also listed in the readable code!)

The second item of the AND condition is the third item in the! Section of the Ruby reference. True if the html_options key rel value is not nofollow.

add_method_to_attributes! method lines 3-7

Executed when the above-mentioned AND condition is satisfied.

if html_options["rel"].blank?
  html_options["rel"] = "nofollow"
else
  html_options["rel"] = "#{html_options["rel"]} nofollow"
end

Let's go at once. When the value of the key rel of html_options is empty, the character string nofollow is assigned. If the value of the key rel of html_options contains some value, add the string nofollow in addition to the set value.

When interpreted in combination with the if statement on the second line, rel = "nofollow" will be displayed regardless of whether the value is set in rel for methods other than get.

By the way, the meaning of nofollow is ["Exclude links from crawl"](https://www.seohacks.net/blog/column/1140/#:~:text=rel%3D%E2%80%9Dnofollow%E2 % 80% 9D% E3% 81% AE% E6% 84% 8F% E5% 91% B3,% E3% 81% AA% E3% 81% 8F% E3% 81% AA% E3% 82% 8B% E3% 81% A8% E3% 81% 84% E3% 81% 86% E3% 81% 93% E3% 81% A8% E3% 81% A7% E3% 81% 99% E3% 81% AD% E3% 80% 82) That's right.

I'm not sure why it's better to add nofollow to methods other than get, and I can only guess, so I'll omit it. For more information on how to use rel itself, see [here](https://saruwakakun.com/html-css/basic/link-rel#:~:text=%E2%86%91%20rel%E3%81%AF%E3 % 80% 8Crelation% EF% BC% 88% E9% 96% A2% E4% BF% 82,% E3% 81% A7% E7% A4% BA% E3% 81% 99% E3% 82% 8F% E3% 81% 91% E3% 81% A7% E3% 81% 99% E3% 81% AD% E3% 80% 82)

add_method_to_attributes! Method 8th line.

html_options["data-method"] = method

On the contrary, if it is specified as get method, it branches to else statement by the if statement on the second line. The above code will be executed.

The HTTP method type is assigned to the key "data-method" of html_options. Since it is "data-", it is defined as a custom attribute.

Finally, the interpretation of the add_method_to_attributes! method is complete.

Return to reading convert_options_to_data_attributes

(Repost)

def convert_options_to_data_attributes(options, html_options)
  if html_options
    html_options = html_options.stringify_keys
    html_options["data-remote"] = "true".freeze if link_to_remote_options?(options) || link_to_remote_options?(html_options)

    method = html_options.delete("method".freeze)

    add_method_to_attributes!(html_options, method) if method

    html_options
  else
    link_to_remote_options?(options) ? { "data-remote" => "true".freeze } : {}
  end
end

7th line.

Returns the hash html_options with the two values assigned on lines 4 and 6.

Lines 8-9

If no value is passed to html_options

link_to_remote_options?(options) ? { "data-remote" => "true".freeze } : {}

Is executed.

It's a ternary operator. First, link_to_remote_options? is called.

def link_to_remote_options?(options)
  if options.is_a?(Hash)
    options.delete("remote".freeze) || options.delete(:remote)
  end
end

link_to_remote_options? method

The contents of the if statement are executed when the value of options is a hash

However, even if remote is set, if remote: false is defined, the value of one item will be false. Two items are called there, and this time the remote setting is deleted because there is no description of freeze.

In short, link_to_remote_options? Returns false if OR false is not specified, and true if the value of remote is true.

Again, reading the else statement of the convert_options_to_data_attributes method

If the ternary operator returns true for one item, that is, remote: true is set, the symbol "data-remote" => true is the return value of convert_options_to_data_attributes.

The reason why the value of the key data-remote of html_options is set in any of the if statements in convert_options_to_data_attributes is that the custom attribute is set regardless of whether the remote setting value is passed to the argument of the link_to method, options or html_options. It seems that the value of remote is displayed in html.

The pattern of how to write the passing of the value to link_to is omitted here.

Return to link_to

(Repost)

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

Up to the 4th line, the value of html_options has been formatted.

Next is the 5th line.

url = url_for(options)

The url_for method is called.

def url_for(options)
    if options[:only_path]
      path_for options
    else
      full_url_for options
    end
end

path_for is a method that outputs a relative path, and full_url_for is a method that outputs an absolute path. Outputs the relative path when the value of the option key only_path is true.

6th line of link_to

html_options["href"] ||= url

Assign the generated path to the key href of html_options. What you can see from this is that you can forcibly specify the URL by using href as the key for the third factor of link_to.

Whether it's used or not ...

Last 7th line

content_tag("a", name || url, html_options, &block)

[content_tag is a view helper]. (https://apidock.com/rails/ActionView/Helpers/TagHelper/content_tag) Output html tag.

The first argument is the type of html tag, in this case .

The second argument is the character string to display. In this case, if name does not exist, the URL will be automatically displayed as a character string.

If you pass class: "XXX" etc. in hash format to html_options, it will be displayed as text .

Also, as you can see from the description & block, if a block is passed to link_to (if it is written in the format link_to ~ do ~ end), the block is passed to content_tag as it is.

This is the end of reading comprehension of link_to.

Rough flow

① Arrange the order of the assigned arguments ② Format values such as url and method ③ Display with content_tag

that's all.

Impressions I tried this time

** First point ** By knowing the method definition in the first place, it seems that you will be able to output as you expected. For example, if you use a view helper, you can reduce the effort of writing code by trial and error ⇔ building and outputting the desired display.

** Second point ** ||=Or&I had seen how to write a block, but I managed to write the code without it, so I didn't understand the meaning and it was a good opportunity.

For example||=In that case, it means that if the variable is undefined or nil, it is assigned, but if you write it without knowing it, you can simply express in one line where you would write complicatedly using an if statement.

As I mentioned at the beginning, by learning how to write good and smart code, you will be able to write code that is easy to maintain and error-free.


I will continue to learn in the future!

Recommended Posts

I tried to understand how the rails method "link_to" is defined
I tried to understand how the rails method "redirect_to" is defined
How to use the link_to method
[Rails] How to use the map method
[Rails] How to omit the display of the character string of the link_to method
[Rails] I tried to raise the Rails version from 5.0 to 5.2
I tried to organize the session in Rails
How to remove the underline displayed by Rails link_to
[Rails] I don't know how to use the model ...
I tried to introduce Bootstrap 4 to the Rails 6 app [for beginners]
[Rails] I tried using the button_to method for the first time
I want to expand the clickable part of the link_to method
How to use the include? method
[Rails] I tried deleting the application
I tried to understand nil guard
I tried to sort the data in descending order, ascending order / Rails
I tried to implement the image preview function with Rails / jQuery
[Java] I tried to make a maze by the digging method ♪
I examined the concept of the process to understand how Docker works
How to write the view when Vue is introduced in Rails?
I tried to summarize the methods used
I tried to introduce CircleCI 2.0 to Rails app
[Java] How to use the toString () method
How to have params in link_to method
I tried to implement the Iterator pattern
I tried to summarize the Stream API
What is Docker? I tried to summarize
Method definition location Summary of how to check When defined in the project and Rails / Gem
[Rails] How to use helper method, confimartion
[Introduction to Java] I tried to summarize the knowledge that I think is essential
[Rails] How to solve the time lag of created_at after save method
Output of how to use the slice method
The code I used to connect Rails 3 to PostgreSQL 10
How to use the replace () method (Java Silver)
I tried to set tomcat to run the Servlet.
[Ruby on Rails] How to use session method
How to check Rails commands in the terminal
[Ruby basics] How to use the slice method
[Rails / ActiveRecord] I want to validate the value before the type is converted (_before_type_cast)
[JavaScript] The strongest case when I tried to summarize the parts I do not understand
[Rails] How to operate the helper method used in the main application with Administrate
[JDBC ③] I tried to input from the main method using placeholders and arguments.
[Rails] How to connect to an external API using HTTP Client (I tried connecting to Qiita API)
How to set the display time to Japan time in Rails
I tried to organize the cases used in programming
[rails] How to use devise helper method before_action: authenticate_user!
I tried to summarize the state transition of docker
I tried to decorate the simple calendar a little
[Rails] I tried playing with the comment send button
[Ruby on Rails] How to change the column name
05. I tried to stub the source of Spring Boot
I tried to reduce the capacity of Spring Boot
[Rails] How to change the column name of the table
How to uninstall Rails
I tried to make FizzBuzz that is uselessly flexible
I tried to summarize various link_to used this time
Rails6 I tried to introduce Docker to an existing application
[Rails] I want to display the link destination of link_to in a separate tab
I want to call the main method using reflection
[Rails] How to get the contents of strong parameters
[Rough commentary] I want to marry the pluck method