subroutine 0.7.0 → 0.8.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
- 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