flex_validations 0.1.0

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.
@@ -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: []