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