granite 0.13.0 → 0.14.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 +4 -4
- data/app/controllers/granite/controller.rb +6 -5
- data/lib/granite/action/instrumentation.rb +23 -0
- data/lib/granite/action/performer.rb +5 -4
- data/lib/granite/action.rb +2 -0
- data/lib/granite/context_proxy/data.rb +20 -0
- data/lib/granite/{performer_proxy → context_proxy}/proxy.rb +6 -7
- data/lib/granite/context_proxy.rb +34 -0
- data/lib/granite/projector.rb +3 -3
- data/lib/granite/rspec/perform_action.rb +89 -0
- data/lib/granite/rspec.rb +1 -0
- data/lib/granite/version.rb +1 -1
- metadata +8 -5
- data/lib/granite/performer_proxy.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d0524a0055b0ea48aeab0691a1063cf332455b87094e2a78dbb6b66cc24b168e
|
4
|
+
data.tar.gz: e07a9c6a636eed28916860e213beea548aa92943da442b7d39019b346079e3fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 217ac1d410c18e838f648af770c610b24ad4a28fc70e37514060509b5ac0a9303907912da5a2e7151162df2d842c78cdf3dd4c03e64ae464427bea9707923410
|
7
|
+
data.tar.gz: 2824f499e042ce4df4565d450367ccf3c33b711586c496c752fde675c4a1bd4d2a5810b92c84916603436981ef61c100583d89865e7f5c758c5033fbc3d97e20
|
@@ -14,11 +14,12 @@ module Granite
|
|
14
14
|
before_action :authorize_action!
|
15
15
|
|
16
16
|
def projector
|
17
|
-
@projector ||=
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
@projector ||=
|
18
|
+
begin
|
19
|
+
projector_class = action_class.public_send(projector_name)
|
20
|
+
projector_class = projector_class.with(projector_context) if respond_to?(:projector_context, true)
|
21
|
+
projector_class.new(projector_params)
|
22
|
+
end
|
22
23
|
end
|
23
24
|
helper_method :projector
|
24
25
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Granite
|
2
|
+
class Action
|
3
|
+
module Instrumentation
|
4
|
+
def perform!(*, **)
|
5
|
+
instrument_perform(:perform!) { super }
|
6
|
+
end
|
7
|
+
|
8
|
+
def perform(*, **)
|
9
|
+
instrument_perform(:perform) { super }
|
10
|
+
end
|
11
|
+
|
12
|
+
def try_perform!(*, **)
|
13
|
+
instrument_perform(:try_perform!) { super }
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def instrument_perform(using, &block)
|
19
|
+
ActiveSupport::Notifications.instrument('granite.perform_action', action: self, using: using, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'granite/
|
1
|
+
require 'granite/context_proxy'
|
2
2
|
|
3
3
|
module Granite
|
4
4
|
class Action
|
@@ -8,15 +8,16 @@ module Granite
|
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
10
|
included do
|
11
|
-
include
|
12
|
-
attr_reader :
|
11
|
+
include ContextProxy
|
12
|
+
attr_reader :ctx
|
13
13
|
end
|
14
14
|
|
15
15
|
def initialize(*args)
|
16
|
-
@
|
16
|
+
@ctx = self.class.proxy_context
|
17
17
|
super
|
18
18
|
end
|
19
19
|
|
20
|
+
delegate :performer, to: :ctx, allow_nil: true
|
20
21
|
delegate :id, to: :performer, prefix: true, allow_nil: true
|
21
22
|
end
|
22
23
|
end
|
data/lib/granite/action.rb
CHANGED
@@ -5,6 +5,7 @@ require 'active_support/callbacks'
|
|
5
5
|
|
6
6
|
require 'granite/action/types'
|
7
7
|
require 'granite/action/error'
|
8
|
+
require 'granite/action/instrumentation'
|
8
9
|
require 'granite/action/performing'
|
9
10
|
require 'granite/action/performer'
|
10
11
|
require 'granite/action/precondition'
|
@@ -48,6 +49,7 @@ module Granite
|
|
48
49
|
include Policies
|
49
50
|
include Projectors
|
50
51
|
prepend AssignAttributes
|
52
|
+
prepend Instrumentation
|
51
53
|
|
52
54
|
handle_exception ActiveRecord::RecordInvalid do |e|
|
53
55
|
merge_errors(e.record.errors)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Granite
|
2
|
+
module ContextProxy
|
3
|
+
# Contains all the arbitrary data that is passed to BA with `with`
|
4
|
+
class Data
|
5
|
+
attr_reader :performer
|
6
|
+
|
7
|
+
def self.wrap(data)
|
8
|
+
if data.is_a?(self)
|
9
|
+
data
|
10
|
+
else
|
11
|
+
new(**data || {})
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(performer: nil)
|
16
|
+
@performer = performer
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,23 +1,22 @@
|
|
1
1
|
module Granite
|
2
|
-
module
|
3
|
-
# Proxy
|
4
|
-
# performer-enabled context.
|
2
|
+
module ContextProxy
|
3
|
+
# Proxy which wraps the following method calls with BA context.
|
5
4
|
#
|
6
5
|
class Proxy
|
7
6
|
extend Granite::Ruby3Compatibility
|
8
7
|
|
9
|
-
def initialize(klass,
|
8
|
+
def initialize(klass, context)
|
10
9
|
@klass = klass
|
11
|
-
@
|
10
|
+
@context = context
|
12
11
|
end
|
13
12
|
|
14
13
|
def inspect
|
15
|
-
"<#{@klass}
|
14
|
+
"<#{@klass}ContextProxy #{@context}>"
|
16
15
|
end
|
17
16
|
|
18
17
|
ruby2_keywords def method_missing(method, *args, &block)
|
19
18
|
if @klass.respond_to?(method)
|
20
|
-
@klass.
|
19
|
+
@klass.with_context(@context) do
|
21
20
|
@klass.public_send(method, *args, &block)
|
22
21
|
end
|
23
22
|
else
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'granite/context_proxy/data'
|
2
|
+
require 'granite/context_proxy/proxy'
|
3
|
+
|
4
|
+
module Granite
|
5
|
+
# This concern contains class methods used for actions and projectors
|
6
|
+
#
|
7
|
+
module ContextProxy
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
PROXY_CONTEXT_KEY = :granite_proxy_context
|
12
|
+
|
13
|
+
def with(data)
|
14
|
+
Proxy.new(self, Data.wrap(data))
|
15
|
+
end
|
16
|
+
|
17
|
+
def as(performer)
|
18
|
+
with(performer: performer)
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_context(context)
|
22
|
+
old_context = proxy_context
|
23
|
+
Thread.current[PROXY_CONTEXT_KEY] = context
|
24
|
+
yield
|
25
|
+
ensure
|
26
|
+
Thread.current[PROXY_CONTEXT_KEY] = old_context
|
27
|
+
end
|
28
|
+
|
29
|
+
def proxy_context
|
30
|
+
Thread.current[PROXY_CONTEXT_KEY]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/granite/projector.rb
CHANGED
@@ -2,11 +2,11 @@ require 'granite/projector/controller_actions'
|
|
2
2
|
require 'granite/projector/error'
|
3
3
|
require 'granite/projector/helpers'
|
4
4
|
require 'granite/projector/translations'
|
5
|
-
require 'granite/
|
5
|
+
require 'granite/context_proxy'
|
6
6
|
|
7
7
|
module Granite
|
8
8
|
class Projector
|
9
|
-
include
|
9
|
+
include ContextProxy
|
10
10
|
include ControllerActions
|
11
11
|
include Helpers
|
12
12
|
include Translations
|
@@ -42,7 +42,7 @@ module Granite
|
|
42
42
|
private
|
43
43
|
|
44
44
|
def build_action(*args)
|
45
|
-
action_class.
|
45
|
+
action_class.with(self.class.proxy_context).new(*args)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
RSpec::Matchers.define :perform_action do |klass|
|
2
|
+
chain :using do |using|
|
3
|
+
@using = using
|
4
|
+
end
|
5
|
+
|
6
|
+
chain :as do |performer|
|
7
|
+
@performer = performer
|
8
|
+
end
|
9
|
+
|
10
|
+
chain :with do |attributes|
|
11
|
+
@attributes = attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
match do |block|
|
15
|
+
@klass = klass
|
16
|
+
@using ||= :perform!
|
17
|
+
|
18
|
+
@payloads = []
|
19
|
+
subscriber = ActiveSupport::Notifications.subscribe('granite.perform_action') do |_, _, _, _, payload|
|
20
|
+
@payloads << payload
|
21
|
+
end
|
22
|
+
|
23
|
+
block.call
|
24
|
+
|
25
|
+
ActiveSupport::Notifications.unsubscribe(subscriber)
|
26
|
+
|
27
|
+
@payloads.detect { |payload| action_matches?(payload[:action]) && payload[:using] == @using }
|
28
|
+
end
|
29
|
+
|
30
|
+
failure_message do
|
31
|
+
output = "expected to call #{performed_entity}"
|
32
|
+
add_performer_message(output, @performer) if defined?(@performer)
|
33
|
+
add_attributes_message(output, @attributes) if defined?(@attributes)
|
34
|
+
|
35
|
+
similar_payloads = @payloads.select { |payload| class_matches?(payload[:action]) && payload[:using] == @using }
|
36
|
+
if similar_payloads.present?
|
37
|
+
output << "\nreceived calls to #{performed_entity}:"
|
38
|
+
similar_payloads.each { |payload| add_message_from_payload(output, payload) }
|
39
|
+
end
|
40
|
+
|
41
|
+
output
|
42
|
+
end
|
43
|
+
|
44
|
+
failure_message_when_negated do
|
45
|
+
"expected not to call #{performed_entity}"
|
46
|
+
end
|
47
|
+
|
48
|
+
supports_block_expectations
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def add_message_from_payload(output, payload)
|
53
|
+
action = payload[:action]
|
54
|
+
add_performer_message(output, action.performer) if defined?(@performer)
|
55
|
+
add_attributes_message(output, actual_attributes(action)) if defined?(@attributes)
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_performer_message(output, performer)
|
59
|
+
output << "\n AS #{performer.inspect}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_attributes_message(output, attributes)
|
63
|
+
output << "\n WITH #{attributes.inspect}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def performed_entity
|
67
|
+
"#{@klass}##{@using}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def actual_attributes(action)
|
71
|
+
@attributes.keys.to_h { |attr| [attr, action.public_send(attr)] }
|
72
|
+
end
|
73
|
+
|
74
|
+
def action_matches?(action)
|
75
|
+
class_matches?(action) && performer_matches?(action) && attributes_match?(action)
|
76
|
+
end
|
77
|
+
|
78
|
+
def class_matches?(action)
|
79
|
+
action.is_a?(@klass)
|
80
|
+
end
|
81
|
+
|
82
|
+
def performer_matches?(action)
|
83
|
+
!defined?(@performer) || action.performer == @performer
|
84
|
+
end
|
85
|
+
|
86
|
+
def attributes_match?(action)
|
87
|
+
!defined?(@attributes) || match(@attributes).matches?(actual_attributes(action))
|
88
|
+
end
|
89
|
+
end
|
data/lib/granite/rspec.rb
CHANGED
data/lib/granite/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: granite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Toptal Engineering
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -323,6 +323,7 @@ files:
|
|
323
323
|
- lib/granite/action.rb
|
324
324
|
- lib/granite/action/error.rb
|
325
325
|
- lib/granite/action/exceptions_handling.rb
|
326
|
+
- lib/granite/action/instrumentation.rb
|
326
327
|
- lib/granite/action/performer.rb
|
327
328
|
- lib/granite/action/performing.rb
|
328
329
|
- lib/granite/action/policies.rb
|
@@ -346,10 +347,11 @@ files:
|
|
346
347
|
- lib/granite/base.rb
|
347
348
|
- lib/granite/config.rb
|
348
349
|
- lib/granite/context.rb
|
350
|
+
- lib/granite/context_proxy.rb
|
351
|
+
- lib/granite/context_proxy/data.rb
|
352
|
+
- lib/granite/context_proxy/proxy.rb
|
349
353
|
- lib/granite/dispatcher.rb
|
350
354
|
- lib/granite/error.rb
|
351
|
-
- lib/granite/performer_proxy.rb
|
352
|
-
- lib/granite/performer_proxy/proxy.rb
|
353
355
|
- lib/granite/projector.rb
|
354
356
|
- lib/granite/projector/controller_actions.rb
|
355
357
|
- lib/granite/projector/error.rb
|
@@ -369,6 +371,7 @@ files:
|
|
369
371
|
- lib/granite/rspec.rb
|
370
372
|
- lib/granite/rspec/action_helpers.rb
|
371
373
|
- lib/granite/rspec/have_projector.rb
|
374
|
+
- lib/granite/rspec/perform_action.rb
|
372
375
|
- lib/granite/rspec/projector_helpers.rb
|
373
376
|
- lib/granite/rspec/raise_validation_error.rb
|
374
377
|
- lib/granite/rspec/satisfy_preconditions.rb
|
@@ -399,7 +402,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
399
402
|
- !ruby/object:Gem::Version
|
400
403
|
version: '0'
|
401
404
|
requirements: []
|
402
|
-
rubygems_version: 3.3.
|
405
|
+
rubygems_version: 3.3.9
|
403
406
|
signing_key:
|
404
407
|
specification_version: 4
|
405
408
|
summary: Another business actions architecture for Rails apps
|
@@ -1,34 +0,0 @@
|
|
1
|
-
require 'granite/performer_proxy/proxy'
|
2
|
-
|
3
|
-
module Granite
|
4
|
-
# This concern contains class methods used for actions and projectors
|
5
|
-
#
|
6
|
-
module PerformerProxy
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
module ClassMethods
|
10
|
-
def as(performer)
|
11
|
-
Proxy.new(self, performer)
|
12
|
-
end
|
13
|
-
|
14
|
-
def with_proxy_performer(performer)
|
15
|
-
key = proxy_performer_key
|
16
|
-
old_performer = Thread.current[key]
|
17
|
-
Thread.current[key] = performer
|
18
|
-
yield
|
19
|
-
ensure
|
20
|
-
Thread.current[key] = old_performer
|
21
|
-
end
|
22
|
-
|
23
|
-
def proxy_performer
|
24
|
-
Thread.current[proxy_performer_key]
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def proxy_performer_key
|
30
|
-
:"granite_proxy_performer_#{hash}"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|