Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby on Rails Reading Guide

Ruby on Rails Reading Guide

Slides for RailsConf 2014 talk "Ruby on Rails Reading Guide" http://www.railsconf.com/

Akira Matsuda

April 23, 2014
Tweet

More Decks by Akira Matsuda

Other Decks in Programming

Transcript

  1. pp self name: Akira Matsuda GitHub: amatsuda Twitter: @a_matsuda committer

    of: Ruby, Rails, Haml from: Tokyo, Japan founder of: Asakusa.rb an organizer of: RubyKaigi
  2. Why should we read Rails? We're "Software Writers" (DHH) We'd

    better know what's happening underneath our apps You'll occasionally hit Rails bugs
  3. We're "Software Writers" (DHH) You need to read good software

    if you want to be a good "software writer"
  4. Short code does a lot of works class UsersController <

    ApplicationController def index @users = User.all end end
  5. Rails.has_many :bugs We often hit them in production apps We

    need to x them We need to be familiar with the codebase
  6. Chapters Let's start reading! How Rails server boots (1) What

    is Railties? How Rails server boots (2) Rails and Rack middleware Routes Controllers and actions
  7. Pro tip: Use gem-src How can you nd the GH

    repo for the gem you use? Don't want to manually git-clone every gem that you read or patch? Use gem-src
  8. amatsuda/gem-src Installation (as an rbenv plugin) % git clone https://github.com/

    amatsuda/gem-src.git ~/.rbenv/ plugins/gem-src Con guration % echo "gemsrc_clone_root: ~/ src" >> ~/.gemrc
  9. gem-src (a social coder's best friend) Usage % gem i

    rails #=> automatically git-clone You no more need to memorize the gem author name, or search in GitHub!
  10. rails.gemspec s.add_dependency 'activesupport', version s.add_dependency 'actionpack', version s.add_dependency 'actionview', version

    s.add_dependency 'activemodel', version s.add_dependency 'activerecord', version s.add_dependency 'actionmailer', version s.add_dependency 'railties', version
  11. rails.gemspec It has no code, but instead it de nes

    several dependencies It means that the rails gem is a meta package to install these 7 gems
  12. Directories in the rails project % lsd # aliased to

    ls -ld *(-/DN) actionmailer actionpack actionview activemodel activerecord activesupport railties
  13. Rails is a full-stack MVC framework All the Rails MVC

    components are in this one repository
  14. Summary: Let's start reding! rails gem is a meta package

    that depends on other 7 gems including the whole MVC components
  15. railties.gemspec s.summary = 'Tools for creating, working with, and running

    Rails applications.' s.description = 'Rails internals: application bootup, plugins, generators, and rake tasks.'
  16. rails/commands/ commands_tasks.rb module Rails class CommandsTasks def server ... require

    "rails/commands/server" Rails::Server.new.tap do |server| require APP_PATH ... server.start ennnd
  17. rails/commands/server.rb ... require 'rails' module Rails class Server < ::Rack::Server

    def initialize(*) super ENV["RAILS_ENV"] ||= options[:environment] end def start super ennnd
  18. What we learned Railties de nes these core classes MyApp::Application

    < Application < Engine < Railtie < Initializable
  19. Pro tip: Tools to read it through Vim + unite.vim

    (unite-outline) rdefs RubyMine
  20. Railroad tie "a rectangular support for the rails in railroad

    tracks" "Generally laid perpendicular to the rails" "Railroad ties were traditionally made of wood"
  21. Railties in Rails % git grep Rails::Railtie actionmailer/lib/action_mailer/railtie.rb: class Railtie

    < Rails::Railtie # :nodoc: actionpack/lib/action_controller/railtie.rb: class Railtie < Rails::Railtie #:nodoc: actionpack/lib/action_dispatch/railtie.rb: class Railtie < Rails::Railtie # :nodoc: actionview/lib/action_view/railtie.rb: class Railtie < Rails::Railtie # :nodoc: activemodel/lib/active_model/railtie.rb: class Railtie < Rails::Railtie # :nodoc: activerecord/lib/active_record/railtie.rb: class Railtie < Rails::Railtie # :nodoc: activesupport/lib/active_support/i18n_railtie.rb: class Railtie < Rails::Railtie activesupport/lib/active_support/railtie.rb: class Railtie < Rails::Railtie # :nodoc:
  22. Each component has its own Railtie inheriting Rails::Railtie module ActionMailer

    class Railtie < Rails::Railtie module ActionController class Railtie < Rails::Railtie module ActionDispatch class Railtie < Rails::Railtie module ActionView class Railtie < Rails::Railtie module ActiveModel class Railtie < Rails::Railtie module ActiveRecord class Railtie < Rails::Railtie
  23. What's written in a Railtie? (active_model/railtie.rb) require "active_model" require "rails"

    module ActiveModel class Railtie < Rails::Railtie # :nodoc: config.eager_load_namespaces << ActiveModel initializer "active_model.secure_password" do ActiveModel::SecurePassword.min_cost = Rails.env.test? end ennd
  24. What's written in a Railtie? Mainly we see two kinds

    of class method calls, con g and initializer
  25. What is con g? (railtie/railtie.rb) module Rails class Railtie def

    config @config ||= Railtie::Configuration.new ennnd
  26. Railtie::Con guration? (railtie/con guration.rb) module Rails class Railtie class Configuration

    def method_missing(name, *args, &blk) if name.to_s =~ /=$/ @@options[$`.to_sym] = args.first elsif @@options.key?(name) @@options[name] else super end ennnnd
  27. Railtie::Con guration? It accepts any method call and stores the

    given method name & value in a class level Hash as {method_name: value}
  28. What is initializer? (rails/initializable.rb) module Rails module Initializable class Initializer

    attr_reader :name, :block def initialize(name, context, options, &block) ... @name, @context, @options, @block = name, context, options, block ennd module ClassMethods def initializer(name, opts = {}, &blk) ... initializers << Initializer.new(name, nil, opts, &blk) ennnnd
  29. What is initializer? It just keeps the given block as

    a Proc instance (with a name and options)
  30. What Railtie is doing Setting some values via con g.foobar

    = 'baz' Storing Procs given to initializer method calls
  31. One more thing about Rails::Railtie Prohibited to be instantiated from

    outside self.instance returns the singleton instance Why is it a class despite you can't instantiate?
  32. Why is it a class despite you can't instantiate? In

    order to be able to be inherited
  33. What's gonna happen when Rails::Railtie is inherited? (rails/railtle.rb) module Rails

    class Railtie class << self def inherited(base) unless base.abstract_railtie? subclasses << base ennnnnd
  34. Summary: What is railties? Railties is the core of Rails

    that provides Railtie, Engine, Application, etc. We can nd "Railtie"s in other Rails components A "Railtie" can keep "con g" and "initializer" values Railties ties up these "Railtie"s
  35. rails/commands/ commands_tasks.rb module Rails class CommandsTasks def server ... require

    "rails/commands/server" Rails::Server.new.tap do |server| require APP_PATH ... server.start ennnd
  36. rails/commands/server.rb ... require 'rails' module Rails class Server < ::Rack::Server

    def initialize(*) super ENV["RAILS_ENV"] ||= options[:environment] end def start super ennnd
  37. I'm not gonna do rack code reading here But method

    calls go on like start => wrapped_app => app => build_app_and_options_from_co n g => Rack::Builder.parse_ le => Rack::Builder.new_from_string
  38. Pro tip: `puts caller` Find it difficult to follow the

    method calls? Add `puts caller` to con g.ru le and start the server.
  39. Rails.root/con g.ru # This file is used by Rack-based servers

    to start the application. puts caller require ::File.expand_path('../config/environment', __FILE__) run Rails.application
  40. `puts caller` from con g.ru #{GEM_HOME}/rack-1.5.2/lib/rack/builder.rb:55:in `instance_eval' #{GEM_HOME}/rack-1.5.2/lib/rack/builder.rb:55:in `initialize' #{Rails.root}/config.ru:in

    `new' #{Rails.root}/config.ru:in `<main>' #{GEM_HOME}/rack-1.5.2/lib/rack/builder.rb:49:in `eval' #{GEM_HOME}/rack-1.5.2/lib/rack/builder.rb:49:in `new_from_string' #{GEM_HOME}/rack-1.5.2/lib/rack/builder.rb:40:in `parse_file' #{GEM_HOME}/rack-1.5.2/lib/rack/server.rb:126:in `build_app_and_options_from_config' #{GEM_HOME}/rack-1.5.2/lib/rack/server.rb:82:in `app' #{GEM_HOME}/railties-4.1.0.rc1/lib/rails/commands/server.rb:50:in `app' #{GEM_HOME}/rack-1.5.2/lib/rack/server.rb:163:in `wrapped_app' #{GEM_HOME}/railties-4.1.0.rc1/lib/rails/commands/server.rb:130:in `log_to_stdout' #{GEM_HOME}/railties-4.1.0.rc1/lib/rails/commands/server.rb:67:in `start' #{GEM_HOME}/railties-4.1.0.rc1/lib/rails/commands/commands_tasks.rb:82:in `block in server' #{GEM_HOME}/railties-4.1.0.rc1/lib/rails/commands/commands_tasks.rb:77:in `tap' #{GEM_HOME}/railties-4.1.0.rc1/lib/rails/commands/commands_tasks.rb:77:in `server' #{GEM_HOME}/railties-4.1.0.rc1/lib/rails/commands/commands_tasks.rb:41:in `run_command!' #{GEM_HOME}/railties-4.1.0.rc1/lib/rails/commands.rb:17:in `<top (required)>' ./bin/rails:8:in `require' ./bin/rails:8:in `<main>'
  41. Pro tip: `puts caller` Remember to use caller when you

    get lost in the method call hell.
  42. rails/all.rb require "rails" %w( active_record action_controller action_view action_mailer rails/test_unit sprockets

    ).each do |framework| begin require "#{framework}/railtie" rescue LoadError end end
  43. run_initializers (rails/initializable.rb) module Rails module Initializable def run_initializers(group=:default, *args) ...

    initializers.tsort_each do |initializer| initializer.run(*args) if initializer.belongs_to?(group) end ... ennd
  44. How the initializers are run? Calls #run method for each

    Initializer object in the initializers collection
  45. Initializer#run It simply instance_execs the stored Proc in some context

    stored in @context @context is actually the application instance
  46. All the Initializers (rails/application.rb) module Rails class Application < Engine

    ... def initializers Bootstrap.initializers_for(self) + railties_initializers(super) + Finisher.initializers_for(self) ennnd
  47. What's in the initializers collection? (rails/initializable.rb) module Rails module Initializable

    module ClassMethods def initializer(name, opts = {}, &blk) ... initializers << Initializer.new(name, nil, opts, &blk) ennnnd
  48. What's in the initializers collection? Instances of Rails::Initializable::Initializer Each Initializer

    just holds a Proc instance (and a name and options) given to Rails::Railtie::Initializable.initializ e method call
  49. rails/commands/server.rb ... require 'rails' module Rails class Server < ::Rack::Server

    def initialize(*) super ENV["RAILS_ENV"] ||= options[:environment] end def start super ennnd
  50. Summary: How Rails server boots Require all gems in the

    Gem le (bundler) Load all Railties and Engines (con g/application.rb) De ne YourApp::Application inheriting Rails::Application (con g/application.rb) Run Railtie#initializer de ned in each Railtie, Engine, and Application Load each Engine's load_path and routes, then load con g/initializers/* Run Rails.application (con g.ru) => The next chapter
  51. What's app? (railties/lib/rails/engine.rb) module Rails class Engine ... def app

    @app ||= begin config.middleware = config.middleware.merge_into(default_middleware_stack) config.middleware.build(endpoint) end ennnd
  52. What is con g.middleware? (rails/engine/con guration.rb) module Rails class Engine

    class Configuration < ::Rails::Railtie::Configuration def middleware @middleware ||= Rails::Configuration::MiddlewareStackProxy.new ennnnd
  53. app = default_middlewarestack + endpoint (railties/lib/rails/engine.rb) module Rails class Engine

    ... def app @app ||= begin config.middleware = config.middleware.merge_into(default_middleware_stack) config.middleware.build(endpoint) end ennnd
  54. What's the endpoint? (rails/engine.rb) module Rails class Engine < Railtie

    def endpoint self.class.endpoint || routes end def routes @routes ||= ActionDispatch::Routing::RouteSet.new ... ennnd
  55. Summary: Building middleware stack The Rack server calls `Rails.application.call` It

    calls the Rails default middleware stack and the endpoint The endpoint is "routes"
  56. What is @router? (action_dispatch/routing/route_set.rb) module ActionDispatch module Routing class RouteSet

    ... def initialize(request_class = ActionDispatch::Request) ... @set = Journey::Routes.new @router = Journey::Router.new(@set, { :parameters_key => PARAMETERS_KEY, :request_class => request_class}) @formatter = Journey::Formatter.new @set ennnnd
  57. Rails.application.routes is also a Rack app This is how Rails.application's

    endpoint accepts the Rack request But for now, let's go back to routes.draw DSL
  58. routes.draw (action_dispatch/routing/route_set.rb) module ActionDispatch module Routing class RouteSet def draw(&block)

    ... eval_block(block) ... end def eval_block(block) ... mapper = Mapper.new(self) mapper.instance_exec(&block) ennnnd
  59. AD::Routing::Mapper#get (action_dispatch/routing/mapper.rb) module ActionDispatch module Routing class Mapper def get(*args,

    &block) map_method(:get, args, &block) end def map_method(method, args, &block) ... match(*args, options, &block) ... end def match(path, *rest) ... decomposed_match(_path, route_options) end def decomposed_match(path, options) add_route(path, options) ennnnnd
  60. Mapper#add_route (action_dispatch/routing/mapper.rb) module ActionDispatch module Routing class Mapper ... def

    add_route(action, options) ... mapping = Mapping.new(@set, @scope, URI.parser.escape(path), options) app, conditions, requirements, defaults, as, anchor = mapping.to_route @set.add_route(app, conditions, requirements, defaults, as, anchor) ennnnnd
  61. add_routes returns a Journey::Route (action_dispatch/journey/routes.rb) module ActionDispatch module Journey class

    Routes def add_route(app, path, conditions, defaults, name = nil) route = Route.new(name, app, path, conditions, defaults) route.precedence = routes.length routes << route ennnnd
  62. Mapper#to_route => app (action_dispatch/routing/mapper.rb) module ActionDispatch module Routing class Mapper

    ... def to_route [ app, conditions, requirements, defaults, options[:as], options[:anchor] ] end def app Constraints.new(endpoint, blocks, @set.request_class) ennnnnnd
  63. Constraints.new (action_dispatch/routing/mapper.rb) module ActionDispatch module Routing class Mapper class Constraints

    def self.new(app, constraints, request = Rack::Request) if constraints.any? super(app, constraints, request) else app end ennnnnd
  64. What is endpoint? (action_dispatch/routing/mapper.rb) module ActionDispatch module Routing class Mapper

    class Mapping ... def endpoint to.respond_to?(:call) ? to : dispatcher ennnnnnd
  65. "foo#bar" does not respond_to :call (action_dispatch/routing/mapper.rb) module ActionDispatch module Routing

    class Mapper class Mapping ... def dispatcher Routing::RouteSet::Dispatcher.new( :defaults => defaults) ennnnnnd
  66. Dispatcher is a Rack app (action_dispatch/routing/route_set.rb) module ActionDispatch module Routing

    class RouteSet class Dispatcher def call(env) ... dispatch(controller, params[:action], env) end def dispatch(controller, action, env) controller.action(action).call(env) ennnnnd
  67. Rails.application.router module ActionDispatch module Routing class RouteSet def initialize(request_class =

    ActionDispatch::Request) ... @set = Journey::Routes.new @router = Journey::Router.new(@set, { :parameters_key => PARAMETERS_KEY, :request_class => request_class}) @formatter = Journey::Formatter.new @set ennnnd
  68. Summary: Routes Rails.application.routes is a Rack app Each routes' endpoint

    is a Rack app Each controller's each action is a Rack app e.g. 'foo#bar' becomes a Rack app generated by FooController.action('bar') Everything is a Rack app
  69. controller.action(action).call(env) (action_dispatch/routing/route_set.rb) module ActionDispatch module Routing class RouteSet class Dispatcher

    def call(env) ... dispatch(controller, params[:action], env) end def dispatch(controller, action, env) controller.action(action).call(env) ennnnnd
  70. Creating a Request object, and adding a Proc to the

    stack (action_controller/metal.rb) module ActionController class Metal < AbstractController::Base def self.action(name, klass = ActionDispatch::Request) middleware_stack.build(name.to_s) do |env| new.dispatch(name, klass.new(env)) end ennnd
  71. Creating a Response object (action_controller/metal/rack_delegation.rb) module ActionController module RackDelegation def

    dispatch(action, request) set_response!(request) super(action, request) end def set_response!(request) @_response = ActionDispatch::Response.new @_response.request = request ennnd
  72. AC::Metal#dispatch (action_controller/metal.rb) module ActionController class Metal < AbstractController::Base def dispatch(name,

    request) @_request = request @_env = request.env @_env['action_controller.instance'] = self process(name) to_a ennnd
  73. Controller#process (abstract_controller/base.rb) module AbstractController class Base def process(action, *args) ...

    process_action(action_name, *args) end private def process_action(method_name, *args) send_action(method_name, *args) end alias send_action send ennnd
  74. Summary: Controllers and actions The request goes to FooController.action('bar') FooController.action('bar')

    sets the Rails Request and Response objects to the controller instance Then call goes to FooController#bar via `send` call
  75. end