Sinclair

This gem helps the creation of complex concern with class methods
Yard Documentation
https://www.rubydoc.info/gems/sinclair/1.3.0
Installation
- Install it
gem install sinclair
- Or add Sinclairn to your `Gemfile` and `bundle install`:
gem 'sinclair'
bundle install sinclair
Usage
The concern builder can actully be used in two ways, as an stand alone object capable of adding methods to your class or by extending it for more complex logics
- Stand Alone usage:
class Clazz
end
builder = Sinclair.new(Clazz)
builder.add_method(:twenty, '10 + 10')
builder.add_method(:eighty) { 4 * twenty }
builder.build
instance = Clazz.new
puts "Twenty => #{instance.twenty}" # Twenty => 20
puts "Eighty => #{instance.eighty}" # Eighty => 80
- Extending the builder
class ValidationBuilder < Sinclair
delegate :expected, to: :options_object
def initialize(klass, ={})
super
end
def add_validation(field)
add_method("#{field}_valid?", "#{field}.is_a?#{expected}")
end
def add_accessors(fields)
klass.send(:attr_accessor, *fields)
end
end
module MyConcern
extend ActiveSupport::Concern
class_methods do
def validate(*fields, expected_class)
builder = ::ValidationBuilder.new(self, expected: expected_class)
validatable_fields.concat(fields)
builder.add_accessors(fields)
fields.each do |field|
builder.add_validation(field)
end
builder.build
end
def validatable_fields
@validatable_fields ||= []
end
end
def valid?
self.class.validatable_fields.all? do |field|
public_send("#{field}_valid?")
end
end
end
class MyClass
include MyConcern
validate :name, :surname, String
validate :age, :legs, Integer
def initialize(name: nil, surname: nil, age: nil, legs: nil)
@name = name
@surname = surname
@age = age
@legs = legs
end
end
instance = MyClass.new
the instance will respond to the methods
```name``` ```name=``` ```name_valid?```
```surname``` ```surname=``` ```surname_valid?```
```age``` ```age=``` ```age_valid?```
```legs``` ```legs=``` ```legs_valid?```
```valid?```.
valid_object = MyClass.new(
name: :name,
surname: 'surname',
age: 20,
legs: 2
)
valid_object.valid? # returns true
invalid_object = MyClass.new(
name: 'name',
surname: 'surname',
age: 20,
legs: 2
)
invalid_object.valid? # returns false
- Caching the result
If wanted, the result of the method can be stored in an
instance variable with the same name
class MyModel
attr_accessor :base, :expoent
end
builder = Sinclair.new(MyModel)
builder.add_method(:cached_power, cached: true) do
base ** expoent
end
# equivalent of builder.add_method(:cached_power) do
# @cached_power ||= base ** expoent
# end
builder.build
model.base = 3
model.expoent = 2
model.cached_power # returns 9
model.expoent = 3
model.cached_power # returns 9 (from cache)
RSspec matcher
You can use the provided matcher to check that your builder is adding a method correctly
class DefaultValue
delegate :build, to: :builder
attr_reader :klass, :method, :value
def initialize(klass, method, value)
@klass = klass
@method = method
@value = value
end
private
def builder
@builder ||= Sinclair.new(klass).tap do |b|
b.add_method(method) { value }
end
end
end
require 'sinclair/matchers'
RSpec.configure do |config|
config.include Sinclair::Matchers
end
RSpec.describe DefaultValue do
let(:klass) { Class.new }
let(:method) { :the_method }
let(:value) { Random.rand(100) }
let(:builder) { described_class.new(klass, method, value) }
let(:instance) { klass.new }
context 'when the builder runs' do
it do
expect do
described_class.new(klass, method, value).build
end.to add_method(method).to(instance)
end
end
context 'when the builder runs' do
it do
expect do
described_class.new(klass, method, value).build
end.to add_method(method).to(klass)
end
end
end
> bundle exec rspec
DefaultValue
when the builder runs
should add method 'the_method' to #<Class:0x0000000146c160> instances
when the builder runs
should add method 'the_method' to #<Class:0x0000000143a1b0> instances