Lotus development is going well. The experiment of open source a framework per month is sustainable. I have the time to cleanup the code, write a good documentation and deliver great solutions.
This month, I’m proud to announce Lotus::Controller.
It’s a small but powerful and fast framework. It works standalone or with Lotus::Router and it implements the Rack protocol.
Actions
The core of Lotus::Controller are the actions. An action is an HTTP endpoint. This is the biggest difference with other frameworks where they use huge classes as controllers. Think of Rails, where a single controller is responsible of many actions and holds too much informations. Lotus is simple: one class per action.
require 'rubygems'
require 'lotus/controller'
class Show
include Lotus::Action
def call(params)
@article = Article.find params[:id]
end
end
With this design I wanted to solve a some annoying problems.
An action is an object, whose ownership belongs to its author. She or he, should be free to build their own hierarchy between classes. Lotus offers Ruby modules to be included instead of superclasses to be inherited.
Smaller classes are high cohesive components, where the instance variables have a strong relationship between them. This level of isolation prevents accidental data leaks and less moving parts.
A tiny API of one method makes straightforward the usage of Lotus::Controller.
Its argument (params
), makes it easy to integrate with existing Rack applications.
It returns automatically a serialized Rack response.
A side benefit of this architecture is to take over the control of instantiate an action.
require 'rubygems'
require 'lotus/controller'
class Show
include Lotus::Action
def initialize(repository = Article)
@repository = repository
end
def call(params)
@article = @repository.find params[:id]
end
end
action = Show.new(MemoryArticleRepository)
response = action.call({ id: 23 })
assert_equal response[0], 200
In the example above we define Article
as the default repository, but during the testing we’re using a stub.
In this way we can avoid hairy setup steps for our tests, and avoid to hit the database.
Also notice that we’re not simulating HTTP requests, but only calling the method that we want to examine.
Imagine how fast can be a unit test like this.
Exposures
Instance variables represent the internal state of an object. From an outside perspective we don’t know which is that state. The simplest and recommended way to get this information is to ask for it. This mechanism is called Encapsulation. It’s one of the pillars of Object Oriented Programming.
The instance variables of an action are necessary for returning the body of an HTTP response.
While we’re creating that result from the inside of an action, we can access these informations directly.
External objects can retrieve them with getters. These getters are defined with a simple DSL: #expose
.
require 'rubygems'
require 'lotus/controller'
class Show
include Lotus::Action
expose :article
def call(params)
@article = Article.find params[:id]
end
end
action = Show.new
action.call({ id: 23 })
assert_equal 23, action.article.id
puts action.exposures
# => { article: <Article:0x007f965c1d0318 @id=23> }
No Rendering, Please
Lotus::Controller helps to build pure HTTP endpoints, rendering belongs to other layers of MVC. It provides a private setter for the body of the response.
require 'rubygems'
require 'lotus/controller'
class Show
include Lotus::Action
def call(params)
self.body = 'Hello, World!'
end
end
Views and presenters can manipulate the body of the returned response.
require 'rubygems'
require 'lotus/controller'
class Show
include Lotus::Action
expose :article
def call(params)
@article = Article.find params[:id]
end
end
action = Show.new
response = action.call({ id: 23 })
response[2] = ArticlePresenter.new(action.article).render
Other features
Lotus::Controller offers a set of powerful features: callbacks, automatic management for exceptions and mime types. It also supports redirects, cookies and sessions. They are explained in detail in the README and the API documentation.
Roadmap
On March 23rd I will release Lotus::View.
{% include _lotusml.html %}