subroutine 0.5.0 → 0.5.1

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
  SHA1:
3
- metadata.gz: adec63683815f30335f67edb81ae73e313cf7046
4
- data.tar.gz: 07726344ba242224d384a4ad6c38af2f9985cddd
3
+ metadata.gz: 3acb651d981b9e158e41c5e36b9fcf2fc644fb51
4
+ data.tar.gz: ff3571355496f5202702a26ab935a1d7944c73a3
5
5
  SHA512:
6
- metadata.gz: 7cfbdb1c872febd5ed53e2d6ef3466faf32fb20e02cfb4a8485c8c8e0f528ddfd3b10671063a22c173c6c81409337fd1e4f29fe218f5ae3e8797231108f207c5
7
- data.tar.gz: 048c5ad5a14f44aba3f8bfedbdb02ed8617e7d6ffe92656b8a7f99140eda49d0216aac64f368da7182f9fd7a0ed515f6062a57af23ab6b9049a49d18a315bae2
6
+ metadata.gz: e651e0bbbcb6a355d6f07613becee56d67c08b7016b72660cda62608956a0b7db58006ed8ff45b58346ec827b02b8af2d1b2b6ad191d55ccb42c9935f24a4dff
7
+ data.tar.gz: 9fbb7f7d91c60a1c71ac4a965179efd9f45a665e93bc9c9bfb70da2ef0d07d06cdebeb2ef3526f968477c71c7eaa4515b20c097aa5bf66d8cdbd5141150a11f9
data/.travis.yml CHANGED
@@ -2,7 +2,6 @@ language: ruby
2
2
  sudo: false
3
3
 
4
4
  rvm:
5
- - 1.9.3
6
5
  - 2.0.0
7
6
  - 2.1.1
8
7
  - jruby
@@ -10,19 +9,4 @@ rvm:
10
9
  gemfile:
11
10
  - gemfiles/am40.gemfile
12
11
  - gemfiles/am41.gemfile
13
- - gemfiles/am42.gemfile
14
-
15
- matrix:
16
- exclude:
17
- - rvm: 1.8.7
18
- gemfile: gemfiles/am40.gemfile
19
- - rvm: 1.8.7
20
- gemfile: gemfiles/am41.gemfile
21
- - rvm: 1.8.7
22
- gemfile: gemfiles/am42.gemfile
23
- - rvm: 1.9.2
24
- gemfile: gemfiles/am40.gemfile
25
- - rvm: 1.9.2
26
- gemfile: gemfiles/am41.gemfile
27
- - rvm: 1.9.2
28
- gemfile: gemfiles/am42.gemfile
12
+ - gemfiles/am42.gemfile
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Subroutine
2
2
 
3
- A gem that provides an interface for creating feature-driven operations. It utilizes the command pattern, enables the usage of "ops" as "form objects", and just all-around enables clear, concise, meaningful code.
3
+ A gem that provides an interface for creating feature-driven operations. You've probably heard at least one of these terms: "service objects", "form objects", or maybe even "commands". Subroutine calls these "ops" and really it's just about enabling clear, concise, testable, and meaningful code.
4
4
 
5
5
  ## Examples
6
6
 
@@ -17,21 +17,18 @@ class SignupOp < ::Subroutine::Op
17
17
  validates :email, presence: true
18
18
  validates :password, presence: true
19
19
 
20
- attr_reader :signed_up_user
20
+ outputs :signed_up_user
21
21
 
22
22
  protected
23
23
 
24
24
  def perform
25
- u = build_user
26
- u.save!
27
-
25
+ u = create_user!
28
26
  deliver_welcome_email!(u)
29
27
 
30
- @signed_up_user = u
31
- true
28
+ output :signed_up_user, u
32
29
  end
33
30
 
34
- def build_user
31
+ def create_user
35
32
  User.new(params)
36
33
  end
37
34
 
@@ -106,7 +103,7 @@ class Api::Controller < ApplicationController
106
103
  end
107
104
  ```
108
105
 
109
- With ops, your controllers are essentially just connections between routes, operations, and templates.
106
+ With ops, your controllers are essentially just connections between routes, operations, and whatever you use to build responses.
110
107
 
111
108
  ```ruby
