yema 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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