Look at the code of the last iteration.
Up to this point, we just threw new classes, new models, new tests, and new routes into a few files. This works for small projects, but in a team it is very problematic if everybody works on the same files. So lets change that!
One file should solve one problem. Usually that means that there is exactly one class in it, but with routes, it could look a bit different.
We notice some changes, that we could do:
- Split the
models.rb
file into atext.rb
and anuser.rb
- Split the
controller.rb
into anauthentication.rb
and atext_controller.rb
. - Split the
tests.rb
intouser_test.rb
andserver_test.rb
.
This seems simple enough and it turns out, aside from the second point, it really is.
So lets first take the model:
# models.rb
require 'rubygems'
require 'bcrypt'
class Text
...
end
class User
...
end
So we can split this up into:
# app/models/text.rb
module Models
class Text
...
end
end
# app/models/user.rb
require 'rubygems'
require 'bcrypt'
module Models
class User
...
end
end
- You can define name spaces with
module
. You could also add another name space with your application name.
Now instead of including models
, we would include app/models/user.rb
or
app/models/text.rb
. Because of the name space, we would need to access the
classes via Models::User
, which is very cumbersome, so we include
the
Models
name space into the active one to get that out of the way.
We had one test file, that included everything from everywhere. We can transform
# tests.rb
def relative(path) { ... }
# includes all models:
require relative('controller.rb')
# only used by server test:
require 'rack/test'
include Rack::Test::Methods
describe User do
...
end
describe "Server" do
...
end
can be transformed into
# user_test.rb
def relative(path) { ... }
# explicit about includes:
require relative('../app/model/user.rb')
require relative('../app/model/text.rb')
# include module
include Models
describe User do
...
end
# server_test.rb
def relative(path) { ... }
# This time, we need the text controller:
require relative('../app/controller/text_controller.rb')
require 'rack/test'
include Rack::Test::Methods
include Controllers
describe "Server" do
...
end
This is actually a bit more difficult. If you looked at the Sinatra documentation, there are two ways of doing things: The classic approach, that we took before and the modular. With the modular approach, we define modules (actually classes) for controllers and can then plug them together.
First of all, the require 'sinatra'
has to go, it sets up everything for a
classic application, something we explicitly don't want it to do. Instead, we
define controller classes, that inherit from Sinatra::Base
in the
sinatra/base
file. But surely, we need some configuration constants, like
for example the location of the views
folder. For this, place the following
in the root directory and include it in every file:
# config.rb
...
Views = relative('views')
Fortunately the get
, post
, and delete
definitions are relatively easy to
take appart into the different classes, so we can define
module Controllers
class Authentication < Sinatra::Base
module Helpers
def user
@user
end
end
helpers Sinatra::ContentFor
helpers Helpers
set :views, Views
...
end
end
- You typically need to include the helper methods of extensions with
helpers
. The argument must be a module, not a class. - The
views
folder must be set manually. If this bothers you, you can write a minimal subclass ofSinatra::Base
that does exactly this and subclass every of your controllers from that. - The
@user
variable is not visible from outside this class, but the#user
method can be used. It must be packed into a module, that can later be included viahelpers
.
The TextController
will use some things of the Authentication controller,
namely the user
. Be sure to include the authentication.rb
and then declare
the Authentication
helpers.
# text_controller.rb
class TextController < Sinatra::Base
helpers Sinatra::ContentFor
helpers Authentication::Helpers
set :views, Views
...
end
Finally, you need to edit all the views and replace @user
with user
.
In the main.rb
file at the root of your directory, you need to include all
controllers and define a main application which uses the other classes.
Finally, you run that class.
class Main < Sinatra::Base
enable :sessions
set :views, Views
use Controllers::Authentication
use Controllers::TextController
get("/") { redirect to("/text") }
end
Main.run!
Try it, fix the errors, repeat, but this should allow you to make modular
Sinatra applications. You should also consider to move views for different
controllers into different folders, you can do that with :'path/to/view'
in
the controller. Have fun!