112
109
  class UsersController < ::Api::Controller
@@ -201,14 +198,9 @@ class MyOp < ::Subroutine::Op
201
198
  end
202
199
 
203
200
  def validate_first_name_is_not_bob
204
- return true unless field_provided?(:first_name)
205
-
206
- if first_name.downcase == 'bob'
201
+ if field_provided?(:first_name) && first_name.downcase == 'bob'
207
202
  errors.add(:first_name, 'should not be bob')
208
- return false
209
203
  end
210
-
211
- true
212
204
  end
213
205
  end
214
206
  ```
@@ -224,7 +216,6 @@ class MyOp < ::Subroutine::Op
224
216
  puts params.inspect
225
217
  puts defaults.inspect
226
218
  puts params_with_defaults.inspect
227
- true
228
219
  end
229
220
  end
230
221
 
@@ -241,20 +232,18 @@ MyOp.submit(name: "foobar")
241
232
 
242
233
  #### Execution
243
234
 
244
- Every op must implement a `perform` instance method. This is the method which will be executed if all validations pass.
245
- The return value of this op determines whether the operation was a success or not. Truthy values are assumed to be successful,
246
- while falsy values are assumed to be failures. In general, returning `true` at the end of the perform method is desired.
235
+ Every op must implement a `perform` method. This is the method which will be executed if all validations pass.
236
+ When the the `perform` method is complete, the Op determins success based on whether `errors` is empty.
247
237
 
248
238
  ```ruby
249
- class MyOp < ::Subroutine::Op
239
+ class MyFailingOp < ::Subroutine::Op
250
240
  field :first_name
251
241
  validates :first_name, presence: true
252
242
 
253
243
  protected
254
244
 
255
245
  def perform
256
- $logger.info "#{first_name} submitted this op"
257
- true
246
+ errors.add(:base, "This will never succeed")
258
247
  end
259
248
 
260
249
  end
@@ -266,10 +255,10 @@ Notice we do not declare `perform` as a public method. This is to ensure the "pu
266
255
 
267
256
  Reporting errors is very important in Subroutine Ops since these can be used as form objects. Errors can be reported a couple different ways:
268
257
 
269
- 1. `errors.add(:key, :error)` That is, the way you add errors to an ActiveModel object. Then either return false from your op OR raise an error like `raise ::Subroutine::Failure.new(this)`.
258
+ 1. `errors.add(:key, :error)` That is, the way you add errors to an ActiveModel object.
270
259
  2. `inherit_errors(error_object_or_activemodel_object)` Same as `errors.add`, but it iterates an existing error hash and inherits the errors. As part of this iteration,
271
- it checks whether the key in the provided error_object matches a field (or aka of a field) in our op. If there is a match, the error will be placed on
272
- that field, but if there is not, the error will be placed on `:base`. Again, after adding the errors to our op, we must return `false` from the perform method or raise a Subroutine::Failure.
260
+ it checks whether the key in the provided error_object matches a field (or alias of a field) in our op. If there is a match, the error will be placed on
261
+ that field, but if there is not, the error will be placed on `:base`.
273
262
 
274
263
  ```ruby
275
264
  class MyOp < ::Subroutine::Op
@@ -283,12 +272,12 @@ class MyOp < ::Subroutine::Op
283
272
 
284
273
  if first_name == 'bill'
285
274
  errors.add(:first_name, 'cannot be bill')
286
- return false
275
+ return
287
276
  end
288
277
 
289
278
  if first_name == 'john'
290
279
  errors.add(:first_name, 'cannot be john')
291
- raise Subroutine::Failure.new(this)
280
+ return
292
281
  end
293
282
 
294
283
  unless _user.valid?
@@ -296,10 +285,7 @@ class MyOp < ::Subroutine::Op
296
285
  # if there are :first_name or :firstname errors on _user, they will be added to our :first_name
297
286
  # if there are :last_name, :lastname, or :surname errors on _user, they will be added to our :last_name
298
287
  inherit_errors(_user)
299
- return false
300
288
  end
301
-
302
- true
303
289
  end
304
290
 
305
291
  def _user
@@ -317,7 +303,7 @@ The `Subroutine::Op` class' `submit` and `submit!` methods have identical signat
317
303
 
318
304
  ```ruby
