Rails
Created at 2017-04-21T16:32:41+09:00

Just to make it fun using Rails.

TODO

  • [x] ActionController, ActionDispatch::Routing
  • [x] ActionView
  • [x] session and cookie (ActionDispatch::Session, AD::Session::CookieStore, ::Rack::Session, AD::Cookies)
  • [x] ActiveModel, ActiveRecord
  • [x] IPC background processing (redis, sidekiq, ActiveJob)
  • [x] Puma: forking and threading in front of Rails rack app
  • [ ] testing facility (ActtiveRecord (db integration), ActionController (http request/response))
  • [ ] caching (ActionSupport::Cache)
  • [ ] logging
  • [ ] authentication

Rails

[ Data structure ]

YourApplication < Rails::Application < Rails::Engine < Rails::Railtie
Rails.application (instance of YourApplication)

Rails::Engine
'-' ActionDispatch::Routing::RouteSet
  '-' Journey::Routes
    '-* Journey::Route
  '-' Journey::Router
  '-' NamedRouteCollection

ActionDispatch::Request (include Rack::Request::Helpers, Env)
ActionDispatch::Session::CookieStore < AbstractStore < ::Rack::Session::Abstract::Persisted

ActionController::Base < Metal < AbstractController::Base
  - and other a lot module chains
    ActionController::Base, ActionController::Rescue, AbstractController::Callbacks, ActiveSupport::Callbacks,
    ActionController::Renderers, ActionController::Rendering, ActionView::Layouts, ActionView::Rendering,
    AbstractController::Logger, AbstractController::Rendering, ActionController::Metal, AbstractController::Base

ActionView::Base
ActionView::Renderer
ActionView::LookupContext


