Rebuilding Rails - Free Chapters
Rebuilding Rails - Free Chapters
Rebuilding Rails - Free Chapters
Working Through!
Cheating!
11
1. Zero to It Works!!
13
In the Rough!
13
15
17
Review!
18
In Rails!
19
Exercises!
21
21
22
23
27
Sample Source!
27
On the Rack!
27
Routing Around!
28
It Almost Worked!!
32
Review!
34
Exercises!
34
34
36
37
In Rails!
37
39
Sample Source!
39
42
44
Putting It Together!
45
Review!
47
Exercises!
48
48
49
In Rails!
52
4. Rendering Views!
53
Sample Source!
53
53
55
57
Controller Names!
58
Review!
59
Exercises!
59
59
62
63
In Rails!
64
3
5. Basic Models!
66
Sample Source!
66
File-Based Models!
66
71
Queries!
72
73
Review!
75
Exercises!
76
76
77
78
In Rails!
79
6. Request, Response !
80
Sample Source!
80
80
83
Review!
86
Exercises!
86
86
87
In Rails!
88
89
Sample Source!
89
89
90
4
91
92
93
96
98
Review!
100
Exercises!
100
100
102
102
In Rails!
103
8. Rack Middleware!
105
Sample Source!
105
105
107
109
Built-In Middleware!
110
Third-Party Middleware!
112
113
114
Review!
116
Exercises!
42
42
42
42
5
In Rails!
43
9. Real Routing!
44
Sample Source!
44
Routing Languages!
44
45
49
Configuring a Router!
50
53
56
Review!
58
Exercises!
58
58
59
59
In Rails!
60
42
Ruby 2.0!
42
Windows!
42
Mac OS X!
42
Ubuntu Linux!
42
Others!
43
43
Windows!
43
Mac OS X!
43
Ubuntu Linux!
43
6
Others!
43
Bundler!
43
SQLite!
44
Windows!
44
Mac OS X!
44
Ubuntu Linux!
44
Others!
45
Other Rubies!
45
Working Through
Each chapter is about building a system in a Rails-like framework,
which well call Rulers (like, Ruby on Rulers). Rulers is much
simpler than the latest version of Rails. But once you build the
simple version, youll know what the complicated version does
and a lot of how it works.
Later chapters have a link to source code -- thats what bookstandard Rulers looks like at the end of the previous chapter. You
can download the source and work through any specific chapter
youre curious about.
Late in each chapter are suggested features and exercises.
Theyre easy to skip over, and theyre optional. But youll get
much more out of this book if you stop after each chapter and
think about what you want in your framework. What does Rails
do that you want to know more about? What doesnt Rails do but
you really want to? Does Sinatra have something awesome?
The best features to add are the ones you care about!
Cheating
You can download next chapters sample code from GitHub
instead of typing chapter by chapter. Youll get a lot more out of
the material if you type it yourself, make mistakes yourself and,
yes, painstakingly debug it yourself. But if theres something you
just cant get, use the sample code and move on. Itll be easier
on your next time through.
It may take you more than once to get everything perfectly. Come
back to code and exercises that are too much. Skip things but
come back and work through them later. If the books version is
hard for you to get, go read the equivalent in Rails, or in a simpler
framework like Sinatra. Sometimes youll just need to see a
concept explained in more than one way.
Oh, and you usually dont get the source code to the exercises,
just the main chapter. Theres a little bit of challenge left even for
cheaters. Youre welcome.
At the end of the chapter are pointers into the Rails source for the
Rails version of each system. Reading Rails source is optional.
But even major components (ActiveRecord, ActionPack) are still
around 25,000 lines - short and readable compared to most
frameworks. And generally youre looking for some specific
smaller component, often between a hundred and a thousand
lines.
Youll also be a better Rails programmer if you take the time to
read good source code. Rails code is very rich in Ruby tricks and
interesting examples of metaprogramming.
10
11
12
1. Zero to It Works!
Now that youre set up, its time to start building. Like Rails, your
framework will be a gem (a Ruby library) that an application can
include and build on. Throughout the book, well call our
framework Rulers, as in Ruby on Rulers.
In the Rough
First create a new, empty gem:
$ bundle gem rulers
create rulers/Gemfile
create rulers/Rakefile
create rulers/LICENSE.txt
create rulers/README.md
create rulers/.gitignore
create rulers/rulers.gemspec
create rulers/lib/rulers.rb
create rulers/lib/rulers/version.rb
Initializating git repo in src/rulers
13
gem.authors =
gem.email =
gem.homepage =
gem.summary =
gem.description =
["Singleton Ruby-Webster"]
["webster@singleton-rw.org"]
""
%q{A Rack-based Web Framework}
%q{A Rack-based Web Framework,
but with extra awesome.}
You'll also want to make sure to use your library. Add a Gemfile:
# best_quotes/Gemfile
source 'https://rubygems.org'
gem 'rulers' # Your gem name
Racks run means call that object for each request. In this
case the proc returns success (200) and Hello, world! along with
the HTTP header to make your browser display HTML.
Now you have a simple application which shows "Hello, world!"
You can start it up by typing "rackup -p 3001" and then pointing a
web browser to "http://localhost:3001". You should see the text
"Hello, world!" which comes from your config.ru file.
16
(Problems? If you cant find the rackup command, make sure you
updated your PATH environment variable to include the gems
directory, back when you were installing Ruby and various gems!
A ruby manager like rvm or rbenv can do this for you.)
Build the gem again and install it (gem build rulers.gemspec; gem
install rulers-0.0.1.gem). Now change into your application
directory, best_quotes.
Now you can use the Rulers::Application class. Under config,
open a new file config/application.rb and add the following:
# best_quotes/config/application.rb
require "rulers"
17
module BestQuotes
class Application < Rulers::Application
end
end
Now when you type "rackup -p 3001" and point your browser to
"http://localhost:3001", you should see "Hello from Ruby on
Rulers!". You've made an application and it's using your
framework!
Review
In this chapter, you created a reusable Ruby library as a gem.
You included your gem into a sample application. You also set up
a simple Rack application that you can build on using a Rackup
file, config.ru. You learned the very basics of Rack, and hooked
all these things together so that they're all working.
From here on out you'll be adding and tweaking. But this chapter
was the only time you start from a blank slate and create
something from nothing. Take a bow!
18
In Rails
By default Rails includes not one, but five reusable gems. The
actual "Rails" gem contains very little code. Instead, it delegates
to the supporting gems. Rails itself just ties them together.
Rails allows you to change out many components - you can
specify a different ORM, a different testing library, a different Ruby
template library or a different JavaScript library. So the
descriptions below arent always 100% accurate for applications
that customize heavily.
Below are the basic Rails gems -- not dependencies and libraries,
but the fundamental pieces of Rails itself.
ActiveSupport is a compatibility library including methods that
aren't necessarily specific to Rails. You'll see ActiveSupport
used by non-Rails libraries because it contains such a lot of
useful baseline functionality. ActiveSupport includes methods
like how Rails changes words from single to plural, or
CamelCase to snake_case. It also includes significantly
better time and date support than the Ruby standard library.
ActiveModel hooks into features of your models that aren't
really about the database - for instance, if you want a URL for
a given model, ActiveModel helps you there. It's a thin
wrapper around many different ActiveModel implementations
to tell Rails how to use them. Most commonly, ActiveModel
implementations are ORMs (see ActiveRecord, below), but
they can also use non-relational storage like MongoDB,
Redis, Memcached or even just local machine memory.
ActiveRecord is an Object-Relational Mapper (ORM). That
means that it maps between Ruby objects and tables in a
SQL database. When you query from or write to the SQL
19
20
Exercises
Exercise One: Reloading Rulers
Let's add a bit of debugging to the Rulers framework.
# rulers/lib/rulers.rb
module Rulers
class Application
def call(env)
`echo debug > debug.txt`;
[200, {'Content-Type' => 'text/html'},
["Hello from Ruby on Rulers!"]]
end
end
end
21
Have you seen &:+ before? Its a fun trick. :+ means the
symbol + just like :foo means the symbol foo. The & means
pass as a block -- the code block in curly-braces that usually
comes after. So youre passing a symbol as if it were a block.
Ruby knows to convert a symbol into a proc that calls it. When
you do it with plus, you get add these together.
Now add require "rulers/array" to the top of lib/rulers.rb.
That will include it in all Rulers apps.
Youll need to go into the rulers directory and git add . before you
rebuild the gem (git add .; gem build rulers.gemspec; gem install
rulers-0.0.1.gem). Thats because rulers.gemspec is actually
calling git to find out what files to include in your gem. Have a
look at this line from rulers.gemspec:
gem.files = `git ls-files`.split($/)
22
git ls-files will only show files git knows about -- the split is just to
get the individual lines of output. If you create a new file, be sure
to git add . before you rebuild the gem or you wont see it!
Now with your new rulers/array.rb file, any application including
Rulers will be able to write [1, 2, 37, 9].sum and get the sum of the
array. Go ahead, add a few more methods that could be useful to
the applications that will use your framework.
Exercise Three: Test Early, Test Often
Since were building a Rack app, the rack-test gem is a
convenient way to test it. Lets do that.
Add rack-test as a development (not runtime) dependency to your
gemspec:
# rulers/rulers.gemspec, near the bottom
# ...
gem.add_runtime_dependency "rack"
gem.add_development_dependency "rack-test"
gem.add_development_dependency "test-unit"
end
23
The require_relative just means require, but check from this files
directory, not the load path. Its a fun, simple trick.
This test creates a new TestApplication class, creates an
instance, and then gets / on that instance. It checks for error
with last_response.ok? and that the body text contains Hello.
25
The line get / above can be post /my/url if you prefer, or any
other HTTP method and URL.
Now, write at least one more test.
26
Sample Source
Sample source for all chapters is on GitHub:
http://github.com/noahgibbs/rulers
http://github.com/noahgibbs/best_quotes
Once youve cloned the repositories, in EACH directory do git
checkout -b chapter_2_mine chapter_2 to create a new branch
called chapter_2_mine for your commits.
On the Rack
Last chapters big return values for Rack can take some
explaining. So lets do that. Heres one:
27
Lets break that down. The first number, 200, is the HTTP status
code. If you returned 404 then the web browser would show a
404 message -- page not found. If you returned 500, the browser
should say that there was a server error.
The next hash is the headers. You can return all sorts of headers
to set cookies, change security settings and many other things.
The important one for us right now is Content-Type, which must
be text/html. That just lets the browser know that we want the
page rendered as HTML rather than text, JSON, XML, RSS or
something else.
And finally, theres the content. In this case we have only a single
part containing a string. So the browser would show Hello!
Soon well examine Racks env object, which is a hash of
interesting values. For now all you need to know is that one of
those values is called PATH_INFO, and its the part of the URL
after the server name but minus the query parameters, if any.
Thats the part of the URL that tells a Rails application what
controller and action to use.
Routing Around
A request arrives at your web server or application server. Rack
passes it through to your code. Rulers will need to route the
request -- that means it takes the URL from that request and
answers the question, "what controller and what action handle
this request?" We're going to start with very simple routing.
28
Specifically, were going to start with what was once Rails default
routing. That means URLs of the form http://host.com/category/
action are routed to CategoryController#action.
Under "rulers", open lib/rulers.rb.
# rulers/lib/rulers.rb
require "rulers/version"
require "rulers/routing"
module Rulers
class Application
def call(env)
klass, act = get_controller_and_action(env)
controller = klass.new(env)
text = controller.send(act)
[200, {'Content-Type' => 'text/html'},
[text]]
end
end
class Controller
def initialize(env)
@env = env
end
def env
@env
end
end
end
29
This is very simple routing, so well just get a controller and action
as simply as possible. We split the URL on /. The 4 just
means split no more than 4 times. So the split assigns an empty
string to _ from before the first slash, then the controller, then
the action, and then everything else un-split in one lump. For now
we throw away everything after the second / - but its still in the
environment, so its not really gone.
30
That looks like a decent controller, even if its not quite like Rails.
Youll need to add it to the application manually since you havent
added magic Rails-style autoloading for your controllers yet. So
open up best_quotes/config/application.rb. Youre going to add
the following lines after require rulers and before declaring your
app:
# best_quotes/config/application.rb (excerpt)
$LOAD_PATH << File.join(File.dirname(__FILE__),
"..", "app",
"controllers")
require "quotes_controller"
31
It Almost Worked!
Now, have a look at the console where you ran rackup. Look up
the screen. See that error? Its possible you wont on some
browsers, but its likely you have an error like this:
NameError: wrong constant name
Favicon.icoController
.../gems/rulers-0.0.3/lib/rulers/routing.rb:9:in
`const_get'
.../gems/rulers-0.0.3/lib/rulers/routing.rb:9:in
`get_controller_and_action'
.../gems/rulers-0.0.3/lib/rulers.rb:7:in `call'
.../gems/rack-1.4.1/lib/rack/lint.rb:48:in
`_call'
(...more lines...)
32
33
A horrible hack? Definitely. And eventually well fix it. For now,
that will let you see your real errors without gumming up your
terminal with unneeded ones.
Review
Youve just set up very basic routing, and a controller action that
you can route to. If you add more controller actions, you get more
routes. Rulers 0.0.2 would be just barely enough to set up an
extremely simple web site. Well add much more as we go along.
Youve learned a little more about Rack -- see the Rails section
of this chapter for even more. Youve also seen a little bit of Rails
magic with LOAD_PATH and const_get, both of which well see
more of later.
Exercises
Exercise One: Debugging the Rack Environment
Open app/controllers/quotes_controller.rb, and change it to this:
# best_quotes/app/controllers/quotes_controller.rb
class QuotesController < Rulers::Controller
def a_quote
"There is nothing either good or bad " +
"but thinking makes it so." +
"\n<pre>\n#{env}\n</pre>"
end
end
Now restart the server -- you dont need to rebuild the gem if you
just change the application. Reload the browser, and you should
34
see a big hash table full of interesting information. Its all in one
line, so Ill reformat mine for you:
{"GATEWAY_INTERFACE"=>"CGI/1.1", "PATH_INFO"=>"/
quotes/a_quote", "QUERY_STRING"=>"",
"REMOTE_ADDR"=>"127.0.0.1",
"REMOTE_HOST"=>"localhost",
"REQUEST_METHOD"=>"GET", "REQUEST_URI"=>"http://
localhost:3001/quotes/a_quote", "SCRIPT_NAME"=>"",
"SERVER_NAME"=>"localhost", "SERVER_PORT"=>"3001",
"SERVER_PROTOCOL"=>"HTTP/1.1",
"SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/
1.9.3/2012-11-10)", "HTTP_HOST"=>"localhost:3001",
"HTTP_CONNECTION"=>"keep-alive",
"HTTP_CACHE_CONTROL"=>"max-age=0",
"HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; Intel
Mac OS X 10_6_8) AppleWebKit/537.11 (KHTML, like
Gecko) Chrome/23.0.1271.64 Safari/537.11",
"HTTP_ACCEPT"=>"text/html,application/xhtml
+xml,application/xml;q=0.9,*/*;q=0.8",
"HTTP_ACCEPT_ENCODING"=>"gzip,deflate,sdch",
"HTTP_ACCEPT_LANGUAGE"=>"en-US,en;q=0.8",
"HTTP_ACCEPT_CHARSET"=>"ISO-8859-1,utf-8;q=0.7,*;q=
0.3", "rack.version"=>[1, 1], "rack.input"=>#>,
"rack.errors"=>#>>, "rack.multithread"=>true,
"rack.multiprocess"=>false, "rack.run_once"=>false,
"rack.url_scheme"=>"http", "HTTP_VERSION"=>"HTTP/
1.1", "REQUEST_PATH"=>"/quotes/a_quote"}
That looks like a lot, doesnt it? Its everything your application
gets from Rack. When your Rails controller uses accessors like
post?, under the covers its checking the Rack environment. If
35
In Rails
ActionPack in Rails includes the controllers. Rails also has an
ApplicationController which inherits from its controller base class,
and then each individual controller inherits from that. Your
framework could do that too!
Different Rails versions had substantially different routers. You
can read about the current one in Rails Routing from the Outside
In: http://guides.rubyonrails.org/routing.html. The routing
Rulers is doing is similar to the old-style Rails 1.2 routes (still the
37
38
Sample Source
Sample source for all chapters is on GitHub:
http://github.com/noahgibbs/rulers
http://github.com/noahgibbs/best_quotes
Once youve cloned the repositories, in EACH directory do git
checkout -b chapter_3_mine chapter_3 to create a new branch
called chapter_3_mine for your commits.
Wheres My Constant?
First, lets see how const_missing works.
39
Try putting this into a file called const_missing.rb and running it:
# some_directory/const_missing.rb
class Object
def self.const_missing(c)
STDERR.puts "Missing constant: #{c.inspect}!"
end
end
Bobo
When you run it, you should see Missing constant: :Bobo. So
that would let us know the class was used but not loaded. That
seems promising. But we still get an error.
By the way -- youll see STDERR.puts repeatedly through this
book. When debugging or printing error messages I like to use
STDERR because its a bit harder to redirect than a normal puts
and so youre more likely to see it even when using a log file,
background process or similar.
Youll also see a lot of inspect. For simple structures, inspect
shows them exactly as youd type them into Ruby -- strings with
quotes, numbers bare, symbols with a leading colon and so on.
Much like STDERR.puts, its great to get inspect into your
fingers every time youre debugging -- then when you have a
problem where something is the wrong type, youll generally know
exactly whats wrong. Make it a habit now!
Try creating another file, this one called bobo.rb, next to
const_missing.rb. It should look like this:
# some_directory/bobo.rb
40
class Bobo
def print_bobo
puts "Bobo!"
end
end
41
Despite the package name, this actually installs Ruby 1.9.2, which
should work great.
42
Others
Google for install Ruby on <my operating system>. If youre
using an Amiga, email me!
Others
Google for install git on <my operating system>.
Bundler
Bundler is a gem that Rails 3 uses to manage all the various Ruby
gems that a library or application in Ruby uses these days. The
43
Other gems will be installed via Bundler later. It uses a file called
a Gemfile that just declares what gems your library or app uses,
and where to find them.
SQLite
Windows
Go to sqlite.org. Pick the most recent stable version and scroll
down until you see Precompiled Binaries for Windows. There is
a This is what youll be using.
Mac OS X
Mac OS X ships with SQLite3. If the SQLite3 gem is installed
correctly it should use it without complaint.
Ubuntu Linux
Youll want to use apt-get (or similar) to install SQLite. Usually
thats:
> sudo apt-get install sqlite3 libsqlite3-dev
44
Others
Google for install sqlite3 on <my operating system>.
Other Rubies
If youre adventurous, you know about other Ruby
implementations (e.g. JRuby, Rubinius). You may need to adjust
some specific code snippets if you use one.
45