Skip to content

Commit

Permalink
Ready to work
Browse files Browse the repository at this point in the history
* refactored logic

* perfect rubocop and code style

* added docs

* added travis

* class_builder_spec and rubocop-rspec features

* added applicator_spec, renamed binding to binding_

* applciator_with_otions_spec

* styles in verifier_spec

* added XVerifier::VERSION spec

* updated rubocop-rspec, updated styling

* updated travis config

- use with newest bundler
- use ruby 2.3.4 for 2.3 family
- add ruby 2.4.1 for 2.4 family

* fixed bug with MethodExtractor on Binding binding

* review fixes

* specs for InstanceEvaluator#caller_line

* Added readme

* grammar
  • Loading branch information
JelF committed Apr 3, 2017
1 parent f3ab521 commit 533d3e4
Show file tree
Hide file tree
Showing 16 changed files with 813 additions and 66 deletions.
15 changes: 15 additions & 0 deletions .rubocop.yml
@@ -1,3 +1,6 @@
require:
- rubocop-rspec

AllCops:
TargetRubyVersion: 2.3
Exclude:
Expand All @@ -14,3 +17,15 @@ Metrics/BlockLength:
Lint/AmbiguousBlockAssociation:
# REVIEW: why is that dangerous?
Enabled: false

RSpec/FilePath:
CustomTransform:
XVerifier: xverifier

RSpec/MessageSpies:
# Spies are not always usable, i.e. we want to stub something we haven't created
EnforcedStyle: receive

RSpec/NestedGroups:
# Extra nesting is good, not bad!
Enabled: false
6 changes: 6 additions & 0 deletions .travis.yml
@@ -0,0 +1,6 @@
sudo: false
language: ruby
rvm:
- 2.3.4
- 2.4.1
before_install: gem install bundler -v 1.14.6
176 changes: 176 additions & 0 deletions README.md
@@ -0,0 +1,176 @@
# XVerifier v0.1
[![Build Status](https://travis-ci.org/umbrellio/xverifier.svg?branch=master)]
(https://travis-ci.org/umbrellio/xverifier)

This gem consists of several dependent components, which all could be
used standalone. The most important one is [Verifier](#Verifier),
but understanding of [Applicator](#Applicator) and
[ApplicatorWithOptions](#ApplicatorWithOptions) helps understand it's API.
The least one, [ClassBuilder](#ClassBuilder) only used in private APIs,
but it's own API also public

## ClassBuilder

example:

```lang=ruby
Abstract = Struct.new(:data)
extend XVerifier::ClassBuilder::Mixin
class WithString < self
def self.build_class(x)
self if x.is_a?(String)
end
end
Generic = Class.new(self)
self.buildable_classes = [WithString, Generic]
# or, vise versa
def self.buildable_classes
[WithString, Generic]
end
end
Abstract.build("foo") # => WithString.new("foo")
Abstract.build(:foo) # => Generic.new("foo")
```

or see lib/verifier/applicator.rb

Why don't just use Uber::Builder?
([Uber](https://github.com/apotonick/uber) is cool, you should try it)
There are two reasons: firstly, it is unnecessary dependency.
We dont want npm hell, aren't we? Uber::Builder realy does not do much work,
it's just a pattern. Secondly, this implementation looks for me
to be more clear, because children instead of parent are deciding would
they handle arguments.

So to use it you have to:

1. Write some classes with duck type `.class_builder(*args)`

2. Invoke `XVerifier::ClassBuilder.new([<%= array_of_classes %>]).call(*args)`

3. ????

4. PROFIT

It's simple and clear, but not very sugarish. So, otherwise, you may do
following:

1. Write an abstract class

2. Extend `XVerifier::ClassBuilder::Mixin`

3. Inherit abstract class in different implementations

4. If some implementations have common ancestors
(not including abstract class), you can implement common ancestor's
`.build_class` in terms of super (i.e.
`def self.build_class(x); super if x.is_a?(String); end`)

5. Change `.build_class` of other classes like `self if ...`.
Don't change default implementation's `.build_class`

6. Setup `.buildable_classes` on abstract class, mention only direct chldren
if you done step 4

7. Optionally redefine `.build` in abstract class, if you want
to separate `build_class` and constructor params

8. Use `.build` instead of `new`

## Applicator

Applicator is designed to wrap applying of
[applicable](https://en.wikipedia.org/wiki/Sepulka) objects
to some binding in some context

example:

```lang=ruby
object = OpenStruct.new(foo: :bar)
Applicator.call(:foo, object, {}) # => :bar
Applicator.call('foo', object, {}) # => :bar
Applicator.call('context', object, {}) # => {}
Applicator.call(-> { foo }, object, {}) # => :bar
Applicator.call(->(context) { context[foo] }, object, bar: :baz) # => :baz
Applicator.call(true, object, {}) # => true
foo = :bar
Applicator.call(:foo, binding, {}) # => :bar
Applicator.call('object.foo', binding, {}) # => :bar
```

Applicator is good, but in most case
[ApplicatorWithOptions](#ApplicatorWithOptions) would be better solution.

## ApplicatorWithOptions

ApplicatorWithOptions is an applicator with options.
The options are `if: ` and `unless: `. Same as in ActiveModel::Validations,
they are applied to same binding and main action would be executed
if `if: ` evaluates to truthy and `unless: ` evaluates to falsey

See examples:

```lang=ruby
ApplicatorWithOptions.new(:foo, if: -> { true }).call(binding, {}) # => foo
ApplicatorWithOptions.new(:foo, if: -> (context) { context[:bar] })
.call(binding, { bar: true }) # => foo
ApplicatorWithOptions.new(:foo, if: { bar: true }).call(binding, :bar) # => foo
ApplicatorWithOptions.new(:foo, unless: -> { true })
.call(binding, {}) # => nil
ApplicatorWithOptions.new(:foo, unless: -> (context) { context[:bar] })
.call(binding, { bar: true }) # => foo
ApplicatorWithOptions.new(:foo, unless: { bar: true })
.call(binding, :bar) # => nil
```

## Verifier

The last but most interesting component is Verifier.
Verifiers use ApplciatorWithOptions to execute generic procedures.
Procedures should call `message!` if they want to yield something.
Note, that you should implement `message!` by yourself (in terms of super)

```lang=ruby
class MyVerifier < XVerifier::Verifier
Message = Struct.new(:text)
verify :foo, if: { foo: true }
private
def message!(text)
super { Message.new(text) }
end
def foo
message!('Something is wrong') if Fixnum != Bignum
end
end
```

In addition to Applicator power, you also can nest your verifiers
to split some logic

```lang=ruby
class MyVerifier < XVerifier::Verifier
Message = Struct.new(:text)
verify ChildVerifier, if: -> (context) { cotnext[:foo] }
private
def message!(text)
super { Message.new(text) }
end
end
class ChildVerifier < MyVerifier
verify %q(message!("it's alive!"))
end
```
6 changes: 5 additions & 1 deletion lib/xverifier.rb
@@ -1,9 +1,13 @@
# frozen_string_literal: true

# @todo write a good readme
# XVerifier provides several usefull classes, but most important
# is Verifier. Other provided classes are it's dependency.
# See README.md or their own documentation for more info about usage
module XVerifier
autoload :VERSION, 'xverifier/version'

autoload :Applicator, 'xverifier/applicator'
autoload :ApplicatorWithOptions, 'xverifier/applicator_with_options'
autoload :ClassBuilder, 'xverifier/class_builder'
autoload :Verifier, 'xverifier/verifier'
end

0 comments on commit 533d3e4

Please sign in to comment.