Decoupling with Lotus

Share this article

cover

There’s no question that the term “DSL-Oriented-Programming” could be applied to Rails.

According to the Lotus website (no longer available), Lotus is a web framework that aims to make Ruby web development more object-oriented. Lotus features encapsulation of MVC layers and no monkey-patching.

We won’t be making an app in Lotus, but we’ll be looking at its constituents to see what it offers.

Rails is magical for a reason. When we see that Lotus takes away a lot of that magic, it’s easy to be skeptical. So, try to keep an open mind.

How It’s Different

On the surface, Lotus initially looks like another “me-too” framework that accomplishes things just like Rails. But as we’ll see, Lotus also supports a less opinionated, more object-oriented approach to building apps.

Instead of one framework, Lotus is currently composed of several gems:

  • Lotus::Router
  • Lotus::Utils
  • Lotus::Validations
  • Lotus::Model
  • Lotus::View
  • Lotus::Controller
  • Lotus::Assets (not officially part of framework yet)

The gems are designed to be as independent as possible, so if you like, you can just use Lotus::Router for routing and Mongoid or Sequel for models.

Keep in mind that Lotus is early in development, so some of this is likely to change.

Lotus::Router

In Rails, it’s easy to forget that everything goes through Rack underneath the surface. Lotus, on the other hand, lives just a couple houses down from Rack.

$ gem install lotus-router --version 0.2.0
$ gem install rack --version 1.6.0

Lotus lets you place a different Rack app at each route. So you could have a sinatra app persisting to mongoid at ‘/dashboard’ and a camping app persisting to PostgreSQL at ‘/posts’.

Here’s a very basic app that uses Lotus::Router.

require 'lotus/router'
require 'rack'

@router = Lotus::Router.new
@router.get '/', to: ->(env) { [200, {}, ['Hello from Lotus']] }

def app(env)
@router.call(env)
end

Rack::Handler::WEBrick.run method(:app)

env is the hash that contains information about the request and the server. It’s the lingua franca of the Rack middleware system.

If you like, Lotus::Router does provide a very familiar DSL.

Lotus::Router.new do
get '/', to: ->(env) { [200, {}, ['Hello World']] }
get '/some-rack-app', to: SomeRackApp.new
get '/posts', to: 'posts#index'
get '/posts/:id', to: 'posts#show'

mount Api::RackApp, at: '/api'

namespace 'admin' do
get '/users', to: AdminUsers::Index
end

resources 'users' do
member do
patch '/award'
end

collection do
get '/search'
end
end
end

Lotus::Model

$ gem install lotus-model -- version 0.2.0

The way lotus handles models is probably the least magical thing about it.
Right now the persistent data situation is broken up into:

  • Lotus::Model::Entity – defines attributes (for objects, not tables)
  • Lotus::Model::Repository – mediates between entities and persistence layer (this is where you call CRUD methods)
  • Lotus::Model::Mapper – keeps entities separate from database details
  • Lotus::Model::Adapter – implements persistence logic
  • Lotus::Model::Adapters::SomeAdapter::Query – query implementation for an
    adapter
  • Lotus::Model::Adapters::SomeAdapter::Command – gets passed the query in
    the adapter
  • Lotus::Model::Adapters::SomeAdapter::Collection – insertion/manipulation
    implementation for an adapter

So, about 50 hours of Gregg Pollack talking and pointing. Here’s what the system
looks like in practice:

require 'lotus/model'

class Post
include Lotus::Entity
attributes :title, :content
end

class PostRepository
include Lotus::Repository
end

Lotus::Model.configure do
adapter type: :sql, uri: 'postgres://localhost/database'

mapping do
collection :posts do
entity Post
respository PostRepository

attribute :id, Integer
attribute :title, String
attribute :content, String
end
end
end

Lotus::Model.load!

post = Post.new(title: 'First Post', content: 'This is my first post.')
post = PostRepository.create(post)

puts post.id # => 1

p = PostRepository.find(post.id)
p == post # => true

Obvious question: what about the database schema? Migrations, how do they work?

Right now, you’re on your own. The latest information regarding migrations is a trello
card
marked as “Planned”. In gitter, the Sequel gem is mentioned as a way of going about migrations, although you could use it for the entire model as well.

Lotus::Validations

I you haven’t, install the gem.

$ gem install lotus-validations --version 0.2.2

Lotus validations work like ActiveRecord in that a class specifies attributes and their associated validations. Unlike in ActiveRecord, Lotus validations are separate from persistence logic. This makes it easy to use them for a wider variety of things.

require 'lotus/validations'

class User
include Lotus::Validations

attribute :name, format: /\A[a-zA-z]+\z/
end

robert = User.new(name: 'Robert')
puts robert.valid? # => true

invalid = User.new(name: '!Robert')
puts invalid.valid? # => false

Note that since the validations library does not deal with persistence, uniqueness validations are not implemented. According to the Lotus philosophy, uniqueness should be enforced in the database itself (MongoDB example).

Lotus::View

The Lotus view layer is based on the principle of separation between views and templates. Lotus views are objects, so they can be tested.

$ gem install lotus-view --version 0.3.0

You might be wondering what a view object would do. Lotus views take on some of the responsibilities typically handled by controllers in Rails, like rendering and data-sharing.

Here’s what using a Lotus view looks like:

require 'lotus/view'

module Posts
class Index
include Lotus::View
end

class XMLIndex < Index
format :xml
end
end

Lotus::View.configure do
root 'app/templates'
end

Lotus::View.load!

posts = PostRepository.all