[ Initialization ]
- (Rack server reads config.ru (assume it's Rack::Handler::WEBrick here)) =>
  - require_relative 'config/environment' =>
    - require_relative 'application' =>
      - Bundler.require
      - (class YourApplication < Rails::Application) =>
        - Rails::Application.inherited =>
          - Rails.app_class = YourApplication
    - Rails.application => Rails.app_class.instance =>
      - YourApplication.instance =>
        - Rails::Application.instance =>
          - Rails::Railtie.instance => new =>
            - YourApplication#initialize =>
              - Rails::Application#initialize => ...
          - Rails::Application#run_load_hooks! => ...
    - Rails.application.initialize! => Rails::Application#initialize! =>
      - Rails::Initializable#run_initializers =>
        - ...besides a lot some others ...
  - Rack::Handler::WEBrick.run Rails.application


[ Route construction ]
- "routes.rb" is loaded (Application#routes_reloader and Engine.initializer(:add_routing_paths) or Engine#eager_load!)
- Rails.application.routes.draw (e.g. match('somepath', to: 'ctrl#act', via: :get)) =>
  - ActionDispatch::Routing::RouteSet#draw =>
    - eval_block =>
      - Mapper.new => @scope = Scope.new
      - Mapper#instance_exec =>
        - match => map_match => decomposed_match => add_route =>
          - Mapping.build => Mapping.new
          - RouteSet#add_route(mapping, name) =>
            - Journey::Routes#add_route =>
              - Mapping#make_route =>
                - application => app => Routing::RouteSet::Dispatcher.new
                - Journey::Route.new(..., application, ...)
              - routes << route
            - named_routes[name] = route


[ Rack entrypoint (some possibly lazy initilaization is also written here e.g. middleware stack) ]
- (Rack server calls rack application (e.g. Rack::Handler::Webrick#service)) =>
  - Rails.application.call(env) => Rails::Engine#call =>
    - req = build_request(env) =>
      - req = ActionDispatch::Request.new(env)
      - req.routes = routes =>
        - ActionDispatch::Routing::RouteSet.new_with_config(config (i.e. Engine::Configuration)) =>
          - new => initialize =>
            - named_routes = NamedRouteCollection.new
            - @set = Journey::Routes.new (aliased as routes)
            - @router = Journey::Router.new
    - app =>
      - default_middleware_stack =>
        - ActionDispatch::MiddlewareStack.new
        - (if it's Rails::Application) DefaultMiddlewareStack#build_stack =>
          - use ::Rails::Rack::Logger
          - use config.session_store (e.g. ActionDispatch::Session::CookieStore)
          - ...and a lot others
      - build_middleware =>
        - config => Engine::Configuration.new => Rails::Configuration::MiddlewareStackProxy.new
      - MiddlewareStackProxy#merge_into
      - MiddlewareStack#build(endpoint (a.k.a. routes)) =>
        - ActionDispatch::MiddlewareStack::Middleware#build recursively through registered middlewares
    - app.call(req.env) =>
      - (for simplicity, assumes rack stack is [use Rails::Rack::Logger, run YourApplication.routes])
      - Rails::Rack::Logger#call(env) =>
        - request = ActionDispatch::Request.new(env)
        - call_app(request, env) =>
          - Rails.logger.info { started_request_message(request) } (e.g. "Started <method> <path> for <ip> at <timestamp>")
        - YourApplication.routes.call (SEE BELOW)

- YourApplication.routes.call (RouteSet#call) =>
  - Journey::Router#serve =>
    - find_routes(req) (produces matched Journey::Route) => (TODO)
    - status, headers, body = route.app.serve(req) (Routing::RouteSet::Dispatcher#serve) =>
      - params = req.path_parameters
      - controller = controller(req) =>
        - ActionDispatch::Request#controller_class =>
          - ActiveSupport::Dependencies.constantize params[:controller]
      - res = controller.make_response!(req) =>
        - SomeController
      - dispatch => SomeController.dispatch(action, req, res) (SEE BELOW)
    - return [status, headers, body]


[ Controller instantiation ]
- SomeController.dispatch => ActionController::Metal.dispatch =>
  - new
  - Metal#dispatch => AbstractController::Base#process =>
    - AbstractController::Base#process_action (see AbstractController::Base#ancestors for a bunch of overridings) =>
      - send_action (aliased as send) =>
        - (assume there is SomeController#show with render method call as an example)
        - ActionController::Instrumentation#render =>
          - ActionController::Rendering#render =>
            - AbstractController::Rendering#render =>
              - rendered_body = render_to_body =>
                - ActionController::Renderes#render_to_body => _render_to_body_with_renderer (here :json, :js is handed)
                - (if above routine didn't match) super => ActionView::Rendering#render_to_body (SEE BELOW)
              - ActionController::Base#response_body= rendered_body


[ Template rendering ]
- ActionView::Rendering#render_to_body => _render_template =>
  - view_context =>
    - view_context_class => Class.new(ActionView::Base)
    - ActionView::ViewPaths#lookup_context => ActionView::LookupContext.new
    - view_renderer => ActionView::Renderer.new(lookup_context)
    - view_context_class.new(view_renderer)
  - ActionView::Renderer#render(view_context) => render_template =>
    - TemplateRenderer.new
    - TemplateRenderer#render =>
      - determine_template => AbstractRenderer#find_template => LookupContext#find_template (aliased to find) =>
        - ActionView::PathSet#find => ... =>
          - ActionView::Resolver#find_all => PathResolver#find_templates (within cached block) => query =>
            - extract_handler_and_format_and_variant =>
              - Template.handler_for_extension (assume it's Template::Handlers::ERB instance)
            - Template.new
      - render_template => render_with_layout =>
        - find_layout => find_template
        - yield => Template#render =>
          - compile! => compile =>
            - Handlers::ERB#call (generate ruby code using Handlers::ERB::Erubi (patched version of ::Erubi::Engine))
            - module_eval on view (it's an instance of ActionView::Base with many helpers)
              (this defines method to execute Erubi's generated code)
          - call dynamically defined method
        - Template#render (for layout)

Session, Cookie (a.k.a. HTTP State Management Mechanism)

works as middleware and other complicated stack.

(bind selected store to request)
- ActionDispatch::Session::CookieStore#call =>
  - Rack::Session::Abstract::Persisted#call =>
    - ActionDispatch::Session::CookieStore#prepare_session =>
      - AD::Request::Session.create =>
        - Session.find => AD::Request#get_header(Rack::RACK_SESSION)
        - Session.new
        - Session#merge => load_for_write! => load! => CookieStore#load_session =>
          - unpacked_cookie_data =>
            - Rack::Request#fetch_header("action_dispatch.request.unsigned_session_cookie")
            - CookieStore#get_cookie => cookie_jar => Request#cookie_jar (defined in ad/middleware/cookies.rb) =>
              - cookie_jar = AD::Cookies::CookieJar.build => new
            - CookieJar#signed_or_encrypted => encrypted => EncryptedCookieJar.new
            - EncryptedCookieJar#[@key] => AbstractCookieJar#[] =>
              - EncryptedCookieJar#parse =>
                - ActiveSupport::MessageEncryptor#decrypt_and_verify
                - SerializedCookieJars#deserialize
          - persistent_session_id! =>
            - AD::Session::Compatibility#generate_sid
          - return a pair of session id and "session data" (i.e. plain Hash, which is (de)serialized via SerializedCookieJars)
        - Session.set => AD::Request#set_header(Rack::RACK_SESSION, ...) (i.e. Rack::Request)
    - @app.call
    - Rack::Session::Abstract::Persisted#commit_session =>
      - AD::Session::CookieStore#write_session
      - AD::Session::CookieStore#set_cookie =>
        - EncryptedCookieJar#[@key]= => AbstractCookieJar#[]= =>
          - EncryptedCookieJar#commit =>
            - ActiveSupport::MessageEncryptor#encrypt_and_sign
            - SerializedCookieJars#serialize

Relation database

[ Data structure ]
(AM = ActiveModel, AR = ActiveRecord, CA = ConnectionAdapters)

AM::AttributeAssignment (#assign_attributes)
AM::AttributeMethods (.define_attribute_methods)

AR::Base
AR::Core (#initialize, #find, #find_by, .connection_handler)
AR::ModelSchema (.table_name, .inheritance_column, .load_schema, .columns_hash, .attribute_types)
AR::Attributes (.define_attribute)
AR::AttributeMethods (.define_attribute_methods, #attributes)
 (include PrimaryKey (.primary_key) , ...)

AR::Associations (.has_one, .has_many, .belongs_to)
AR::Persistense (.create, #update, #destroy)
AR::Querying (methods delegation to :all (e.g. .first, .second, .count, etc...))
AR::Scoping (.current_scope)
AR::Scoping::Named (.all)

AR::Relation (#build, #create, #first_or_create, #insert)
AR::Relation::QueryMethods (#where)

AR::ConnectionHandling (.establish_connection, #mysql2_connection)
AR::CA::Mysql2Adapter    (include MySQL::DatabaseStatements)
  < AbstractMysqlAdapter (include MySQL::SchemaStatements))
    < AbstractAdapter    (include DatabaseStatements, SchemaStatements)

AR::Schema (.define) < ActiveRecord::Migration::Current

AR::Associations::Builder::HasMany < CollectionAssociation < Association
AR::Associations::HasManyAssociation < CollectionAssociation < Association
YourModel::AR_Associations_CollectionProxy < AR::Associations::CollectionProxy < Relation


[ connection management ]
- AR::Core.included => ConnectionHandler.new => @owner_to_pool = Concurrent::Map.new

- (ActiveRecord::Railtie#initializer "active_record.initialize_database") =>
  - AR::ConnectionHandling.establish_connection (as AR::Base.establish_connection) =>
    - ConnectionHandler#establish_connection =>
      - ConnectionSpecification::Resolver.new and #spec =>
        - adapter_method = "#{spec[:adapter]}_connection" (e.g. "mysql2_connection")
        - ConnectionSpecification.new
      - ConnectionPool.new =>
        - Reaper.new and #run =>
        - @thread_cached_conns = Concurrent::Map.new
        - @available = ConnectionLeasingQueue.new
      - update #owner_to_pool hash

- (SomeModel.count => ... => AR::Calculations#execute_simple_calculation) =>
  - AR::ConnectionHandling.connection (as in AR::Base.connection) => retrieve_connection =>
    - ConnectionHandler#retrieve_connection =>
      - retrieve_connection_pool =>
        - establish_connection if not found in #owner_to_pool hash
      - return ConnectionPool#connection =>
        - checkout (if not have in @thread_cached_conns) =>
          - acquire_connection => checkout_new_connection => new_connection =>
            - Active::Record.<adapter-method> (e.g. ActiveRecord::ConnectionHandling.mysql2_connection) =>
              - Mysql2::Client.new => ...
              - CA::Mysql2Adapter.new =>
                - super =>
                  - @connection = <Mysql2::Client's instance>
                  - @schema_cache = SchemaCache.new
                  - @lock = Monitor.new


[ Schema-to-Class mapping ]
- AR::AttributeDecorators.load_schema! =>
  - (super) AR::Attributes.load_schema! =>
    - (super) AR::ModelSchema.load_schema =>
      - AR::ConnectionHandling.connection => ...
      - SchemaCache#columns_hash => columns => SchemaStatements#columns => AbstractMysqlAdapter#column_definitions
        - execute "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
      - AR::Attributes.define_attribute (for each column) =>
        - ModelSchema#attribute_types[]=

[ Model interface ]
- User.new => AR::Core#initialize =>
  - User.define_attribute_methods =>
    - AR::AttributeMethods.define_attribute_methods =>
      - attribute_names => attribute_types => AR::ModelSchema.attribute_types => load_schema! (SEE ABOVE)
      - (super) AM::AttributeMethods.define_attribute_methods =>
        - define_attribute_method =>
          - for each attribute_method_matchers
            - send('define_method_xxx', ...)
              (e.g. AR::AttributeMethods::Read.define_method_attribute,
                    AR::AttributeMethods::Write.define_method_attribute=,
                    AR::AttributeMethods::Dirty.attribute_changed?, etc...)
  - @attributes = User._default_attributes.deep_dup


[ "Relation" interface ]
- User < AR::Base =>
  - AR::Delegation::DelegateCache#inherited (because AR::Base extend DelegateCache) =>
    - initialize_relation_delegate_cache =>
      - define constant inheriting each AR::Relation, AR::Associations::CollectionProxy, AR::AssociationRelation

- AR::Associations.has_many (e.g. User.has_many(:posts))  =>
  - r = Builder::HasMany.build(User, :posts) =>
    - create_reflection =>
      - AR::Reflection.create(:has_many, :posts, ..., User) =>
        - AR::Reflection::HasManyReflection.new(:posts, ..., User)
    - define_accessors =>
      - define_readers =>
        - User#class_eval "def posts(...); association(:posts).reader(...); end"
        - User#class_eval "def posts_ids(...); ... ids_reader; end"
      - define_writers =>
        - User#class_eval "def posts=(...); association(:posts).write(...); end "
        - User#class_eval "def posts_ids=(...); ... ids_writer; end "
    - define_callbacks, define_validations
  - Reflection.add_reflection(User, :posts, r) => User._reflections= ...

- User#posts =>
  - AR::Associations#association(:posts) =>
    - AR::Reflection._reflect_on_association(:posts)
    - Reflection::HasManyReflection#association_class => Associations::HasManyAssociation
    - Associations::HasManyAssociation.new
  - Associations::CollectionAssociation#reader (as HasManyAssociation) =>
    - AR::Delegation.create (as CollectionProxy) =>
      - Delagation.relation_class_for(Post) =>
        - Post.relation_delegate_class(ActiveRecord::Associations::CollectionProxy) =>
          - Post::ActiveRecord_Associations_CollectionProxy
      - Post::ActiveRecord_Associations_CollectionProxy.new(Post, #<AR::Associations::HasManyAssociation>) =>
        - AR::Core.arel_table => Arel::Table.new
        - (super) AR::Relation#initialize

- user.posts.to_a =>
  - AR::Relation#to_a => AR::CollectionProxy#records => load_target =>
    - HasManyAssociation#load_target => find_target =>
      - StatementCache.create =>
        - CA::DatabaseStatements#cacheable_query =>
          - StatementCache.query => Query.new
        - new
      - AssociationScope.create
      - StatementCache#execute =>
        - AR::Querying#find_by_sql (as Post) =>
          - CA::DatabaseStatements#select_all => ...

- User.all.includes(:posts).to_a =>
  - AR::QueryMethods#includes (as AR::Relation (or User::ActiveRecord_Relation)) =>
    - includes! =>
      - includes_values (generated method within QueryMethods)
  - AR::Relation#to_a =>
    - records => load => exec_query =>
      - @records = find_with_associations => ?
      - build_preloader => AR::Associations::Preloader.new
      - Preloader#preload(@records, :posts) =>
        - preloaders_on(:posts, records, ...) => preloaders_for_one =>
          - grouped_records => User#assocation(:posts)
          - preloader_for(#<AR::Reflection::HasManyReflection>, records, Post) => AR::Associations::Preloader::HasMany
          - HasMany.new
          - HasMany#run(#<Preloader>) => Association#run => preload =>
            - CollectionAssociation#preload =>
              - Association#associated_records_by_owner => load_records =>
                - records_for =>
                  - build_scope => ...
                  - Relation#where(association_key_name => owner_keys) (e.g. Post::AR_Relation#where(user_id: <user ids>))
                - Relation#load => exec_query ...
              - association.target.concat


[ Transaction ]
(automatic transactional update (create, save, destroy as well))
- AR::Persistence#update =>
  - AR::Transactions#with_transaction_returning_status
    - AR::Base.transaction (SEE BELOW)
      - add_to_transaction => (book keeping for possible callbacks (e.g. after_commit, after_rollback))
      - (yield)
        - AttributeAssignment#assign_attributes
        - Persistence#save
      - clear_transaction_record_state for exception AR::Rollback
      - raise ActiveRecord::Rollback if save returned false

(transaction method)
- AR::Transactions.transaction (AR::Base.transaction) =>
  - CA::DatabaseStatements#transactioin =>
    - CA::TransactionManager#within_new_transaction =>
      - AbstractAdapter#lock
      - Monitor#synchronize
        - TransactionManager#begin_transaction =>
          - Monitor#synchronize
            - @stack.push(RealTransaction.new) =>
              - AbstractMysqlAdapter#begin_db_transaction => execute "BEGIN"
        - (yeild block provided to transaction)
        - (if Exception)
          - rollback_transaction => ...
        - (otherwise)
          - commit_transaction => ...


[ Simple example ]
(assume User < ActiveRecord::Base)
- User.count (deletated to :all) =>
  - ActiveRecord::Scoping::Named.all =>
    - default_scoped =>
      - ActiveRecord::Scoping::Default::ClassMethods#build_default_scope => ?
      - ActiveRecord::Core.relation (returns instance of User::ActiveRecord_Relation) =>
        - arel_table => Arel::Table.new
        - Relation.create =>
          - ActiveRecord::Delegation::ClassMethods#relation_class_for =>
          - User::ActiveRecord_Relation.new => AR::Relation#initialize
        - (extend QueryMethods)
      - ActiveRecord::SpawnMethods#spawn =>
  - ActiveRecord::Calculations#count => calculate => perform_calculation =>
    - execute_simple_calculation =>
      - User.connection (this is the entry for AR::Base.connection, SEE ABOVE)
      - ActiveRecord::ConnectionAdapters::MySQL::DatabaseStatements#select_all =>
        - (super) => CA::QueryCache#select_all =>
          - (super) => CA::DatabaseStatements#select_all =>
            - CA::DatabaseStatements#select => CA::MySQL::DatabaseStatements#exec_query =>
              - execute_and_free => execute =>
                - CA::AbstractMysqlAdapter#execute => Mysql2::Client#query => ...
              - ActiveRecord::Result.new


[ Schema definition/migration utility ]
- rake db:migrate =>
  - (loading routine from application root Rakefile)
    - require_relative 'config/application' => ... require 'active_record/railtie' =>
      - AR::Railtie =>
        - rake_tasks => load "active_record/railties/databases.rake"
    - Rails.application.load_tasks (from root Rakefile) =>
      - Engine#load_tasks => Application#run_tasks_blocks =>
        - Railtie#run_tasks_blocks (for each railties) (which includes above databases.rake)
  - (rake task chain)
    - task environment => Application#require_environment! => require 'config/environment' => ...
    - task load_config
    - AR::Tasks::DatabaseTasks.migrate =>
      - Migrator.migrate => up =>
        - migrations => MigrationProxy.new
        - new =>
          - AR::SchemaMigration.create_table => connection.create_table(...)
          - AR::InternalMetadata.create_table
        - Migrator#migrate =>
          - migrate_without_lock =>
            - for each runnable,
            - execute_migration_in_transaction =>
              - (check if it's already) migrated => load_migrated => Migrator.get_all_versions => SchemaMigration.all_versions
              - ddl_transaction => AR::Base.transaction if !disable_ddl_transaction && adapter.supports_ddl_transactions?
              - MigrationProxy#migrate =>
                - load_migration =>
                  - require 'db/migrate/xxx_create_some_table.rb' (where CreateSomeTable < AR::Migration[<some-version>])
                  - CreateSomeTable.new
                - CreateSomeTable#migrate =>
                  - exec_migration => change (here is user defined method) (include e.g. create_table) =>
                    - Migration#method_missing(:create_table, ...) => connection.create_table (SEE THIS EXAMPLE BELOW)
              - record_version_state_after_migrating =>
                - AR::SchemaMigration.create!(version: ...)
          - record_environment =>
            - AR::InternalMetadata[:environment] = AR::Migrator.current_environment
    - task _dump => task schema:dump =>
      - AR::SchemaDumper.dump => new and #dump =>
        - header, extensions
        - tables =>
          - for each CA::SchemaStatements#tables (as connection.tables) =>
            - data_source_sql => 'SELECT table_name FROM information_schema.tables'
          - table =>
            - print each CA::SchemaStatements#columns => ...
            - indexes_in_create =>
              - print each CA::MySQL::SchemaStatements#indexes => ...
          - foreign_keys


- AR::CA::SchemaStatements#create_table =>
  - create_table_definition => TableDefinition.new
  - yield =>
    - (example: t.string(...))
      AR::CA::ColumnMethods#string =>
      - AR::CA::SchemaDefinition.column =>
        - @columns_hash[name] = new_column_definition =>
          - create_column_definition => ColumnDefinition.new
    - (example: index)
      AR::CA::index => indexes<<
  - schema_creation => SchemaCreation.new
  - SchemaCreation#accept =>
  - AbstractMysqlAdapter#execute => ...

- AR::CA::SchemaStatement#add_foreign_key =>
  - create_alter_table =>
    - TableDefinition.new
    - AlterTable.new
  - AlterTable#add_foreign_key => ForeignKeyDefinition.new
  - schema_creation => SchemaCreation.new
  - sql = SchemaCreation#accept(#<AlterTable>) =>
    - visit_AlterTable =>
      - sql = "ALTER TABLE some_table"
      - visit_AddForeignKey =>
        - "ADD " + visit_ForeignKeyDefition =>
          - "CONSTRAINT some_constraint FOREIGN KEY some_column REFERENCES other_table (other_column)"
  - execute sql => ...

ActiveJob, SideKiq

[ Initialization ]
(config.active_job.query_adapter = :sidekiq)
- ActiveJob::Railtie.initializer "active_job.set_configs"
  - ActiveJob::QueueAdapter.queue_adapter= :sidekiq =>
    - _queue_adapter = interpret_adapter(:sidekiq) => AJ::QueueAdapters::SidekiqAdapter

[ ActiveJob interface ]
(SomeJob < ActiveJob::Base)
- SomeJob.perform_later => AJ::Enqueuing.perform_later =>
  - job_or_instantiate => SomeJob.new
  - Enqueuing#enqueue =>
    - SidekiqAdapter#enqueue (as queue_adapter.enqueue) =>
      - Sidekiq::Client.push => ...


[ Sidekiq (Job runner process) ]
- Sidekiq::CLI#run =>
  - boot_system =>
    - require 'sidekiq/rails' =>
      - Sidekiq::Rails < Rails::Engine (is this for admin sidekiq stats view ?)
    - require '.../config/environment' (load user rails app)
  - Sidekiq::Launcher.new and #run =>
    - Scheduled::Poller#start =>
      - safe_thread
        - (while loop)
          - enqueue => Enq#enqueue_jobs =>
            - Sidekiq.redis
            - Sidekiq::Client.push (pick scheduled job and push to main queue ?)
          - wait => ConnectionPool::TimedStack#pop (sleep a moment ?)
    - Manager#start =>
      - Processor#start =>
        - safe_thread run =>
          - (while loop) process_one =>
            - fetch => get_one => Sidekiq::BasicFetch#retrieve_work =>
              - Sidekiq.redis
              - ::Redis#brpop (this blocks) => ...
              - UnitOfWork.new
            - process =>
              - dispatch => AJ::QueueAdapters::SidekiqAdapter::JobWrapper.new
              - execute_job => JobWrapper#perform
                - AJ::QueueAdapters::SidekiqAdapter::JobWrapper#perform =>
                  - AJ::Execution.execute =>
                    - AJ::Core.deserialize => SomeJob.new
                    - AJ::Execution#perform_now => SomeJob#perform (user implementation)

Caching

[ Initialization ]
- ActionSupport::Cache

[ Use from anywhere ]
- Rails.cache => Rails.application.cache =>

[ Use from view ]
- ActionView::Base#cache =>

Puma (Rack server)

(puma.rb)
rackup 'config.ru'
bind 'unix:///xxx.sock'
prune_bundler
threads 5, 10
workers 2


(config.ru)
require_relative 'config/environment' =>
  - require_relative 'application' =>
    - require_relative 'boot' => require 'bundler/setup'
    - require 'rails/all'
    - Bundler.require
    - YourApplication < Rails::Application
  - Rails.application.initialize => ...
run Rails.application


(bin/puma)
- Puma::CLI.new =>
  - Launcher.new =>
    - @binder = Binder.new
    - prune_bundler => ??
    - Cluster.new(self, @events) =>
      - @phase = 0
      - @workers = []
- Puma::CLI#run =>
  - Launcher#run =>
    - setup_signals =>
      - Signal.trap "SIGUSR1" { phased_restart }
    - Cluster#run =>
      - Binder#parse =>
        - add_unix_listener =>
          - UNIXServer.new
          - UNIXServer#listen
          - @ios<<
      - start_control =>
        - Puma::App::Status.new
        - Puma::Server.new
        - Server#run => ...
      - spawn_workers =>
        - fork
        - (child)
          - worker =>
            - Runner#start_server =>
              - app => @launcher.config.app (e.g. Rails.application)
              - Puma::Server.new(app, ...) =>
                - @binder = Binder.new
              - Server#inherit_binder(@launcher.binder) => @binder = bind
            - @worker_write << "b#{Process.pid} (IPC to parent process)
            - Thread.new (worker health check IPC thread ?)
              - while true, @worker_write << !p<pid>{ "backlog": ..., "running": ... }
            - Server#run =>
              - ThreadPool.new
                (the block defined here will be executed in the thread later in ThreadPool#spawn_thread, SEE BELOW)
              - Reactor.new, #run_in_thread => Thread.new { run_internal } =>
                - while true
                  - IO.select sockets
                  - Client#try_to_finish and ThreadPool#<< client (SEE BELOW for what this does)
              - Thread.new { handle_servers } =>
                - sockets = [check] + @binder.ios
                - while @status == :run
                  - IO.select sockets
                  - Socket#accept_nonblock
                  - Client.new =>
                    - parser = HttpParser.new
                    - @read_header = true (initially header reading mode)
                  - ThreadPool#<< client => spawn_thread =>
                    - Thread.new
                      - while true
                        - block.call (this blog is given to ThreadPool.new SEE ABOVE)
                          - Client#eagerly_finish => try_to_finish =>
                            - (first parse header part (a.k.a. @read_header))
                              - read_nonblock(CHUNK_SIZE)
                              - HttpParser#execute => ?
                              - if HttpParser#finished? setup_body => ...
                              - otherwise return false
                          - if (Client#eagerly_finish returns true)
                            - process_client =>
                              - while true
                                - handle_request =>
                                  - @app.call(env) => (Rails.application.call)
                                  - fast_write => syswrite
                          - otherwise Reactor#add(client) (Reactor will Client#try_to_finish later on, SEE ABOVE)
            - Thread#join
        - (parent)
          - @workers << Worker.new(...)
      - while (@status == :run)
        - if @phased_restart start_phased_restart (SEE BELOW)
        - check_workers =>
          - cull_workers
          - spawn_workers
          - if all_workers_booted? (this line make sure there's always at least one worker living)
            - find @workers where Worker@phase != Cluster@phase (point is one worker at each this check_workers)
            - Worker#term => Process.kill
        - IO.select([read], nil, nil, WORKER_CHECK_INTERVAL) (IPC with children workers)
        - (for IPC from spawned worker process) Worker#boot!


(On 'SIGUSR1' by pumactl phased-restart)
- Launcher#phased_restart =>
  - Cluster#phased_restart =>
    - @phased_restart = true
    - wakeup! => write "!"
- (from Cluster instance's main loop, SEE ABOVE)
  - Cluster#start_phased_restart => @phase += 1

Authentication

[ Data structure ]

Warden::Manager
Warden::Proxy
Warden::Strategies::Base

Devise::Engine
Devise::Mapping
Devise::Models
Devise::Strategies::DatabaseAuthenticatable (inherits Warden::Strategies::Base)
ActionDispatch::Routing::Mapping (patching)
Devise::Controllers::Helpers
Devise::Controllers::SignInOut


[ Initialization ]

- Devise::Engine
  - config.app_middleware.use Warden::Manager

- Devise.setup =>
  - require 'devise/orm/activerecord' =>
    - ActiveSupport.on_load(:active_record) { extend Devise::Models }

- Devise::Models#devise(:database_authenticatable) =>
  - include Devise::Models::Authenticatable
  - Devise::Models.const_get('DatabaseAuthenticatable') => Devise::Models::DatabaseAuthenticatable
  - include Devise::Models::DatabaseAuthenticatable

- 'devise/models/database_authenticatable' require 'devise/strategies/database_authenticatable' =>
  - Warden::Strategies.add(:database_authenticatable, Devise::Strategies::DatabaseAuthenticatable)

- ActionDispatch::Routing::Mapping#devise_for('users') =>
  - Devise.add_mapping('users') => Devise::Mapping.new
  - devise_scope('users') =>
    - constraint (request.env["devise.mapping"] = Devise.mappings['users'])
  - devise_session => resource :session (route definition)

- ActionDispatch::Routing::RouteSet.prepend Devise::RouteSet
- Devise::RouteSet#finalize! =>
  - Devise.configure_warden! =>
    - for each Devise::Mapping in Devise.mappings
      - Warden::Config#scope_defaults mapping.name, strategies: mapping.strategies =>
      - set Warden::Config#serialize_into_session be Devise::Models::Authenticable::ClassMethods#serialize_into_session
      - same as Warden::Config#serialize_from_session


[ Request lifecycle (rack call lifecycle) ]

- Wraden::Manager#call =>
  - env['warden'] = Warden::Proxy.new(env, self)

- Devise::SessionsController#create =>
  - Devise::Controllers::Helpers#warden => request.env['warden']
  - Warden::Proxy#authenticate! =>
    - _perform_authentication =>
      - _run_strategies_for =>
        - strategy = _fetch_strategy =>
          - Warden::Strategies[:database_authenticatable] => Devise::Strategies::DatabaseAuthenticatable
          - Devise::Strategies::DatabaseAuthenticatable.new(@env, scope)
        - Devise::Strategies::DatabaseAuthenticatable#_run => authenticate! =>
          - resource = Devise::Models::DatabaseAuthenticable::ClassMethods#find_for_database_authentication =>
            - Authenticable::ClassMethods#find_for_authentication =>
              - to_adapter
      - set_user => ?
    - throw(:warden) on unsuccesful authentication
  - DeviseController#resource_name => devise_mapping => request.env["devise.mapping"] (setup on constraint of devise_scope)
  - Devise::Controllers::SignInOut#sign_in => ?

(on throw(:warden) during warden.authenticate!)
- Warden::Manager#call catch(:warden) and process_unauthentiacted =>
  - call_failure_app => config.failure_app.call (e.g. by default this will be Devise::Delegator#call) =>
    - Devise::FailureApp#call (FailureApp < ActionController::Metal) => respond => redirect =>
      - flash[:alert] = i18n_message => format User.authentication_keys
      - redirect_to redirect_url => scope_url