- Notifications
You must be signed in to change notification settings - Fork0
Solving LeetCode problems in the best way. Python, Java, C++, JavaScript, Go, C# and Ruby are supported! Official website👇🏻:
upcse/leetcode-python-java
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
- I suggest you learn Rackhttp://rack.github.io/ first.
You need to know that an object respond tocall
method is the most important convention.
So which is the object withcall
method in Rails App? I will answer this question in Part 1.
- You need a good IDE with debugging function. I useRubyMine.
How rails start your application?
How rails process every request?
How rails combine ActionController, ActionView and Routes?
I should start with the command$ rails server
. But I put this to Part 4. Because it's not interesting.
First, I will give you a piece of important code.
# ./gems/railties-5.2.2/lib/rails/commands/server/server_command.rbmoduleRailsmoduleCommandclassServerCommand <Basedefperform# ...Rails::Server.new(server_options).tapdo |server|# APP_PATH is '/Users/your_name/your-project/config/application'.# require APP_PATH will create the 'Rails.application' object.# 'Rails.application' is 'YourProject::Application.new'.# Rack server will start 'Rails.application'.requireAPP_PATHDir.chdir(Rails.application.root)server.startendendendendclassServer < ::Rack::Serverdefstart#...# 'wrapped_app' is invoked in method 'log_to_stdout'.# It will get an well prepared app from './config.ru' file.# It will use the app created at the 'perform' method in Rails::Command::ServerCommand.wrapped_appsuper# Will invoke ::Rack::Server#start.endendend
A rack server need to start with an App. The App should have acall
method.
config.ru
is the conventional entry file for rack app. So let's view it.
# ./config.rurequire_relative'config/environment'runRails.application# It seems that this is the app.
Let's test it byRails.application.respond_to?(:call) # Returned 'true'
.
Let's step intoRails.application
.
# ./gems/railties-5.2.2/lib/rails.rbmoduleRailsclass <<self@application=@app_class=nilattr_accessor:app_class# Oh, 'application' is a class method for module 'Rails'. It is not an object.# But it returns an object which is an instance of 'app_class'.# So it is important for us to know what class 'app_class' is.defapplication@application ||=(app_class.instanceifapp_class)endendend
BecauseRails.application.respond_to?(:call) # Returned 'true'.
,app_class.instance
has acall
method.
When wasapp_class
set?
moduleRailsclassApplication <Engineclass <<selfdefinherited(base)# This is a hooked method.Rails.app_class=base# This line set the 'app_class'.endendendend
Rails::Application
is inherited like below,
# ./config/application.rbmoduleYourProject# The hooked method `inherited` defined in eigenclass of 'Rails::Application' is invoked.classApplication <Rails::Applicationendend
YourProject::Application
will become theRails.app_class
.
You may have a question: how we reach this file (./config/application.rb
)?
Let's look back toconfig.ru
to see the first line of this filerequire_relative 'config/environment'
.
# ./config/environment.rb# Load the Rails application.require_relative'application'# Let's step into this line.# Initialize the Rails application.Rails.application.initialize!
# ./config/application.rbrequire_relative'boot'require'rails/all'# Require the gems listed in Gemfile, including any gems# you've limited to :test, :development, or :production.Bundler.require(*Rails.groups)moduleYourProject# The hooked method `inherited` defined in eigenclass of 'Rails::Application' is invoked.classApplication <Rails::Applicationconfig.load_defaults5.2config.i18n.default_locale=:zhendend
Let's replaceapp_class.instance
toYourProject::Application.instance
.
But where is thecall
method?call
method should be a method ofYourProject::Application.instance
.
Thecall
method processes every request. Here it is.
# ./gems/railties/lib/rails/engine.rbmoduleRailsclassEngine <Railtiedefcall(env)# This method will process every request. It is invoked by Rack. So it is very important.req=build_requestenvapp.callreq.env# The 'app' object we will discuss later.endendend# ./gems/railties/lib/rails/application.rbmoduleRailsclassApplication <Engineendend# ./config/application.rbmoduleYourProjectclassApplication <Rails::Applicationendend
Ancestor's chain isYourProject::Application < Rails::Application < Rails::Engine < Rails::Railtie
.
SoYourProject::Application.new.respond_to?(:call) # Will return 'true'
.
But what doesapp_class.instance
really do?
instance
is just a method name. What we really need isapp_class.new
.
Let's look at the definition of instance.
# ./gems/railties/lib/rails/application.rbmoduleRailsclassApplication <Enginedefinstancesuper.run_load_hooks!# This line confused me.endendend
After a deep research, I realized that this code is equal to
definstancereturn_value=super# Keyword 'super' will call the ancestor's same name method: 'instance'.return_value.run_load_hooks!end
# ./gems/railties/lib/rails/railtie.rbmoduleRailsclassRailtiedefinstance# 'Rails::Railtie' is the top ancestor.# Now 'app_class.instance' is 'YourProject::Application.new'.@instance ||=newendendend
AndYourProject::Application.new
isRails.application
.
moduleRailsdefapplication@application ||=(app_class.instanceifapp_class)endend
Rack server will startRails.application
in the end.
It is the most important object in the whole Rails object.
And you'll only have oneRails.application
in one process. Multiple thread shared only oneRails.application
.
We first time see the config is in./config/application.rb
.
# ./config/application.rb#...moduleYourProjectclassApplication <Rails::Application# Actually, config is a method of YourProject::Application.# It is defined in it's grandfather's father: Rails::Railtieconfig.load_defaults5.2# Let's go to see what is configconfig.i18n.default_locale=:zhendend
moduleRailsclassRailtieclass <<selfdelegate:config,to::instance# Method :config is defined here.definstance@instance ||=new# return an instance of YourProject::Application.endendendclassEngine <RailtieendclassApplication <Engineclass <<selfdefinstance# This line is equal to:# return_value = super # 'super' will call :instance method in Railtie, which will return an instance of YourProject::Application.# return_value.run_load_hooks!super.run_load_hooks!endenddefrun_load_hooks!returnselfif@ran_load_hooks@ran_load_hooks=true# ...self# return self! self is an instance of YourProject::Application. And it is Rails.application.end# This is the method config.defconfig# It is an instance of class Rails::Application::Configuration.# Please notice that Rails::Application is father of YourProject::Application (self's class).@config ||=Application::Configuration.new(self.class.find_root(self.class.called_from))endendend
In the end,YourProject::Application.config
will becomeRails.application.config
.
YourProject::Application.config === Rails.application.config # return ture.
Invoke Class's 'config' method become invoke the class's instance's 'config' method.
moduleRailsclass <<selfdefconfigurationapplication.configendendend
SoRails.configuration === Rails.application.config # return ture.
.
moduleRailsclassApplicationclassConfiguration < ::Rails::Engine::ConfigurationendendclassEngineclassConfiguration < ::Rails::Railtie::Configurationattr_accessor:middlewaredefinitialize(root=nil)super()#...@middleware=Rails::Configuration::MiddlewareStackProxy.newendendendclassRailtieclassConfigurationendendend
Imagine we have this route for the home page.
# ./config/routes.rbRails.application.routes.drawdoroot'home#index'# HomeController#indexend
Rack need acall
method to process request.
Rails provide this call method inRails::Engine#call
.
# ./gems/railties/lib/rails/engine.rbmoduleRailsclassEngine <Railtiedefcall(env)# This method will process every request. It is invoked by Rack.req=build_requestenvapp.callreq.env# The 'app' method is blow.enddefapp# You may want to know when does the @app first time initialized.# It is initialized when 'config.ru' is load by rack server.# Please look at Rack::Server#build_app_and_options_from_config for more information.# When Rails.application.initialize! (in ./config/environment.rb), @app is initialized.@app ||@app_build_lock.synchronize{# '@app_build_lock = Mutex.new', so multiple threads share one '@app'.@app ||=begin# In the end, config.middleware will be an instance of ActionDispatch::MiddlewareStack with preset instance variable @middlewares (which is an Array).stack=default_middleware_stack# Let's step into this line# 'middleware' is a 'middleware_stack'!config.middleware=build_middleware.merge_into(stack)config.middleware.build(endpoint)# look at this endpoint belowend}#@app is #<Rack::Sendfile:0x00007ff14d905f60# @app=#<ActionDispatch::Static:0x00007ff14d906168# @app=#<ActionDispatch::Executor:0x00007ff14d9061b8# ...# @app=#<Rack::ETag:0x00007fa1e540c4f8# @app=#<Rack::TempfileReaper:0x00007fa1e540c520# @app=#<ActionDispatch::Routing::RouteSet:0x00007fa1e594cbe8># ># ...# >## ># >@append# Defaults to an ActionDispatch::Routing::RouteSet.defendpointActionDispatch::Routing::RouteSet.new_with_config(config)endendclassApplication <Enginedefdefault_middleware_stackdefault_stack=DefaultMiddlewareStack.new(self,config,paths)default_stack.build_stack# Let's step into this line.endclassDefaultMiddlewareStackattr_reader:config,:paths,:appdefinitialize(app,config,paths)@app=app@config=config@paths=pathsenddefbuild_stackActionDispatch::MiddlewareStack.newdo |middleware|ifconfig.force_sslmiddleware.use ::ActionDispatch::SSL,config.ssl_optionsendmiddleware.use ::Rack::Sendfile,config.action_dispatch.x_sendfile_headerifconfig.public_file_server.enabledheaders=config.public_file_server.headers ||{}middleware.use ::ActionDispatch::Static,paths["public"].first,index:config.public_file_server.index_name,headers:headersendifrack_cache=load_rack_cacherequire"action_dispatch/http/rack_cache"middleware.use ::Rack::Cache,rack_cacheendifconfig.allow_concurrency ==false# User has explicitly opted out of concurrent request# handling: presumably their code is not threadsafemiddleware.use ::Rack::Lockendmiddleware.use ::ActionDispatch::Executor,app.executormiddleware.use ::Rack::Runtimemiddleware.use ::Rack::MethodOverrideunlessconfig.api_onlymiddleware.use ::ActionDispatch::RequestIdmiddleware.use ::ActionDispatch::RemoteIp,config.action_dispatch.ip_spoofing_check,config.action_dispatch.trusted_proxiesmiddleware.use ::Rails::Rack::Logger,config.log_tagsmiddleware.use ::ActionDispatch::ShowExceptions,show_exceptions_appmiddleware.use ::ActionDispatch::DebugExceptions,app,config.debug_exception_response_formatunlessconfig.cache_classesmiddleware.use ::ActionDispatch::Reloader,app.reloaderendmiddleware.use ::ActionDispatch::Callbacksmiddleware.use ::ActionDispatch::Cookiesunlessconfig.api_onlyif !config.api_only &&config.session_storeifconfig.force_ssl &&config.ssl_options.fetch(:secure_cookies,true) && !config.session_options.key?(:secure)config.session_options[:secure]=trueendmiddleware.useconfig.session_store,config.session_optionsmiddleware.use ::ActionDispatch::Flashendunlessconfig.api_onlymiddleware.use ::ActionDispatch::ContentSecurityPolicy::Middlewareendmiddleware.use ::Rack::Headmiddleware.use ::Rack::ConditionalGetmiddleware.use ::Rack::ETag,"no-cache"middleware.use ::Rack::TempfileReaperunlessconfig.api_onlyendendendendend
As we see in the Rack middleware stack, the last one is
@app=#<ActionDispatch::Routing::RouteSet:0x00007fa1e594cbe8>
# ./gems/actionpack5.2.2/lib/action_dispatch/routing/route_set.rbmoduleActionDispatchmoduleRoutingclassRouteSetdefinitialize(config=DEFAULT_CONFIG)@set=Journey::Routes.new@router=Journey::Router.new(@set)enddefcall(env)req=make_request(env)# return ActionDispatch::Request.new(env)req.path_info=Journey::Router::Utils.normalize_path(req.path_info)@router.serve(req)# Let's step into this line.endendend# ./gems/actionpack5.2.2/lib/action_dispatch/journey/router.rbmoduleJourneyclassRouterclassRoutingError < ::StandardErrorendattr_accessor:routesdefinitialize(routes)@routes=routesenddefserve(req)find_routes(req).eachdo |match,parameters,route|# Let's step into 'find_routes'set_params=req.path_parameterspath_info=req.path_infoscript_name=req.script_nameunlessroute.path.anchoredreq.script_name=(script_name.to_s +match.to_s).chomp("/")req.path_info=match.post_matchreq.path_info="/" +req.path_infounlessreq.path_info.start_with?"/"endparameters=route.defaults.mergeparameters.transform_values{ |val|val.dup.force_encoding(::Encoding::UTF_8)}req.path_parameters=set_params.mergeparameters# route is an instance of ActionDispatch::Journey::Route.# route.app is an instance of ActionDispatch::Routing::RouteSet::Dispatcher.status,headers,body=route.app.serve(req)# Let's step into method 'serve'if"pass" ==headers["X-Cascade"]req.script_name=script_namereq.path_info=path_inforeq.path_parameters=set_paramsnextendreturn[status,headers,body]end[404,{"X-Cascade"=>"pass"},["Not Found"]]enddeffind_routes(req)routes=filter_routes(req.path_info).concatcustom_routes.find_all{ |r|r.path.match(req.path_info)}routes=ifreq.head?match_head_routes(routes,req)elsematch_routes(routes,req)endroutes.sort_by!(&:precedence)routes.map!{ |r|match_data=r.path.match(req.path_info)path_parameters={}match_data.names.zip(match_data.captures){ |name,val|path_parameters[name.to_sym]=Utils.unescape_uri(val)ifval}[match_data,path_parameters,r]}endendendend# ./gems/actionpack5.2.2/lib/action_dispatch/routing/route_set.rbmoduleActionDispatchmoduleRoutingclassRouteSetclassDispatcher <Routing::Endpointdefserve(req)params=req.path_parameters# params: { action: 'index', controller: 'home' }controller=controller(req)# controller: HomeController# The definition of make_response! is ActionDispatch::Response.create.tap do |res| res.request = request; endres=controller.make_response!(req)dispatch(controller,params[:action],req,res)# Let's step into this line.rescueActionController::RoutingErrorif@raise_on_name_errorraiseelsereturn[404,{"X-Cascade"=>"pass"},[]]endendprivatedefcontroller(req)req.controller_classrescueNameError=>eraiseActionController::RoutingError,e.message,e.backtraceenddefdispatch(controller,action,req,res)controller.dispatch(action,req,res)# Let's step into this line.endendendendend# ./gems/actionpack-5.2.2/lib/action_controller/metal.rbmoduleActionControllerclassMetal <AbstractController::Baseabstract!defself.controller_name@controller_name ||=name.demodulize.sub(/Controller$/,"").underscoreenddefself.make_response!(request)ActionDispatch::Response.new.tapdo |res|res.request=requestendendclass_attribute:middleware_stack,default:ActionController::MiddlewareStack.newdefself.inherited(base)base.middleware_stack=middleware_stack.dupsuperend# Direct dispatch to the controller. Instantiates the controller, then# executes the action named +name+.defself.dispatch(name,req,res)ifmiddleware_stack.any?middleware_stack.build(name){ |env|new.dispatch(name,req,res)}.callreq.envelse# self is HomeController, so in this line Rails will new a HomeController instance.# See `HomeController.ancestors`, you can find many parents classes.# These are some typical ancestors of HomeController.# HomeController# < ApplicationController# < ActionController::Base# < ActiveRecord::Railties::ControllerRuntime (module included)# < ActionController::Instrumentation (module included)# < ActionController::Rescue (module included)# < AbstractController::Callbacks (module included)# < ActionController::ImplicitRender (module included)# < ActionController::BasicImplicitRender (module included)# < ActionController::Renderers (module included)# < ActionController::Rendering (module included)# < ActionView::Layouts (module included)# < ActionView::Rendering (module included)# < ActionDispatch::Routing::UrlFor (module included)# < AbstractController::Rendering (module included)# < ActionController::Metal# < AbstractController::Basenew.dispatch(name,req,res)# Let's step into this line.endenddefdispatch(name,request,response)set_request!(request)set_response!(response)process(name)# Let's step into this line.request.commit_flashto_aenddefto_aresponse.to_aendendend# .gems/actionpack-5.2.2/lib/abstract_controller/base.rbmoduleAbstractControllerclassBasedefprocess(action, *args)@_action_name=action.to_sunlessaction_name=_find_action_name(@_action_name)raiseActionNotFound,"The action '#{action}' could not be found for#{self.class.name}"end@_response_body=nilprocess_action(action_name, *args)# Let's step into this line.endendend# .gems/actionpack-5.2.2/lib/action_controller/metal/instrumentation.rbmoduleActionControllermoduleInstrumentationdefprocess_action(*args)raw_payload={controller:self.class.name,action:action_name,params:request.filtered_parameters,headers:request.headers,format:request.format.ref,method:request.request_method,path:request.fullpath}ActiveSupport::Notifications.instrument("start_processing.action_controller",raw_payload.dup)ActiveSupport::Notifications.instrument("process_action.action_controller",raw_payload)do |payload|begin# self: #<HomeController:0x00007fcd3c5dfd48>result=super# Let's step into this line.payload[:status]=response.statusresultensureappend_info_to_payload(payload)endendendendend# .gems/actionpack-5.2.2/lib/action_controller/metal/rescue.rbmoduleActionControllermoduleRescuedefprocess_action(*args)super# Let's step into this line.rescueException=>exceptionrequest.env["action_dispatch.show_detailed_exceptions"] ||=show_detailed_exceptions?rescue_with_handler(exception) ||raiseendendend# .gems/actionpack-5.2.2/lib/abstract_controller/callbacks.rbmoduleAbstractController# = Abstract Controller Callbacks## Abstract Controller provides hooks during the life cycle of a controller action.# Callbacks allow you to trigger logic during this cycle. Available callbacks are:## * <tt>after_action</tt># * <tt>before_action</tt># * <tt>skip_before_action</tt># * ...moduleCallbacksdefprocess_action(*args)run_callbacks(:process_action)do# self: #<HomeController:0x00007fcd3c5dfd48>super# Let's step into this line.endendendend# .gems/actionpack-5.2.2/lib/action_controller/metal/rendering.rbmoduleActionControllermoduleRenderingdefprocess_action(*)self.formats=request.formats.map(&:ref).compactsuper# Let's step into this line.endendend# .gems/actionpack-5.2.2/lib/abstract_controller/base.rbmoduleAbstractControllerclassBasedefprocess_action(method_name, *args)# self: #<HomeController:0x00007fcd3c5dfd48>, method_name: 'index'send_action(method_name, *args)# In the end, method 'send_action' is method 'send' as the below line shown.endaliassend_actionsendendend# .gems/actionpack-5.2.2/lib/action_controller/metal/basic_implicit_render.rbmoduleActionControllermoduleBasicImplicitRenderdefsend_action(method, *args)# self: #<HomeController:0x00007fcd3c5dfd48>, method_name: 'index'# Because 'send_action' is an alias of 'send', so# self.send('index', *args) will goto HomeController#index.x=superx.tap{default_renderunlessperformed?}# Let's step into 'default_render' later.endendend# ./your_project/app/controllers/home_controller.rbclassHomeController <ApplicationController# Will go back to BasicImplicitRender#send_action when method 'index' is done.defindex# Question: How does this instance variable '@users' in HomeController can be accessed in './app/views/home/index.html.erb' ?# Will answer this question later.@users=User.all.pluck(:id,:name)endend
# ./app/views/home/index.html.erb<divclass="container"><h1class="display-4 font-italic"><%= t('home.banner_title') %><%= @users %></h1></div>
# .gems/actionpack-5.2.2/lib/action_controller/metal/implicit_render.rbmoduleActionController# Handles implicit rendering for a controller action that does not# explicitly respond with +render+, +respond_to+, +redirect+, or +head+.moduleImplicitRenderdefdefault_render(*args)# Let's step into template_exists?iftemplate_exists?(action_name.to_s,_prefixes,variants:request.variant)# Rails have found the default template './app/views/home/index.html.erb', so render it.render(*args)# Let's step into this line laterelsifany_templates?(action_name.to_s,_prefixes)message="#{self.class.name}\##{action_name} is missing a template " \"for this request format and variant.\n" \"\nrequest.formats:#{request.formats.map(&:to_s).inspect}" \"\nrequest.variant:#{request.variant.inspect}"raiseActionController::UnknownFormat,messageelsifinteractive_browser_request?message="#{self.class.name}\##{action_name} is missing a template " \"for this request format and variant.\n\n" \"request.formats:#{request.formats.map(&:to_s).inspect}\n" \"request.variant:#{request.variant.inspect}\n\n" \"NOTE! For XHR/Ajax or API requests, this action would normally " \"respond with 204 No Content: an empty white screen. Since you're " \"loading it in a web browser, we assume that you expected to " \"actually render a template, not nothing, so we're showing an " \"error to be extra-clear. If you expect 204 No Content, carry on. " \"That's what you'll get from an XHR or API request. Give it a shot."raiseActionController::UnknownFormat,messageelselogger.info"No template found for#{self.class.name}\##{action_name}, rendering head :no_content"ifloggersuperendendendend# .gems/actionview-5.2.2/lib/action_view/lookup_context.rbmoduleActionViewclassLookupContextmoduleViewPaths# Rails find out that the default template is './app/views/home/index.html.erb'defexists?(name,prefixes=[],partial=false,keys=[], **options)@view_paths.exists?(*args_for_lookup(name,prefixes,partial,keys,options))endalias:template_exists?:exists?endendend# .gems/actionpack-5.2.2/lib/action_controller/metal/instrumentation.rbmoduleActionControllermoduleInstrumentationdefrender(*args)render_output=nilself.view_runtime=cleanup_view_runtimedoBenchmark.ms{# self: #<HomeController:0x00007fa7e9c54278>render_output=super# Let's step into super}endrender_outputendendend# .gems/actionpack-5.2.2/lib/action_controller/metal/rendering.rbmoduleActionControllermoduleRendering# Check for double render errors and set the content_type after rendering.defrender(*args)raise ::AbstractController::DoubleRenderErrorifresponse_bodysuper# Let's step into superendendend# .gems/actionpack-5.2.2/lib/abstract_controller/rendering.rbmoduleAbstractControllermoduleRendering# Normalizes arguments, options and then delegates render_to_body and# sticks the result in <tt>self.response_body</tt>.defrender(*args, &block)options=_normalize_render(*args, &block)rendered_body=render_to_body(options)# Let's step into this line.ifoptions[:html]_set_html_content_typeelse_set_rendered_content_typerendered_formatendself.response_body=rendered_bodyendendend# .gems/actionpack-5.2.2/lib/action_controller/metal/renderers.rbmoduleActionControllermoduleRenderersdefrender_to_body(options)_render_to_body_with_renderer(options) ||super# Let's step into this line and super later.end# For this example, this method return nil in the end.def_render_to_body_with_renderer(options)# The '_renderers' is defined at line 31: class_attribute :_renderers, default: Set.new.freeze.# '_renderers' is an instance predicate method. For more information,# see ./gems/activesupport/lib/active_support/core_ext/class/attribute.rb_renderers.eachdo |name|ifoptions.key?(name)_process_options(options)method_name=Renderers._render_with_renderer_method_name(name)returnsend(method_name,options.delete(name),options)endendnilendendend# .gems/actionpack-5.2.2/lib/action_controller/metal/renderers.rbmoduleActionControllermoduleRenderingdefrender_to_body(options={})super ||_render_in_priorities(options) ||" "# Let's step into superendendend
# .gems/actionview-5.2.2/lib/action_view/rendering.rbmoduleActionViewmoduleRenderingdefrender_to_body(options={})_process_options(options)_render_template(options)# Let's step into this line.enddef_render_template(options)variant=options.delete(:variant)assigns=options.delete(:assigns)context=view_context# We will step into this line later.context.assignassignsifassignslookup_context.rendered_format=nilifoptions[:formats]lookup_context.variants=variantifvariantview_renderer.render(context,options)# Let's step into this line.endendend# .gems/actionview-5.2.2/lib/action_view/renderer/renderer.rbmoduleActionViewclassRendererdefrender(context,options)ifoptions.key?(:partial)render_partial(context,options)elserender_template(context,options)# Let's step into this line.endend# Direct access to template rendering.defrender_template(context,options)TemplateRenderer.new(@lookup_context).render(context,options)# Let's step into this line.endendend# .gems/actionview-5.2.2/lib/action_view/renderer/template_renderer.rbmoduleActionViewclassTemplateRenderer <AbstractRendererdefrender(context,options)@view=context@details=extract_details(options)template=determine_template(options)prepend_formats(template.formats)@lookup_context.rendered_format ||=(template.formats.first ||formats.first)render_template(template,options[:layout],options[:locals])# Let's step into this line.enddefrender_template(template,layout_name=nil,locals=nil)view,locals=@view,locals ||{}render_with_layout(layout_name,locals)do |layout|# Let's step into this lineinstrument(:template,identifier:template.identifier,layout:layout.try(:virtual_path))do# template: #<ActionView::Template:0x00007f822759cbc0>template.render(view,locals){ |*name|view._layout_for(*name)}# Let's step into this lineendendenddefrender_with_layout(path,locals)layout=path &&find_layout(path,locals.keys,[formats.first])content=yield(layout)iflayoutview=@viewview.view_flow.set(:layout,content)layout.render(view,locals){ |*name|view._layout_for(*name)}elsecontentendendendend# .gems/actionview-5.2.2/lib/action_view/template.rbmoduleActionViewclassTemplatedefrender(view,locals,buffer=nil, &block)instrument_render_templatedo# self: #<ActionView::Template:0x00007f89bab1efb8# @identifier="/path/to/your/project/app/views/home/index.html.erb"# @source="<div class='container'\n ..."# >compile!(view)# method_name: "_app_views_home_index_html_erb___3699380246341444633_70336654511160" (This method is defined in 'def compile(mod)' below)# view: #<#<Class:0x00007ff10d6c9d18>:0x00007ff10ea050a8>, view is an instance of <a subclass of ActionView::Base> which has same instance variables in the instance of HomeController.# The method 'view.send' will return the result html!view.send(method_name,locals,buffer, &block)endrescue=>ehandle_render_error(view,e)end# Compile a template. This method ensures a template is compiled# just once and removes the source after it is compiled.defcompile!(view)returnif@compiled# Templates can be used concurrently in threaded environments# so compilation and any instance variable modification must# be synchronized@compile_mutex.synchronizedo# Any thread holding this lock will be compiling the template needed# by the threads waiting. So re-check the @compiled flag to avoid# re-compilationreturnif@compiledifview.is_a?(ActionView::CompiledTemplates)mod=ActionView::CompiledTemplateselsemod=view.singleton_classendinstrument("!compile_template")docompile(mod)# Let's step into this line.end# Just discard the source if we have a virtual path. This# means we can get the template back.@source=nilif@virtual_path@compiled=trueendenddefcompile(mod)encode!# @handler: #<ActionView::Template::Handlers::ERB:0x00007ff10e1be188>code=@handler.call(self)# Let's step into this line.# Make sure that the resulting String to be eval'd is in the# encoding of the codesource=<<-end_src.dup def#{method_name}(local_assigns, output_buffer) _old_virtual_path, @virtual_path = @virtual_path,#{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code} ensure @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer end end_src# Make sure the source is in the encoding of the returned codesource.force_encoding(code.encoding)# In case we get back a String from a handler that is not in# BINARY or the default_internal, encode it to the default_internalsource.encode!# Now, validate that the source we got back from the template# handler is valid in the default_internal. This is for handlers# that handle encoding but screw upunlesssource.valid_encoding?raiseWrongEncodingError.new(@source,Encoding.default_internal)end# source: def _app_views_home_index_html_erb___1187260686135140546_70244801399180(local_assigns, output_buffer)# _old_virtual_path, @virtual_path = @virtual_path, "home/index";_old_output_buffer = @output_buffer;;# @output_buffer = output_buffer || ActionView::OutputBuffer.new;# @output_buffer.safe_append='<div># <h1># '.freeze;# @output_buffer.append=( t('home.banner_title') );# @output_buffer.append=( @users );# @output_buffer.safe_append='# </h1># </div># '.freeze;# @output_buffer.to_s# ensure# @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer# endmod.module_eval(source,identifier,0)# This line will actually define the method '_app_views_home_index_html_erb___1187260686135140546_70244801399180'# mod: ActionView::CompiledTemplatesObjectSpace.define_finalizer(self,Finalizer[method_name,mod])end# .gems/actionview-5.2.2/lib/action_view/template/handler/erb.rbmoduleHandlersclassERBdefcall(template)# First, convert to BINARY, so in case the encoding is# wrong, we can still find an encoding tag# (<%# encoding %>) inside the String using a regular# expressiontemplate_source=template.source.dup.force_encoding(Encoding::ASCII_8BIT)erb=template_source.gsub(ENCODING_TAG,"")encoding= $2erb.force_encodingvalid_encoding(template.source.dup,encoding)# Always make sure we return a String in the default_internalerb.encode!self.class.erb_implementation.new(erb,escape:(self.class.escape_whitelist.include?template.type),trim:(self.class.erb_trim_mode =="-")).srcendendendendend
It's time to answer the question before:
How can instance variable like@users
defined inHomeController
be accessed in./app/views/home/index.html.erb
?
# ./gems/actionview-5.2.2/lib/action_view/rendering.rbmoduleActionViewmoduleRenderingdefview_contextview_context_class.new(# Let's step into this line later.view_renderer,view_assigns,# Let's step into this line.self)enddefview_assigns# self: #<HomeController:0x00007f83ecfed310>protected_vars=_protected_ivars# instance_variables is an instance method of Object and it will return an array. And the array contains @users.variables=instance_variablesvariables.reject!{ |s|protected_vars.include?s}ret=variables.each_with_object({}){ |name,hash|hash[name.slice(1,name.length)]=instance_variable_get(name)}# ret: {"marked_for_same_origin_verification"=>true, "users"=>[[1, "Lane"], [2, "John"], [4, "Frank"]]}retenddefview_context_class# will return a subclass of ActionView::Base.@_view_context_class ||=self.class.view_context_classend# How this ClassMethods works? Please look at ActiveSupport::Concern in ./gems/activesupport-5.2.2/lib/active_support/concern.rb# FYI, the method 'append_features' will be executed before method 'included'.# https://apidock.com/ruby/v1_9_3_392/Module/append_featuresmoduleClassMethodsdefview_context_class# self: HomeController@view_context_class ||=beginsupports_path=supports_path?routes=respond_to?(:_routes) &&_routeshelpers=respond_to?(:_helpers) &&_helpersClass.new(ActionView::Base)doifroutesincluderoutes.url_helpers(supports_path)includeroutes.mounted_helpersendifhelpersincludehelpersendendendendendendend# ./gems/actionview-5.2.2/lib/action_view/base.rbmoduleActionViewclassBasedefinitialize(context=nil,assigns={},controller=nil,formats=nil)@_config=ActiveSupport::InheritableOptions.newifcontext.is_a?(ActionView::Renderer)@view_renderer=contextelselookup_context=context.is_a?(ActionView::LookupContext) ?context :ActionView::LookupContext.new(context)lookup_context.formats=formatsifformatslookup_context.prefixes=controller._prefixesifcontroller@view_renderer=ActionView::Renderer.new(lookup_context)end@cache_hit={}assign(assigns)# Let's step into this line.assign_controller(controller)_prepare_contextenddefassign(new_assigns)@_assigns=new_assigns.eachdo |key,value|instance_variable_set("@#{key}",value)# This line will set the instance variables in HomeController like '@users' to itself.endendendend
Assume your rails project app class name isYourProject::Application
(defined in./config/application.rb
).
First, I will give you a piece of important code.
# ./gems/railties-5.2.2/lib/rails/commands/server/server_command.rbmoduleRailsclassServer < ::Rack::Serverdefstart#...log_to_stdoutsuper# Will invoke ::Rack::Server#start.ensureputs"Exiting"unless@options &&options[:daemonize]enddeflog_to_stdout# 'wrapped_app' will get an well prepared app from './config.ru' file.# It's the first time invoke 'wrapped_app'.# The app is an instance of YourProject::Application.# The app is not created in 'wrapped_app'.# It has been created when `require APP_PATH` in previous code,# just at the 'perform' method in Rails::Command::ServerCommand.wrapped_app# ...endendend
Then, let's start rails byrails server
. The commandrails
locates at./bin/
.
#!/usr/bin/env rubyAPP_PATH=File.expand_path('../config/application',__dir__)require_relative'../config/boot'require'rails/commands'# Let's look at this file.
# ./railties-5.2.2/lib/rails/commands.rbrequire"rails/command"aliases={"g"=>"generate","d"=>"destroy","c"=>"console","s"=>"server","db"=>"dbconsole","r"=>"runner","t"=>"test"}command=ARGV.shiftcommand=aliases[command] ||command# command is 'server'Rails::Command.invokecommand,ARGV# Let's step into this line.
# ./railties-5.2.2/lib/rails/command.rbmoduleRailsmoduleCommandclass <<selfdefinvoke(full_namespace,args=[], **config)# ...# In the end, we got this result: {"rails server" => Rails::Command::ServerCommand}command=find_by_namespace(namespace,command_name)# command value is Rails::Command::ServerCommand# command_name is 'server'command.perform(command_name,args,config)# Rails::Command::ServerCommand.performendendendend
# ./gems/railties-5.2.2/lib/rails/commands/server/server_command.rbmoduleRailsmoduleCommandclassServerCommand <Base# There is a class method 'perform' in the Base class.definitializeend# 'perform' here is a instance method. But for Rails::Command::ServerCommand.perform, 'perform' is a class method.# Where is this 'perform' class method? Answer: In the parent class 'Base'.defperform# ...Rails::Server.new(server_options).tapdo |server|#...server.startendendendendend
Inheritance relationship:Rails::Command::ServerCommand < Rails::Command::Base < Thor
# ./gems/railties-5.2.2/lib/rails/command/base.rbmoduleRailsmoduleCommandclassBase <Thor# https://github.com/erikhuda/thor Thor is a toolkit for building powerful command-line interfaces.class <<self# command is 'server'defperform(command,args,config)#...dispatch(command,args.dup,nil,config)# Thor.dispatchendendendendend
# ./gems/thor/lib/thor.rbclassThorclass <<self# meth is 'server'defdispatch(meth,given_args,given_opts,config)# ...instance=new(args,opts,config)# Here will new a Rails::Command::ServerCommand instance.# ...# command is {Thor::Command}#<struct Thor::Command name="server" ...>instance.invoke_command(command,trailing ||[])# Method 'invoke_command' is in Thor::Invocation.endendend# ./gems/thor/lib/thor/invocation.rbclassThormoduleInvocation# This module is included in Thor. Thor is grandfather of Rails::Command::ServerCommanddefinvoke_command(command, *args)# 'invoke_command' is defined at here.# ...command.run(self, *args)# command is {Thor::Command}#<struct Thor::Command name="server" ...>endendend# ./gems/thor/lib/thor.rbclassThor# ...includeThor::Base# Will invoke hook method 'Thor::Base.included(self)'end# ./gems/thor/lib/thor/base.rbmoduleThormoduleBaseclass <<selfdefincluded(base)# hook method when module 'Thor::Base' included.base.extendClassMethodsbase.send:include,Invocation# 'Invocation' included in 'Thor'. So 'invoke_command' will be an instance method of Rails::Command::ServerCommandbase.send:include,ShellendendmoduleClassMethods# This is also a hook method. In the end,# this method will help to "alias_method('server', 'perform')".# The 'server' is the 'server' for `$ rails server`.# So it's important. We will discuss it later.defmethod_added(meth)# ...# here self is {Class} Rails::Command::ServerCommandcreate_command(meth)# meth is 'perform'. Let's step into this line.endendendend# ./gems/railties-5.2.2/lib/rails/command/base.rbmoduleRailsmoduleCommandmoduleBase# Rails::Command::Base is father of Rails::Command::ServerCommandclass <<selfdefcreate_command(meth)ifmeth =="perform"# Instance method 'server' of Rails::Command::ServerCommand will be delegated to 'perform' method now.alias_method('server',meth)endendendendendend# ./gems/thor/lib/thor/command.rbclassThorclassCommanddefrun(instance,args=[])#...# instance is {Rails::Command::ServerCommand}#<Rails::Command::ServerCommand:0x00007fa5f319bf40>instance.__send__(name, *args)# name is 'server'. Will actually invoke 'instance.perform(*args)'.endendend# ./gems/railties-5.2.2/lib/rails/commands/server/server_command.rbmoduleRailsmoduleCommand# In ServerCommand class, there is no instance method 'server' explicitly defined.# It is defined by a hook method 'method_added'classServerCommand <Basedefperform# ...Rails::Server.new(server_options).tapdo |server|# APP_PATH is '/Users/your_name/your-project/config/application'.# require APP_PATH will create the 'Rails.application' object.# 'Rails.application' is 'YourProject::Application.new'.# Rack server will start 'Rails.application'.requireAPP_PATHDir.chdir(Rails.application.root)server.start# Let's step into this line.endendendendend# ./gems/railties-5.2.2/lib/rails/commands/server/server_command.rbmoduleRailsclassServer < ::Rack::Serverdefstartprint_boot_information# All lines in the block of trap() will not be executed# unless a signal of terminating the process (like `$ kill -9 process_id`) has been received.trap(:INT)do#...exitendcreate_tmp_directoriessetup_dev_cachinglog_to_stdout# This line is important. Although the method name seems not. Let step into this line.super# Will invoke ::Rack::Server#start. I will show you later.ensureputs"Exiting"unless@options &&options[:daemonize]enddeflog_to_stdout# 'wrapped_app' will get an well prepared app from './config.ru' file.# It's the first time invoke 'wrapped_app'.# The app is an instance of YourProject::Application.# The app is not created in 'wrapped_app'.# It has been created when `require APP_PATH` in previous code,# just at the 'perform' method in Rails::Command::ServerCommand.wrapped_app# ...endendend# ./gems/rack-2.0.6/lib/rack/server.rbmoduleRackclassServerdefwrapped_app@wrapped_app ||=build_app(app# Let's step into this line.)enddefapp@app ||=build_app_and_options_from_config# Let's step into this line.@appenddefbuild_app_and_options_from_config# ...# self.options[:config] is 'config.ru'. Let's step into this line.app,options=Rack::Builder.parse_file(self.options[:config],opt_parser)# ...appenddefstart(&blk)#...wrapped_apptrap(:INT)doifserver.respond_to?(:shutdown)server.shutdownelseexitendend# server is {Module} Rack::Handler::Puma# wrapped_app is {YourProject::Application} #<YourProject::Application:0x00007f7fe5523f98>server.run(wrapped_app,options, &blk)# We will step into this line later.endendend# ./gems/rack/lib/rack/builder.rbmoduleRackmoduleBuilderdefself.parse_file(config,opts=Server::Options.new)cfgfile= ::File.read(config)# config is 'config.ru'app=new_from_string(cfgfile,config)returnapp,optionsend# Let's guess what will 'run Rails.application' do in config.ru?# First, we will get an instance of YourProject::Application.# Then we will run it. But 'run' may isn't what you are thinking about.# Because the 'self' object in config.ru is an instance of Rack::Builder,# so 'run' is an instance method of Rack::Builder.# Let's look at the definition of the 'run' method:# def run(app)# @run = app # Just set an instance variable :)# enddefself.new_from_string(builder_script,file="(rackup)")# Rack::Builder implements a small DSL to iteratively construct Rack applications.eval"Rack::Builder.new {\n" +builder_script +"\n}.to_app",TOPLEVEL_BINDING,file,0endendend# ./gems/puma-3.12.0/lib/rack/handler/puma.rbmoduleRackmoduleHandlermodulePumadefself.run(app,options={})conf=self.config(app,options)# ...launcher= ::Puma::Launcher.new(conf,:events=>events)begin# Let's stop our journey here. It's puma's turn now.# Puma will run your app (instance of YourProject::Application)launcher.runrescueInterruptputs"* Gracefully stopping, waiting for requests to finish"launcher.stopputs"* Goodbye!"endendendendend
Now puma has been started successfully with your app (instance of YourProject::Application) running.
About
Solving LeetCode problems in the best way. Python, Java, C++, JavaScript, Go, C# and Ruby are supported! Official website👇🏻:
Resources
Uh oh!
There was an error while loading.Please reload this page.