interaktor 0.2.0 → 0.4.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
  SHA256:
3
- metadata.gz: be48f704b8d52c81c07e9708d1e90230ead270e4da3ddf74e8e4e6251e677978
4
- data.tar.gz: '09e99c6f8129b44ff287aa9e8a742327cac3a232f13d7596ece9b428a575bc28'
3
+ metadata.gz: d187b41c956972596b434b05aa5e4b3f8558457953327e305c5d76165f7c195b
4
+ data.tar.gz: e123fbeb3e8c1db843cb41b54023ca71d077a6947f0a3a50e0ec5bd78dfd4ddb
5
5
  SHA512:
6
- metadata.gz: ea1fe86854da930c537f9fb69864accdf8cb1948d8dacae4b64685ff88508964e99eb968826a12e5653791f492c1e8af6426b734838c5ac152c581dfee6399ba
7
- data.tar.gz: 0d8c4406b6d3ee81f39c6adc56118ce4e204465743eed6dc5f342f3b3b5a9a03722f9b04ed48dab265ef9bfbcb54e047476245fdf000295b150e2a835f8087e6
6
+ metadata.gz: 5549218310ebd0e3d0505eac4d43302f83c9508a0bee463f3a8c0f2042de5bfd25954aec67beae050a140606b14e75ce1e7ff8f0866726e5a24a09dd8071b4e6
7
+ data.tar.gz: a7eabf0b930c9efd51ec3407e119234d6a1d29aa3d88494bacde287cc0e3d67ae9882707d8636ddba7289c6889f536bcdbad22eebc47a8344b659828a0718f4b
@@ -1,8 +1,9 @@
1
1
  name: Publish
2
2
 
3
3
  on:
4
+ # Manually publish
5
+ workflow_dispatch:
4
6
  push:
5
- branches: [master]
6
7
  tags:
7
8
  - v*
8
9
 
@@ -12,11 +13,11 @@ jobs:
12
13
  runs-on: ubuntu-latest
13
14
 
14
15
  steps:
15
- - uses: actions/checkout@v2
16
+ - uses: actions/checkout@v3
16
17
  - name: Set up Ruby
17
- uses: actions/setup-ruby@v1
18
+ uses: ruby/setup-ruby@v1
18
19
  with:
19
- ruby-version: 2.5.x
20
+ ruby-version: 3.2
20
21
 
21
22
  - name: Publish to RubyGems
22
23
  run: |
@@ -7,10 +7,12 @@ jobs:
7
7
  strategy:
8
8
  fail-fast: false
9
9
  matrix:
10
- os: [ubuntu, macos]
11
- ruby: [2.5, 2.6, 2.7, head, debug]
12
- runs-on: ${{ matrix.os }}-latest
10
+ os: [ubuntu-latest, macos-latest]
11
+ ruby: [2.5, 2.6, 2.7, 3.0, head, debug]
12
+ runs-on: ${{ matrix.os }}
13
13
  continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }}
14
+ env:
15
+ BUNDLE_GEMFILE: "Gemfile.ci"
14
16
  steps:
15
17
  - uses: actions/checkout@v2
16
18
  - name: Set up Ruby
@@ -18,7 +20,5 @@ jobs:
18
20
  with:
19
21
  ruby-version: ${{ matrix.ruby }}
20
22
  bundler-cache: true
21
- - name: Install dependencies
22
- run: bundle install
23
23
  - name: Run tests
24
24
  run: bundle exec rspec
data/.gitignore CHANGED
@@ -4,6 +4,7 @@
4
4
  .config
5
5
  .yardoc
6
6
  Gemfile.lock
7
+ Gemfile.ci.lock
7
8
  InstalledFiles
8
9
  _yardoc
9
10
  coverage
@@ -15,3 +16,5 @@ spec/reports
15
16
  test/tmp
16
17
  test/version_tmp
17
18
  tmp
19
+ spec/examples.txt
20
+ /vendor
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.5.8
1
+ 3.1.3
data/Gemfile CHANGED
@@ -2,12 +2,12 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
+ gem "guard-rspec", require: false
5
6
  gem "rubocop"
6
7
  gem "rubocop-performance"
7
8
  gem "rubocop-rspec"
8
9
  gem "rufo", "~> 0.12.0"
9
10
  gem "solargraph"
10
- gem "guard-rspec", require: false
11
11
 
12
12
  group :test do
13
13
  gem "pry-byebug", platforms: [:mri]
data/Gemfile.ci ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "rspec", "~> 3.9.0"
6
+ gem "simplecov"
data/README.md CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  **DISCLAIMER: Interaktor is under active development. Feel free to use it, but until 1.0 is released, any update could break compatibility with an older version.**
7
7
 