319
305
  op = MyOp.submit({foo: 'bar'})
320
- # if the op succeeds it will be returned, otherwise it false will be returned.
306
+ # if the op succeeds it will be returned, otherwise false will be returned.
321
307
  ```
322
308
 
323
309
  #### Via the class' `submit!` method
@@ -364,8 +350,6 @@ class UserUpdateOp < ::Subroutine::Op
364
350
  first_name: first_name,
365
351
  last_name: last_name
366
352
  )
367
-
368
- true
369
353
  end
370
354
  end
371
355
  ```
@@ -380,8 +364,6 @@ class RecordTouchOp < ::Subroutine::Op
380
364
 
381
365
  def perform
382
366
  record.touch
383
-
384
- true
385
367
  end
386
368
  end
387
369
  ```
@@ -402,8 +384,6 @@ class SayHiOp < ::Subroutine::Op
402
384
 
403
385
  def perform
404
386
  puts "#{current_user.name} says: #{say_what}"
405
-
406
- true
407
387
  end
408
388
  end
409
389
  ```
@@ -435,7 +415,11 @@ class AccountSetSecretOp < ::Subroutine::Op
435
415
 
436
416
  require_user!
437
417
  authorize :authorize_first_name_is_john
438
-
418
+
419
+ # If you use a policy-based authorization framework like pundit:
420
+ # `policy` is a shortcut for the following:
421
+ # authorize -> { unauthorized! unless policy.can_set_secret? }
422
+
439
423
  policy :can_set_secret?
440
424
 
441
425
  string :secret
@@ -446,8 +430,6 @@ class AccountSetSecretOp < ::Subroutine::Op
446
430
  def perform
447
431
  account.secret = secret
448
432
  current_user.save!
449
-
450
- true
451
433
  end
452
434
 
453
435
  def authorize_first_name_is_john
@@ -478,7 +460,7 @@ Subroutine::Factory.define :signup do
478
460
  inputs :password, "password123"
479
461
 
480
462
  # by default, the op will be returned when the factory is used.
481
- # this `output` returns the value of the accessor on the resulting op
463
+ # this `output` returns the value of the `user` output on the resulting op
482
464
  output :user
483
465
  end
484
466
 
@@ -15,6 +15,12 @@ module Subroutine
15
15
 
16
16
  end
17
17
 
18
+ class AuthorizationNotDeclaredError < ::StandardError
19
+ def initialize(msg = nil)
20
+ super(msg || "Authorization management has not been declared on this class")
21
+ end
22
+ end
23
+
18
24
  def self.included(base)
19
25
  base.instance_eval do
20
26
  extend ::Subroutine::Auth::ClassMethods
@@ -67,7 +73,7 @@ module Subroutine
67
73
  end
68
74
 
69
75
  def initialize(*args)
70
- raise "Authorization management has not been declared on this class" if(!self.class.authorization_declared)
76
+ raise Subroutine::Auth::AuthorizationNotDeclaredError.new if(!self.class.authorization_declared)
71
77
 
72
78
  super(args.extract_options!)
73
79
  @skip_auth_checks = false
data/lib/subroutine/op.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  require 'active_support/core_ext/hash/indifferent_access'
2
+ require 'active_support/core_ext/object/duplicable'
3
+ require 'active_support/core_ext/object/deep_dup'
2
4
  require 'active_model'
3
5
 
4
6
  require "subroutine/failure"
@@ -307,7 +309,13 @@ module Subroutine
307
309
  self._fields.each_pair do |field, config|
308
310
  unless config[:default].nil?
309
311
  deflt = config[:default]
310
- deflt = deflt.call if deflt.respond_to?(:call)
312
+ if deflt.respond_to?(:call)
313
+ deflt = deflt.call
314
+ elsif deflt.duplicable? # from active_support
315
+ # Some classes of default values need to be duplicated, or the instance field value will end up referencing
316
+ # the class global default value, and potentially modify it.
317
+ deflt = deflt.deep_dup # from active_support
318
+ end
311
319
  defaults[field] = type_caster.cast(deflt, config[:type])