# Uses Posts::Index and "posts/index.html.erb"
Posts::Index.render(format: :html, posts: posts)

# Uses Posts::XMLIndex and "posts/index.xml.erb"
Posts::Index.render(format: :xml, posts: posts)

Lotus::Controller

In Lotus, a controller is nothing more than a plain Ruby module that contains a group of actions.

$ gem install lotus-controller --version 0.3.1

In Rails, an action is a method on a controller. In Lotus, an Action is an
object.

require "lotus/controller"

class Show
include Lotus::Action

def call(params)
self.status = 210
self.body = 'Hello World'
self.headers.merge!({ 'X-Some-Custom-Header' => 'TRUDAT'})
end
end

show = Show.new
puts show.call({ some_param: 5}).inspect
# => [210, {"X-Some-Custom-Header"=>"TRUDAT", "Content-Type"=>"application/octet-stream; charset=utf-8"}, ["Hello World"]]

The main action method to be concerned with is #call which returns a serialized Rack::Response.

Because actions are objects, we can use dependency injection to create generic actions. The consequence is it’s easier to create testable actions.

require "lotus/controller"

class Show
include Lotus::Action

def initialize(repository = Post)
@repository = repository
end

def call(params)
@whatever = @repository.find params[:id]
end
end

show = Show.new(MemoryPostRepository)
show.call({ id: 5 })

Lotus lets you supply before callbacks like Rails’ before_action. Except now you’re doing it in the action instead of the controller. This might seem to defeat the purpose, but you still want reusable methods in your actions in case you want to add them to other actions. For example, maybe you have an authentication method that you want to add to an action just by including a mixin and setting it in before.

require "lotus/controller"
require "your_project/mixins/authentication

class Show
include Lotus::Action
include YourProject::Mixins::Authentication

before { authenticate! }

def call(params)
end
end

In a Rails controller, actions share data with views by setting instance variables and sharing context. Lotus accomplishes this with the expose DSL command. By default, all Lotus actions expose #params and #errors.

require "lotus/controller"

class Show
include Lotus::Action

expose :post

def call(params)
@post = Post.find params[:id]
end
end

action = Show.new
action.call({ id: 5 })

puts action.post.id # => 5
puts action.exposures # => { post: <a Post object> }

For security, Lotus actions provide whitelisting of allowed parameters.

require 'lotus/controller'

class Upload
include Lotus::Action

params do
param :title
param :content
end

def call(params)
puts params[:title] # => "The Foo in The Bar"
puts params[:l33texploit] # => nil
end
end

Note: You do not do any rendering in Lotus actions. Rendering occurs in views as shown in the previous section. Lotus controllers are just pure HTTP endpoints for the router.

Lotus::Utils

$ gem install lotus-utils --version 0.3.3

If you have used Ruby on Rails, you have used ActiveSupport. It’s the module that provides syntactic sugar like this:

28.days.ago

Currently, Lotus::Utils is not a Lotus-equivalent of Rails’ ActiveSupport. It’s mostly a collection of various data type enhancements. Something to note is the lack of monkey-patching.

Here’s what you get at the moment:

  • Lotus::Utils::String – lets you #underscore and #demodulize
  • Lotus::Utils::PathPrefix – subclasses Lotus::Utils::String
  • Lotus::Utils::Class – lets you load classes by pattern name from a given
    namespace
  • Lotus::Utils::Hash – wraps Hash and adds some methods like
    #symbolize! and #stringify!.
  • Lotus::Utils::Attributes – sets/gets a Lotus hash where all the keys are
    coerced to strings (think ActiveSupport::HashWithIndifferentAccess), performs a deep duplication with #to_h
  • Lotus::Utils::LoadPaths – “A collection of loading paths” (unsure what
    this really does for us, please leave a comment if you know)
  • Lotus::Utils::Kernel – contains type coercion methods
  • Lotus::Utils::ClassAttributes – “inheritable class level variable
    accessors”
  • Lotus::Utils::Callback – stores and runs procs
  • Lotus::Utils::Deprecation – used to print deprecation messages

These different components in the lotus-utils gem will need to be required separately (except in a couple of situations where one depends on another).

require 'lotus/utils/string'

puts Lotus::Utils::String.new('MyClass').underscore
# => 'my_class'
puts Lotus::Utils::String.new('Lotus::Utils::Hash').demodulize
# => 'Hash'
puts Lotus::Utils::String.new('my_class').classify
# => 'MyClass'

Note that it does not look like the goal is to replace ActiveSupport.

Here is a snippet of conversation on gitter where someone asks the maintainer about adding ActiveSupport-style calculations:

lotus_chat

So, if you were hoping to have an ActiveSupport that you can load in piecemeal instead of autoloading monkey-patches, it looks like you will have to wait.

Lotus::Assets

lotus-assets is not official just yet, but a version 0.0.0 gem is up. Right now the trello roadmap lists “Assets helpers” and “Development precompilers (CoffeeScript and SASS)” as in
development.

Conclusion

Lotus certainly makes some interesting points. It probably had many of you at “no monkey patching”. On the other hand, the magic in Rails is what brings the boys to the Yard. Does it really need to be fixed by purists? You decide. At the very least, if the lack of testability or reusability in Rails frustrates you, this is the project you want to be working on.

Luca Guidi, the creator of Lotus, put out this post at the beginning of the year to discuss Lotus’ roadmap. It’s definitely worth a read, so check it out.

Robert QuallsRobert Qualls
View Author

Robert is a voracious reader, Ruby aficionado, and other big words. He is currently looking for interesting projects to work on and can be found at his website.

GlennG
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week