Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Solving LeetCode problems in the best way. Python, Java, C++, JavaScript, Go, C# and Ruby are supported! Official website👇🏻:

NotificationsYou must be signed in to change notification settings

upcse/leetcode-python-java

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 

Repository files navigation

Part 0: Before you research Rails 5 source code

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

  1. You need a good IDE with debugging function. I useRubyMine.

What you will learn from this tutorial?

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

Part 1: Your app: an instance of YourProject::Application.

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.

Part 2: config

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

Part 3: Every request and response.

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

Part 4: What does$ rails server do?

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

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp