subroutine 0.7.0 → 0.8.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
- SHA256:
3
- metadata.gz: 7416fb00f0a0322ec831337e3afaf019d21cc83f4573afa13e8485a64ffb8e7c
4
- data.tar.gz: 0d40d5a0843b57c35e046ecfcaedcbf4471386ec64a01577723c8717d98ebdc1
2
+ SHA1:
3
+ metadata.gz: 7a1e060b5a23f95d926f8df63e2eb9fc12745a93
4
+ data.tar.gz: 1205b94faff4d024aa9670cbd0cee003ea3bff5f
5
5
  SHA512:
6
- metadata.gz: 449ff84a202510087c5ab59a6f1809d1b59a1ec77f7e496fb37a6d93369bd5f1a1589fb102bc739763f3c1f3a74614558bb2c70d2ba17448f7df3398a2b1b498
7
- data.tar.gz: 6e779574462dc657ce38422a094c556bcf36171698f8c02e165ce850e8aef0e79a7030e1c614332965efc4399c1fc7f8cbd33dc7570d7c92b8e8b5bb562f36a1
6
+ metadata.gz: c3313881107db6835f530a41b17f0702f407a2c83413cd7705c00ef4b02017b54566037ad464231651e04948a8416ad4fc02c6939e36d2ac814383fb542829ba
7
+ data.tar.gz: 75a5d2fe57b634ff5d4e84e105621c769679f065bc1f72c49b835b988972c39fe2d041fbda7f8c8ff99e3c51d79df51ab01ce63299a3b46d88e2a39096d73f2f
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.5.1
1
+ 2.4.6
data/.travis.yml CHANGED
@@ -2,12 +2,19 @@ language: ruby
2
2
  sudo: false
3
3
 
4
4
  rvm:
5
- - 2.0.0
6
- - 2.1.1
7
- - 2.2.3
8
- - 2.5.1
9
- - jruby
5
+ - 2.4.6
6
+ - 2.5.5
7
+ - 2.6.3
10
8
 
11
9
  gemfile:
12
10
  - gemfiles/am41.gemfile
13
11
  - gemfiles/am42.gemfile
12
+ - gemfiles/am50.gemfile
13
+ - gemfiles/am51.gemfile
14
+ - gemfiles/am52.gemfile
15
+ - gemfiles/am60.gemfile
16
+
17
+ matrix:
18
+ exclude:
19
+ - rvm: 2.4.6
20
+ gemfile: gemfiles/am60.gemfile
data/Gemfile CHANGED
@@ -3,4 +3,4 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in subroutine.gemspec
4
4
  gemspec
5
5
 
6
- gem 'activemodel', '~> 4.2.0'
6
+ gem 'activemodel', '~> 5.2.3'
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec :path => '../'
4
4
 
5
- gem 'activemodel', '~> 3.2.21'
5
+ gem 'activemodel', '~> 5.0.7.2'
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec :path => '../'
4
4
 
5
- gem 'activemodel', '~> 3.0.20'
5
+ gem 'activemodel', '~> 5.1.7'
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec :path => '../'
4
4
 
5
- gem 'activemodel', '~> 3.1.12'
5
+ gem 'activemodel', '~> 5.2.3'
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec :path => '../'
4
4
 
5
- gem 'activemodel', '~> 4.0.13'
5
+ gem 'activemodel', '~> 6.0.0.rc1'
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+ require "active_support/core_ext/object/duplicable"
5
+ require "active_support/core_ext/hash/indifferent_access"
6
+ require "active_support/core_ext/object/deep_dup"
7
+
8
+ require "subroutine/type_caster"
9
+ require "subroutine/association"
10
+
11
+ module Subroutine
12
+ module Fields
13
+
14
+ extend ActiveSupport::Concern
15
+
16
+ included do
17
+ class_attribute :_fields
18
+ self._fields = {}
19
+ attr_reader :original_params
20
+ attr_reader :params, :defaults
21
+ end
22
+
23
+ module ClassMethods
24
+ # fields can be provided in the following way:
25
+ # field :field1, :field2
26
+ # field :field3, :field4, default: 'my default'
27
+ def field(*fields)
28
+ options = fields.extract_options!
29
+
30
+ fields.each do |f|
31
+ _field(f, options)
32
+ end
33
+ end
34
+ alias_method :fields, :field
35
+
36
+ def inputs_from(*things)
37
+ options = things.extract_options!
38
+ excepts = options.key?(:except) ? Array(options.delete(:except)) : nil
39
+ onlys = options.key?(:only) ? Array(options.delete(:only)) : nil
40
+
41
+ things.each do |thing|
42
+ thing._fields.each_pair do |field_name, opts|
43
+ next if excepts && excepts.include?(field_name)
44
+ next if onlys && !onlys.include?(field_name)
45
+
46
+ if opts[:association]
47
+ include ::Subroutine::Association unless included_modules.include?(::Subroutine::Association)
48
+ association(field_name, opts)
49
+ else
50
+ field(field_name, opts)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ alias_method :fields_from, :inputs_from
56
+
57
+ def respond_to_missing?(method_name, *args, &block)
58
+ ::Subroutine::TypeCaster.casters.key?(method_name.to_sym) || super
59
+ end
60
+
61
+ def method_missing(method_name, *args, &block)
62
+ caster = ::Subroutine::TypeCaster.casters[method_name.to_sym]
63
+ if caster
64
+ options = args.extract_options!
65
+ options[:type] = method_name.to_sym
66
+ args.push(options)
67
+ field(*args, &block)
68
+ else
69
+ super
70
+ end
71
+ end
72
+
73
+ protected
74
+
75
+ def _field(field_name, options = {})
76
+ self._fields = _fields.merge(field_name.to_sym => options)
77
+
78
+ class_eval <<-EV, __FILE__, __LINE__ + 1
79
+
80
+ def #{field_name}=(v)
81
+ config = #{field_name}_config
82
+ v = ::Subroutine::TypeCaster.cast(v, config)
83
+ @params["#{field_name}"] = v
84
+ end
85
+
86
+ def #{field_name}
87
+ @params.has_key?("#{field_name}") ? @params["#{field_name}"] : @defaults["#{field_name}"]
88
+ end
89
+
90
+ def #{field_name}_config
91
+ _fields[:#{field_name}]
92
+ end
93
+
94
+ EV
95
+ end
96
+ end
97
+
98
+ def setup_fields(inputs = {})
99
+ @original_params = inputs.with_indifferent_access
100
+ @params = sanitize_params(@original_params)
101
+ @defaults = sanitize_defaults
102
+ end
103
+
104
+ # check if a specific field was provided
105
+ def field_provided?(key)
106
+ @params.key?(key)
107
+ end
108
+
109
+ # if you want to use strong parameters or something in your form object you can do so here.
110
+ # by default we just slice the inputs to the defined fields
111
+ def sanitize_params(inputs)
112
+ out = {}.with_indifferent_access
113
+ _fields.each_pair do |field, config|
114
+ next unless inputs.key?(field)
115
+
116
+ out[field] = ::Subroutine::TypeCaster.cast(inputs[field], config)
117
+ end
118
+
119
+ out
120
+ end
121
+
122
+ def params_with_defaults
123
+ @defaults.merge(@params)
124
+ end
125
+
126
+ def sanitize_defaults
127
+ defaults = {}.with_indifferent_access
128
+
129
+ _fields.each_pair do |field, config|
130
+ next if config[:default].nil?
131
+
132
+ deflt = config[:default]
133
+ if deflt.respond_to?(:call)
134
+ deflt = deflt.call
135
+ elsif deflt.duplicable? # from active_support
136
+ # Some classes of default values need to be duplicated, or the instance field value will end up referencing
137
+ # the class global default value, and potentially modify it.
138
+ deflt = deflt.deep_dup # from active_support
139
+ end
140
+ defaults[field] = ::Subroutine::TypeCaster.cast(deflt, config)
141
+ end
142
+
143
+ defaults
144
+ end
145
+
146
+ end
147
+ end
data/lib/subroutine/op.rb CHANGED
@@ -1,50 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support/core_ext/hash/indifferent_access'
4
- require 'active_support/core_ext/object/duplicable'
5
- require 'active_support/core_ext/object/deep_dup'
6
- require 'active_model'
3
+ require "active_model"
7
4
 
8
- require 'subroutine/failure'
9
- require 'subroutine/type_caster'
10
- require 'subroutine/filtered_errors'
11
- require 'subroutine/output_not_set_error'
12
- require 'subroutine/unknown_output_error'
5
+ require "subroutine/fields"
6
+ require "subroutine/failure"
7
+ require "subroutine/filtered_errors"
8
+ require "subroutine/output_not_set_error"
9
+ require "subroutine/unknown_output_error"
13
10
 
14
11
  module Subroutine
15
12
  class Op
13
+
14
+ include ::Subroutine::Fields
16
15
  include ::ActiveModel::Model
17
16
  include ::ActiveModel::Validations::Callbacks
18
17
 
19
18
  DEFAULT_OUTPUT_OPTIONS = {
20
- required: true
19
+ required: true,
21
20
  }.freeze
22
21
 
23
22
  class << self
24
- ::Subroutine::TypeCaster.casters.each_key do |caster|
25
- next if method_defined?(caster)
26
-
27
- class_eval <<-EV, __FILE__, __LINE__ + 1
28
- def #{caster}(*args)
29
- options = args.extract_options!
30
- options[:type] = #{caster.inspect}
31
- args.push(options)
32
- field(*args)
33
- end
34
- EV
35
- end
36
-
37
- # fields can be provided in the following way:
38
- # field :field1, :field2
39
- # field :field3, :field4, default: 'my default'
40
- def field(*fields)
41
- options = fields.extract_options!
42
-
43
- fields.each do |f|
44
- _field(f, options)
45
- end
46
- end
47
- alias fields field
48
23
 
49
24
  def outputs(*names)
50
25
  options = names.extract_options!
@@ -66,33 +41,6 @@ module Subroutine
66
41
  end
67
42
  alias ignore_errors ignore_error
68
43
 
69
- def inputs_from(*ops)
70
- options = ops.extract_options!
71
- excepts = options.key?(:except) ? Array(options.delete(:except)) : nil
72
- onlys = options.key?(:only) ? Array(options.delete(:only)) : nil
73
-
74
- ops.each do |op|
75
- op._fields.each_pair do |field_name, op_options|
76
- next if excepts && excepts.include?(field_name)
77
- next if onlys && !onlys.include?(field_name)
78
-
79
- if op_options[:association]
80
- include ::Subroutine::Association unless included_modules.include?(::Subroutine::Association)
81
- association(field_name, op_options)
82
- else
83
- field(field_name, op_options)
84
- end
85
- end
86
- end
87
- end
88
-
89
- def inherited(child)
90
- super
91
- child._fields = _fields.dup
92
- child._error_map = _error_map.dup
93
- child._error_ignores = _error_ignores.dup
94
- end
95
-
96
44
  def submit!(*args)
97
45
  op = new(*args)
98
46
  op.submit!
@@ -109,59 +57,36 @@ module Subroutine
109
57
  protected
110
58
 
111
59
  def _field(field_name, options = {})
112
- _fields[field_name.to_sym] = options
60
+ result = super(field_name, options)
113
61
 
114
62
  if options[:aka]
115
63
  Array(options[:aka]).each do |as|
116
- _error_map[as.to_sym] = field_name.to_sym
64
+ self._error_map = _error_map.merge(as.to_sym => field_name.to_sym)
117
65
  end
118
66
  end
119
67
 
120
68
  _ignore_errors(field_name) if options[:ignore_errors]
121
69
 
122
- class_eval <<-EV, __FILE__, __LINE__ + 1
123
-
124
- def #{field_name}=(v)
125
- config = #{field_name}_config
126
- v = ::Subroutine::TypeCaster.cast(v, config)
127
- @params["#{field_name}"] = v
128
- end
129
-
130
- def #{field_name}
131
- @params.has_key?("#{field_name}") ? @params["#{field_name}"] : @defaults["#{field_name}"]
132
- end
133
-
134
- def #{field_name}_config
135
- _fields[:#{field_name}]
136
- end
137
-
138
- EV
70
+ result
139
71
  end
140
72
 
141
73
  def _ignore_errors(field_name)
142
- _error_ignores[field_name.to_sym] = true
74
+ self._error_ignores = _error_ignores.merge(field_name.to_sym => true)
143
75
  end
76
+
144
77
  end
145
78
 
146
79
  class_attribute :_outputs
147
80
  self._outputs = {}
148
81
 
149
- class_attribute :_fields
150
- self._fields = {}
151
-
152
82
  class_attribute :_error_map
153
83
  self._error_map = {}
154
84
 
155
85
  class_attribute :_error_ignores
156
86
  self._error_ignores = {}
157
87
 
158
- attr_reader :original_params
159
- attr_reader :params, :defaults
160
-
161
88
  def initialize(inputs = {})
162
- @original_params = inputs.with_indifferent_access
163
- @params = sanitize_params(@original_params)
164
- @defaults = sanitize_defaults
89
+ setup_fields(inputs)
165
90
  @outputs = {}
166
91
  end
167
92
 
@@ -217,10 +142,6 @@ module Subroutine
217
142
  end
218
143
  end
219
144
 
220
- def params_with_defaults
221
- @defaults.merge(@params)
222
- end
223
-
224
145
  protected
225
146
 
226
147
  # these enable you to 1) add log output or 2) add performance monitoring such as skylight.
@@ -248,11 +169,6 @@ module Subroutine
248
169
  raise NotImplementedError
249
170
  end
250
171
 
251
- # check if a specific field was provided
252
- def field_provided?(key)
253
- @params.key?(key)
254
- end
255
-
256
172
  # applies the errors in error_object to self
257
173
  # returns false so failure cases can end with this invocation
258
174
  def inherit_errors(error_object)
@@ -273,37 +189,5 @@ module Subroutine
273
189
  false
274
190
  end
275
191
 
276
- # if you want to use strong parameters or something in your form object you can do so here.
277
- # by default we just slice the inputs to the defined fields
278
- def sanitize_params(inputs)
279
- out = {}.with_indifferent_access
280
- _fields.each_pair do |field, config|
281
- next unless inputs.key?(field)
282
-
283
- out[field] = ::Subroutine::TypeCaster.cast(inputs[field], config)
284
- end
285
-
286
- out
287
- end
288
-
289
- def sanitize_defaults
290
- defaults = {}.with_indifferent_access
291
-
292
- _fields.each_pair do |field, config|
293
- next if config[:default].nil?
294
-
295
- deflt = config[:default]
296
- if deflt.respond_to?(:call)
297
- deflt = deflt.call
298
- elsif deflt.duplicable? # from active_support
299
- # Some classes of default values need to be duplicated, or the instance field value will end up referencing
300
- # the class global default value, and potentially modify it.
301
- deflt = deflt.deep_dup # from active_support
302
- end
303
- defaults[field] = ::Subroutine::TypeCaster.cast(deflt, config)
304
- end
305
-
306
- defaults
307
- end
308
192
  end
309
193
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Subroutine
4
4
  MAJOR = 0
5
- MINOR = 7
5
+ MINOR = 8
6
6
  PATCH = 0
7
7
  PRE = nil
8
8
 
data/lib/subroutine.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'subroutine/version'
4
- require 'subroutine/op'
3
+ require "subroutine/version"
4
+ require "subroutine/fields"
5
+ require "subroutine/op"
data/subroutine.gemspec CHANGED
@@ -20,9 +20,9 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency "activemodel", ">= 4.0.0"
22
22
 
23
- spec.add_development_dependency "bundler", "~> 1.7"
24
- spec.add_development_dependency "rake", "~> 10.0"
25
-
23
+ spec.add_development_dependency "bundler"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "m"
26
26
  spec.add_development_dependency "minitest"
27
27
  spec.add_development_dependency "minitest-reporters"
28
28
  spec.add_development_dependency "mocha"
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+ require "subroutine/fields"
5
+
6
+ module Subroutine
7
+ class FieldsTest < TestCase
8
+
9
+ class Whatever
10
+
11
+ include Subroutine::Fields
12
+
13
+ string :foo, default: "foo"
14
+ integer :bar, default: -> { 3 }
15
+
16
+ def initialize(options = {})
17
+ setup_fields(options)
18
+ end
19
+
20
+ end
21
+
22
+ def test_fields_are_configured
23
+ assert_equal 2, Whatever._fields.size
24
+ assert_equal :string, Whatever._fields[:foo][:type]
25
+ assert_equal :integer, Whatever._fields[:bar][:type]
26
+ end
27
+
28
+ def test_field_defaults_are_handled
29
+ instance = Whatever.new
30
+ assert_equal "foo", instance.foo
31
+ assert_equal 3, instance.bar
32
+ end
33
+
34
+ def test_fields_can_be_provided
35
+ instance = Whatever.new(foo: "abc", bar: nil)
36
+ assert_equal "abc", instance.foo
37
+ assert_nil instance.bar
38
+ end
39
+
40
+ def test_field_provided
41
+ instance = Whatever.new(foo: "abc")
42
+ assert_equal true, instance.field_provided?(:foo)
43
+ assert_equal false, instance.field_provided?(:bar)
44
+ end
45
+
46
+ def test_params
47
+ instance = Whatever.new(foo: "abc")
48
+ assert_equal({ "foo" => "abc" }, instance.params)
49
+ assert_equal({ "foo" => "abc", "bar" => 3 }, instance.params_with_defaults)
50
+ assert_equal({ "foo" => "foo", "bar" => 3 }, instance.defaults)
51
+ end
52
+
53
+ end
54
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: subroutine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Nelson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-15 00:00:00.000000000 Z
11
+ date: 2019-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -28,30 +28,44 @@ dependencies:
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '1.7'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '1.7'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '10.0'
47
+ version: '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: '10.0'
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: m
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: minitest
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -109,16 +123,17 @@ files:
109
123
  - LICENSE.txt
110
124
  - README.md
111
125
  - Rakefile
112
- - gemfiles/am30.gemfile
113
- - gemfiles/am31.gemfile
114
- - gemfiles/am32.gemfile
115
- - gemfiles/am40.gemfile
116
126
  - gemfiles/am41.gemfile
117
127
  - gemfiles/am42.gemfile
128
+ - gemfiles/am50.gemfile
129
+ - gemfiles/am51.gemfile
130
+ - gemfiles/am52.gemfile
131
+ - gemfiles/am60.gemfile
118
132
  - lib/subroutine.rb
119
133
  - lib/subroutine/association.rb
120
134
  - lib/subroutine/auth.rb
121
135
  - lib/subroutine/failure.rb
136
+ - lib/subroutine/fields.rb
122
137
  - lib/subroutine/filtered_errors.rb
123
138
  - lib/subroutine/op.rb
124
139
  - lib/subroutine/output_not_set_error.rb
@@ -129,6 +144,7 @@ files:
129
144
  - test/subroutine/association_test.rb
130
145
  - test/subroutine/auth_test.rb
131
146
  - test/subroutine/base_test.rb
147
+ - test/subroutine/fields_test.rb
132
148
  - test/subroutine/type_caster_test.rb
133
149
  - test/support/ops.rb
134
150
  - test/test_helper.rb
@@ -152,20 +168,21 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
168
  version: '0'
153
169
  requirements: []
154
170
  rubyforge_project:
155
- rubygems_version: 2.7.7
171
+ rubygems_version: 2.6.14.4
156
172
  signing_key:
157
173
  specification_version: 4
158
174
  summary: Feature-driven operation objects.
159
175
  test_files:
160
- - gemfiles/am30.gemfile
161
- - gemfiles/am31.gemfile
162
- - gemfiles/am32.gemfile
163
- - gemfiles/am40.gemfile
164
176
  - gemfiles/am41.gemfile
165
177
  - gemfiles/am42.gemfile
178
+ - gemfiles/am50.gemfile
179
+ - gemfiles/am51.gemfile
180
+ - gemfiles/am52.gemfile
181
+ - gemfiles/am60.gemfile
166
182
  - test/subroutine/association_test.rb
167
183
  - test/subroutine/auth_test.rb
168
184
  - test/subroutine/base_test.rb
185
+ - test/subroutine/fields_test.rb
169
186
  - test/subroutine/type_caster_test.rb
170
187
  - test/support/ops.rb
171
188
  - test/test_helper.rb