This is first tech post about different aspects of creating Ruby on Rails microservices. This microservice is a complex wizard form. It allows to fill the data in step-by-step wizard.
It works without any persistence layer, just passing the data through. Ruby on Rails microservice without database.
To work with forms without any database at the backend our first decision was using the ActiveModel. It helps us to create the models with a given set of fields, render it in the web form, send on a server, and validate. So, we needed some kind of Form Objects.
class Box include ActiveModel::Model cattr_reader :allowed_attributes do %i(packing clothes drawings) end validates(*allowed_attributes, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 100, allow_nil: true }) end
While developing we find out that a Box should have a default parameters. For instance, zeros. To make things working we used code like this.
Box.new(packing: 0, clothes: 0, drawings: 0)
There are 3 attributes only 🙂
After some time, the following code appeared in the app:
module MyCoolAttribute extend ActiveSupport::Concern module ClassMethods def init_all_with(value) opts = allowed_attributes.each_with_object({}) do |key, acc| acc = value end new(opts) end end end class Box include MyCoolAttribute end # usage @box = Box.init_all_with(0)
Another way was using fabrics
class Box def self.new_with_defaults new(packing: 0, clothes: 0, drawings: 0) end end
It becomes boring 🙁
Fortunately there are plenty of alternatives to ActiveModel, so called attributes-on-steroids libraries. These are:
Now we use dry-types from the Dry-rb collection as the replacement of ActiveModel
# declaring types module Types include Dry::Types.module ZeroBox = Int.optional.default(0) end class Box < Dry::Types::Struct # allows to not declare the whole set of options constructor_type :schema attribute :packing, Types::ZeroBox attribute :clothes, Types::ZeroBox attribute :drawings, Types::ZeroBox end Box.new.to_h # {:packing => 0, :clothes => 0, :drawings => 0}
That’s it!
P.S. dry-rb is the collection of the following small libraries, we will talk cover some of them in the future.
- dry-validation Powerful data validation based on predicate logic
- dry-types Flexible type system with many built-in types
- dry-transaction Business transaction DSL
- dry-container Simple and thread-safe IoC container
- dry-auto_inject Container-agnostic constructor injection mixin
- dry-equalizer Simple mixin providing equality methods
- dry-component Organize your code into reusable components
- dry-configurable Thread-safe configuration mixin
- dry-logic Predicate logic with composable rules
- dry-result_matcher Expressive match API for operating on Either results