subroutine 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4f44648d17e8af9de061d865e831e95811de6ff4
4
- data.tar.gz: b5347035ebf89c8e17a45b49333fb9b1a0812555
3
+ metadata.gz: a1eb7328166962d1dfee177ad4311926c7958217
4
+ data.tar.gz: f5133fc409be40473d1c14a0ae1880b1d007a35d
5
5
  SHA512:
6
- metadata.gz: 9e8cb47942615ef6cee08df82ff32c026a1824aac18e3e07dacc74e71bb23c30f9959493491d3cb358ec6e4daccbe155c0562b4fa0794efd85b29a9ffa39a5cd
7
- data.tar.gz: aedf7e3533db2a99570634e97a74cc82bb95673ed9f256153b7844a94638ce49436717c7912b7a552dbe13985c603078977270ff3f527128379b28cc03be5680
6
+ metadata.gz: a9bb3b82b53ef32a8f0d236c4e2561d7c56ce5c233f520a06bded564017e32e7d017758f43e0dff4d80c425b476e5ea95562d1f0414b7c26bd4e0d426efa8244
7
+ data.tar.gz: d4752345399d5ed62c138438ac80050c55de4ca84163d3c2121aed0bd7b404ffd36cc1caf6447d83a28bf570602f007bb5c55d59b70965e7dd6224831f350702
data/.gitignore CHANGED
@@ -13,3 +13,4 @@
13
13
  *.a
14
14
  mkmf.log
15
15
  *.gem
16
+ .byebug_history
data/README.md CHANGED
@@ -343,93 +343,123 @@ op.submit!
343
343
  # if the op succeeds nothing will be raised, otherwise a ::Subroutine::Failure will be raised.
344
344
  ```
345
345
 
346
- ## Extending Subroutine::Op
346
+ ## Built-in Extensions
347
347
 
348
- Great, so you're sold on using ops. Let's talk about how I usually standardize their usage in my apps. The most common thing needed is `current_user`. For this reason I usually follow the rails convention of declaring an "Application" op which declares all of my common needs. I hate writing `ApplicationOp` all the time so I usually call it `BaseOp`.
348
+ ### Subroutine::Association
349
+
350
+ The `Subroutine::Association` module provides an interface for loading ActiveRecord instances easily.
349
351
 
350
352
  ```ruby
351
- class BaseOp < ::Subroutine::Op
353
+ class UserUpdateOp < ::Subroutine::Op
354
+ include ::Subroutine::Association
352
355
 
353
- attr_reader :current_user
356
+ association :user
354
357
 
355
- def initialize(*args)
356
- params = args.extract_options!
357
- @current_user = args[0]
358
- super(params)
359
- end
358
+ string :first_name, :last_name
360
359
 
361
- end
362
- ```
360
+ protected
363
361
 
364
- So now I can pass the current user as my first argument to any op constructor. The next most common case is permissions. In a common role-based system things become pretty easy. I usually just add a class method which declares the minimum required role.
362
+ def perform
363
+ user.update_attributes(
364
+ first_name: first_name,
365
+ last_name: last_name
366
+ )
365
367
 
366
- ```ruby
367
- class SendInvitationOp < BaseOp
368
- require_role :admin
368
+ true
369
+ end
369
370
  end
370
371
  ```
371
372
 
372
- In the case of a more complex permission system, I'll usually utilize pundit but still standardize the check as a validation.
373
-
374
373
  ```ruby
375
- class BaseOp < ::Subroutine::Op
374
+ class RecordTouchOp < ::Subroutine::Op
375
+ include ::Subroutine::Association
376
376
 
377
- validate :validate_permissions
377
+ association :record, polymorphic: true
378
378
 
379
379
  protected
380
380
 
381
- # default implementation is to allow access.
382
- def validate_permissions
383
- true
384
- end
381
+ def perform
382
+ record.touch
385
383
 
386
- def not_authorized!
387
- errors.add(:current_user, :not_authorized)
388
- false
384
+ true
389
385
  end
390
386
  end
387
+ ```
388
+
389
+ ### Subroutine::Auth
390
+
391
+ The `Subroutine::Auth` module provides basic bindings for application authorization. It assumes that, optionally, a `User` will be provided as the first argument to an Op. It forces authorization to be declared on each class it's included in.
392
+
393
+ ```ruby
394
+ class SayHiOp < ::Subroutine::Op
395
+ include ::Subroutine::Auth
396
+
397
+ require_user!
391
398
 
