yema 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +70 -0
  7. data/Rakefile +1 -0
  8. data/lib/yema.rb +33 -0
  9. data/lib/yema/error.rb +11 -0
  10. data/lib/yema/exceptions.rb +3 -0
  11. data/lib/yema/registry/rule.rb +26 -0
  12. data/lib/yema/rule.rb +32 -0
  13. data/lib/yema/rule/format.rb +29 -0
  14. data/lib/yema/rule/inclusion.rb +29 -0
  15. data/lib/yema/rule/length.rb +34 -0
  16. data/lib/yema/rule/required.rb +17 -0
  17. data/lib/yema/rule/strong_type.rb +58 -0
  18. data/lib/yema/validations.rb +30 -0
  19. data/lib/yema/validator.rb +25 -0
  20. data/lib/yema/version.rb +3 -0
  21. data/lib/yema/virtus/attribute.rb +48 -0
  22. data/lib/yema/virtus/attribute/array.rb +12 -0
  23. data/lib/yema/virtus/attribute/numeric.rb +12 -0
  24. data/lib/yema/virtus/attribute/string.rb +16 -0
  25. data/lib/yema/virtus/builder.rb +17 -0
  26. data/lib/yema/virtus/builder/format.rb +13 -0
  27. data/lib/yema/virtus/builder/inclusion.rb +13 -0
  28. data/lib/yema/virtus/builder/length.rb +13 -0
  29. data/lib/yema/virtus/builder/required.rb +13 -0
  30. data/lib/yema/virtus/builder/strong_type.rb +18 -0
  31. data/lib/yema/virtus/validations.rb +28 -0
  32. data/spec/integration/rule/format_spec.rb +23 -0
  33. data/spec/integration/rule/inclusion_spec.rb +28 -0
  34. data/spec/integration/rule/length_spec.rb +40 -0
  35. data/spec/integration/rule/required_spec.rb +22 -0
  36. data/spec/integration/rule/strong_type_spec.rb +27 -0
  37. data/spec/integration/virtus/combination_spec.rb +58 -0
  38. data/spec/integration/virtus/format_spec.rb +22 -0
  39. data/spec/integration/virtus/inclusion_spec.rb +26 -0
  40. data/spec/integration/virtus/length_spec.rb +36 -0
  41. data/spec/integration/virtus/required_spec.rb +21 -0
  42. data/spec/integration/virtus/strong_type_spec.rb +40 -0
  43. data/spec/shared/invalid_options.rb +6 -0
  44. data/spec/shared/invalid_resource.rb +6 -0
  45. data/spec/shared/valid_options.rb +6 -0
  46. data/spec/shared/valid_resource.rb +6 -0
  47. data/spec/spec_helper.rb +9 -0
  48. data/spec/unit/rule/format/valid_options_spec.rb +13 -0
  49. data/spec/unit/rule/inclusion/valid_options_spec.rb +15 -0
  50. data/spec/unit/rule/length/valid_options_spec.rb +16 -0
  51. data/spec/unit/rule/required_options_spec.rb +18 -0
  52. data/spec/unit/rule/strong_type/valid_options_spec.rb +13 -0
  53. data/spec/unit/validations/rules_spec.rb +15 -0
  54. data/spec/unit/validations/valid_spec.rb +41 -0
  55. data/spec/unit/validator/errors_spec.rb +26 -0
  56. data/spec/unit/validator/valid_predicate_spec.rb +31 -0
  57. data/spec/unit/virtus/not_supported_type_spec.rb +11 -0
  58. data/yema.gemspec +26 -0
  59. metadata +210 -0