312
320
  end
313
321
  end
@@ -315,7 +323,6 @@ module Subroutine
315
323
  defaults
316
324
  end
317
325
 
318
-
319
326
  end
320
327
 
321
328
  end
@@ -2,7 +2,7 @@ module Subroutine
2
2
 
3
3
  MAJOR = 0
4
4
  MINOR = 5
5
- PATCH = 0
5
+ PATCH = 1
6
6
  PRE = nil
7
7
 
8
8
  VERSION = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
@@ -8,7 +8,7 @@ module Subroutine
8
8
  end
9
9
 
10
10
  def test_it_throws_an_error_if_authorization_is_not_defined
11
- assert_raises "Authorization management has not been declared on this class" do
11
+ assert_raises ::Subroutine::Auth::AuthorizationNotDeclaredError do
12
12
  MissingAuthOp.new
13
13
  end
14
14
  end
@@ -10,7 +10,7 @@ module Subroutine
10
10
 
11
11
  def test_inherited_fields
12
12
  op = ::AdminSignupOp.new
13
- assert_equal [:email, :password, :priveleges], op._fields.keys.sort
13
+ assert_equal [:email, :password, :privileges], op._fields.keys.sort
14
14
  end
15
15
 
16
16
  def test_class_attribute_usage
@@ -70,10 +70,10 @@ module Subroutine
70
70
 
71
71
  assert_nil op.email
72
72
  assert_nil op.password
73
- assert_equal 'min', op.priveleges
73
+ assert_equal 'min', op.privileges
74
74
 
75
- op.priveleges = 'max'
76
- assert_equal 'max', op.priveleges
75
+ op.privileges = 'max'
76
+ assert_equal 'max', op.privileges
77
77
  end
78
78
 
79
79
  def test_validations_are_evaluated_before_perform_is_invoked
@@ -157,37 +157,58 @@ module Subroutine
157
157
  }, op.params)
158
158
 
159
159
  assert_equal({
160
- "priveleges" => "min",
160
+ "privileges" => "min",
161
161
  }, op.defaults)
162
162
 
163
163
  assert_equal({
164
164
  "email" => "foo",
165
- "priveleges" => "min",
165
+ "privileges" => "min",
166
166
  }, op.params_with_defaults)
167
167
  end
168
168
 
169
169
  def test_it_allows_defaults_to_be_overridden
170
- op = ::AdminSignupOp.new(email: "foo", priveleges: nil)
170
+ op = ::AdminSignupOp.new(email: "foo", privileges: nil)
171
171
 
172
172
  assert_equal({
173
173
  "email" => "foo",
174
- "priveleges" => nil
174
+ "privileges" => nil
175
175
  }, op.params)
176
176
 
177
177
  assert_equal({
178
- "priveleges" => "min",
178
+ "privileges" => "min",
179
179
  }, op.defaults)
180
180
 
181
181
  assert_equal({
182
182
  "email" => "foo",
183
- "priveleges" => nil,
183
+ "privileges" => nil,
184
184
  }, op.params_with_defaults)
185
185
  end
186
186
 
187
+ def test_it_overriding_default_does_not_alter_default
188
+ op = ::AdminSignupOp.new(email: "foo")
189
+ op.privileges << "bangbang"
190
+
191
+ op = ::AdminSignupOp.new(email: "foo", privileges: nil)
192
+
193
+ assert_equal({
194
+ "email" => "foo",
195
+ "privileges" => nil
196
+ }, op.params)
197
+
198
+ assert_equal({
199
+ "privileges" => "min",
200
+ }, op.defaults)
201
+
202
+ assert_equal({
203
+ "email" => "foo",
204
+ "privileges" => nil,
205
+ }, op.params_with_defaults)
206
+ end
207
+
187
208
  def test_it_overrides_defaults_with_nils
