A loose collection of Ruby related code that currently has no specific place to live in.

HasAttributes Module

require 'edgycircle_toolbox/has_attributes'

class Dummy
  include EdgycircleToolbox::HasAttributes
  attributes :a, :b
end

dummy = Dummy.new({ a: 1, b: 2, c: 3 })

dummy.a # => 1
dummy.b # => 2
dummy.c # => NoMethodError

dummy.attributes # => [:a, :b]

CQRS CommandResult

Returning self from add_error, add_event and set_command allows us write concise code like return result.add_error(:authentication_error) unless authenticated?.

require 'edgycircle_toolbox/cqrs/command_result'

command = :command_a
result = EdgycircleToolbox::CQRS::CommandResult.new(command)

result.add_error(:error) # => result
result.add_event(:event) # => result
result.set_command(:command_b) # => result
result.failure? # => true

CQRS Handler Module

A handler class has to implement call(command) and accept a command instance as parameter.

require 'edgycircle_toolbox/cqrs/handler'

class DummyHandler
  include EdgycircleToolbox::CQRS::Handler

  def call(command)
    # Business Logic
  end
end

CQRS Command Module

A command class can have a schema and attributes. The schema can be used by the command bus to validate the data before a command is built. Currently the definitions are redundant, changing this could be an improvement made in the future. Every command has a default attribute id and a schema to validate its presence and make sure it's a string.

The module uses dry-validation for the schema and its validation.

require 'edgycircle_toolbox/cqrs/command'

class DummyCommand
    include EdgycircleToolbox::CQRS::Command

    schema do
      required(:title).filled(:str?)
    end

    attributes :title
  end

CQRS Command Bus

Can build commands from a parameter hash if the data fits the commands schema. A submitted command is passed to the corresponding handler. Events from the command handler get published on the message bus. Both build and submit return a CommandResult.

require 'edgycircle_toolbox/cqrs/command_bus'

command_bus = EdgycircleToolbox::CQRS::CommandBus.new
command_bus.register EnterTicketCommand, EnterTicketHandler

command_result = command_bus.build(parameter_hash)
command_result = command_bus.submit(command_result.command)

CQRS Message Bus

require 'edgycircle_toolbox/cqrs/message_bus'

class RandomEvent
end

class CommonEvent
end

EdgycircleToolbox::CQRS::MessageBus.subscribe([RandomEvent], ->(event) { puts 'Output 1' })
EdgycircleToolbox::CQRS::MessageBus.subscribe([RandomEvent, CommonEvent], ->(event) { puts 'Output 2' })

EdgycircleToolbox::CQRS::MessageBus.publish(RandomEvent.new)
# =>
# Output 1
# Output 2

EdgycircleToolbox::CQRS::MessageBus.publish(CommonEvent.new)
# =>
# Output 2

CQRS Model Collector

Can be used to collect data based on events.

require 'edgycircle_toolbox/cqrs/model_collector'

class RandomEvent
end

class CommonEvent
end

EdgycircleToolbox::CQRS::ModelCollector.register(RandomEvent, ->(event) { [1, 2] })
EdgycircleToolbox::CQRS::ModelCollector.register(RandomEvent, ->(event) { [3] })
EdgycircleToolbox::CQRS::ModelCollector.register(CommonEvent, ->(event) { [4] })

EdgycircleToolbox::CQRS::ModelCollector.for_events([
  RandomEvent.new, 
  CommonEvent.new
])
# =>
# [1, 2, 3, 4]

Sonapi Resource Module

require 'edgycircle_toolbox/sonapi/resource'

class CommandResource
  include EdgycircleToolbox::Sonapi::Resource

  type "commands"

  dynamic_attributes Proc.new { |object| object.attribute_names - [:id] }
  parameter_filter Proc.new { |name, value| true }
end

class TicketResource
  include EdgycircleToolbox::Sonapi::Resource

  type "tickets"

  attribute :estimate
end

Sonapi Dynamic Resource

require 'edgycircle_toolbox/sonapi/dynamic_resource'

class Dummy
end

class Tree
end

class DummyResource
  include EdgycircleToolbox::Sonapi::Resource

  type "dummies"
end

class TreeResource
  include EdgycircleToolbox::Sonapi::Resource

  type "trees"
end

EdgycircleToolbox::Sonapi::DynamicResource.register(Dummy, DummyResource)
EdgycircleToolbox::Sonapi::DynamicResource.register(Tree, TreeResource)

EdgycircleToolbox::Sonapi::DynamicResource.serialize([Dummy.new, Tree.new])

Sonapi Error Resource

The ErrorResource can serialize errors conforming to the SerializableError interface.

require 'edgycircle_toolbox/sonapi/error_resource'

EdgycircleToolbox::Sonapi::ErrorResource.serialize([error_a, error_b])

Sonapi SerializableError Module

An error class has to implement the title, pointer and detail methods.

require 'edgycircle_toolbox/sonapi/serializable_error'

class EmailTakenError
  include EdgycircleToolbox::Sonapi::SerializableError

  def initialize(email)
    @email = email
  end

  def title
    "Email Taken Error"
  end

  def pointer
    nil
  end

  def detail
    "The email #{@email} is already taken, please use something different"
  end
end

Sonapi ValidationError

require 'edgycircle_toolbox/sonapi/validation_error'

EdgycircleToolbox::Sonapi::ValidationError.new(:title, "Title is not long enough")