392
- class SendInvitationOp < BaseOp
399
+ string :say_what, default: "hi"
393
400
 
394
401
  protected
395
402
 
396
- def validate_permissions
397
- unless UserPolicy.new(current_user).send_invitations?
398
- return not_authorized!
399
- end
403
+ def perform
404
+ puts "#{current_user.name} says: #{say_what}"
400
405
 
401
406
  true
402
407
  end
403
-
404
408
  end
405
409
  ```
406
410
 
407
- Clearly there are a ton of ways this could be implemented but that should be a good jumping-off point.
411
+ ```ruby
412
+ user = User.find("john")
413
+ SayHiOp.submit!(user)
414
+ # => John says: hi
415
+
416
+ SayHiOp.submit!(user, say_what: "hello")
417
+ # => John says: hello
418
+
419
+
420
+ SayHiOp.submit!
421
+ # => raises Subroutine::Auth::NotAuthorizedError
422
+ ```
423
+
424
+ There are a handful of authorization configurations:
425
+
426
+ 1. `require_user!` - ensures that a user is provided
427
+ 2. `require_no_user!` - ensures that a user is not present
428
+ 3. `no_user_requirements!` - explicitly doesn't matter
408
429
 
409
- Performance monitoring is also important to me so I've added a few hooks to observe what's going on during an op's submission. I'm primarily using Skylight at the moment.
430
+ In addition to these top-level authorization declarations you can provide custom authorizations like so:
410
431
 
411
432
  ```ruby
412
- class BaseOp < ::Subroutine::Op
433
+ class AccountSetSecretOp < ::Subroutine::Op
434
+ include ::Subroutine::Auth
435
+
436
+ require_user!
437
+ authorize :authorize_first_name_is_john
438
+
439
+ policy :can_set_secret?
440
+
441
+ string :secret
442
+ belongs_to :account
413
443
 
414
444
  protected
415
445
 
416
- def observe_submission
417
- Skylight.instrument category: 'op.submission', title: "#{self.class.name}#submit" do
418
- yield
419
- end
446
+ def perform
447
+ account.secret = secret
448
+ current_user.save!
449
+
450
+ true
420
451
  end
421
452
 
422
- def observe_validation
423
- Skylight.instrument category: 'op.validation', title: "#{self.class.name}#valid?" do
424
- yield
453
+ def authorize_first_name_is_john
454
+ unless current_user.first_name == "john"
455
+ unauthorized!
425
456
  end
426
457
  end
427
458
 
428
- def observe_perform
429
- Skylight.instrument category: 'op.perform', title: "#{self.class.name}#perform" do
430
- yield
431
- end
459
+ def policy
460
+ ::UserPolicy.new(current_user, current_user)
432
461
  end
462
+
433
463
  end
