smart_core 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 386622ee9696b78aaf7e86cfcc5eaa6e11591bab6607a355d85b9fbfa90d513a
4
- data.tar.gz: 9a905a991a12f36dc775076afa4f9069c099c33351e1d19911be4087789f8858
3
+ metadata.gz: e2ff8c25822c588b21c3d5785f04998fb1bb838e30b6046cc7e1706891133c6b
4
+ data.tar.gz: 307a1f6b397dba1339c9e6d4ea701343b349c22261b59e42ae35ccce735b7ff7
5
5
  SHA512:
6
- metadata.gz: 1d7d6a650e4b4656e932883da4669b3eb2659a1b32e68d70db5eaf48f6d37edd8470ac2400ab3822df60a475ebd669c761899ed08d4613282ef8738be22eda5f
7
- data.tar.gz: 648eb736394268e455e478431df012b7658cb434e8fc5ab506fe16f3f81a005fd7fa3fa1c1d1777a0395cd8899490ffc1a47c1f3e05314cf5a1aca078c4662b1
6
+ metadata.gz: 7755b70015cc2cb2c0cc76c9e3436cda0f7d76170f9109e3e20655ce6e29c16a530b1b522398355329eaa64e5cd139153ba3c93043372b6e734dbfe4994b47a8
7
+ data.tar.gz: e580d37f900e4e969464b094d4fc4d4795edcc6bbd90e482716dfcbc7147e327543dcf7e8c29ae5905a15ccd29a362b1b3873cc68972e742c3a5fc579696062b
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
1
  --color
2
- --format=progress
2
+ --format progress
3
3
  --require spec_helper
data/.travis.yml CHANGED
@@ -2,15 +2,19 @@ language: ruby
2
2
  sudo: false
3
3
  before_install: gem install bundler
4
4
  cache: bundler
5
- rvm:
6
- - 2.3.8
7
- - 2.4.5
8
- - 2.5.3
9
- - 2.6.0
10
- - ruby-head
11
- - jruby-head
12
- script:
13
- - bundle exec rspec
5
+ matrix:
6
+ fast_finish: true
7
+ include:
8
+ - rvm: 2.3.8
9
+ - rvm: 2.4.5
10
+ - rvm: 2.5.3
11
+ - rvm: 2.6.0
12
+ - rvm: ruby-head
13
+ - rvm: jruby-head
14
+ allow_failures:
15
+ - rvm: ruby-head
16
+ - rvm: jruby-head
17
+ script: bundle exec rspec
14
18
  notifications:
15
19
  slack:
16
20
  secure: CUM3xnSJvn/y6Z+epQrO5uSk1EUuP4S+ugcS+48nlWD6eEzhYZwcBAK9tvD6kYGOPzoWyKSbW6Is1kzSunNr6f8ICc8HtrVHzoHE8UCzwZ7FpjgD0JgMgC4+xLLBO6lV0Hc7dNh4KLAhy4Zvbr09DPjcG8UW9/QgUdn+wBf9LHMgA+2SpBPEzMwz6pMbWW/+kkoCz2KU877kEfuFFGfvoOEFWTxUUhuCYi5a1SmTjm9A7CFTEWNkX8yHJwdgVgONjvm9lnhTeV0DvMeEOhk8Kx/hzQ2OGIicXZXWyZFkzNzpQm5xE12Hs4rY7mGPbo3bsYRDH9Ys6yf4fpNf0oZ/1agzl/gnDzacA2wL2b+eq1uZblE/fdwrNp+GIH0R+Ec2rsVKM7kh71sA6Xp1pzFb2DnHj5O4QFaPMsLyIZqD0BkVt4pNfwi4ZUB56sWkYeRQGm8x2Beh02IyFfxI65lY5kZrW6GjXx5eFeOHcmzQzeADWLSBf4iw01G/A/QqwZzlZ3fm0fRJi8BKDh3Jj0IrGndE9Bl+E7uOClKKngTPxOVLOLo/vY5mfI8M94f99kr0J45XkoAdc0SQfi3hDa/tHaakI5GJsmByjgOQwV7OU/2wzJoXAg/RPYjsYcwnTgzm9bLXPhQzfM8rUS5vLTOWIW+N0QKmWml7eWVtiedcwXA=
data/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # Changelog
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+ ## [0.2.0] - 2019-01-20
5
+ ### Added
6
+ - **Service object** abstraction (`SmartCore::Operation`);
7
+ - **Validator**
8
+ - Support for `&block` attribute at object instantiation;
9
+
4
10
  ## [0.1.0] - 2019-01-05
5
11
  ### Added
6
12
  - Validation object abstraction;
data/README.md CHANGED
@@ -1,12 +1,75 @@
1
- # SmartCore · [![Build Status](https://travis-ci.org/0exp/smart_core.svg?branch=master)](https://travis-ci.org/0exp/smart_core) [![Coverage Status](https://coveralls.io/repos/github/0exp/smart_core/badge.svg?branch=master)](https://coveralls.io/github/0exp/smart_core?branch=master)
1
+ # SmartCore · [![Gem Version](https://badge.fury.io/rb/smart_core.svg)](https://badge.fury.io/rb/smart_core) [![Build Status](https://travis-ci.org/0exp/smart_core.svg?branch=master)](https://travis-ci.org/0exp/smart_core) [![Coverage Status](https://coveralls.io/repos/github/0exp/smart_core/badge.svg?branch=master)](https://coveralls.io/github/0exp/smart_core?branch=master)
2
2
 
3
3
  In active development.
4
4
 