188
- op = ::AdminSignupOp.new(email: "foo", priveleges: nil)
209
+ op = ::AdminSignupOp.new(email: "foo", privileges: nil)
189
210
  assert_equal({
190
- "priveleges" => nil,
211
+ "privileges" => nil,
191
212
  "email" => "foo"
192
213
  }, op.params)
193
214
  end
@@ -9,7 +9,7 @@ module Subroutine
9
9
 
10
10
  def test_integer_inputs
11
11
  op.integer_input = nil
12
- assert_equal nil, op.integer_input
12
+ assert_nil op.integer_input
13
13
 
14
14
  op.integer_input = 'foo'
15
15
  assert_equal 0, op.integer_input
@@ -29,7 +29,7 @@ module Subroutine
29
29
 
30
30
  def test_number_inputs
31
31
  op.number_input = nil
32
- assert_equal nil, op.number_input
32
+ assert_nil op.number_input
33
33
 
34
34
  op.number_input = 4
35
35
  assert_equal 4.0, op.number_input
@@ -43,7 +43,7 @@ module Subroutine
43
43
 
44
44
  def test_string_inputs
45
45
  op.string_input = nil
46
- assert_equal nil, op.string_input
46
+ assert_nil op.string_input
47
47
 
48
48
  op.string_input = ""
49
49
  assert_equal '', op.string_input
@@ -60,7 +60,7 @@ module Subroutine
60
60
 
61
61
  def test_boolean_inputs
62
62
  op.boolean_input = nil
63
- assert_equal nil, op.boolean_input
63
+ assert_nil op.boolean_input
64
64
 
65
65
  op.boolean_input = 'yes'
66
66
  assert_equal true, op.boolean_input
@@ -101,7 +101,7 @@ module Subroutine
101
101
 
102
102
  def test_hash_inputs
103
103
  op.object_input = nil
104
- assert_equal nil, op.object_input
104
+ assert_nil op.object_input
105
105
 
106
106
  op.object_input = ''
107
107
  assert_equal({}, op.object_input)
@@ -121,7 +121,7 @@ module Subroutine
121
121
 
122
122
  def test_array_inputs
123
123
  op.array_input = nil
124
- assert_equal nil, op.array_input
124
+ assert_nil op.array_input
125
125
 
126
126
  op.array_input = ''
127
127
  assert_equal [], op.array_input
@@ -138,7 +138,7 @@ module Subroutine
138
138
 
139
139
  def test_date_inputs
140
140
  op.date_input = nil
141
- assert_equal nil, op.date_input
141
+ assert_nil op.date_input
142
142
 
143
143
  op.date_input = "2022-12-22"
144
144
  assert_equal ::Date, op.date_input.class
@@ -172,7 +172,7 @@ module Subroutine
172
172
 
173
173
  def test_time_inputs
174
174
  op.time_input = nil
175
- assert_equal nil, op.time_input
175
+ assert_nil op.time_input
176
176
 
177
177
  op.time_input = "2022-12-22"
178
178
  assert_equal ::Time, op.time_input.class
@@ -199,7 +199,7 @@ module Subroutine
199
199
 
200
200
  def test_iso_date_inputs
201
201
  op.iso_date_input = nil
202
- assert_equal nil, op.iso_date_input
202
+ assert_nil op.iso_date_input
203
203
 
204
204
  op.iso_date_input = "2022-12-22"
205
205
  assert_equal ::String, op.iso_date_input.class
@@ -212,7 +212,7 @@ module Subroutine
212
212
 
213
213
  def test_iso_time_inputs
214
214
  op.iso_time_input = nil
215
- assert_equal nil, op.iso_time_input
215
+ assert_nil op.iso_time_input
216
216
 
217
217
  op.iso_time_input = "2022-12-22T10:30:24Z"
218
218
  assert_equal ::String, op.iso_time_input.class
data/test/support/ops.rb CHANGED
@@ -67,7 +67,7 @@ end
67
67
 
68
68
  class AdminSignupOp < ::SignupOp
69
69
 
70
- field :priveleges, :default => 'min'
70
+ field :privileges, :default => 'min'
71
71
 
72
72
  protected
73
73
 
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.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Nelson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-20 00:00:00.000000000 Z
11
+ date: 2017-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel