AKIRA MATSUDA: So. Welcome to Ruby on Rails internal talk.
This is titled Ruby on Rails Hacking Guide,
which is deeply inspired by Minero Alki's Legendary Ruby
book titles Rails- for, Ruby Hacking Guide.
So, before I get started, let me just introduce
myself. I am Akira Matsuda - amatsuda on GitHub
and Twitter. I'm a committer of Ruby and Ruby
on Rails projects and I'm from Japan.
As a individual on GitHub, I created a pagination
library called kaminari and active_decoractor, action_args and et cetera,
et cetera. Such libraries.
As a Rails committer, ranked in here, in the
bottom, like, twenty-seventh. I've done 374 commits so far
and, since I made my first commit about six
years ago.
I initially titled this talk as Ruby on Rails
Hacking Guide, but I slightly changed my mind after
I listened to DHH's keynote. This talk should be
titled like Ruby on Rails Reading Guide. Since we're
actually gonna spend the whole forty minutes just for
reading the whole framework code.
And, not gonna talk anything about, like, science. But
it's like, reading a novel, reading a story. So,
why should we, why should we read Rails? I
think we have three big reasons here.
Firstly, as DHH defined, we're software writers, and you
need to read a lot in order to be
a good writer, right. This is obvious. So, we
need to read Rails to be a good Rails
programmer. And if you don't understand any piece of
code in your application, that means you don't really
understand your application.
So, don't let a magical piece of code exist
in your app. I know that Rails applications are
filled with such magics like, for example, like this.
It's such simple, just one line of Ruby code,
in just one method. But it, it really does
a lot of things for you, right. But we
need to be able to explain why, why is
this controller inheriting from ApplicationController inheriting from ActionController::Base? We.
Or, how the http request actually calls this in
index Ruby method. Or why are we setting this
u- users relation into a controller instance variable. Et
cetera, et cetera.
Or, think about this example. This, this, this is
a typical Rails-ish DSL. This reads good. It's pretty
straight forward for readers, as a documentation. But, you're
not an application reader, right. We, we're application writer.
So you have to write this. And as a
writer, you don't, don't usually write when you don't
understand what it means, right.
So, so a question. What, what, what is this
Rails.application.routes.draw, or get in the second line. Looks like
a method call taking a hash parameter. What is
it doing? Who actually knows, who here actually knows
what's gonna happen with this code? Can explain?
Right. So, if you think, if you think you
can explain how this define a route, if, if
you cannot explain. Sorry. If you cannot explain that
means for, it's just a magic. And that's why
you're, you're in this room now, right. So, you'll
soon get to know what the get means.
And as the third reason to read Rails code
is because Rails.has_many :bugs. So, as I told you,
I patched a lot. And mostly I, I patch
Rails because I hit bugs in my production application.
And I guess, you all have the same experience.
I'm sure. Rails is basically buggy. And we need
to deal with them. With the bugs.
So, naturally, Rails programmer job is not just to
maintain the application, but you also need to maintain
Rails framework as well. It's our job. And it's
your job. So we need to be familiar with
the code base.
And strangely, this talk is kind of categorized as
a novice talk. But, I'm not sure. I'm, I'm
gonna talk about the Rails internal. So. I hope
you enjoy.
Let's begin. This is what I'm gonna talk. So,
first of all, Ruby on Rails is a open-source
software on GitHub. There's no C-extension inside. So. I
mean, there's, there's no C-code. 100% Ruby. That means,
if you're a Ruby programmer, you just can read
it and, and you can write it if necessary.
You can get its source code here, by git
cloning from GitHub. By the way, here's a tiny
tip. How you can find the GitHub repository for
the gem you use. You can use, you can
use a gem named gem-src. It's on, it's here
on GitHub. And please take a look at this
repository. This is how you can install it and
how you can use it.
I'm not gonna explain here. But. So, what this
does is after this gem installed, it will automatically
git, git clone this source from GitHub repository. So,
you, you need not to memorize the author's name
or you can not, you don't need to search
on GitHub. As a gem install, it automatically gets
the code from GitHub. It's so useful for social
coders.
Anyway. Let's go back to the Rails source. Firstly,
let's see what's included in the gem. To know
what's included in gem, take a look at the
gemspec. So this is taken from the file rails.gemspec.
Here it defines the files included. It looks like
it doesn't really include any program. It's just documents,
right.
And you can find these declarations. A bunch of
add_dependencies. So that means Rails has no code inside,
but instead if defines several dependencies. It means that
this is a meta, meta-gem, meta-package, to install these
seven gems, right.
And these seven gems are in the Rails repository.
And Rails is a full-stack framework, a full-stack MVC
framework. And all these M and V and C
are included in this one repository. So we have
model, view, and controller.
And we have these gems. I'm sorry it's, can
you read it? I'm sorry it's hard to read.
Anyway. We have ActiveRecord, ActionPack and ActionView. And this
is something called model, and these are views, and
these are controllers.
So, Rails gem is a meta-package that depends on
these seven gems.
So let's take a look at the Rails server
command and, and let me explain how, how the
Rails actually boots. For, starting up the Rails server,
you just need to run Rails server command. So,
what's happening internally when you execute this command?
When I hit the Rails command it actually executes
bin slash rails in your application. So, let's read
that file first. That file should look like this.
It requires config/boot inside your application first, and then
requires rails/commands.
Rails/boot looks like this. It just bundles something. I'm
gonna skip the bundler part, because it's, it's out
of today's scope, but it, it, it kind of
tweaks the $LOAD_PATH nicely.
And so the second line. Rails/commands. It requires rails/commands.
So where is "rails" directory? You can find "rails"
directory in the railties gem. There's a gem called
railties, which I, I didn't explain. So, next chapter
is about railties.
What, what actually railties is? Railties is a very
wisely-crafted tiny library, initially created by Yehuda Katz and
Carl, Carl Lerche. So let's dive into it.
Here's the gemspec. The gemspec says this is about
Rails internals. And the gemspec shows that now this
gem includes some libraries. Ruby files. Actually, it includes
like this. So, when you step inside the railties
lib directory, you'll see these. One directory and one
file called rails dot rb.
So these's the rails directory. And this is how
Rails/commands dot rb looks like in the railties gem.
It requires commands_tasks, and then invokes a CommandTask with
a given command name. ARGV, right.
In this case, so, in this case server, right.
So this is the file named commands_tasks dot rb.
It requires rails/commands/server first. So let's take a look
at rails/commands/server. And this is rails/commands/server. It requires, at
very top, it requires rails. So, then jump to
rails dot rb. Rails dot rb was at the
very top of the repository, right.
So this is how rails dot rb looks like.
It requires rails/application. So, let's go to rails/application. And
it defines a top-level module named Rails.
So rails/application, it requires rails/engine, and it defines a
Rails Application class, inheriting Rails Engine.
So, jump to rails/engine. Now, rails/engine requires rails/railtie and
it, Rails Engine class inherits from Rails Railtie. So,
next file is rails/railtie.
Railtie, it looks like this. It requires rails/initializable and
rails/configuration. And defines Rails Railtie class, that includes Rails
Initializable. So next follow the, next file is rails/initializable.
It defines a Rails Initizable- Initializer.
So, what we learned so far is, railties defines
these core classes, like Application, Engine, Railtie, Initializable. Which
are very, very important. Actually, the core of Rails.
So railties is like this. It's the core of
Rails, in the middle.
And we had something called Plugin as well, but
it was gone since Rails 4.
So, let's skip it. So railties is a library
providing something underneath the Rails Application. And I have
another answer for you. Railties is, according to Wikipedia,
this is the definition of something called Railroad tie.
It's a rectangular piece of wood which is laid
underneath the rails, sometimes called sleeper, railway sleeper.
So this is rail ties. Consider this. The applications
are the trains that run on Rails, and railties,
Rails is the framework, and railties is something lies
underneath the Rails framework. This is why they named
it railties, I guess. This is a beautiful metaphor,
isn't it?
So how does it work? Or, how can we
use it? This is how, how, how it's work.
We can git grep the whole railtie repository, and
you can find some uses of railtie. These are
in, not only in railties, they're actually, these are
in mailers, ActiveRecord, ActionPack, blah, blah, blah.
We're gonna take it. So, so let's take a,
take a look at it, like this. So, each
component has its own railtie, inheriting from Rails::Railtie, like
this. This is how we use railtie.
So, railtie is not only in the core, but
in, we can find railties everywhere. Railtie. It's inside
each component, right. And, so, this is what's written
inside the railtie class.
It has some, mainly we, we see two kinds
of class method calls, config and initializer, right. These
are class method calls to railtie class. So, what's
config?
This is the definition. Railtie config create something. Instantiates
something like Railtie::Configuration. And so config is a instance
of Railtie::Configuration. Railtie::Configuration is something like this. It, it
has one method_missing method. And it accepts any method
call and just stores the given name and arguments
and block into a class-level hash.
So, what is initializer? Initializer looks like this. The,
it just keeps the given block as a proc
instance, with a name, with a given name, right.
So, this is everything. This is what railties is
doing. It, it sets something, some values via config
method call and initializer method call. And to, to
use them for the later use.
And railties is actually class, but it's prohibited to
instantiate. So, you cannot make a instance of the
railtie class. Instead, you just can inherit and, when
inherited, it, it kind of memorizes the, the children
class names.
So, this is a summary what, of what railties
is doing. Railties is a core of Rails that
provides Railtie, Engine, Application, et cetera, and we can
find Railtie in other Rails components like ActiveRecord, ActionPack.
And a railtie can keep config and initializer values.
And Railtie ties up these railties, all right.
So, let's see how, how actually Rails server ties
these up. And how, how Rails server boots. So,
let's go back to rails/commands/commands_tasks dot rb. After it
requiring rails/commands/server, it executes, it requires the application and
then executes server dot start.
Server dot start is defined here, because it was
defined in Rails/commands/server. Server dot start calls, just calls
super. And super is, super class of this class
is Rack::Server.
So, welcome to the Rack world. Now, now we
need to read the Rack code. Oh my god.
This is not Rack hacking guide, so I'm just
gonna skip it. It. Anyway. Finally calls something like
this, via rack/builder. It evaluates this Ruby string. Like,
Rack::Builder dot new taking contents of config dot ru
in your application.
And Rack::Builder just instance_evals the given block. So, this
is what's written inside your application's config dot ru.
It requires config/environment dot rb first, and runs Rails.application.
Run is also a Rack command, I mean, Rack
method.
So, I just, I just, like, explained very fast,
like, how the method, method call goes. How method
call graph looks like. So, I guess you find
it difficult to follow, and how can it actually
follow these method, method call graphs?
Here's one tip. Just, puts caller from, in a
file inside your application. For, example, in config dot
ru file, like this. Just add a puts caller
here. And run Rails server. Then, you'll get an
output like this in your console.
And these, these are basically what I explained. Like,
we see commands/task and server and, like, Rack::Builder.
So, this is very useful. Like, when you get
lost in the Ruby, Ruby jungle, use puts caller
in caller locations, and that'll show you the path
to the place you are.
All right. Anybody, but. The config dot ru file
requires config/environment.rb, and this is, as you know, what's
written inside the config/environment.rb. It requires config/application dot rb
and calls Rails.application.initialize!
So, Rails.application.initialize! is the very, very important method. As
you may notice from, because it, it has an
exclamation mark. This is what's written in application.rb. Since,
Rails 4 point 1, it, it really has nothing
in its, in its class.
But, but it defines a class called Application, inheriting
from Rails::Application.
Before that, it requires rails/all and bundles all the
bundled gems. So this is rails dot, rails/all dot
rb. What it actually does is something like this.
It defines, so, it requires all these railties that
I showed you before. Railties defines configs and initializers,
as we have seen already.
And then, we call Rails.application.initialize exclamation. So, so we,
we define initializers here and load all initializers and
then initialize exclamation, right.
So, this is the initialize method. It run_initializers, and
run_initializers, it calls run, run method for, for each
initialize- initializable object in the initializers collection.
So initializable looks like this. Run just executes the
given argument. I mean, instance_exec through stored proc inside
the same context, right. I mean, in this case,
at sign context is actually the application instance, so
it just instance_execs the stored procs in the initializers.
In the application's context.
So, I said initializers collection. Initializers collection is this.
And initializers, so, initializers, yeah. In rai- railties initializers
was created like this. So, what, what's in the
initializer's collection is instances of Rails initializer, initializable initializer.
Which, each initializer just, just holds a proc instance,
right.
And as we've seen already these are called from
each Railtie classes.
So. This is, this is what initialize do. And,
so let's go back to the server. As we've
seen, the start method calls super. I'm sorry. I'll
skip this.
So. Summary. So, this is what we, what the
railtie does for booting up the Rails server. Finally,
it runs Rails::Application in config dot ru. Do you
remember? It's written in config dot ru file. Run.
Run the application.
So what is run? What is run method? It's
defined like this. And with this, with this definition,
the Rack server calls Rails.application dot call. Rails.application responds
to call. So you can call this. That means
Rails.application is a Rack application.
And Rails.application as a Rack application, it, it has
a call method like this. It calls super. The
super, the Rails.application super class was Rails::Engine. So, so
jump to Rails::Engine.
Rails::Engine has this like call method. OK. The call
goes to app dot call. App is something like
this. It's a config middleware that contains default middleware
stack, and the endpoint, something called endpoint. Look at
this.
So, what's the end point? End point is a
route. Route is a ActionDispatch::Routing::RouteSet by default. OK.
So. So, the next thing is routes. Route is
usually defined in config/routes dot rb like this. So,
let's think of this simple route. Routes, Rails.application.routes is,
is ActionDispatch::Routing::RouteSet. It's defined here in ActionDispatch gem, I'm
sorry. ActionPack gem in action_dispatch directory.
It, it also responds to call, as it's a
Rack application. And call goes to router dot call.
Router is defined like this. Journey::Router.new. So, so router
is a Journey thing.
Then, so Rails.application routes is also a Rack application
that defines endpoint, and it finally goes to the
Journey thing. But for now let's go back to
the Routes.draw DSL.
Here.
So, routes.draw is defined like this. It just evaluates
the given block. In the ActionDispatch routing mapper. So,
this DSL actually calls ActionDispatch::Routing::Mapper#get method, right. And get
method is like this. It calls map_method. map_method calls
match. match calls decomposed_match, and decomposed_match calls add_route.
So, anyway. It finally goes to Mapper#add_route. And Mapper
add_route calls Mapping dot new route. And add_route returns
a, finally, it returns a Journey route. And then,
like, adds the given, sorry, the traded route into
the collection. Journey::Routes collection.
So. So, this is what the Journey does. And,
what's this? Yeah. It finally calls Mapper to_route, Mapping#to_route,
so to_route is defined like this.
I'm just gonna skip this. It's, it just returns
the given app parameter, which was the endpoint given
into the methods parameter. So it finally, finally it,
it returns endpoint. And endpoint is if, if it
responds_to call, just returns that. And if not, call
Dispatcher.
So. So the, the. The hash value can, could
be respond_to call, for example, like this. Or mounted
engine, for example. But usually it doesn't respond to
call, so it calls dispatcher. Dispatcher is a Routing::RouteSet::Dispatcher.
And Dispatcher is also a Rack app, so it
can be called. And the call the Dispatcher goes
to, dispatch method.
Dispatch, so, it finally goes at the bottom line.
It finally goes to controller dot action with a
given action name, dot call. So, so finally, what,
what the router finally calls is this. FooController dot
action method with a given action, action name. And
it returns something callable.
So, this is the route DSL. get "/hello" =>
"foo#bar" maps to FooController dot action('bar').
This is about Journey. And I think I have
no time, so I'm gonna skip this. I'm gonna
skip this long Journey. Anyway, it resolves the route.
So, this is a summary. The, the main point
is that 'foo#bar' becomes a Rack application generated by
FooController dot action('bar').
So, the final chapter. Controllers and actions. This, what's
gonna happen when the server got a request? It,
it, it just calls this. So what is this?
Firstly, the action method is defined like this. It's,
it calls the new dot dispatch inside the block.
New, new means Controller dot new.
So, dispatch, dispatch method creates set, call set_response! and
then calls super. So, what this dispatch method does
is creates Rails response object. And then dispatch method,
then creates request object. Whoops.
All right. It sets the request object into the
instance. And then calls process method. Dispatch finally calls
ActionController::Metal#process. So, this is what's written inside the process
method. process method calls process_action, and process_action calls send_action,
and send_action is aliased to send. Ruby's send method,
right.
So, FooController dot action('bar') creates the Rails Request and
Response objects into the controller instance. Then, it just
calls, calls the method name bar via Ruby send
method, OK.
So, it can be like, FooController dot new, with
something, dot bar. It's a very simple Ruby method
call, finally. So, this is what's happening inside the
HTTP request. When the Rails server accepts the HTTP
request, it, it kind of, the Journey kind of,
somehow, resolves the results, and finally calls this simple
Ruby method. OK?
And I guess it's, yeah. I guess it's time.
I have thirty, thirty seconds left. So.
Obviously there's more and more and more topics about
Rails internals, but unfortunately I had, I just had
forty minutes. Forty minutes was not enough to talk
about, like, ActiveRecord internals or helpers or ActionPack, ActionController
- these things.
So, this is the end of my talk. But
I'm kind of planning to write a book titled
Ruby on Rails Hacking Guide. So, so hopefully. I'm
planning to write it in Japanese first and then
translate it into English later. So, hopefully can find
it, like, next year, maybe.
And, yeah. I'll write everything in that book. So
please wait until the book is coming. Thank you
very much.