@@ -0,0 +1,25 @@
1
+ module Yema
2
+ class Validator
3
+
4
+ attr_reader :rule, :resource
5
+
6
+ def initialize(rule, resource)
7
+ @rule, @resource = rule, resource
8
+ end
9
+
10
+ def value
11
+ resource.public_send(rule.attribute_name)
12
+ end
13
+
14
+ def valid?
15
+ rule.matches?(value)
16
+ end
17
+
18
+ def errors
19
+ errors = []
20
+ errors << Error.new(rule, resource) unless valid?
21
+ errors.to_set
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Yema
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,48 @@
1
+ module Yema
2
+ module Virtus
3
+ class Attribute
4
+
5
+ attr_reader :attribute
6
+ DEFAULT_BUILDERS = [ Builder::StrongType, Builder::Required ].freeze
7
+
8
+ def initialize(attribute)
9
+ @attribute = attribute
10
+ end
11
+
12
+ def self.specific_builders
13
+ [].freeze
14
+ end
15
+
16
+ def self.builders
17
+ DEFAULT_BUILDERS + specific_builders
18
+ end
19
+
20
+ def rules
21
+ rules = []
22
+ self.class.builders.each do |builder|
23
+ rule = builder.new(attribute).build
24
+ rules << rule if rule
25
+ end
26
+ rules.to_set
27
+ end
28
+
29
+ def self.build(attribute)
30
+ klass = determine_type(attribute)
31
+ klass.new(attribute)
32
+ end
33
+
34
+ private
35
+
36
+ # TODO: find a better way for mapping virtus type
37
+ def self.determine_type(attribute)
38
+ if attribute.kind_of?(::Virtus::Attribute::String); self::String
39
+ elsif attribute.kind_of?(::Virtus::Attribute::Numeric); self::Numeric
40
+ elsif attribute.kind_of?(::Virtus::Attribute::Array); self::Array
41
+ elsif attribute.kind_of?(::Virtus::Attribute::Boolean); self
42
+ else
43
+ raise TypeError, "#{attribute.class} is not supported"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,12 @@
1
+ module Yema
2
+ module Virtus
3
+ class Attribute
4
+ class Array < self
5
+
6
+ def self.specific_builders
7
+ super + [Builder::Length].freeze
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Yema
2
+ module Virtus
3
+ class Attribute
4
+ class Numeric < self
5
+
6
+ def self.specific_builders
7
+ super + [Builder::Inclusion].freeze
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ module Yema
2
+ module Virtus
3
+ class Attribute
4
+ class String < self
5
+
6
+ def self.specific_builders
7
+ super + [
8
+ Builder::Length,
9
+ Builder::Format,
10
+ Builder::Inclusion,
11
+ ].freeze
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module Yema
2
+ module Virtus
3
+ class Builder
4
+
5
+ attr_reader :attribute
6
+
7
+ def initialize(attribute)
8
+ @attribute = attribute
9
+ end
10
+
11
+ def options
12
+ attribute.options
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module Yema
2
+ module Virtus
3
+ class Builder
4
+ class Format < self
5
+
6
+ def build
7
+ format = options.fetch(:format, false)
8
+ Rule::Format.new(attribute.name, format: format) if format
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Yema
2
+ module Virtus
3
+ class Builder
4
+ class Inclusion < self
5
+
6
+ def build
7
+ within = options.fetch(:within, false)
8
+ Rule::Inclusion.new(attribute.name, within: within) if within
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Yema
2
+ module Virtus
3
+ class Builder
4
+ class Length < self
5
+
6
+ def build
7
+ length = options.fetch(:length, false)
8
+ Rule::Length.new(attribute.name, length: length) if length
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Yema
2
+ module Virtus
3
+ class Builder
4
+ class Required < self
5
+
6
+ def build
7
+ required = options.fetch(:required, false)
8
+ Rule::Required.new(attribute.name) if required
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ module Yema
2
+ module Virtus
3
+ class Builder
4
+ class StrongType < self
5
+
6
+ def build
7
+ strict = options.fetch(:strict, :high)
8
+ unless strict == :none
9
+ Yema::Rule::StrongType.new(attribute.name,
10
+ type: attribute.class.primitive,
11
+ member_type: attribute.options[:member_type],
12
+ strict: strict)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ module Yema
2
+ module Virtus
3
+ module Validations
4
+
5
+ def self.included(base)
6
+ base.instance_eval do
7
+ include ::Virtus::ValueObject
8
+ include Yema::Validations
9
+ extend ClassMethods
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ private
16
+
17
+ def virtus_add_attribute(attribute)
18
+ super
19
+ build_validation(attribute)
20
+ end
21
+
22
+ def build_validation(attribute)
23
+ rules.merge(Yema::Virtus::Attribute.build(attribute).rules)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yema::Rule::Format, '#errors' do
4
+
5
+ subject { rule.errors(resource) }
6
+
7
+ let(:rule) { described_class.new(attribute, options) }
8
+ let(:resource) { stub('resource', attribute => value) }
9
+ let(:attribute) { :foo }
10
+ let(:options) { { format: /foo/ } }
11
+
12
+ [
13
+ 'foo',
14
+ 'afoo',
15
+ 'fooa',
16
+ 'afooa',
17
+ ].each do |value|
18
+ it_behaves_like "valid resource", value
19
+ end
20
+
21
+ it_behaves_like "invalid resource", 'abcdds'
22
+
23
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yema::Rule::Inclusion, '#errors' do
4
+
5
+ subject { rule.errors(resource) }
6
+
7
+ let(:rule) { described_class.new(attribute, options) }
8
+ let(:resource) { stub('resource', attribute => value) }
9
+ let(:attribute) { :foo }
10
+
11
+ context "with range options" do
12
+ let(:options) { { within: 3..6 } }
13
+
14
+ [3, 5].each do |value|
15
+ it_behaves_like "valid resource", value
16
+ end
17
+
18
+ it_behaves_like "invalid resource", 2
19
+ end
20
+
21
+ context "with array options" do
22
+ let(:options) { { within: ["a", "b"] } }
23
+
24
+ it_behaves_like "valid resource", "a"
25
+ it_behaves_like "invalid resource", "c"
26
+ end
27
+
28
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yema::Rule::Length, '#errors' do
4
+
5
+ subject { rule.errors(resource) }
6
+
7
+ let(:rule) { described_class.new(attribute, options) }
8
+ let(:resource) { stub('resource', attribute => value) }
9
+ let(:attribute) { :foo }
10
+ let(:options) { { length: 5 } }
11
+
12
+ it_behaves_like "valid resource", "asdfg"
13
+ it_behaves_like "invalid resource", "longer_string"
14
+ it_behaves_like "invalid resource", ""
15
+
16
+ [
17
+ nil,
18
+ false,
19
+ true,
20
+ 10000,
21
+ ].each do |value|
22
+ context "with invalid value: #{value.inspect}" do
23
+ let(:value) { value }
24
+ specify { expect{subject}.to raise_error(ArgumentError) }
25
+ end
26
+ end
27
+
28
+ context "when options is a range" do
29
+ let(:options) { { length: 3..6 } }
30
+
31
+ [
32
+ "abc",
33
+ "abcabc",
34
+ ].each do |value|
35
+ it_behaves_like "valid resource", value
36
+ end
37
+ it_behaves_like "invalid resource", "abcabca"
38
+ end
39
+
40
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yema::Rule::Required, '#errors' do
4
+
5
+ subject { rule.errors(resource) }
6
+
7
+ let(:rule) { described_class.new(attribute, options) }
8
+ let(:resource) { stub('resource', attribute => value) }
9
+ let(:attribute) { :foo }
10
+ let(:options) { {} }
11
+
12
+ [
13
+ nil,
14
+ "",
15
+ {},
16
+ [],
17
+ ].each do |value|
18
+ it_behaves_like "invalid resource", value
19
+ end
20
+
21
+ it_behaves_like "valid resource", "bar"
22
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yema::Rule::StrongType, '#errors' do
4
+
5
+ subject { rule.errors(resource) }
6
+
7
+ let(:rule) { described_class.new(attribute, options) }
8
+ let(:resource) { stub('resource', attribute => value) }
9
+ let(:attribute) { :foo }
10
+ let(:options) { {type: String}.merge(mode) }
11
+ let(:mode) { {} }
12
+
13
+ context "allow_nil mode" do
14
+ let(:mode) { {strict: :allow_nil} }
15
+
16
+ it_behaves_like "valid resource", nil
17
+ it_behaves_like "invalid resource", 1
18
+ it_behaves_like "valid resource", 'string'
19
+ end
20
+
21
+ context "default mode" do
22
+ it_behaves_like "invalid resource", nil
23
+ it_behaves_like "invalid resource", 1
24
+ it_behaves_like "valid resource", 'string'
25
+ end
26
+
27
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Combination' do
4
+
5
+ subject { described_class.new(value).errors }
6
+
7
+ let(:described_class) do
8
+ Class.new do
9
+ include Yema::Virtus::Validations
10
+ attribute :name, String
11
+ attribute :username, String, required: true, format: /langit/
12
+ attribute :salutation, String, within: ['mr', 'ms']
13
+ attribute :address, String, strict: :allow_nil
14
+ attribute :age, Integer, within: 1..26
15
+ attribute :height, Integer, required: false, default: 173
16
+ attribute :active, Virtus::Attribute::Boolean
17
+ attribute :pages, Array[String], length: 2..3
18
+ attribute :phones, Array
19
+ self
20
+ end
21
+ end
22
+
23
+ it_behaves_like "valid resource", {
24
+ name: 'handi',
25
+ username: 'langitbiru',
26
+ salutation: 'mr',
27
+ age: '23',
28
+ active: false,
29
+ pages: [1, 2, '3a'],
30
+ phones: [1, 2, '3a'],
31
+ }
32
+
33
+ describe "check value" do
34
+ subject { described_class.new(value) }
35
+
36
+ let(:value) do
37
+ {
38
+ name: 'handi',
39
+ username: 'langitbiru',
40
+ salutation: 'mr',
41
+ age: '23',
42
+ pages: [1, 2, 3],
43
+ phones: [1, 2, 3],
44
+ }
45
+ end
46
+
47
+ its(:name) { should eql('handi') }
48
+ its(:username) { should eql('langitbiru') }
49
+ its(:salutation) { should eql('mr') }
50
+ its(:address) { should be_nil }
51
+ its(:age) { should eql(23) }
52
+ its(:height) { should eql(173) }
53
+ its(:active) { should be_false }
54
+ specify { subject.pages.count.should eql(3) }
55
+ specify { subject.phones.count.should eql(3) }
56
+ end
57
+
58
+ end