flex_validations 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8b91f3503d30d7f0028969276730c2af54c6b5dbfb2651e36979129d2df053b1
4
+ data.tar.gz: e3a9730549710713188d931fba95a79dd598e5707046e0bd29f970e4686a2470
5
+ SHA512:
6
+ metadata.gz: d5870aadb9073d2b6c456b048ef77ca023fc765f0255fe3d9732f91c71ceb4b9ee44ed5f16f18fe68bc114348812bd6b1a23d51a1e709539fe0a01636c550e87
7
+ data.tar.gz: 3d74776ce480bc2c0fa6e60747f90455533840fec083aa6da73d4274a73fd738cea72c6242dea7075094b6835d46727c20a56aebf701eb95e98988e2456cd81d
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,15 @@
1
+ ---
2
+ include:
3
+ - "**/*.rb"
4
+ exclude:
5
+ - spec/**/*
6
+ - test/**/*
7
+ - vendor/**/*
8
+ - ".bundle/**/*"
9
+ require: []
10
+ domains: []
11
+ reporters:
12
+ - rubocop
13
+ - require_not_found
14
+ require_paths: []
15
+ max_files: 5000
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.5.5
6
+ before_install: gem install bundler -v 2.1.2
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in validations.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
8
+ gem "pry"
@@ -0,0 +1,40 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ flex_validations (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.2)
10
+ diff-lcs (1.3)
11
+ method_source (0.9.2)
12
+ pry (0.12.2)
13
+ coderay (~> 1.1.0)
14
+ method_source (~> 0.9.0)
15
+ rake (12.3.3)
16
+ rspec (3.9.0)
17
+ rspec-core (~> 3.9.0)
18
+ rspec-expectations (~> 3.9.0)
19
+ rspec-mocks (~> 3.9.0)
20
+ rspec-core (3.9.1)
21
+ rspec-support (~> 3.9.1)
22
+ rspec-expectations (3.9.0)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.9.0)
25
+ rspec-mocks (3.9.1)
26
+ diff-lcs (>= 1.2.0, < 2.0)
27
+ rspec-support (~> 3.9.0)
28
+ rspec-support (3.9.2)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ flex_validations!
35
+ pry
36
+ rake (~> 12.0)
37
+ rspec (~> 3.0)
38
+
39
+ BUNDLED WITH
40
+ 2.1.2
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Dmitry Bochkarev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,99 @@
1
+ # FlexValidations
2
+
3
+ Object oriented validations gem with descriptive messages
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'flex_validations'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install flex_validations
20
+
21
+ ## Usage
22
+
23
+ ### Simple predicate
24
+
25
+ ```ruby
26
+ positive = FlexValidations::Predicate.new(:positive?)
27
+ integer = FlexValidations::Predicate.new(:is_a?, Integer)
28
+
29
+ string = FlexValidations::Predicate.new(:is_a?, String)
30
+ present = FlexValidations::Predicate.new(:present?)
31
+ ```
32
+
33
+ ### Chain
34
+
35
+ ```ruby
36
+ short = FlexValidations::Chain.new \
37
+ FlexValidations::Call.new(:length),
38
+ FlexValidations::Predicate.new(:<, 256)
39
+ ```
40
+
41
+ ### And
42
+
43
+ ```ruby
44
+ positive_integer = FlexValidations::And.new(integer, positive)
45
+ title = FlexValidations::And.new(string, present, short)
46
+ ```
47
+
48
+ ### Or
49
+
50
+ ```ruby
51
+ subtitle = FlexValidations::Or.new \
52
+ FlexValidations::Predicate.new(:nil?),
53
+ title
54
+ ```
55
+
56
+ ### Decoration
57
+
58
+ ```ruby
59
+ have_keys = lambda do |*keys|
60
+ FlexValidations::Chain.new \
61
+ FlexValidations::Call.new(:keys),
62
+ FlexValidations::Decorate.new(Set),
63
+ FlexValidations::Predicate.new(:==, Set.new(keys))
64
+ end
65
+
66
+ hash_of = lambda do |attrs|
67
+ FlexValidations::And.new(
68
+ *attrs.map do |key, validation|
69
+ FlexValidations::Chain.new \
70
+ FlexValidations::Call.new(:[], key),
71
+ validation
72
+ end
73
+ end
74
+ end
75
+
76
+ product =
77
+ FlexValidations::And.new \
78
+ have_keys["id", "title"],
79
+ hash_of[
80
+ "id" => positive_integer,
81
+ "title" => title,
82
+ "subtitle" => subtitle
83
+ ]
84
+ ```
85
+
86
+ ## Development
87
+
88
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
89
+
90
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
91
+
92
+ ## Contributing
93
+
94
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/validations.
95
+
96
+
97
+ ## License
98
+
99
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "validations"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,25 @@
1
+ require_relative 'lib/flex_validations/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "flex_validations"
5
+ spec.version = FlexValidations::VERSION
6
+ spec.authors = ["Dmitry Bochkarev"]
7
+ spec.email = ["dimabochkarev@gmail.com"]
8
+
9
+ spec.summary = %q{Object Oriented Validation Library}
10
+ spec.description = %q{Object Oriented Validation Library}
11
+ spec.homepage = "https://github.com/DmitryBochkarev/flex_validations"
12
+ spec.license = "MIT"
13
+
14
+ spec.metadata["homepage_uri"] = spec.homepage
15
+ spec.metadata["source_code_uri"] = "https://github.com/DmitryBochkarev/flex_validations"
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "flex_validations/version"
4
+
5
+ require "flex_validations/result"
6
+ require "flex_validations/result_message"
7
+ require "flex_validations/validation"
8
+
9
+ require "flex_validations/decorate"
10
+ require "flex_validations/predicate"
11
+ require "flex_validations/call"
12
+ require "flex_validations/all"
13
+ require "flex_validations/and"
14
+ require "flex_validations/or"
15
+ require "flex_validations/chain"
16
+
17
+ module FlexValidations
18
+ # @api private
19
+ class List
20
+ def initialize(items)
21
+ @items = items
22
+ end
23
+
24
+ def to_s
25
+ listing = @items.map do |item|
26
+ i = "- #{item}"
27
+
28
+ if i.end_with?('.')
29
+ i
30
+ else
31
+ "#{i};"
32
+ end
33
+ end.join("\n")
34
+
35
+ if listing.end_with?('.')
36
+ listing
37
+ elsif listing.end_with?(';')
38
+ "#{listing[0..-2]}."
39
+ else
40
+ "#{listing}."
41
+ end
42
+ end
43
+ end
44
+
45
+ # @api private
46
+ class NumberedList
47
+ def initialize(items)
48
+ @items = items
49
+ end
50
+
51
+ def to_s
52
+ listing = @items.map.with_index(1) do |item, n|
53
+ i = "#{n}. #{item}"
54
+
55
+ if i.end_with?('.')
56
+ i
57
+ else
58
+ "#{i};"
59
+ end
60
+ end.join("\n")
61
+
62
+ if listing.end_with?('.')
63
+ listing
64
+ elsif listing.end_with?(';')
65
+ "#{listing[0..-2]}."
66
+ else
67
+ "#{listing}."
68
+ end
69
+ end
70
+ end
71
+
72
+ # @api private
73
+ class IndentedString
74
+ # @param original [#to_s]
75
+ def initialize(original, level: 2, indentation: ' ')
76
+ @original = original
77
+ @level = level
78
+ @indentation = indentation
79
+ end
80
+
81
+ def to_s
82
+ @original.to_s.lines.map do |line|
83
+ "#{@indentation * @level}#{line}"
84
+ end.join('')
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexValidations
4
+ # Check if all elements in enumerable are valid
5
+ #
6
+ # @example
7
+ # all_odd = FlexValidations::All.new(odd)
8
+ # all_odd.validate([1, 2, 3]).success? # => false
9
+ class All
10
+ # @param validation [FlexValidations::Validation] validation which performed on each element
11
+ def initialize(validation)
12
+ @validation = validation
13
+ end
14
+
15
+ # @param value [#each] Value to be validated
16
+ #
17
+ # @return [FlexValidations::Result]
18
+ def validate(value)
19
+ return not_enumerable(value) unless value.respond_to?(:each, false)
20
+
21
+ value.each.with_index do |element, index|
22
+ res = @validation.validate(element)
23
+
24
+ return failed(value, res, element, index) if res.fail?
25
+ end
26
+
27
+ success(value)
28
+ end
29
+
30
+ # @return [String]
31
+ def to_s
32
+ "all elements should pass following validation:\n" \
33
+ "#{IndentedString.new(@validation)}"
34
+ end
35
+
36
+ class SuccessMessage
37
+ include ResultMessage
38
+
39
+ def initialize(validation)
40
+ @validation = validation
41
+ end
42
+
43
+ def to_s
44
+ "all elements passed following validation:\n" \
45
+ "#{IndentedString.new(@validation)}"
46
+ end
47
+ end
48
+
49
+ class NotEnumerableMessage
50
+ include ResultMessage
51
+
52
+ def initialize(value)
53
+ @value = value
54
+ end
55
+
56
+ def to_s
57
+ "#{@value.inspect} of #{@value.class} isn't respond to method \"each\""
58
+ end
59
+ end
60
+
61
+ class FailedMessage
62
+ include ResultMessage
63
+
64
+ def initialize(res, element, index)
65
+ @res = res
66
+ @element = element
67
+ @index = index
68
+ end
69
+
70
+ def to_s
71
+ "validation for #{@element.inspect} at index #{@index} failed:\n" \
72
+ "#{IndentedString.new(@res.message)}"
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def success(value)
79
+ Result::Success::Simple.new \
80
+ self,
81
+ SuccessMessage.new(@validation),
82
+ value,
83
+ value
84
+ end
85
+
86
+ def not_enumerable(value)
87
+ Result::Fail::Simple.new \
88
+ self,
89
+ NotEnumerableMessage.new(value),
90
+ value,
91
+ false
92
+ end
93
+
94
+ def failed(value, res, element, index)
95
+ Result::Fail::Composite.new \
96
+ self,
97
+ res,
98
+ FailedMessage.new(res, element, index),
99
+ value,
100
+ res.raw
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexValidations
4
+ # Perform all validations on value to succeed
5
+ #
6
+ # @example
7
+ # positive_integer = FlexValidations::And.new \
8
+ # FlexValidations::Predicate.new(:is_a?, Integer),
9
+ # FlexValidations::Predicate.new(:positive?)
10
+ # [-2, -1.5, -1, 0, 1, 1.5, 2].select(&positive_integer) #=> [1, 2]
11
+ #
12
+ # @example Description
13
+ # > puts positive_integer
14
+ # all validations should succeed:
15
+ # - value.is_a?(Integer) should succeed;
16
+ # - value.positive? should succeed.
17
+ #
18
+ # @example Success result description
19
+ # > puts positive_integer.validate(1)
20
+ # all validations succeed:
21
+ # - 1.is_a?(Integer) succeed;
22
+ # - 1.positive? succeed.
23
+ #
24
+ # @example Fail description
25
+ # > puts positive_integer.validate(-2)
26
+ # -2.positive? failed
27
+ class And
28
+ include Validation
29
+
30
+ # @param validations [Array<FlexValidations::Validation>] all validations that
31
+ # value should satisfy
32
+ #
33
+ # @return [FlexValidations::Validation]
34
+ def initialize(*validations)
35
+ @validations = validations
36
+ end
37
+
38
+ # @param value [Object] Value to be validated
39
+ #
40
+ # @return [FlexValidations::Result]
41
+ def validate(value)
42
+ successes = []
43
+
44
+ @validations.each do |validation|
45
+ res = validation.validate(value)
46
+
47
+ return failed(res, value) if res.fail?
48
+
49
+ successes.push(res)
50
+ end
51
+
52
+ success(successes, value)
53
+ end
54
+
55
+ # @return [String]
56
+ def to_s
57
+ "all validations should succeed:\n" \
58
+ "#{IndentedString.new(List.new(@validations))}"
59
+ end
60
+
61
+ class SuccessMessage
62
+ include ResultMessage
63
+
64
+ def initialize(successes)
65
+ @successes = successes
66
+ end
67
+
68
+ def to_s
69
+ "all validations succeed:\n#{IndentedString.new(List.new(@successes))}"
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def success(successes, value)
76
+ Result::Success::Simple.new \
77
+ self,
78
+ SuccessMessage.new(successes),
79
+ value,
80
+ value
81
+ end
82
+
83
+ def failed(res, value)
84
+ Result::Fail::Composite.new(self, res, nil, value, res.raw)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexValidations
4
+ class Call
5
+ include Validation
6
+
7
+ def initialize(method, *args)
8
+ @method = method
9
+ @args = args
10
+ end
11
+
12
+ # @param value [Object] Value to be validated
13
+ #
14
+ # @return [FlexValidations::Result]
15
+ def validate(value)
16
+ return not_respond(value) unless value.respond_to?(@method, false)
17
+
18
+ begin
19
+ ret = value.public_send(@method, *@args)
20
+ success(value, ret)
21
+ rescue => e
22
+ failed(value, e)
23
+ end
24
+ end
25
+
26
+ # @return [String]
27
+ def to_s
28
+ args = "(#{@args.map(&:inspect).join(', ')})" if @args.length > 0
29
+
30
+ "value.#{@method}#{args} should not raise error"
31
+ end
32
+
33
+ class SuccessMessage
34
+ include ResultMessage
35
+
36
+ def initialize(value, method, args, ret)
37
+ @value = value
38
+ @method = method
39
+ @args = args
40
+ @ret = ret
41
+ end
42
+
43
+ def to_s
44
+ args = "(#{@args.map(&:inspect).join(', ')})" if @args.length > 0
45
+
46
+ "#{@value.inspect}.#{@method}#{args} returned #{@ret.inspect}"
47
+ end
48
+ end
49
+
50
+ class NotRespondMessage
51
+ include ResultMessage
52
+
53
+ def initialize(value, method)
54
+ @value = value
55
+ @method = method
56
+ end
57
+
58
+ def to_s
59
+ "#{@value.inspect} of #{@value.class} isn't respond to method #{@method}"
60
+ end
61
+ end
62
+
63
+ class FailedMessage
64
+ include ResultMessage
65
+
66
+ def initialize(value, method, args, error)
67
+ @value = value
68
+ @method = method
69
+ @args = args
70
+ @error = error
71
+ end
72
+
73
+ def to_s
74
+ args = "(#{@args.map(&:inspect).join(', ')})" if @args.length > 0
75
+
76
+ "#{@value.inspect}.#{@method}#{args} raised error #{@error.class}: #{@error}"
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def success(value, ret)
83
+ msg = SuccessMessage.new(value, @method, @args, ret)
84
+
85
+ Result::Success::Simple.new(self, msg, value, ret)
86
+ end
87
+
88
+ def not_respond(value)
89
+ msg = NotRespondMessage.new(value, @method)
90
+
91
+ Result::Fail::Simple.new(self, msg, value, false)
92
+ end
93
+
94
+ def failed(value, error)
95
+ msg = FailedMessage.new(value, @method, @args, error)
96
+
97
+ Result::Fail::Simple.new(self, msg, value, error)
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexValidations
4
+ class Chain
5
+ include Validation
6
+
7
+ def initialize(*validations)
8
+ @validations = validations
9
+ end
10
+
11
+ # @param value [Object] Value to be validated
12
+ #
13
+ # @return [FlexValidations::Result]
14
+ def validate(value)
15
+ successes = []
16
+ v = value
17
+ @validations.each do |validation|
18
+ res = validation.validate(v)
19
+
20
+ return failed(value, successes, res) if res.fail?
21
+
22
+ successes.push(res)
23
+
24
+ v = res.raw
25
+ end
26
+
27
+ SuccessResult.new(self, successes, value, successes.last.raw)
28
+ end
29
+
30
+ # @return [String]
31
+ def to_s
32
+ "chain of validations should succeed:\n#{IndentedString.new(NumberedList.new(@validations))}"
33
+ end
34
+
35
+ class SuccessResult
36
+ include Result
37
+ include Result::Success
38
+
39
+ attr_reader :validation, :results, :value, :raw
40
+
41
+ def initialize(validation, results, value, raw)
42
+ @validation = validation
43
+ @results = results
44
+ @value = value
45
+ @raw = raw
46
+ end
47
+
48
+ def message
49
+ SuccessMessage.new(@results)
50
+ end
51
+ end
52
+
53
+ class SuccessMessage
54
+ include ResultMessage
55
+
56
+ def initialize(results)
57
+ @results = results
58
+ end
59
+
60
+ def to_s
61
+ "chain of validation for #{@value.inspect} succeed:\n#{IndentedString.new(NumberedList.new(@results))}"
62
+ end
63
+ end
64
+
65
+ class FailedMessage
66
+ include ResultMessage
67
+
68
+ def initialize(value, successes, res)
69
+ @successes = successes
70
+ @res = res
71
+ @value = value
72
+ end
73
+
74
+ def to_s
75
+ list = @successes + [@res]
76
+
77
+ "chain of validation for #{@value.inspect} failed:\n#{IndentedString.new(NumberedList.new(list))}"
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def failed(value, successes, res)
84
+ Result::Fail::Simple.new(self, FailedMessage.new(value, successes, res), value, res.raw)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexValidations
4
+ # Not a really validation but handy class for complex validations
5
+ #
6
+ # @example
7
+ # hash = FlexValidations::Chain.new(
8
+ # FlexValidations::Call.new(:keys),
9
+ # FlexValidations::Decorate.new(Set),
10
+ # FlexValidations::Predicate.new(:==, Set.new(["id", "title"]))
11
+ # )
12
+ class Decorate
13
+ include Validation
14
+
15
+ def initialize(decorator_class, *decorator_args)
16
+ @decorator_class = decorator_class
17
+ @decorator_args = decorator_args
18
+ end
19
+
20
+ # @param value [Object] Value to be validated
21
+ #
22
+ # @return [FlexValidations::Result]
23
+ def validate(value)
24
+ new_val = @decorator_class.new value, *@decorator_args
25
+
26
+ Result::Success::Simple.new(self, SuccessMessage.new(value, new_val), value, new_val)
27
+ end
28
+
29
+ # @return [String]
30
+ def to_s
31
+ "decorate value with #{@decorator_class}"
32
+ end
33
+
34
+ class SuccessMessage
35
+ def initialize(value, ret)
36
+ @value = value
37
+ @ret = ret
38
+ end
39
+
40
+ def to_s
41
+ "decorated #{@value.inspect} now #{@ret.inspect}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexValidations
4
+ class Or
5
+ include Validation
6
+
7
+ def initialize(*validations)
8
+ @validations = validations
9
+ end
10
+
11
+ # @param value [Object] Value to be validated
12
+ #
13
+ # @return [FlexValidations::Result]
14
+ def validate(value)
15
+ fails = []
16
+
17
+ @validations.each do |validation|
18
+ res = validation.validate(value)
19
+
20
+ return Result::Success::Composite.new(self, validation, res.message, value, res.raw) if res.success?
21
+
22
+ fails.push(res)
23
+ end
24
+
25
+ Result::Fail::Simple.new(self, FailedMessage.new(fails), value, value)
26
+ end
27
+
28
+ # @return [String]
29
+ def to_s
30
+ "any of validation should succeed:\n#{IndentedString.new(List.new(@validations))}"
31
+ end
32
+
33
+ class FailedMessage
34
+ include ResultMessage
35
+
36
+ def initialize(fails)
37
+ @fails = fails
38
+ end
39
+
40
+ def to_s
41
+ "all validations failed:\n#{IndentedString.new(List.new(@fails))}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexValidations
4
+ # Call predicate on object
5
+ #
6
+ # @example
7
+ # odd = FlexValidations::Predicate.new(:odd?)
8
+ # odd.validate(1).success? #=> true
9
+ #
10
+ # @example Description
11
+ # > puts odd
12
+ # value.odd? should succeed
13
+ #
14
+ # @example Description of success result
15
+ # > puts odd.validate(1)
16
+ # 1.odd? succeed
17
+ #
18
+ # @example Description of fail result
19
+ # > puts odd.validate(2)
20
+ # 2.odd? failed
21
+ #
22
+ # @example Description when value doesn't respond to method
23
+ # > puts odd.validate("foo")
24
+ # "foo" of String isn't respond to method odd?
25
+ class Predicate
26
+ include Validation
27
+
28
+ def initialize(method, *args)
29
+ @method = method
30
+ @args = args
31
+ end
32
+
33
+ # @param value [Object] Value to be validated
34
+ #
35
+ # @return [FlexValidations::Result]
36
+ def validate(value)
37
+ return not_respond(value) unless value.respond_to?(@method, false)
38
+
39
+ ret = value.public_send(@method, *@args)
40
+
41
+ return failed(value, ret) unless ret
42
+
43
+ success(value, ret)
44
+ end
45
+
46
+ # @return [String]
47
+ def to_s
48
+ args = "(#{@args.map(&:inspect).join(', ')})" if @args.length > 0
49
+
50
+ "value.#{@method}#{args} should succeed"
51
+ end
52
+
53
+ class SuccessMessage
54
+ include ResultMessage
55
+
56
+ def initialize(value, method, args, ret)
57
+ @value = value
58
+ @method = method
59
+ @args = args
60
+ @ret = ret
61
+ end
62
+
63
+ def to_s
64
+ args = "(#{@args.map(&:inspect).join(', ')})" if @args.length > 0
65
+
66
+ "#{@value.inspect}.#{@method}#{args} succeed"
67
+ end
68
+ end
69
+
70
+ class NotRespondMessage
71
+ include ResultMessage
72
+
73
+ def initialize(value, method)
74
+ @value = value
75
+ @method = method
76
+ end
77
+
78
+ def to_s
79
+ "#{@value.inspect} of #{@value.class} isn't respond to method #{@method}"
80
+ end
81
+ end
82
+
83
+ class FailedMessage
84
+ include ResultMessage
85
+
86
+ def initialize(value, method, args)
87
+ @value = value
88
+ @method = method
89
+ @args = args
90
+ end
91
+
92
+ def to_s
93
+ args = "(#{@args.map(&:inspect).join(', ')})" if @args.length > 0
94
+
95
+ "#{@value.inspect}.#{@method}#{args} failed"
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ def success(value, ret)
102
+ msg = SuccessMessage.new(value, @method, @args, ret)
103
+
104
+ Result::Success::Simple.new(self, msg, value, ret)
105
+ end
106
+
107
+ def not_respond(value)
108
+ msg = NotRespondMessage.new(value, @method)
109
+
110
+ Result::Fail::Simple.new(self, msg, value, false)
111
+ end
112
+
113
+ def failed(value, ret)
114
+ msg = FailedMessage.new(value, @method, @args)
115
+
116
+ Result::Fail::Simple.new(self, msg, value, ret)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexValidations
4
+ # @abstract
5
+ module Result
6
+ # @abstract
7
+ #
8
+ # @return [Boolean]
9
+ def success?
10
+ raise 'not implemented'
11
+ end
12
+
13
+ # @abstract
14
+ #
15
+ # @return [Boolean]
16
+ def fail?
17
+ raise 'not implemented'
18
+ end
19
+
20
+ alias failure? fail?
21
+
22
+ # @abstract
23
+ #
24
+ # @return [FlexValidations::ResultMessage]
25
+ def message
26
+ raise 'not implemented'
27
+ end
28
+
29
+ # @abstract Original validation of result
30
+ #
31
+ # @return [FlexValidations::Validation]
32
+ def validation
33
+ raise 'not implemented'
34
+ end
35
+
36
+ # @abstract Original object on which {#validation} was performed
37
+ #
38
+ # @return [Object]
39
+ def value
40
+ raise 'not implemented'
41
+ end
42
+
43
+ # @abstract
44
+ #
45
+ # @return [Object]
46
+ def raw
47
+ raise 'not implemented'
48
+ end
49
+
50
+ def to_s
51
+ message.to_s
52
+ end
53
+
54
+ module Success
55
+ def success?
56
+ true
57
+ end
58
+
59
+ def fail?
60
+ false
61
+ end
62
+
63
+ class Simple
64
+ include Result
65
+ include Success
66
+
67
+ attr_reader :validation, :message, :value, :raw
68
+
69
+ def initialize(validation, message, value, raw)
70
+ @validation = validation
71
+ @message = message
72
+ @value = value
73
+ @raw = raw
74
+ end
75
+ end
76
+
77
+ class Composite
78
+ include Result
79
+ include Success
80
+
81
+ attr_reader :validation, :original, :value, :raw
82
+
83
+ def initialize(validation, original, message, value, raw)
84
+ @validation = validation
85
+ @original = original
86
+ @message = message
87
+ @value = value
88
+ @raw = raw
89
+ end
90
+
91
+ def message
92
+ @message || @original.message
93
+ end
94
+ end
95
+ end
96
+
97
+ module Fail
98
+ def success?
99
+ false
100
+ end
101
+
102
+ def fail?
103
+ true
104
+ end
105
+
106
+ class Simple
107
+ include Result
108
+ include Fail
109
+
110
+ attr_reader :validation, :message, :value, :raw
111
+
112
+ def initialize(validation, message, value, raw)
113
+ @validation = validation
114
+ @message = message
115
+ @value = value
116
+ @raw = raw
117
+ end
118
+ end
119
+
120
+ class Composite
121
+ include Result
122
+ include Fail
123
+
124
+ attr_reader :validation, :original, :value, :raw
125
+
126
+ def initialize(validation, original, message, value, raw)
127
+ @validation = validation
128
+ @original = original
129
+ @message = message
130
+ @value = value
131
+ end
132
+
133
+ def message
134
+ @message || @original.message
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexValidations
4
+ # @abstract
5
+ module ResultMessage
6
+ # @abstract
7
+ #
8
+ # @return [String]
9
+ def to_s
10
+ raise 'not implemented'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexValidations
4
+ # @abstract Validation should define {#validate} and {#to_s} methods
5
+ module Validation
6
+ # @abstract Do validation of value
7
+ #
8
+ # @param value [Object] Value to be validated
9
+ #
10
+ # @return [FlexValidations::Result]
11
+ #
12
+ # @example
13
+ # odd.validate(1).success? #=> true
14
+ #
15
+ # @example
16
+ # odd.validate(2).fail? #=> true
17
+ def validate(value)
18
+ raise 'not implemented'
19
+ end
20
+
21
+ # @abstract Returns description of validation
22
+ #
23
+ # @return [String]
24
+ def to_s
25
+ raise 'not implemented'
26
+ end
27
+
28
+ # @param [Object] Value to match
29
+ #
30
+ # @return [Boolean]
31
+ #
32
+ # @example
33
+ # case 1
34
+ # when odd
35
+ # puts "its odd number"
36
+ # end
37
+ def ===(value)
38
+ validate(value).success? || super
39
+ end
40
+
41
+ # @return [Proc]
42
+ #
43
+ # @example
44
+ # [1, 2, 3].select(&odd) #=> [1, 3]
45
+ def to_proc
46
+ proc { |value| validate(value).success? }
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexValidations
4
+ VERSION = "0.1.0"
5
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flex_validations
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dmitry Bochkarev
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-01-11 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Object Oriented Validation Library
14
+ email:
15
+ - dimabochkarev@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rspec"
22
+ - ".solargraph.yml"
23
+ - ".travis.yml"
24
+ - Gemfile
25
+ - Gemfile.lock
26
+ - LICENSE.txt
27
+ - README.md
28
+ - Rakefile
29
+ - bin/console
30
+ - bin/setup
31
+ - flex_validations.gemspec
32
+ - lib/flex_validations.rb
33
+ - lib/flex_validations/all.rb
34
+ - lib/flex_validations/and.rb
35
+ - lib/flex_validations/call.rb
36
+ - lib/flex_validations/chain.rb
37
+ - lib/flex_validations/decorate.rb
38
+ - lib/flex_validations/or.rb
39
+ - lib/flex_validations/predicate.rb
40
+ - lib/flex_validations/result.rb
41
+ - lib/flex_validations/result_message.rb
42
+ - lib/flex_validations/validation.rb
43
+ - lib/flex_validations/version.rb
44
+ homepage: https://github.com/DmitryBochkarev/flex_validations
45
+ licenses:
46
+ - MIT
47
+ metadata:
48
+ homepage_uri: https://github.com/DmitryBochkarev/flex_validations
49
+ source_code_uri: https://github.com/DmitryBochkarev/flex_validations
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubyforge_project:
66
+ rubygems_version: 2.7.9
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: Object Oriented Validation Library
70
+ test_files: []