434
464
  ```
435
465
 
@@ -0,0 +1,132 @@
1
+ module Subroutine
2
+ module Association
3
+
4
+ def self.included(base)
5
+ base.send :extend, ::Subroutine::Association::ClassMethods
6
+ class << base
7
+ alias_method :field_without_associations, :field
8
+ alias_method :field, :field_with_associations
9
+ end
10
+
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+
16
+ def field_with_associations(*args)
17
+ opts = args.extract_options!
18
+ if opts[:association]
19
+ args.each do |arg|
20
+ association(arg, opts)
21
+ end
22
+ else
23
+ field_without_associations(*args, opts)
24
+ end
25
+ end
26
+
27
+ # association :user
28
+ # - user_id
29
+ # - user_type => "User"
30
+
31
+ # association :user, polymorphic: true
32
+ # - user_id
33
+ # - user_type
34
+ # - user => polymorphic_lookup(user_type, user_id)
35
+
36
+ # association :inbound_user_request, as: :request
37
+ # - inbound_user_request_id
38
+ # - inbound_user_request_type => "InboundUserRequest"
39
+ # - request => polymorphic_lookup(inbound_user_request_type, inbound_user_request_id)
40
+
41
+ # association :inbound_user_request, foreign_key: :request_id
42
+ # - request_id
43
+ # - request_type
44
+ # - inbound_user_request => polymorphic_lookup(request_type, request_id)
45
+
46
+ # Other options:
47
+ # - unscoped => set true if the record should be looked up via Type.unscoped
48
+
49
+ def association(field, options = {})
50
+
51
+ if options[:as] && options[:foreign_key]
52
+ raise ArgumentError.new(":as and :foreign_key options should be provided together to an association invocation")
53
+ end
54
+
55
+ class_name = options[:class_name]
56
+
57
+ poly = options[:polymorphic] || !class_name.nil?
58
+ as = options[:as] || field
59
+ unscoped = !!options[:unscoped]
60
+
61
+ klass = class_name.to_s if class_name
62
+
63
+ foreign_key_method = (options[:foreign_key] || "#{field}_id").to_s
64
+ foreign_type_method = foreign_key_method.gsub(/_id$/, "_type")
65
+
66
+
67
+ if poly
68
+ string foreign_type_method
69
+ else
70
+ class_eval <<-EV, __FILE__, __LINE__+1
71
+ def #{foreign_type_method}
72
+ #{as.to_s.camelize.inspect}
73
+ end
74
+ EV
75
+ end
76
+
77
+ integer foreign_key_method
78
+
79
+ field_without_associations as, options.merge(association: true)
80
+
81
+ class_eval <<-EV, __FILE__, __LINE__+1
82
+ def #{as}_with_association
83
+ return @#{as} if defined?(@#{as})
84
+ @#{as} = begin
85
+ #{as}_without_association ||
86
+ polymorphic_instance(#{klass.nil? ? foreign_type_method : klass.to_s}, #{foreign_key_method}, #{unscoped.inspect})
87
+ end
88
+ end
89
+
90
+ def #{as}_with_association=(r)
91
+ @#{as} = r
92
+ #{poly || klass ? "params['#{foreign_type_method}'] = r.nil? ? nil : #{klass.nil? ? "r.class.name" : klass.to_s.inspect}" : ""}
93
+ params['#{foreign_key_method}'] = r.nil? ? nil : r.id
94
+ r
95
+ end
96
+ EV
97
+
98
+ alias_method :"#{as}_without_association", :"#{as}"
99
+ alias_method :"#{as}", :"#{as}_with_association"
100
+
101
+ alias_method :"#{as}_without_association=", :"#{as}="
102
+ alias_method :"#{as}=", :"#{as}_with_association="
103
+
104
+ end
105
+ end
106
+
107
+ def initialize(*args)
108
+ super(*args)
109
+
110
+ self._fields.each_pair do |field, config|
111
+ next unless config[:association]
112
+ next unless @original_params.has_key?(field)
113
+ send("#{field}=", @original_params[field]) # this gets the _id and _type into the params hash
114
+ end
115
+ end
116
+
117
+ def polymorphic_instance(_type, _id, _unscoped = false)
118
+ return nil unless _type && _id
119
+
120
+ klass = _type
121
+ klass = klass.camelize.constantize if klass.is_a?(String)
122
+
123
+ return nil unless klass
124
+
125
+ scope = klass.all
126
+ scope = scope.unscoped if _unscoped
127
+
128
+ scope.find(_id)
129
+ end
130
+
131
+ end
132
+ end
@@ -0,0 +1,97 @@
1
+ module Subroutine
2
+ module Auth
3
+
4
+ class NotAuthorizedError < ::StandardError
5
+
6
+ def initialize(msg = nil)
7
+ msg = I18n.t("errors.#{msg}", default: "Sorry, you are not authorized to perform this action.") if msg.is_a?(Symbol)
8
+ msg ||= I18n.t('errors.unauthorized', default: "Sorry, you are not authorized to perform this action.")
9
+ super msg
10
+ end
11
+
12
+ def status
13
+ 401
14
+ end
15
+
16
+ end
17
+
18
+ def self.included(base)
19
+ base.instance_eval do
20
+ extend ::Subroutine::Auth::ClassMethods
21
+
22
+ class_attribute :authorization_declared
23
+ self.authorization_declared = false
24
+ end
25
+ end
26
+
27
+
28
+ module ClassMethods
29
+
30
+ def authorize(validation_name)
31
+ validate validation_name, unless: :skip_auth_checks?
32
+ end
33
+
34
+ def no_user_requirements!
35
+ self.authorization_declared = true
36
+ end
37
+
38
+ def require_user!
39
+ self.authorization_declared = true
40
+
41
+ validate unless: :skip_auth_checks? do
42
+ unauthorized! unless current_user.present?
43
+ end
44
+ end
45
+
46
+ def require_no_user!
47
+ self.authorization_declared = true
48
+
49
+ validate unless: :skip_auth_checks? do
50
+ unauthorized! :empty_unauthorized if current_user.present?
51
+ end
52
+ end
53
+
54
+ # policy :can_update_user
55
+ # policy :can_do_whatever, policy: :foo_policy
56
+ def policy(*meths)
57
+ opts = meths.extract_options!
58
+ policy_name = opts[:policy] || :policy
59
+ validate unless: :skip_auth_checks? do
60
+ p = self.send(policy_name)
61
+ if !p || meths.any?{|m| !(p.respond_to?("#{m}?") ? p.send("#{m}?") : p.send(m)) }
62
+ unauthorized! opts[:error]
63
+ end
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+ def initialize(*args)
70
+ raise "Authorization management has not been declared on this class" if(!self.class.authorization_declared)
71
+
72
+ super(args.extract_options!)
73
+ @skip_auth_checks = false
74
+ @current_user = args.shift
75
+ end
76
+
77
+ def skip_auth_checks!
78
+ @skip_auth_checks = true
79
+ self
80
+ end
81
+
82
+ def skip_auth_checks?
83
+ !!@skip_auth_checks
84
+ end
85
+
86
+ def current_user
87
+ @current_user = ::User.find(@current_user) if Fixnum === @current_user
88
+ @current_user
89
+ end
90
+
91
+ def unauthorized!(reason = nil)
92
+ reason ||= :unauthorized
93
+ raise ::Subroutine::Auth::NotAuthorizedError.new(reason)
94
+ end
95
+
96
+ end
97
+ end
@@ -1,8 +1,8 @@
1
1
  module Subroutine
2
2
 
3
3
  MAJOR = 0
4
- MINOR = 2
5
- PATCH = 1
4
+ MINOR = 3
5
+ PATCH = 0
6
6
  PRE = nil
7
7
 
8
8
  VERSION = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
data/subroutine.gemspec CHANGED
@@ -25,4 +25,5 @@ Gem::Specification.new do |spec|
25
25
 
26
26
  spec.add_development_dependency "minitest"
27
27
  spec.add_development_dependency "minitest-reporters"
28
+ spec.add_development_dependency "mocha"
28
29
  end
@@ -0,0 +1,58 @@
1
+ require 'test_helper'
2
+
3
+ module Subroutine
4
+ class AuthTest < TestCase
5
+
6
+ def doug
7
+ @doug ||= ::User.new(id: 1, email_address: "doug@example.com")
8
+ end
9
+
10
+ def fred
11
+ @fred ||= ::User.new(id: 2, email_address: "fred@example.com")
12
+ end
13
+
14
+ def test_it_sets_accessors_on_init
15
+ op = SimpleAssociationOp.new user: doug
16
+ assert_equal "User", op.user_type
17
+ assert_equal doug.id, op.user_id
18
+ end
19
+
20
+ def test_it_looks_up_an_association
21
+ all_mock = mock
22
+
23
+ ::User.expects(:all).returns(all_mock)
24
+ all_mock.expects(:find).with(1).returns(doug)
25
+
26
+ op = SimpleAssociationOp.new user_type: "User", user_id: doug.id
27
+ assert_equal doug, op.user
28
+ end
29
+
30
+ def test_it_allows_an_association_to_be_looked_up_without_default_scoping
31
+ all_mock = mock
32
+ unscoped_mock = mock
33
+
34
+ ::User.expects(:all).returns(all_mock)
35
+ all_mock.expects(:unscoped).returns(unscoped_mock)
36
+ unscoped_mock.expects(:find).with(1).returns(doug)
37
+
38
+ op = UnscopedSimpleAssociationOp.new user_type: "User", user_id: doug.id
39
+ assert_equal doug, op.user
40
+ end
41
+
42
+ def test_it_allows_polymorphic_associations
43
+ all_mock = mock
44
+ ::User.expects(:all).never
45
+ ::AdminUser.expects(:all).returns(all_mock)
46
+ all_mock.expects(:find).with(1).returns(doug)
47
+
48
+ op = PolymorphicAssociationOp.new(admin_type: "AdminUser", admin_id: doug.id)
49
+ assert_equal doug, op.admin
50
+ end
51
+
52
+ def test_it_allows_the_class_to_be_set
53
+ op = ::AssociationWithClassOp.new(admin: doug)
54
+ assert_equal "AdminUser", op.admin_type
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,69 @@
1
+ require 'test_helper'
2
+
3
+ module Subroutine
4
+ class AuthTest < TestCase
5
+
6
+ def user
7
+ @user ||= ::User.new(email_address: "doug@example.com")
8
+ end
9
+
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
12
+ MissingAuthOp.new
13
+ end
14
+ end
15
+
16
+ def test_it_throws_an_error_if_require_user_but_none_is_provided
17
+ assert_raises ::Subroutine::Auth::NotAuthorizedError do
18
+ RequireUserOp.submit!
19
+ end
20
+ end
21
+
22
+ def test_it_does_not_throw_an_error_if_require_user_but_none_is_provided
23
+ RequireUserOp.submit! user
24
+ end
25
+
26
+ def test_it_throws_an_error_if_require_no_user_but_one_is_present
27
+ assert_raises ::Subroutine::Auth::NotAuthorizedError do
28
+ RequireNoUserOp.submit! user
29
+ end
30
+ end
31
+
32
+ def test_it_does_not_throw_an_error_if_require_no_user_and_none_is_provided
33
+ RequireNoUserOp.submit!
34
+ end
35
+
36
+ def test_it_does_not_throw_an_error_if_no_user_requirements_and_one_is_provided
37
+ NoUserRequirementsOp.submit! user
38
+ end
39
+
40
+ def test_it_does_not_throw_an_error_if_no_user_requirements_and_none_is_provided
41
+ NoUserRequirementsOp.submit!
42
+ end
43
+
44
+ def test_it_runs_custom_authorizations
45
+ CustomAuthorizeOp.submit! user
46
+
47
+ assert_raises ::Subroutine::Auth::NotAuthorizedError do
48
+ CustomAuthorizeOp.submit! User.new(email_address: "foo@bar.com")
49
+ end
50
+ end
51
+
52
+ def test_it_does_not_run_authorizations_if_explicitly_bypassed
53
+ op = CustomAuthorizeOp.new User.new(email_address: "foo@bar.com")
54
+ op.skip_auth_checks!
55
+ op.submit!
56
+ end
57
+
58
+ def test_it_runs_policies_as_part_of_authorization
59
+ assert_raises ::Subroutine::Auth::NotAuthorizedError do
60
+ PolicyOp.submit! user
61
+ end
62
+
63
+ op = PolicyOp.new
64
+ op.skip_auth_checks!
65
+ op.submit!
66
+ end
67
+
68
+ end
69
+ end
@@ -220,5 +220,19 @@ module Subroutine
220
220
  assert_equal "2022-12-22T10:30:24Z", op.iso_time_input
221
221
  end
222
222
 
223
+ def test_field_provided
224
+ op = ::SignupOp.new()
225
+ assert_equal false, op.send(:field_provided?, :email)
226
+
227
+ op = ::SignupOp.new(email: "foo")
228
+ assert_equal true, op.send(:field_provided?, :email)
229
+
230
+ op = ::DefaultsOp.new()
231
+ assert_equal false, op.send(:field_provided?, :foo)
232
+
233
+ op = ::DefaultsOp.new(foo: "foo")
234
+ assert_equal true, op.send(:field_provided?, :foo)
235
+ end
236
+
223
237
  end
224
238
  end
data/test/support/ops.rb CHANGED
@@ -1,8 +1,12 @@
1
+ require "subroutine/auth"
2
+ require "subroutine/association"
3
+
1
4
  ## Models ##
2
5
 
3
6
  class User
4
7
  include ::ActiveModel::Model
5
8
 
9
+ attr_accessor :id
6
10
  attr_accessor :email_address
7
11
  attr_accessor :password
8
12
 
@@ -109,3 +113,78 @@ class TypeCastOp < ::Subroutine::Op
109
113
  array :array_input, :default => 'foo'
110
114
 
111
115
  end
116
+
117
+ class OpWithAuth < ::Subroutine::Op
118
+ include ::Subroutine::Auth
119
+ def perform
120
+ true
121
+ end
122
+ end
123
+
124
+ class MissingAuthOp < OpWithAuth
125
+ end
126
+
127
+ class RequireUserOp < OpWithAuth
128
+ require_user!
129
+ end
130
+
131
+ class RequireNoUserOp < OpWithAuth
132
+ require_no_user!
133
+ end
134
+
135
+ class NoUserRequirementsOp < OpWithAuth
136
+ no_user_requirements!
137
+ end
138
+
139
+ class CustomAuthorizeOp < OpWithAuth
140
+
141
+ require_user!
142
+ authorize :authorize_user_is_correct
143
+
144
+ protected
145
+
146
+ def authorize_user_is_correct
147
+ unless current_user.email_address.to_s =~ /example\.com$/
148
+ unauthorized!
149
+ end
150
+ end
151
+ end
152
+
153
+ class PolicyOp < OpWithAuth
154
+
155
+ class FakePolicy
156
+ def user_can_access?
157
+ false
158
+ end
159
+ end
160
+
161
+ require_user!
162
+ policy :user_can_access?
163
+
164
+ def policy
165
+ @policy ||= FakePolicy.new
166
+ end
167
+ end
168
+
169
+
170
+ class OpWithAssociation < ::Subroutine::Op
171
+ include ::Subroutine::Association
172
+ end
173
+
174
+ class SimpleAssociationOp < ::OpWithAssociation
175
+
176
+ association :user
177
+
178
+ end
179
+
180
+ class UnscopedSimpleAssociationOp < ::OpWithAssociation
181
+ association :user, unscoped: true
182
+ end
183
+
184
+ class PolymorphicAssociationOp < ::OpWithAssociation
185
+ association :admin, polymorphic: true
186
+ end
187
+
188
+ class AssociationWithClassOp < ::OpWithAssociation
189
+ association :admin, class_name: "AdminUser"
190
+ end
data/test/test_helper.rb CHANGED
@@ -3,11 +3,10 @@ require 'minitest/autorun'
3
3
  require 'minitest/unit'
4
4
 
5
5
  require 'minitest/reporters'
6
+ require 'mocha/mini_test'
6
7
 
7
8
  Minitest::Reporters.use!([Minitest::Reporters::DefaultReporter.new])
8
9
 
9
10
  class TestCase < (MiniTest::Unit::TestCase rescue ::MiniTest::Test); end
10
11
 
11
12
  require_relative 'support/ops'
12
-
13
-
metadata CHANGED
@@ -1,83 +1,97 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: subroutine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.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: 2016-08-22 00:00:00.000000000 Z
11
+ date: 2017-02-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 4.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 4.0.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.7'
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
40
  version: '1.7'
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
47
  version: '10.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
54
  version: '10.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: minitest
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: minitest-reporters
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '>='
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
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
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mocha
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
83
97
  description: An interface for creating feature-driven operations.
@@ -87,8 +101,8 @@ executables: []
87
101
  extensions: []
88
102
  extra_rdoc_files: []
89
103
  files:
90
- - .gitignore
91
- - .travis.yml
104
+ - ".gitignore"
105
+ - ".travis.yml"
92
106
  - Gemfile
93
107
  - LICENSE.txt
94
108
  - README.md
@@ -100,12 +114,16 @@ files:
100
114
  - gemfiles/am41.gemfile
101
115
  - gemfiles/am42.gemfile
102
116
  - lib/subroutine.rb
117
+ - lib/subroutine/association.rb
118
+ - lib/subroutine/auth.rb
103
119
  - lib/subroutine/failure.rb
104
120
  - lib/subroutine/filtered_errors.rb
105
121
  - lib/subroutine/op.rb
106
122
  - lib/subroutine/type_caster.rb
107
123
  - lib/subroutine/version.rb
108
124
  - subroutine.gemspec
125
+ - test/subroutine/association_test.rb
126
+ - test/subroutine/auth_test.rb
109
127
  - test/subroutine/base_test.rb
110
128
  - test/subroutine/type_caster_test.rb
111
129
  - test/support/ops.rb
@@ -120,17 +138,17 @@ require_paths:
120
138
  - lib
121
139
  required_ruby_version: !ruby/object:Gem::Requirement
122
140
  requirements:
123
- - - '>='
141
+ - - ">="
124
142
  - !ruby/object:Gem::Version
125
143
  version: '0'
126
144
  required_rubygems_version: !ruby/object:Gem::Requirement
127
145
  requirements:
128
- - - '>='
146
+ - - ">="
129
147
  - !ruby/object:Gem::Version
130
148
  version: '0'
131
149
  requirements: []
132
150
  rubyforge_project:
133
- rubygems_version: 2.0.14.1
151
+ rubygems_version: 2.6.8
134
152
  signing_key:
135
153
  specification_version: 4
136
154
  summary: Feature-driven operation objects.
@@ -141,6 +159,8 @@ test_files:
141
159
  - gemfiles/am40.gemfile
142
160
  - gemfiles/am41.gemfile
143
161
  - gemfiles/am42.gemfile
162
+ - test/subroutine/association_test.rb
163
+ - test/subroutine/auth_test.rb
144
164
  - test/subroutine/base_test.rb
145
165
  - test/subroutine/type_caster_test.rb
146
166
  - test/support/ops.rb