5
+ ---
6
+
7
+ ## Installation
8
+
9
+ ```ruby
10
+ gem 'smart_core'
11
+ ```
12
+
13
+ ```shell
14
+ bundle install
15
+ # --- or ---
16
+ gem install smart_core
17
+ ```
18
+
19
+ ```ruby
20
+ require 'smart_core'
21
+ ```
22
+
23
+ ---
24
+
5
25
  #### Completed abstractions:
6
26
 
7
- - **Validation object** (`SmartCore::Validator`)
27
+ - [**Operation Object**](#operation-object) (aka `Service Object`) (`SmartCore::Operation`)
28
+ - attribute definition DSL (`param`, `option`, `params`, `options`);
29
+ - yieldable result object abstraction (`Success`, `Failure`, `#success?`, `#failure?`);
30
+ - yieldable `#call` (and `.call`);
31
+ - inheritance works as expected `:)`;
32
+ - no dependencies;
33
+
34
+ - [**Validation Object**](#validation-object) (`SmartCore::Validator`)
8
35
  - support for nested validations;
9
36
  - inheritance works as expected `:)`;
10
37
  - command-style DSL;
11
38
  - thread-safe;
12
39
  - no dependencies;
40
+
41
+ ---
42
+
43
+ #### Operation Object
44
+
45
+ ```ruby
46
+ class Service < SmartCore::Operation
47
+ # soon...
48
+ end
49
+ ```
50
+
51
+ #### Validation Object
52
+
53
+ ```ruby
54
+ class Validator < SmartCore::Validator
55
+ # soon...
56
+ end
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Contributing
62
+
63
+ - Fork it ( https://github.com/0exp/smart_core/fork )
64
+ - Create your feature branch (`git checkout -b feature/my-new-feature`)
65
+ - Commit your changes (`git commit -am 'Add some feature'`)
66
+ - Push to the branch (`git push origin feature/my-new-feature`)
67
+ - Create new Pull Request
68
+
69
+ ## License
70
+
71
+ Released under MIT License.
72
+
73
+ ## Authors
74
+
75
+ [Rustam Ibragimov](https://github.com/0exp)
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.2.0
5
+ class SmartCore::Operation::Attribute
6
+ # @return [Symbol]
7
+ #
8
+ # @api private
9
+ # @since 0.2.0
10
+ attr_reader :name
11
+
12
+ # @return [Hash<Symbol,Any>]
13
+ #
14
+ # @api private
15
+ # @since 0.2.0
16
+ attr_reader :options
17
+
18
+ # @param name [String, Symbol]
19
+ # @param options [Hash<Symbol,Any>] Supported options:
20
+ # - :default (see #default_value) (proc or object)
21
+ # @return [void]
22
+ #
23
+ # @api private
24
+ # @since 0.2.0
25
+ def initialize(name, **options)
26
+ unless name.is_a?(Symbol) || name.is_a?(String)
27
+ raise(
28
+ SmartCore::Operation::IncorrectAttributeNameError,
29
+ 'Attribute name should be a symbol or a string'
30
+ )
31
+ end
32
+
33
+ @name = name
34
+ @options = options # TODO: check for unsupported options (and fail if found)
35
+ end
36
+
37
+ # @return [Boolean]
38
+ #
39
+ # @api private
40
+ # @since 0.2.0
41
+ def has_default_value?
42
+ options.key?(:default)
43
+ end
44
+
45
+ # @return [Any]
46
+ #
47
+ # @raise [SmartCore::Operation::ArgumentError]
48
+ #
49
+ # @api private
50
+ # @since 0.2.0
51
+ def default_value
52
+ default_value = options.fetch(:default) do
53
+ raise(SmartCore::Operation::ArgumentError, 'Default value is not provided.')
54
+ end
55
+
56
+ default_value.is_a?(Proc) ? default_value.call : default_value
57
+ end
58
+
59
+ # @return [SmartCore::Operation::Attribute]
60
+ #
61
+ # @api private
62
+ # @since 0.2.0
63
+ def dup
64
+ self.class.new(name, **options)
65
+ end
66
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.2.0
5
+ class SmartCore::Operation::AttributeDefiner
6
+ # @param operation_klass [Class<SmartCore::Operation>]
7
+ # @return [void]
8
+ #
9
+ # @api private
10
+ # @since 0.2.0
11
+ def initialize(operation_klass)
12
+ @operation_klass = operation_klass
13
+ @definition_lock = Mutex.new
14
+ end
15
+
16
+ # @param param_name [Symbol, String]
17
+ # @return [void]
18
+ #
19
+ # @api private
20
+ # @since 0.2.0
21
+ def define_param(param_name)
22
+ thread_safe do
23
+ parameter = build_attribute(param_name)
24
+ prevent_intersection_with_already_defined_option(parameter)
25
+ append_parameter(parameter)
26
+ end
27
+ end
28
+
29
+ # @param param_names [Array<String, Symbol>]
30
+ # @return [void]
31
+ #
32
+ # @api private
33
+ # @since 0.2.0
34
+ def define_params(*param_names)
35
+ thread_safe do
36
+ parameters = param_names.map do |param_name|
37
+ build_attribute(param_name).tap do |parameter|
38
+ prevent_intersection_with_already_defined_option(parameter)
39
+ end
40
+ end
41
+
42
+ parameters.each { |parameter| append_parameter(parameter) }
43
+ end
44
+ end
45
+
46
+ # @param option_name [Symbol, String]
47
+ # @return [void]
48
+ #
49
+ # @api private
50
+ # @since 0.2.0
51
+ def define_option(option_name, **options)
52
+ thread_safe do
53
+ option = build_attribute(option_name, **options)
54
+ prevent_intersection_with_already_defined_param(option)
55
+ append_option(option)
56
+ end
57
+ end
58
+
59
+ # @param option_names [Array<String, Symbol>]
60
+ # @return [void]
61
+ #
62
+ # @api private
63
+ # @since 0.2.0
64
+ def define_options(*option_names)
65
+ thread_safe do
66
+ options = option_names.map do |option_name|
67
+ build_attribute(option_name).tap do |option|
68
+ prevent_intersection_with_already_defined_param(option)
69
+ end
70
+ end
71
+
72
+ options.each { |option| append_option(option) }
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ # @return [Class<SmartCore::Operation>]
79
+ #
80
+ # @api private
81
+ # @since 0.2.0
82
+ attr_reader :operation_klass
83
+
84
+ # @return [Mutex]
85
+ #
86
+ # @api private
87
+ # @since 0.2.0
88
+ attr_reader :definition_lock
89
+
90
+ # @param attribute_name [Symbol, String]
91
+ # @param attribute_options [Hash<Symbol,Any>]
92
+ # @return [SmartCore::Operation::Attribute]
93
+ #
94
+ # @api private
95
+ # @since 0.2.0
96
+ def build_attribute(attribute_name, **attribute_options)
97
+ SmartCore::Operation::Attribute.new(attribute_name, **attribute_options)
98
+ end
99
+
100
+ # @param parameter [SmartCore::Operation::Attribute]
101
+ # @return [void]
102
+ #
103
+ # @api private
104
+ # @since 0.2.0
105
+ def append_parameter(parameter)
106
+ operation_klass.__params__ << parameter
107
+ operation_klass.send(:attr_reader, parameter.name)
108
+ end
109
+
110
+ # @param option [SmartCore::Operation::Attribute]
111
+ # @return [void]
112
+ #
113
+ # @api private
114
+ # @since 0.2.0
115
+ def append_option(option)
116
+ operation_klass.__options__ << option
117
+ operation_klass.send(:attr_reader, option.name)
118
+ end
119
+
120
+ # @param parameter [SmartCore::Operation::Attribute]
121
+ # @return [void]
122
+ #
123
+ # @raise [SmartCore::Operation::OptionOverlapError]
124
+ #
125
+ # @api private
126
+ # @since 0.2.0
127
+ def prevent_intersection_with_already_defined_option(parameter)
128
+ if operation_klass.__options__.conflicts_with?(parameter)
129
+ raise(
130
+ SmartCore::Operation::OptionOverlapError,
131
+ "You have already defined option with :#{parameter.name} name"
132
+ )
133
+ end
134
+ end
135
+
136
+ # @param option [SmartCore::Operation::Attribute]
137
+ # @return [void]
138
+ #
139
+ # @raise [SmartCore::Operation::ParamOverlapError]
140
+ #
141
+ # @api private
142
+ # @since 0.2.0
143
+ def prevent_intersection_with_already_defined_param(option)
144
+ if operation_klass.__params__.conflicts_with?(option)
145
+ raise(
146
+ SmartCore::Operation::ParamOverlapError,
147
+ "You have already defined param with :#{option.name} name"
148
+ )
149
+ end
150
+ end
151
+
152
+ # @param block [Proc]
153
+ # @return [Any]
154
+ #
155
+ # @api private
156
+ # @since 0.2.0
157
+ def thread_safe(&block)
158
+ definition_lock.synchronize(&block)
159
+ end
160
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.2.0
5
+ class SmartCore::Operation::AttributeSet
6
+ # @since 0.2.0
7
+ include Enumerable
8
+
9
+ # @return [Hash<Symbol, SmartCore::Operation::Attribute>]
10
+ #
11
+ # @api private
12
+ # @since 0.2.0
13
+ attr_reader :attributes
14
+
15
+ # @return [void]
16
+ #
17
+ # @api private
18
+ # @since 0.2.0
19
+ def initialize
20
+ @attributes = {}
21
+ @access_lock = Mutex.new
22
+ end
23
+
24
+ # @param attribute [SmartCore::Operation::Attribute]
25
+ # @return [void]
26
+ #
27
+ # @api private
28
+ # @since 0.2.0
29
+ def add_attribute(attribute)
30
+ thread_safe { attributes[attribute.name] = attribute }
31
+ end
32
+ alias_method :<<, :add_attribute
33
+
34
+ # @param attribute_set [SmartCore::Operation::AttributeSet]
35
+ # @return [void]
36
+ #
37
+ # @api private
38
+ # @sinec 0.2.0
39
+ def concat(attribute_set)
40
+ thread_safe { attributes.merge!(attribute_set.dup.attributes) }
41
+ end
42
+
43
+ # @param attribute [SmartCore::Operation::Attribute]
44
+ # @return [Boolean]
45
+ #
46
+ # @api private
47
+ # @since 0.2.0
48
+ def conflicts_with?(attribute)
49
+ thread_safe { attributes.key?(attribute.name) }
50
+ end
51
+
52
+ # @return [SmartCore::Operation::AttributeSet]
53
+ #
54
+ # @api private
55
+ # @since 0.2.0
56
+ def dup
57
+ thread_safe do
58
+ self.class.new.tap do |duplicate|
59
+ attributes.each_value do |attribute|
60
+ duplicate.add_attribute(attribute.dup)
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ # @return [Integer]
67
+ #
68
+ # @api private
69
+ # @since 0.2.0
70
+ def size
71
+ thread_safe { attributes.size }
72
+ end
73
+
74
+ # @return [Enumerable]
75
+ #
76
+ # @api private
77
+ # @since 0.2.0
78
+ def each(&block)
79
+ thread_safe { block_given? ? attributes.each_value(&block) : attributes.each_value }
80
+ end
81
+
82
+ private
83
+
84
+ # @param block [Proc]
85
+ # @return [Any]
86
+ #
87
+ # @api private
88
+ # @since 0.2.0
89
+ def thread_safe(&block)
90
+ @access_lock.synchronize(&block)
91
+ end
92
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmartCore::Operation
4
+ # @api public
5
+ # @since 0.2.0
6
+ Error = Class.new(StandardError)
7
+
8
+ # @api public
9
+ # @since 0.2.0
10
+ ArgumentError = Class.new(::ArgumentError)
11
+
12
+ # @api public
13
+ # @since 0.2.0
14
+ ParameterError = Class.new(ArgumentError)
15
+
16
+ # @api public
17
+ # @since 0.2.0
18
+ ParamOverlapError = Class.new(ParameterError)
19
+
20
+ # @api public
21
+ # @since 0.2.0
22
+ OptionError = Class.new(ArgumentError)
23
+
24
+ # @api public
25
+ # @since 0.2.0
26
+ OptionOverlapError = Class.new(OptionError)
27
+
28
+ # @api public
29
+ # @since 0.2.0
30
+ IncorrectAttributeNameError = Class.new(Error)
31
+
32
+ # @api public
33
+ # @since 0.2.0
34
+ ResultMethodIntersectionError = Class.new(Error)
35
+
36
+ # @api public
37
+ # @since 0.2.0
38
+ IncompatibleResultObjectKeyError = Class.new(Error)
39
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api public
4
+ # @since 0.2.0
5
+ class SmartCore::Operation::Failure < SmartCore::Operation::Result
6
+ # @return [Array<Symbol|Any>]
7
+ #
8
+ # @api public
9
+ # @since 0.2.0
10
+ alias_method :errors, :__result_attributes__
11
+
12
+ # @param errors [Array<Symbol|Any>]
13
+ # @return [void]
14
+ #
15
+ # @api pubic
16
+ # @since 0.2.0
17
+ def initialize(*errors)
18
+ super(*errors)
19
+ end
20
+
21
+ # @yield [nil]
22
+ # @return [Boolean]
23
+ #
24
+ # @api public
25
+ # @since 0.2.0
26
+ def failure?
27
+ true.tap { yield(self) if block_given? }
28
+ end
29
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmartCore::Operation
4
+ # @api private
5
+ # @since 0.2.0
6
+ module InitializationDSL
7
+ class << self
8
+ # @param base_klass [Class]
9
+ # @return [void]
10
+ #
11
+ # @api private
12
+ # @since 0.2.0
13
+ def included(base_klass) # rubocop:disable Metrics/AbcSize
14
+ base_klass.extend(DSLMethods)
15
+ base_klass.singleton_class.prepend(InitializationMethods)
16
+
17
+ base_klass.instance_variable_set(:@__attr_definer__, AttributeDefiner.new(base_klass))
18
+ base_klass.instance_variable_set(:@__params__, AttributeSet.new)
19
+ base_klass.instance_variable_set(:@__options__, AttributeSet.new)
20
+
21
+ base_klass.singleton_class.prepend(Module.new do
22
+ # @param child_klass [Class]
23
+ # @return [void]
24
+ #
25
+ # @api private
26
+ # @since 0.2.0
27
+ def inherited(child_klass)
28
+ child_klass.singleton_class.prepend(InitializationMethods)
29
+
30
+ child_klass.instance_variable_set(:@__attr_definer__, AttributeDefiner.new(child_klass))
31
+ child_klass.instance_variable_set(:@__params__, AttributeSet.new)
32
+ child_klass.instance_variable_set(:@__options__, AttributeSet.new)
33
+
34
+ child_klass.__params__.concat(__params__)
35
+ child_klass.__options__.concat(__options__)
36
+
37
+ super(child_klass)
38
+ end
39
+ end)
40
+ end
41
+ end
42
+
43
+ # @api private
44
+ # @since 0.2.0
45
+ module InitializationMethods
46
+ # @param parameters [Any]
47
+ # @param options [Hash<Symbol,Any>]
48
+ # @return [Any]
49
+ #
50
+ # @api public
51
+ # @since 0.2.0
52
+ def new(*parameters, **options)
53
+ allocate.tap do |object|
54
+ InstanceBuilder.call(object, self, parameters, options)
55
+ end
56
+ end
57
+ end
58
+
59
+ # @api private
60
+ # @since 0.2.0
61
+ module DSLMethods
62
+ # @param param_name [String, Symbol]
63
+ # @return [void]
64
+ #
65
+ # @api public
66
+ # @since 0.2.0
67
+ def param(param_name)
68
+ __attr_definer__.define_param(param_name)
69
+ end
70
+
71
+ # @param param_names [Array<String, Symbol>]
72
+ # @return [void]
73
+ #
74
+ # @api public
75
+ # @since 0.2.0
76
+ def params(*param_names)
77
+ __attr_definer__.define_params(*param_names)
78
+ end
79
+
80
+ # @param option_name [String, Symbol]
81
+ # @param options [Hash<Symbol,Any>]
82
+ # @return [void]
83
+ #
84
+ # @api public
85
+ # @since 0.2.0
86
+ def option(option_name, **options)
87
+ __attr_definer__.define_option(option_name, **options)
88
+ end
89
+
90
+ # @param option_names [Array<String, Symbol>]
91
+ # @return [void]
92
+ #
93
+ # @api public
94
+ # @since 0.2.0
95
+ def options(*option_names)
96
+ __attr_definer__.define_options(*option_names)
97
+ end
98
+
99
+ # @return [SmartCore::Operation::AttributeSet]
100
+ #
101
+ # @api private
102
+ # @since 0.2.0
103
+ def __params__
104
+ @__params__
105
+ end
106
+
107
+ # @return [SmartCore::Operation::AttributeSet]
108
+ #
109
+ # @api private
110
+ # @since 0.2.0
111
+ def __options__
112
+ @__options__
113
+ end
114
+
115
+ # @return [SmartCore::Operation::AttributeDefiner]
116
+ #
117
+ # @api private
118
+ # @since 0.2.0
119
+ def __attr_definer__
120
+ @__attr_definer__
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.2.0
5
+ class SmartCore::Operation::InstanceBuilder
6
+ class << self
7
+ # @param operation_object [SmartCore::Operation]
8
+ # @param operation_klass [Class<SmartCore::Operation>]
9
+ # @param parameters [Array<Any>]
10
+ # @param options [Hash<Symbol,Any>]
11
+ # @return [SmartCore::Operation]
12
+ #
13
+ # @api private
14
+ # @since 0.2.0
15
+ def call(operation_object, operation_klass, parameters, options)
16
+ new(operation_object, operation_klass, parameters, options).call
17
+ end
18
+ end
19
+
20
+ # @param operation_object [SmartCore::Operation]
21
+ # @param operation_klass [Class<SmartCore::Operation>]
22
+ # @param parameters [Array<Any>]
23
+ # @param options [Hash<Symbol,Any>]
24
+ # @return [void]
25
+ #
26
+ # @api private
27
+ # @since 0.2.0
28
+ def initialize(operation_object, operation_klass, parameters, options)
29
+ @operation_object = operation_object
30
+ @operation_klass = operation_klass
31
+ @parameters = parameters
32
+ @options = options
33
+ end
34
+
35
+ # @return [SmartCore::Operation]
36
+ #
37
+ # @api private
38
+ # @since 0.2.0
39
+ def call
40
+ operation_object.tap do
41
+ prevent_parameters_incomparability
42
+ initialize_parameters
43
+ initialize_options
44
+ call_original_methods
45
+ make_operation_caller_yieldable
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ # @return [SmartCore::Operation]
52
+ #
53
+ # @api private
54
+ # @since 0.2.0
55
+ attr_reader :operation_object
56
+
57
+ # @return [Class<SmartCore::Operation>]
58
+ #
59
+ # @api private
60
+ # @since 0.2.0
61
+ attr_reader :operation_klass
62
+
63
+ # @return [Array<Any>]
64
+ #
65
+ # @api private
66
+ # @since 0.2.0
67
+ attr_reader :parameters
68
+
69
+ # @return [Hash<Symbol,Any>]
70
+ #
71
+ # @api private
72
+ # @since 0.2.0
73
+ attr_reader :options
74
+
75
+ # An array of the required option attribute names (parameters without default values)
76
+ #
77
+ # @return [Array<Symbol>]
78
+ #
79
+ # @api private
80
+ # @since 0.2.0
81
+ def required_options
82
+ operation_klass.__options__.each.reject(&:has_default_value?).map(&:name)
83
+ end
84
+
85
+ # @return [Integer]
86
+ #
87
+ # @api private
88
+ # @since 0.2.0
89
+ def required_attributes_count
90
+ operation_klass.__params__.size
91
+ end
92
+
93
+ # @return [void]
94
+ #
95
+ # @raise [SmartCore::Operation::AttributeError]
96
+ # @raise [SmartCore::Operation::ParameterError]
97
+ # @raise [SmartCore::Operation::OptionError]
98
+ #
99
+ # @api private
100
+ # @since 0.2.0
101
+ def prevent_parameters_incomparability
102
+ raise(
103
+ SmartCore::Operation::ParameterError,
104
+ "Wrong number of parameters " \
105
+ "(given #{parameters.size}, expected #{required_attributes_count})"
106
+ ) unless parameters.size == required_attributes_count
107
+
108
+ missing_options = required_options.reject { |option| options.key?(option) }
109
+
110
+ raise(
111
+ SmartCore::Operation::OptionError,
112
+ "Missing options: :#{missing_options.join(', :')}"
113
+ ) unless missing_options.empty?
114
+ end
115
+
116
+ # @return [void]
117
+ #
118
+ # @api private
119
+ # @since 0.2.0
120
+ def initialize_parameters
121
+ parameter_names = operation_klass.__params__.map(&:name)
122
+ parameter_pairs = Hash[parameter_names.zip(parameters)]
123
+
124
+ parameter_pairs.each_pair do |parameter_name, parameter_value|
125
+ operation_object.instance_variable_set("@#{parameter_name}", parameter_value)
126
+ end
127
+ end
128
+
129
+ # @return [void]
130
+ #
131
+ # @api private
132
+ # @since 0.2.0
133
+ def initialize_options
134
+ operation_klass.__options__.each do |option|
135
+ option_name = option.name
136
+ option_value = options.fetch(option_name) { option.default_value }
137
+
138
+ operation_object.instance_variable_set("@#{option_name}", option_value)
139
+ end
140
+ end
141
+
142
+ # @return [void]
143
+ #
144
+ # @api private
145
+ # @since 0.2.0
146
+ def call_original_methods
147
+ operation_object.send(:initialize, *parameters, **options)
148
+ end
149
+
150
+ # @return [void]
151
+ #
152
+ # @api private
153
+ # @since 0.2.0
154
+ def make_operation_caller_yieldable
155
+ operation_object.singleton_class.prepend(Module.new do
156
+ def call
157
+ super.tap { |result| yield(result) if block_given? }
158
+ end
159
+ end)
160
+ end
161
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.2.0
5
+ class SmartCore::Operation::Result
6
+ # @return [Array<Any>]
7
+ #
8
+ # @api private
9
+ # @since 0.2.0
10
+ attr_reader :__result_attributes__
11
+
12
+ # @return [Hash<Symbol,Any>]
13
+ #
14
+ # @api private
15
+ # @since 0.2.0
16
+ attr_reader :__result_options__
17
+
18
+ # @param result_attributes [Array<Any>]
19
+ # @param result_options [Hash<Symbol,Any>]
20
+ # @return [void]
21
+ #
22
+ # @api public
23
+ # @since 0.2.0
24
+ def initialize(*result_attributes, **result_options)
25
+ @__result_attributes__ = result_attributes
26
+ @__result_options__ = result_options
27
+ end
28
+
29
+ # @return [Boolean]
30
+ #
31
+ # @api public
32
+ # @since 0.2.0
33
+ def success?
34
+ false
35
+ end
36
+
37
+ # @return [Boolean]
38
+ #
39
+ # @api public
40
+ # @since 0.2.0
41
+ def failure?
42
+ false
43
+ end
44
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api public
4
+ # @since 0.2.0
5
+ class SmartCore::Operation::Success < SmartCore::Operation::Result
6
+ # @param result_options [Hash<Symbol,Any>]
7
+ # @return [void]
8
+ #
9
+ # @api public
10
+ # @since 0.2.0
11
+ def initialize(**result_options)
12
+ __prevent_method_overlapping__(result_options)
13
+ super(**result_options) # NOTE: initialize result object
14
+ __define_virtual_result_data_accessors__(result_options)
15
+ end
16
+
17
+ # @yield [nil]
18
+ # @return [Boolean]
19
+ #
20
+ # @api public
21
+ # @since 0.2.0
22
+ def success?
23
+ true.tap { yield(self) if block_given? }
24
+ end
25
+
26
+ private
27
+
28
+ # @param result_options [Hash<Symbol,Any>]
29
+ # @return [void]
30
+ #
31
+ # @raise [SmartCore::Operation::IncompatibleResultKeyError]
32
+ # @raise [SmartCore::Operation::ResultMethodIntersectionError]
33
+ #
34
+ # @api private
35
+ # @since 0.2.0
36
+ def __prevent_method_overlapping__(result_options)
37
+ overlappings = result_options.each_key.each_with_object([]) do |key, overlap|
38
+ overlap << key if self.class.__core_methods__.include?(key)
39
+ end
40
+
41
+ raise(
42
+ SmartCore::Operation::ResultMethodIntersectionError,
43
+ "Result keys can not overlap core methods " \
44
+ "(overlapping keys: #{overlappings.join(', ')})."
45
+ ) if overlappings.any?
46
+ end
47
+
48
+ # @param result_options [Hash<Symbol,Any>]
49
+ # @return [void]
50
+ #
51
+ # @api private
52
+ # @since 0.2.0
53
+ def __define_virtual_result_data_accessors__(result_options)
54
+ result_options.each_key do |result_attribute_name|
55
+ define_singleton_method(result_attribute_name) do
56
+ __result_options__[result_attribute_name]
57
+ end
58
+ end
59
+ end
60
+
61
+ core_methods = (
62
+ instance_methods(false) | private_instance_methods(false) |
63
+ superclass.instance_methods(false) | superclass.private_instance_methods(false)
64
+ ).freeze
65
+
66
+ # @return [Array<Symbol>]
67
+ #
68
+ # @api private
69
+ # @since 0.2.0
70
+ define_singleton_method(:__core_methods__) { core_methods }
71
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api public
4
+ # @since 0.2.0
5
+ class SmartCore::Operation
6
+ require_relative 'operation/exceptions'
7
+ require_relative 'operation/attribute'
8
+ require_relative 'operation/attribute_set'
9
+ require_relative 'operation/result'
10
+ require_relative 'operation/success'
11
+ require_relative 'operation/failure'
12
+ require_relative 'operation/instance_builder'
13
+ require_relative 'operation/attribute_definer'
14
+ require_relative 'operation/initialization_dsl'
15
+
16
+ # @since 0.2.0
17
+ include InitializationDSL
18
+
19
+ class << self
20
+ # @param arguments [Any]
21
+ # @param options [Hash<Symbol,Any>]
22
+ # @param block [Proc]
23
+ # @return [SmartCore::Operation::Success, SmartCore::Operation::Failure]
24
+ #
25
+ # @api public
26
+ # @since 0.2.0
27
+ def call(*arguments, **options, &block)
28
+ new(*arguments, **options).call(&block)
29
+ end
30
+ end
31
+
32
+ # @return [void]
33
+ #
34
+ # @api private
35
+ # @since 0.2.0
36
+ def initialize(*, **); end
37
+
38
+ # @return [SmartCore::Operation::Success, SmartCore::Operation::Failure]
39
+ #
40
+ # @api public
41
+ # @since 0.2.0
42
+ def call
43
+ Success()
44
+ end
45
+
46
+ private
47
+
48
+ # @param result_data [Hash<Symbol,Any>]
49
+ # @return [SmartCore::Operation::Success]
50
+ #
51
+ # @api public
52
+ # @since 0.2.0
53
+ def Success(**result_data) # rubocop:disable Naming/MethodName
54
+ SmartCore::Operation::Success.new(**result_data)
55
+ end
56
+
57
+ # @param errors [Array<Symbol|Any>]
58
+ # @return [SmartCore::Operation::Failure]
59
+ #
60
+ # @api public
61
+ # @since 0.2.0
62
+ def Failure(*errors) # rubocop:disable Naming/MethodName
63
+ SmartCore::Operation::Failure.new(*errors)
64
+ end
65
+ end
@@ -6,7 +6,7 @@ class SmartCore::Validator::AttributeSet
6
6
  # @since 0.1.0
7
7
  include Enumerable
8
8
 
9
- # @return [Hash<Symbol, SmartCore::Validator::Attribute>]
9
+ # @return [Hash<Symbol,SmartCore::Validator::Attribute>]
10
10
  #
11
11
  # @api private
12
12
  # @since 0.1.0
@@ -21,7 +21,7 @@ class SmartCore::Validator::AttributeSet
21
21
  @access_lock = Mutex.new
22
22
  end
23
23
 
24
- # @param attribute [Symbiont::Validator::Attribute]
24
+ # @param attribute [SmartCore::Validator::Attribute]
25
25
  # @return [void]
26
26
  #
27
27
  # @api private
@@ -31,7 +31,7 @@ class SmartCore::Validator
31
31
  # @param attribute_name [String, Symbol]
32
32
  # @return [void]
33
33
  #
34
- # @api private
34
+ # @api public
35
35
  # @since 0.1.0
36
36
  def attribute(attribute_name, default: nil)
37
37
  attribute = SmartCore::Validator::Attribute.new(attribute_name, default)
@@ -86,7 +86,7 @@ class SmartCore::Validator
86
86
  #
87
87
  # @see SmartCore::Validator::Commands::ValidateWith
88
88
  #
89
- # @api private
89
+ # @api public
90
90
  # @since 0.1.0
91
91
  def validate_with(validating_klass, &nested_validations)
92
92
  commands << Commands::ValidateWith.new(validating_klass, nested_validations)
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.2.0
5
+ class SmartCore::Validator
6
+ class InstanceBuilder
7
+ class << self
8
+ # @param validator_object [SmartCore::Validator]
9
+ # @param validator_klass [Class<SmartCore::Validator>]
10
+ # @param arguments [Array<Any>]
11
+ # @param options [Hash<Symbol,Any>]
12
+ # @param block [Proc]
13
+ # @return [SmartCore::Validator]
14
+ #
15
+ # @api private
16
+ # @since 0.2.0
17
+ def call(validator_object, validator_klass, arguments, options, block)
18
+ new(validator_object, validator_klass, arguments, options, block).call
19
+ end
20
+ end
21
+
22
+ # @param validator_object [SmartCore::Validator]
23
+ # @param validator_klass [Class<SmartCore::Validator>]
24
+ # @param arguments [Array<Any>]
25
+ # @param options [Hash<Symbol,Any>]
26
+ # @param block [Proc]
27
+ # @return [void]
28
+ #
29
+ # @api private
30
+ # @since 0.2.0
31
+ def initialize(validator_object, validator_klass, arguments, options, block)
32
+ @validator_object = validator_object
33
+ @validator_klass = validator_klass
34
+ @arguments = arguments
35
+ @options = options
36
+ @block = block
37
+ end
38
+
39
+ # @return [SmartCore::Validator]
40
+ #
41
+ # @api private
42
+ # @since 0.2.0
43
+ def call
44
+ validator_object.tap do
45
+ initialize_core_attributes
46
+ initialize_custom_attributes
47
+ invoke_original_methods
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ # @return [SmartCore::Validator]
54
+ #
55
+ # @api private
56
+ # @since 0.2.0
57
+ attr_reader :validator_object
58
+
59
+ # @return [Class<SmartCore::Validator>]
60
+ #
61
+ # @api private
62
+ # @since 0.2.0
63
+ attr_reader :validator_klass
64
+
65
+ # @return [Array<Any>]
66
+ #
67
+ # @api private
68
+ # @since 0.2.0
69
+ attr_reader :arguments
70
+
71
+ # @return [Hash<Symbol,Any>]
72
+ #
73
+ # @api private
74
+ # @since 0.2.0
75
+ attr_reader :options
76
+
77
+ # @return [Proc]
78
+ #
79
+ # @api private
80
+ # @since 0.2.0
81
+ attr_reader :block
82
+
83
+ # @return [void]
84
+ #
85
+ # @api private
86
+ # @since 0.2.0
87
+ def initialize_core_attributes
88
+ validator_object.instance_variable_set(:@__validation_errors__, ErrorSet.new)
89
+ validator_object.instance_variable_set(:@__invokation_lock__, Mutex.new)
90
+ validator_object.instance_variable_set(:@__access_lock__, Mutex.new)
91
+ end
92
+
93
+ # @return [void]
94
+ #
95
+ # @api private
96
+ # @since 0.2.0
97
+ def initialize_custom_attributes
98
+ validator_klass.attributes.each do |attribute|
99
+ attribute_name = attribute.name
100
+ attribute_value = options.fetch(attribute_name) { attribute.default_value }
101
+
102
+ validator_object.instance_variable_set("@#{attribute_name}", attribute_value)
103
+ end
104
+ end
105
+
106
+ # @return [void]
107
+ #
108
+ # @api private
109
+ # @since 0.2.0
110
+ def invoke_original_methods
111
+ validator_object.send(:initialize, *arguments, **options, &block)
112
+ end
113
+ end
114
+ end
@@ -11,37 +11,22 @@ class SmartCore::Validator
11
11
  require_relative 'validator/invoker'
12
12
  require_relative 'validator/commands'
13
13
  require_relative 'validator/dsl'
14
+ require_relative 'validator/instance_builder'
14
15
 
15
16
  # @since 0.1.0
16
17
  extend DSL
17
18
 
18
19
  class << self
19
- # @param argumants [Any]
20
- # @param options [Hash<Symbol, Object>]
20
+ # @param arguments [Any]
21
+ # @param options [Hash<Symbol,Object>]
22
+ # @param block [Proc]
21
23
  # @return [void]
22
24
  #
23
25
  # @api public
24
26
  # @since 0.1.0
25
- def new(*arguments, **options)
27
+ def new(*arguments, **options, &block)
26
28
  allocate.tap do |object|
27
- object.instance_variable_set(:@__validation_errors__, ErrorSet.new)
28
- object.instance_variable_set(:@__invokation_lock__, Mutex.new)
29
- object.instance_variable_set(:@__access_lock__, Mutex.new)
30
-
31
- attributes.each do |attribute|
32
- attribute_name = attribute.name
33
-
34
- attribute_value =
35
- if options.key?(attribute_name)
36
- options[attribute_name]
37
- else
38
- attribute.default_value
39
- end
40
-
41
- object.instance_variable_set("@#{attribute_name}", attribute_value)
42
- end
43
-
44
- object.send(:initialize, *arguments, **options)
29
+ InstanceBuilder.call(object, self, arguments, options, block)
45
30
  end
46
31
  end
47
32
  end
@@ -56,7 +41,7 @@ class SmartCore::Validator
56
41
  #
57
42
  # @api public
58
43
  # @since 0.1.0
59
- def initialize(*, **); end
44
+ def initialize(*, **, &block); end
60
45
 
61
46
  # @return [Boolean]
62
47
  #
@@ -91,7 +76,7 @@ class SmartCore::Validator
91
76
  end
92
77
  end
93
78
 
94
- # @return [Hash<Symbol, Object>]
79
+ # @return [Hash<Symbol,Object>]
95
80
  #
96
81
  # @api private
97
82
  # @since 0.1.0
@@ -5,5 +5,5 @@ module SmartCore
5
5
  #
6
6
  # @api public
7
7
  # @since 0.1.0
8
- VERSION = '0.1.0'
8
+ VERSION = '0.2.0'
9
9
  end
data/lib/smart_core.rb CHANGED
@@ -5,4 +5,5 @@
5
5
  module SmartCore
6
6
  require_relative 'smart_core/version'
7
7
  require_relative 'smart_core/validator'
8
+ require_relative 'smart_core/operation'
8
9
  end
data/smart_core.gemspec CHANGED
@@ -26,9 +26,10 @@ Gem::Specification.new do |spec|
26
26
 
27
27
  spec.add_development_dependency 'coveralls', '~> 0.8.22'
28
28
  spec.add_development_dependency 'simplecov', '~> 0.16.1'
29
- spec.add_development_dependency 'armitage-rubocop', '~> 0.16.0'
29
+ spec.add_development_dependency 'armitage-rubocop', '~> 0.17.0'
30
30
  spec.add_development_dependency 'rspec', '~> 3.8.0'
31
- spec.add_development_dependency 'bundler', '~> 1.17'
32
- spec.add_development_dependency 'rake', '~> 12.3'
33
- spec.add_development_dependency 'pry', '~> 0.12'
31
+
32
+ spec.add_development_dependency 'bundler'
33
+ spec.add_development_dependency 'rake'
34
+ spec.add_development_dependency 'pry'
34
35
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-05 00:00:00.000000000 Z
11
+ date: 2019-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: coveralls
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.16.0
47
+ version: 0.17.0
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.16.0
54
+ version: 0.17.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -70,44 +70,44 @@ dependencies:
70
70
  name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '1.17'
75
+ version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: '1.17'
82
+ version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rake
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '12.3'
89
+ version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '12.3'
96
+ version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: pry
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: '0.12'
103
+ version: '0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: '0.12'
110
+ version: '0'
111
111
  description: "(in active development) A set of common abstractions"
112
112
  email:
113
113
  - iamdaiver@icloud.com
@@ -128,6 +128,16 @@ files:
128
128
  - bin/console
129
129
  - bin/setup
130
130
  - lib/smart_core.rb
131
+ - lib/smart_core/operation.rb
132
+ - lib/smart_core/operation/attribute.rb
133
+ - lib/smart_core/operation/attribute_definer.rb
134
+ - lib/smart_core/operation/attribute_set.rb
135
+ - lib/smart_core/operation/exceptions.rb
136
+ - lib/smart_core/operation/failure.rb
137
+ - lib/smart_core/operation/initialization_dsl.rb
138
+ - lib/smart_core/operation/instance_builder.rb
139
+ - lib/smart_core/operation/result.rb
140
+ - lib/smart_core/operation/success.rb
131
141
  - lib/smart_core/validator.rb
132
142
  - lib/smart_core/validator/attribute.rb
133
143
  - lib/smart_core/validator/attribute_set.rb
@@ -141,6 +151,7 @@ files:
141
151
  - lib/smart_core/validator/dsl.rb
142
152
  - lib/smart_core/validator/error_set.rb
143
153
  - lib/smart_core/validator/exceptions.rb
154
+ - lib/smart_core/validator/instance_builder.rb
144
155
  - lib/smart_core/validator/invoker.rb
145
156
  - lib/smart_core/version.rb
146
157
  - smart_core.gemspec
@@ -163,7 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
163
174
  - !ruby/object:Gem::Version
164
175
  version: '0'
165
176
  requirements: []
166
- rubygems_version: 3.0.1
177
+ rubygems_version: 3.0.2
167
178
  signing_key:
168
179
  specification_version: 4
169
180
  summary: "(in active development) A set of common abstractions"