8
- **Interaktor** is a fork of [Interaktor by collectiveidea](https://github.com/collectiveidea/interaktor). While Interactor is still used by collectiveidea internally, communication and progress has been slow in adapting to pull requests and issues. This inactivity combined with my desire to dial back on the Interactor's inherent permissivity led me to fork it and create Interaktor.
8
+ **Interaktor** is a fork of [Interactor by collectiveidea](https://github.com/collectiveidea/interactor). While Interactor is still used by collectiveidea internally, communication and progress has been slow in adapting to pull requests and issues. This inactivity combined with my desire to dial back on the Interactor's inherent permissivity led me to fork it and create Interaktor.
9
9
 
10
10
  Fundamentally, Interaktor is the same as Interactor, but with the following changes:
11
11
 
12
- - Required explicit definition of interaktor "attributes" which replaces the concept of the interaktor context. Attributes can be required or optional, and support options like default values.
12
+ - Required explicit definition of interaktor "attributes" which replaces the concept of the interaktor context. Attributes are defined using a schema DSL provided by [dry-schema](https://github.com/dry-rb/dry-schema), which allows for complex validation, if desired.
13
13
  - The interaktor "context" is no longer a public-facing concept, all data/attribute accessors/setters are defined as attributes
14
14
  - Attributes passed to `#fail!` must be defined in advance
15
15
  - Interaktors support early-exit functionality through the use of `#success!`, which functions the same as `#fail!` in that you must define the required success attributes on the interaktor
@@ -34,15 +34,18 @@ Interaktors are used to encapsulate your application's [business logic](http://e
34
34
 
35
35
  Depending on its definition, an interaktor may require attributes to be passed in when it is invoked. These attributes contain everything the interaktor needs to do its work.
36
36
 
37
- You may define `required` or `optional` attributes.
37
+ Attributes are defined using a schema DSL provided by the [dry-schema](https://github.com/dry-rb/dry-schema) gem. It allows the construction of schemas for validating attributes. The schema is typically provided as a block argument to the `input` class method as seen below.
38
+
39
+ This example is an extremely simple case, and dry-schema supports highly complex schema validation, like type checking, nested hash data validation, and more. For more information on defining an attribute schema, please see the [dry-schema documentation website](https://dry-rb.org/gems/dry-schema). This link should take you to the latest version of dry-schema, but be sure to check that the version of dry-schema in your application bundle matches the documentation you are viewing.
38
40
 
39
41
  ```ruby
40
42
  class CreateUser
41
43
  include Interaktor
42
44
 
43
- required :name
44
-
45
- optional :email
45
+ input do
46
+ required(:name)
47
+ optional(:email)
48
+ end
46
49
 
47
50
  def call
48
51
  User.create!(
@@ -53,9 +56,12 @@ class CreateUser
53
56
  end
54
57
 
55
58
  CreateUser.call(name: "Foo Bar")
56
-
57
59
  ```
58
60
 
61
+ `input` will also accept a `Dry::Schema::Params` object directly, if for some reason the schema needs to be constructed elsewhere.
62
+
63
+ **A note about type checking**: Type checking is cool, but Ruby is a dynamic language, and Ruby developers tend to utilize the idea of [duck typing](https://en.wikipedia.org/wiki/Duck_typing). Forcing the attributes of an interaktor to be of a certain type in order to validate might sound like a good idea, but it can often cause problems in situations where you might like to use duck typing, for example, when using stubs in tests.
64
+
59
65
  #### Output attributes
60
66
 
61
67
  Based on the outcome of the interaktor's work, we can require certain attributes. In the example below, we must succeed with a `user_id` attribute, and if we fail, we must provide an `error_messages` attribute.
@@ -66,11 +72,17 @@ The use of `#success!` allows you to early-return from an interaktor's work. If
66
72
  class CreateUser
67
73
  include Interaktor
68
74
 
69
- required :name
75
+ input do
76
+ required(:name).filled(:string)
77
+ end
70
78
 
71
- success :user_id
79
+ success do
80
+ required(:user_id).value(:integer)
81
+ end
72
82
 
73
- failure :error_messages
83
+ failure do
84
+ required(:error_messages).value(array[:string])
85
+ end
74
86
 
75
87
  def call
76
88
  user = User.new(name: name)
@@ -98,7 +110,7 @@ end
98
110
 
99
111
  Normally, however, these exceptions are not seen. In the recommended usage, the caller invokes the interaktor using the class method `.call`, then checks the `#success?` method of the returned object. This works because the `call` class method swallows exceptions. When unit testing an interaktor, if calling custom business logic methods directly and bypassing `call`, be aware that `fail!` will generate such exceptions.
100
112
 
101
- See _Interaktors in the controller_, below, for the recommended usage of `call` and `success?`.
113
+ See _Interaktors in the controller_, below, for the recommended usage of `.call` and `#success?`.
102
114
 
103
115
  ### Hooks
104
116
 
@@ -235,13 +247,19 @@ A basic interaktor is a class that includes `Interaktor` and defines `call`.
235
247
  class AuthenticateUser
236
248
  include Interaktor
237
249
 
238
- required :email
239
- required :password
250
+ input do
251
+ required(:email).filled(:string)
252
+ required(:password).filled(:string)
253
+ end
240
254
 
241
- success :user
242
- success :token
255
+ success do
256
+ required(:user)
257
+ required(:token).filled(:string)
258
+ end
243
259
 
244
- failure :message
260
+ failure do
261
+ required(:message).filled(:string)
262
+ end
245
263
 
246
264
  def call
247
265
  if user = User.authenticate(email, password)
@@ -263,7 +281,9 @@ An organizer is an important variation on the basic interaktor. Its single purpo
263
281
  class PlaceOrder
264
282
  include Interaktor::Organizer
265
283
 
266
- required :order_params
284
+ input do
285
+ required(:order_params).filled(:hash)
286
+ end
267
287
 
268
288
  organize CreateOrder, ChargeCard, SendThankYou
269
289
  end
@@ -304,9 +324,13 @@ In addition, any interaktors that had already run are given the chance to undo t
304
324
  class CreateOrder
305
325
  include Interaktor
306
326
 
307
- required :order_params
327
+ input do
328
+ required(:order_params).filled(:hash)
329
+ end
308
330
 
309
- success :order
331
+ success do
332
+ required(:order)
333
+ end
310
334
 
311
335
  def call
312
336
  order = Order.create(order_params)
@@ -334,13 +358,19 @@ When written correctly, an interaktor is easy to test because it only _does_ one
334
358
  class AuthenticateUser
335
359
  include Interaktor
336
360
 
337
- required :email
338
- required :password
361
+ input do
362
+ required(:email).filled(:string)
363
+ required(:password).filled(:string)
364
+ end
339
365
 
340
- success :user
341
- success :token
366
+ success do
367
+ required(:user)
368
+ required(:token).filled(:string)
369
+ end
342
370
 
343
- failure :message
371
+ failure do
372
+ required(:message).filled(:string)
373
+ end
344
374
 
345
375
  def call
346
376
  if user = User.authenticate(email, password)
@@ -406,12 +436,18 @@ It's a good idea to define your own interfaces to your models. Doing so makes it
406
436
  class AuthenticateUser
407
437
  include Interaktor
408
438
 
409
- required :email
410
- required :password
439
+ input do
440
+ required(:email).filled(:string)
441
+ required(:password).filled(:string)
442
+ end
411
443
 
412
- success :user
444
+ success do
445
+ required(:user)
446
+ end
413
447
 
414
- failure :message
448
+ failure do
449
+ required(:message).filled(:string)
450
+ end
415
451
 
416
452
  def call
417
453
  user = User.find_by(email: email)
@@ -515,4 +551,4 @@ This controller test will have to change very little during the life of the appl
515
551
 
516
552
  ### Rails
517
553
 
518
- Interactor provided [interactor-rails](https://github.com/collectiveidea/interactor-rails), which ensures `app/interactors` is included in your autoload paths, and provides generators for new interactors. I have no intention of maintaining generators but if someone feels strongly enough to submit a pull request to include the functionality in _this_ gem (not a separate Rails one) then I will be happy to take a look. Making sure `app/interactors` is included in your autoload paths is something I would like to do soon.
554
+ Interactor provided [interactor-rails](https://github.com/collectiveidea/interactor-rails), which ensures `app/interactors` is included in your autoload paths, and provides generators for new interactors. I have no intention of maintaining generators but if someone feels strongly enough to submit a pull request to include the functionality in _this_ gem (not a separate Rails one) then I will be happy to take a look. Making sure `app/interaktors` is included in your autoload paths is something I would like to do soon.
data/interaktor.gemspec CHANGED
@@ -1,9 +1,9 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "interaktor"
3
- spec.version = "0.2.0"
3
+ spec.version = "0.4.0"
4
4
 
5
5
  spec.author = "Taylor Thurlow"
6
- spec.email = "taylorthurlow@me.com"
6
+ spec.email = "thurlow@hey.com"
7
7
  spec.description = "A common interface for building service objects."
8
8
  spec.summary = "Simple service object implementation"
9
9
  spec.homepage = "https://github.com/taylorthurlow/interaktor"
@@ -13,6 +13,7 @@ Gem::Specification.new do |spec|
13
13
  spec.required_ruby_version = ">= 2.5"
14
14
  spec.require_path = "lib"
15
15
 
16
+ spec.add_runtime_dependency "dry-schema", "~> 1.0"
16
17
  spec.add_runtime_dependency "zeitwerk", "~> 2.0"
17
18
 
18
19
  spec.add_development_dependency "rake", "~> 13.0"