This is the second project to learn how to write ruby by interpreting the definition of rails method. The theme this time is redirect_to
# File actionpack/lib/action_controller/metal/redirecting.rb, line 56
def redirect_to(options = {}, response_status = {})
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(request, options)
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
end
def redirect_to(options = {}, response_status = {})
Takes two arguments. In both cases, an empty hash is assigned if no arguments are passed.
raise ActionControllerError.new("Cannot redirect to nil!") unless options
Raise error when opsions are empty. By the way, ruby treats everything except false and nil as true, so an empty hash {} will not cause an error. An error will occur if the user dares to assign nil / false.
raise AbstractController::DoubleRenderError if response_body
An error will occur if you have already received an HTTP response. I don't know how response_body is assigned here, but it seems to return an error when multiple HTTP communications are exchanged at the same time.
self.status = _extract_redirect_to_status(options, response_status)
A new method has come out this time as well.
#Fileactionpack/lib/action_controller/metal/redirecting.rb, line 117
def _extract_redirect_to_status(options, response_status)
if options.is_a?(Hash) && options.key?(:status)
Rack::Utils.status_code(options.delete(:status))
elsif response_status.key?(:status)
Rack::Utils.status_code(response_status[:status])
else
302
end
end
if options.is_a?(Hash) && options.key?(:status)
One item of if condition is_a? Method is the type of the receiver (here "options") written in parentheses. A method that returns true if it matches (here "hash").
The two items key? of the if condition are the keys whose names are written in parentheses. A method that returns true if it has.
In other words, if the argument passed to redirect_to is a hash and has the key status, the contents of the if statement will be executed. As you can see in the Rails docs mentioned at the beginning
redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
Status is a parameter set by the programmer who uses the redirect_to method. As you read, you can see what role the parameters play.
Rack::Utils.status_code(options.delete(:status))
Rack is a group of programs that act as an intermediary between Rails applications and application servers. (Called middleware) status_code is a method defined in a library called Utils.
The value assigned to the key status of options by delete method is passed to the status_code method.
def status_code(status)
if status.is_a?(Symbol)
SYMBOL_TO_STATUS_CODE[status] || 500
else
status.to_i
end
end
This method is just a method to display the value of status. It is called SYMBOL_TO_STATUS_CODE only when it is passed by Symbol (determined by is_a on the second line). The conversion hash defined in Rack :: Utils is used.
The definition itself
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
[message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
}.flatten]
It looks like this. The hash HTTP_STATUS_CODES also defined by this code in Rack :: Utils is {: HTTP status name Convert to a hash array called => HTTP Status Code}.
In particular
pry(main)> Rack::Utils::SYMBOL_TO_STATUS_CODE
=> {:continue=>100,
:switching_protocols=>101,
:processing=>102,
:early_hints=>103,
:ok=>200,
:created=>201,
:accepted=>202,
:non_authoritative_information=>203,
:no_content=>204,
:reset_content=>205,
:partial_content=>206,
:multi_status=>207,
:already_reported=>208,
:im_used=>226,
:multiple_choices=>300,
:moved_permanently=>301,
:found=>302,
:see_other=>303,
:not_modified=>304,
:use_proxy=>305,
:"(unused)"=>306,
:temporary_redirect=>307,
:permanent_redirect=>308,
:bad_request=>400,
:unauthorized=>401,
:payment_required=>402,
:forbidden=>403,
:not_found=>404,
:method_not_allowed=>405,
:not_acceptable=>406,
:proxy_authentication_required=>407,
:request_timeout=>408,
:conflict=>409,
:gone=>410,
:length_required=>411,
:precondition_failed=>412,
:payload_too_large=>413,
:uri_too_long=>414,
:unsupported_media_type=>415,
:range_not_satisfiable=>416,
:expectation_failed=>417,
:misdirected_request=>421,
:unprocessable_entity=>422,
:locked=>423,
:failed_dependency=>424,
:too_early=>425,
:upgrade_required=>426,
:precondition_required=>428,
:too_many_requests=>429,
:request_header_fields_too_large=>431,
:unavailable_for_legal_reasons=>451,
:internal_server_error=>500,
:not_implemented=>501,
:bad_gateway=>502,
:service_unavailable=>503,
:gateway_timeout=>504,
:http_version_not_supported=>505,
:variant_also_negotiates=>506,
:insufficient_storage=>507,
:loop_detected=>508,
:bandwidth_limit_exceeded=>509,
:not_extended=>510,
:network_authentication_required=>511}
#Run on the Rails console.
In other words, when you return to the definition of status_code
SYMBOL_TO_STATUS_CODE[status] || 500
When status:: not_found is set in, "404" is returned. If there is no corresponding item, "500" is returned.
If a value such as "404" is displayed in options [: status] instead of symbol, the 4th to 5th lines will return the value as it is in integer format.
else
status.to_i
end
(Repost)
#Fileactionpack/lib/action_controller/metal/redirecting.rb, line 117
def _extract_redirect_to_status(options, response_status)
if options.is_a?(Hash) && options.key?(:status)
Rack::Utils.status_code(options.delete(:status))
elsif response_status.key?(:status)
Rack::Utils.status_code(response_status[:status])
else
302
end
end
So far, I've read what I'm doing on the third line.
elsif response_status.key?(:status)
The key? method makes this conditional statement true when the argument response_status has a key status. The method to be executed is the same as before.
It seems to be an if branch to allow the HTTP status to be returned correctly regardless of whether the status value is passed in options or response_status.
If the HTTP status to be returned is not passed The else statement on lines 6-7 returns 302. (It means "redirect processing".)
redirect_to (Repost)
# File actionpack/lib/action_controller/metal/redirecting.rb, line 56
def redirect_to(options = {}, response_status = {})
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(request, options)
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
end
In the 4th line, I decided the HTTP status code to be returned to the user side.
self.location = _compute_redirect_to_location(request, options)
A new rails method has arrived. The request assigned here is the object that is created every time on the controller. Each parameter of the HTTP request sent from the user side to the rails application side is stored.
What kind of parameters are there? [Rails Guide](https://railsguides.jp/action_controller_overview.html#request%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3 % 83% 88% E3% 81% A8response% E3% 82% AA% E3% 83% 96% E3% 82% B8% E3% 82% A7% E3% 82% AF% E3% 83% 88) I am.
You can retrieve the value like request.host.
The definition itself is made on the ActionDispatch :: Request model. The methods available for this model are listed here (https://api.rubyonrails.org/classes/ActionDispatch/Request.html).
# File actionpack/lib/action_controller/metal/redirecting.rb, line 96
def _compute_redirect_to_location(request, options) #:nodoc:
case options
# The scheme name consist of a letter followed by any combination of
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
# characters; and is terminated by a colon (":").
# See https://tools.ietf.org/html/rfc3986#section-3.1
# The protocol relative scheme starts with a double slash "//".
when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/
options
when String
request.protocol + request.host_with_port + options
when Proc
_compute_redirect_to_location request, instance_eval(&options)
else
url_for(options)
end.delete("\00\\r\n")
end
This method will generate the redirect URL. Conditional branching by case statement is done by the contents of opsions.
when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/
options
http://のように先頭に○○://とつく文字列のときに、この条件がtrueとなります。 Since it is judged that it is in the body of the URL, the value of options is returned as it is.
when String
request.protocol + request.host_with_port + options
Called when options is a string. The URL is generated based on the request object that stores the request information from the user side introduced earlier. http: // and https: // are stored in the protocol. host_with_port will generate hosts after //. Finally, options are given.
The host_with_port method is a simple method that calls the properties = host and port_string stored in the request object and combines them as a string.
# File actionpack/lib/action_dispatch/http/url.rb, line 250
def host_with_port
"#{host}#{port_string}"
end
when Proc
_compute_redirect_to_location request, instance_eval(&options)
What is Proc
A procedural object that objectifies a block with a context (local variable scope or stack frame). (https://docs.ruby-lang.org/ja/latest/class/Proc.html)[https://docs.ruby-lang.org/ja/latest/class/Proc.html]
redirect_to can be used in block format, and it is OK to recognize that it will enter this branch when a block is given.
For example
get 'jokes/:number', to: redirect { |params, request|
path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp")
"http://#{request.host_with_port}/#{path}"
}
You can write something like this.
When the block is called, the _compute_redirect_to_location method is called recursively. For beginners, recursion is the calling of the same method within a method. For example
def method
method
end
It is shaped like. (In this case, it will be an infinite loop. Normally, it is written so that it does not loop infinitely with if branch etc.)
(Repost)
\_compute_redirect_to_location request, instance_eval(&options)
instance_eval (& options) is assigned to the second term of the \ _compute_redirect_to_location (request, options) method.
There is a & in front of options, which means to assign a block. instance_eval is a method that returns the output when the code in the block is executed.
Let's consider the previous example (Repost)
get 'jokes/:number', to: redirect { |params, request|
path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp")
"http://#{request.host_with_port}/#{path}"
}
In this case, the code on the 2nd and 3rd lines will be executed, and as a result, the URL starting with http: // on the 3rd line will be returned as a string. In other words The URL is assigned to the \ _compute_redirect_to_location method. The case statement is called and branches to the conditions on the 9th to 10th lines mentioned above. (Repost)
when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/
options
else
url_for(options)
To summarize the conditions so far http://~といった具体的なURL、ファイル名を指す文字列、ブロックについて分岐されてきたが、いずれにも該当しない場合はこのelse文の内容が実行されることになります。
What comes out here is the url_for method that was also used in link_to.
def url_for(options)
if options[:only_path]
path_for options
else
full_url_for options
end
end
url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :port => '8080'
# => 'http://somehost.org:8080/tasks/testing'
url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :anchor => 'ok', :only_path => true
# => '/tasks/testing#ok'
url_for :controller => 'tasks', :action => 'testing', :trailing_slash => true
# => 'http://somehost.org/tasks/testing/'
url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :number => '33'
# => 'http://somehost.org/tasks/testing?number=33'
(Quote) Generate a URL like this.
end.delete("\00\\r\n")
Delete is called at the end of the case statement.
Since it is called for a character string, the behavior is different from the delete that has appeared so far. According to the Ruby reference
delete(*strs) -> String Generates and returns a string with the characters contained in strs removed.
The reference also includes a reference article for the strs format. I'm not sure because it's less important and I don't understand it, Since \ r and \ n are special characters that represent "return" and "line feed", respectively, it is presumed that they have the meaning of deleting these characters that are mixed in the url. Then I don't know what \ 00 \ is, and maybe I can separate it as \ 0, 0, \, r, \ n.
So far, the \ _compute_redirect_to_location method.
redirect_to (Repost)
# File actionpack/lib/action_controller/metal/redirecting.rb, line 56
def redirect_to(options = {}, response_status = {})
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(request, options)
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
end
The fourth line determines the HTTP status code to return to the user after the redirect. The URL of the redirect destination was decided on the 5th line.
6th line
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
Now, decide the html data to be returned to the user side. Actually, the redirect process is performed based on the value of location, so this html will not be displayed on the user browser.
Here, the value stored in self. ~ Is passed to the browser on the user side as an HTTP response. Looking at the URL of the location property (attribute) in the HTTP header, the user's browser makes the request again.
See here for the browser processing mechanism.
(Repost)
# File actionpack/lib/action_controller/metal/redirecting.rb, line 56
def redirect_to(options = {}, response_status = {})
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(request, options)
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
end
When redirect_to is called
I found that it is the role of generating the response data necessary for that, assuming that the actual processing is left to the client (browser used by the user) rather than performing the processing in this method.
If you deepen your understanding of the processing performed by Rails applications and middleware, it seems that you will be able to implement more complicated processing.