smart_core 0.1.0 → 0.2.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.
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"