dexkit 0.1.0 → 0.2.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/CHANGELOG.md +34 -0
- data/README.md +55 -1
- data/guides/llm/EVENT.md +300 -0
- data/lib/dex/concern.rb +10 -0
- data/lib/dex/event/bus.rb +98 -0
- data/lib/dex/event/execution_state.rb +17 -0
- data/lib/dex/event/handler.rb +77 -0
- data/lib/dex/event/metadata.rb +54 -0
- data/lib/dex/event/processor.rb +61 -0
- data/lib/dex/event/suppression.rb +49 -0
- data/lib/dex/event/trace.rb +56 -0
- data/lib/dex/event.rb +87 -0
- data/lib/dex/event_test_helpers/assertions.rb +70 -0
- data/lib/dex/event_test_helpers.rb +88 -0
- data/lib/dex/operation/async_proxy.rb +30 -36
- data/lib/dex/operation/async_wrapper.rb +3 -19
- data/lib/dex/operation/callback_wrapper.rb +11 -15
- data/lib/dex/operation/jobs.rb +8 -14
- data/lib/dex/operation/lock_wrapper.rb +2 -11
- data/lib/dex/operation/pipeline.rb +5 -5
- data/lib/dex/operation/record_wrapper.rb +10 -38
- data/lib/dex/operation/rescue_wrapper.rb +1 -3
- data/lib/dex/operation/result_wrapper.rb +7 -14
- data/lib/dex/operation/settings.rb +10 -3
- data/lib/dex/operation/transaction_wrapper.rb +7 -20
- data/lib/dex/operation.rb +54 -105
- data/lib/dex/{operation/props_setup.rb → props_setup.rb} +12 -15
- data/lib/dex/test_helpers.rb +3 -1
- data/lib/dex/type_coercion.rb +96 -0
- data/lib/dex/version.rb +1 -1
- data/lib/dexkit.rb +14 -1
- metadata +15 -2
|
@@ -2,20 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Dex
|
|
4
4
|
module LockWrapper
|
|
5
|
-
|
|
6
|
-
base.extend(ClassMethods)
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
LOCK_KNOWN_OPTIONS = %i[timeout].freeze
|
|
5
|
+
extend Dex::Concern
|
|
10
6
|
|
|
11
7
|
module ClassMethods
|
|
12
8
|
def advisory_lock(key = nil, **options, &block)
|
|
13
|
-
|
|
14
|
-
if unknown.any?
|
|
15
|
-
raise ArgumentError,
|
|
16
|
-
"unknown advisory_lock option(s): #{unknown.map(&:inspect).join(", ")}. " \
|
|
17
|
-
"Known: #{LockWrapper::LOCK_KNOWN_OPTIONS.map(&:inspect).join(", ")}"
|
|
18
|
-
end
|
|
9
|
+
validate_options!(options, %i[timeout], :advisory_lock)
|
|
19
10
|
|
|
20
11
|
lock_key = block || key
|
|
21
12
|
|
|
@@ -18,13 +18,13 @@ module Dex
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def add(name, method: :"_#{name}_wrap", before: nil, after: nil, at: nil)
|
|
21
|
-
|
|
21
|
+
validate_positioning!(before, after, at)
|
|
22
22
|
step = Step.new(name: name, method: method)
|
|
23
23
|
|
|
24
24
|
if at == :outer then @steps.unshift(step)
|
|
25
25
|
elsif at == :inner then @steps.push(step)
|
|
26
|
-
elsif before then @steps.insert(
|
|
27
|
-
elsif after then @steps.insert(
|
|
26
|
+
elsif before then @steps.insert(find_index!(before), step)
|
|
27
|
+
elsif after then @steps.insert(find_index!(after) + 1, step)
|
|
28
28
|
else @steps.push(step)
|
|
29
29
|
end
|
|
30
30
|
self
|
|
@@ -44,13 +44,13 @@ module Dex
|
|
|
44
44
|
|
|
45
45
|
private
|
|
46
46
|
|
|
47
|
-
def
|
|
47
|
+
def validate_positioning!(before, after, at)
|
|
48
48
|
count = [before, after, at].count { |v| !v.nil? }
|
|
49
49
|
raise ArgumentError, "specify only one of before:, after:, at:" if count > 1
|
|
50
50
|
raise ArgumentError, "at: must be :outer or :inner" if at && !%i[outer inner].include?(at)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
def
|
|
53
|
+
def find_index!(name)
|
|
54
54
|
idx = @steps.index { |s| s.name == name }
|
|
55
55
|
raise ArgumentError, "pipeline step :#{name} not found" unless idx
|
|
56
56
|
idx
|
|
@@ -2,41 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
module Dex
|
|
4
4
|
module RecordWrapper
|
|
5
|
-
|
|
6
|
-
base.extend(ClassMethods)
|
|
7
|
-
end
|
|
5
|
+
extend Dex::Concern
|
|
8
6
|
|
|
9
7
|
def _record_wrap
|
|
10
|
-
|
|
11
|
-
result = catch(:_dex_halt) { yield }
|
|
12
|
-
|
|
13
|
-
if result.is_a?(Operation::Halt)
|
|
14
|
-
halted = result
|
|
15
|
-
result = halted.success? ? halted.value : nil
|
|
16
|
-
end
|
|
8
|
+
interceptor = Operation::HaltInterceptor.new { yield }
|
|
17
9
|
|
|
18
|
-
if
|
|
10
|
+
if interceptor.success?
|
|
19
11
|
if _record_has_pending_record?
|
|
20
|
-
_record_update_done!(result)
|
|
12
|
+
_record_update_done!(interceptor.result)
|
|
21
13
|
elsif _record_enabled?
|
|
22
|
-
_record_save!(result)
|
|
14
|
+
_record_save!(interceptor.result)
|
|
23
15
|
end
|
|
24
16
|
end
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
result
|
|
18
|
+
interceptor.rethrow!
|
|
19
|
+
interceptor.result
|
|
28
20
|
end
|
|
29
21
|
|
|
30
|
-
RECORD_KNOWN_OPTIONS = %i[params response].freeze
|
|
31
|
-
|
|
32
22
|
module ClassMethods
|
|
33
23
|
def record(enabled = nil, **options)
|
|
34
|
-
|
|
35
|
-
if unknown.any?
|
|
36
|
-
raise ArgumentError,
|
|
37
|
-
"unknown record option(s): #{unknown.map(&:inspect).join(", ")}. " \
|
|
38
|
-
"Known: #{RecordWrapper::RECORD_KNOWN_OPTIONS.map(&:inspect).join(", ")}"
|
|
39
|
-
end
|
|
24
|
+
validate_options!(options, %i[params response], :record)
|
|
40
25
|
|
|
41
26
|
if enabled == false
|
|
42
27
|
set :record, enabled: false
|
|
@@ -101,7 +86,7 @@ module Dex
|
|
|
101
86
|
success_type = self.class.respond_to?(:_success_type) && self.class._success_type
|
|
102
87
|
|
|
103
88
|
if success_type
|
|
104
|
-
|
|
89
|
+
result.nil? ? nil : self.class.send(:_serialize_value, success_type, result)
|
|
105
90
|
else
|
|
106
91
|
case result
|
|
107
92
|
when nil then nil
|
|
@@ -111,25 +96,12 @@ module Dex
|
|
|
111
96
|
end
|
|
112
97
|
end
|
|
113
98
|
|
|
114
|
-
def _record_serialize_typed_result(result, type)
|
|
115
|
-
return nil if result.nil?
|
|
116
|
-
|
|
117
|
-
ref_type = self.class.send(:_dex_find_ref_type, type)
|
|
118
|
-
if ref_type && result.respond_to?(:id)
|
|
119
|
-
result.id
|
|
120
|
-
else
|
|
121
|
-
result.respond_to?(:as_json) ? result.as_json : result
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
99
|
def _record_current_time
|
|
126
100
|
Time.respond_to?(:current) ? Time.current : Time.now
|
|
127
101
|
end
|
|
128
102
|
|
|
129
103
|
def _record_handle_error(error)
|
|
130
|
-
|
|
131
|
-
Rails.logger.warn "[Dex] Failed to record operation: #{error.message}"
|
|
132
|
-
end
|
|
104
|
+
Dex.warn("Failed to record operation: #{error.message}")
|
|
133
105
|
end
|
|
134
106
|
end
|
|
135
107
|
end
|
|
@@ -32,23 +32,16 @@ module Dex
|
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
base.extend(ClassMethods)
|
|
37
|
-
end
|
|
35
|
+
extend Dex::Concern
|
|
38
36
|
|
|
39
37
|
def _result_wrap
|
|
40
|
-
|
|
41
|
-
if
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
halted.value
|
|
45
|
-
else
|
|
46
|
-
raise Dex::Error.new(halted.error_code, halted.error_message, details: halted.error_details)
|
|
47
|
-
end
|
|
48
|
-
else
|
|
49
|
-
_result_validate_success_type!(halted)
|
|
50
|
-
halted
|
|
38
|
+
interceptor = Operation::HaltInterceptor.new { yield }
|
|
39
|
+
if interceptor.error?
|
|
40
|
+
h = interceptor.halt
|
|
41
|
+
raise Dex::Error.new(h.error_code, h.error_message, details: h.error_details)
|
|
51
42
|
end
|
|
43
|
+
_result_validate_success_type!(interceptor.result)
|
|
44
|
+
interceptor.result
|
|
52
45
|
end
|
|
53
46
|
|
|
54
47
|
def error!(code, message = nil, details: nil)
|
|
@@ -8,6 +8,15 @@ module Dex
|
|
|
8
8
|
@_settings[key] = (@_settings[key] || {}).merge(options)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
def validate_options!(options, known, dsl_name)
|
|
12
|
+
unknown = options.keys - known
|
|
13
|
+
return if unknown.empty?
|
|
14
|
+
|
|
15
|
+
raise ArgumentError,
|
|
16
|
+
"unknown #{dsl_name} option(s): #{unknown.map(&:inspect).join(", ")}. " \
|
|
17
|
+
"Known: #{known.map(&:inspect).join(", ")}"
|
|
18
|
+
end
|
|
19
|
+
|
|
11
20
|
def settings_for(key)
|
|
12
21
|
parent_settings = if superclass.respond_to?(:settings_for)
|
|
13
22
|
superclass.settings_for(key) || {}
|
|
@@ -19,8 +28,6 @@ module Dex
|
|
|
19
28
|
end
|
|
20
29
|
end
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
base.extend(ClassMethods)
|
|
24
|
-
end
|
|
31
|
+
extend Dex::Concern
|
|
25
32
|
end
|
|
26
33
|
end
|
|
@@ -2,40 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
module Dex
|
|
4
4
|
module TransactionWrapper
|
|
5
|
-
|
|
6
|
-
base.extend(ClassMethods)
|
|
7
|
-
end
|
|
5
|
+
extend Dex::Concern
|
|
8
6
|
|
|
9
7
|
def _transaction_wrap
|
|
10
8
|
return yield unless _transaction_enabled?
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
interceptor = nil
|
|
13
11
|
result = _transaction_execute do
|
|
14
|
-
|
|
15
|
-
if
|
|
16
|
-
|
|
17
|
-
raise _transaction_adapter.rollback_exception_class if halted.error?
|
|
18
|
-
halted.value
|
|
19
|
-
else
|
|
20
|
-
halted_value
|
|
21
|
-
end
|
|
12
|
+
interceptor = Operation::HaltInterceptor.new { yield }
|
|
13
|
+
raise _transaction_adapter.rollback_exception_class if interceptor.error?
|
|
14
|
+
interceptor.result
|
|
22
15
|
end
|
|
23
16
|
|
|
24
|
-
|
|
17
|
+
interceptor&.rethrow!
|
|
25
18
|
result
|
|
26
19
|
end
|
|
27
20
|
|
|
28
21
|
TRANSACTION_KNOWN_ADAPTERS = %i[active_record mongoid].freeze
|
|
29
|
-
TRANSACTION_KNOWN_OPTIONS = %i[adapter].freeze
|
|
30
22
|
|
|
31
23
|
module ClassMethods
|
|
32
24
|
def transaction(enabled_or_options = nil, **options)
|
|
33
|
-
|
|
34
|
-
if unknown.any?
|
|
35
|
-
raise ArgumentError,
|
|
36
|
-
"unknown transaction option(s): #{unknown.map(&:inspect).join(", ")}. " \
|
|
37
|
-
"Known: #{TransactionWrapper::TRANSACTION_KNOWN_OPTIONS.map(&:inspect).join(", ")}"
|
|
38
|
-
end
|
|
25
|
+
validate_options!(options, %i[adapter], :transaction)
|
|
39
26
|
|
|
40
27
|
case enabled_or_options
|
|
41
28
|
when false
|
data/lib/dex/operation.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
# Wrapper modules (loaded before class body so `include`/`use` can find them)
|
|
4
4
|
require_relative "operation/settings"
|
|
5
|
-
require_relative "operation/props_setup"
|
|
6
5
|
require_relative "operation/result_wrapper"
|
|
7
6
|
require_relative "operation/record_wrapper"
|
|
8
7
|
require_relative "operation/transaction_wrapper"
|
|
@@ -22,112 +21,81 @@ module Dex
|
|
|
22
21
|
def error? = type == :error
|
|
23
22
|
end
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Time => ->(v) { v.is_a?(String) ? Time.parse(v) : v },
|
|
28
|
-
Symbol => ->(v) { v.is_a?(String) ? v.to_sym : v }
|
|
29
|
-
}.tap do |h|
|
|
30
|
-
h[Date] = ->(v) { v.is_a?(String) ? Date.parse(v) : v } if defined?(Date)
|
|
31
|
-
h[DateTime] = ->(v) { v.is_a?(String) ? DateTime.parse(v) : v } if defined?(DateTime)
|
|
32
|
-
h[BigDecimal] = ->(v) { v.is_a?(String) ? BigDecimal(v) : v } if defined?(BigDecimal)
|
|
33
|
-
end.freeze
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
Contract = Data.define(:params, :success, :errors)
|
|
37
|
-
|
|
38
|
-
def self.contract
|
|
39
|
-
Contract.new(
|
|
40
|
-
params: _contract_params,
|
|
41
|
-
success: _success_type,
|
|
42
|
-
errors: _declared_errors
|
|
43
|
-
)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def self._contract_params
|
|
47
|
-
return {} unless respond_to?(:literal_properties)
|
|
48
|
-
|
|
49
|
-
literal_properties.each_with_object({}) do |prop, hash|
|
|
50
|
-
hash[prop.name] = prop.type
|
|
51
|
-
end
|
|
52
|
-
end
|
|
24
|
+
class HaltInterceptor
|
|
25
|
+
attr_reader :result, :halt
|
|
53
26
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
27
|
+
def initialize
|
|
28
|
+
raw = catch(:_dex_halt) { yield }
|
|
29
|
+
if raw.is_a?(Halt)
|
|
30
|
+
@halt = raw
|
|
31
|
+
@result = raw.success? ? raw.value : nil
|
|
32
|
+
else
|
|
33
|
+
@halt = nil
|
|
34
|
+
@result = raw
|
|
35
|
+
end
|
|
62
36
|
end
|
|
63
|
-
nil
|
|
64
|
-
end
|
|
65
37
|
|
|
66
|
-
|
|
67
|
-
|
|
38
|
+
def halted? = !@halt.nil?
|
|
39
|
+
def success? = !error?
|
|
40
|
+
def error? = @halt&.error? || false
|
|
68
41
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
name = prop.name
|
|
72
|
-
raw = hash.key?(name) ? hash[name] : hash[name.to_s]
|
|
73
|
-
result[name] = _dex_coerce_value(prop.type, raw)
|
|
42
|
+
def rethrow!
|
|
43
|
+
throw(:_dex_halt, @halt) if halted?
|
|
74
44
|
end
|
|
75
|
-
result
|
|
76
45
|
end
|
|
77
46
|
|
|
78
|
-
|
|
79
|
-
return type.model_class if type.is_a?(Dex::RefType)
|
|
80
|
-
return _dex_resolve_base_class(type.type) if type.respond_to?(:type)
|
|
47
|
+
RESERVED_PROP_NAMES = %i[call perform async safe initialize].to_set.freeze
|
|
81
48
|
|
|
82
|
-
|
|
83
|
-
|
|
49
|
+
include PropsSetup
|
|
50
|
+
include TypeCoercion
|
|
84
51
|
|
|
85
|
-
|
|
86
|
-
return value unless value
|
|
87
|
-
return value if _dex_find_ref_type(type)
|
|
52
|
+
Contract = Data.define(:params, :success, :errors)
|
|
88
53
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
54
|
+
class << self
|
|
55
|
+
def contract
|
|
56
|
+
Contract.new(
|
|
57
|
+
params: _contract_params,
|
|
58
|
+
success: _success_type,
|
|
59
|
+
errors: _declared_errors
|
|
60
|
+
)
|
|
92
61
|
end
|
|
93
62
|
|
|
94
|
-
|
|
95
|
-
|
|
63
|
+
def inherited(subclass)
|
|
64
|
+
subclass.instance_variable_set(:@_pipeline, pipeline.dup)
|
|
65
|
+
super
|
|
96
66
|
end
|
|
97
67
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
end
|
|
68
|
+
def pipeline
|
|
69
|
+
@_pipeline ||= Pipeline.new
|
|
70
|
+
end
|
|
102
71
|
|
|
103
|
-
|
|
72
|
+
def use(mod, as: nil, wrap: nil, before: nil, after: nil, at: nil)
|
|
73
|
+
step_name = as || _derive_step_name(mod)
|
|
74
|
+
wrap_method = wrap || :"_#{step_name}_wrap"
|
|
75
|
+
pipeline.add(step_name, method: wrap_method, before: before, after: after, at: at)
|
|
76
|
+
include mod
|
|
77
|
+
end
|
|
104
78
|
|
|
105
|
-
|
|
106
|
-
subclass.instance_variable_set(:@_pipeline, pipeline.dup)
|
|
107
|
-
super
|
|
108
|
-
end
|
|
79
|
+
private
|
|
109
80
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
end
|
|
81
|
+
def _contract_params
|
|
82
|
+
return {} unless respond_to?(:literal_properties)
|
|
113
83
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
include mod
|
|
119
|
-
end
|
|
84
|
+
literal_properties.each_with_object({}) do |prop, hash|
|
|
85
|
+
hash[prop.name] = prop.type
|
|
86
|
+
end
|
|
87
|
+
end
|
|
120
88
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
89
|
+
def _derive_step_name(mod)
|
|
90
|
+
base = mod.name&.split("::")&.last
|
|
91
|
+
raise ArgumentError, "anonymous modules require explicit as: parameter" unless base
|
|
124
92
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
93
|
+
base.sub(/Wrapper\z/, "")
|
|
94
|
+
.gsub(/([a-z])([A-Z])/, '\1_\2')
|
|
95
|
+
.downcase
|
|
96
|
+
.to_sym
|
|
97
|
+
end
|
|
129
98
|
end
|
|
130
|
-
private_class_method :_derive_step_name
|
|
131
99
|
|
|
132
100
|
def perform(*, **)
|
|
133
101
|
end
|
|
@@ -149,28 +117,9 @@ module Dex
|
|
|
149
117
|
new(**kwargs).call
|
|
150
118
|
end
|
|
151
119
|
|
|
152
|
-
# Serialization helpers
|
|
153
|
-
|
|
154
|
-
def _props_as_json
|
|
155
|
-
return {} unless self.class.respond_to?(:literal_properties)
|
|
156
|
-
|
|
157
|
-
result = {}
|
|
158
|
-
self.class.literal_properties.each do |prop|
|
|
159
|
-
value = public_send(prop.name)
|
|
160
|
-
ref = self.class.send(:_dex_find_ref_type, prop.type)
|
|
161
|
-
result[prop.name.to_s] = if ref && value
|
|
162
|
-
value.id
|
|
163
|
-
else
|
|
164
|
-
value.respond_to?(:as_json) ? value.as_json : value
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
result
|
|
168
|
-
end
|
|
169
|
-
|
|
170
120
|
include Settings
|
|
171
121
|
include AsyncWrapper
|
|
172
122
|
include SafeWrapper
|
|
173
|
-
include PropsSetup
|
|
174
123
|
|
|
175
124
|
use ResultWrapper
|
|
176
125
|
use LockWrapper
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "set"
|
|
4
|
-
|
|
5
3
|
module Dex
|
|
4
|
+
# Shared prop DSL for Operation and Event.
|
|
5
|
+
#
|
|
6
|
+
# Wraps Literal::Properties' prop/prop? with three Dex-specific behaviors:
|
|
7
|
+
# 1. Reserved name validation — each class defines RESERVED_PROP_NAMES
|
|
8
|
+
# 2. reader: :public by default (Literal defaults to private)
|
|
9
|
+
# 3. Automatic RefType coercion — _Ref(Model) props auto-coerce IDs to records
|
|
6
10
|
module PropsSetup
|
|
11
|
+
extend Dex::Concern
|
|
12
|
+
|
|
7
13
|
def self.included(base)
|
|
8
14
|
base.extend(Literal::Properties)
|
|
9
15
|
base.extend(Literal::Types)
|
|
10
|
-
|
|
16
|
+
super
|
|
11
17
|
end
|
|
12
18
|
|
|
13
19
|
module ClassMethods
|
|
14
|
-
RESERVED_PROP_NAMES = %i[call perform async safe initialize].to_set.freeze
|
|
15
|
-
|
|
16
20
|
def prop(name, type, kind = :keyword, **options, &block)
|
|
17
|
-
|
|
21
|
+
if const_defined?(:RESERVED_PROP_NAMES) && self::RESERVED_PROP_NAMES.include?(name)
|
|
22
|
+
raise ArgumentError, "Property :#{name} is reserved."
|
|
23
|
+
end
|
|
18
24
|
options[:reader] = :public unless options.key?(:reader)
|
|
19
25
|
if type.is_a?(Dex::RefType) && !block
|
|
20
26
|
ref = type
|
|
@@ -36,15 +42,6 @@ module Dex
|
|
|
36
42
|
def _Ref(model_class, lock: false) # rubocop:disable Naming/MethodName
|
|
37
43
|
Dex::RefType.new(model_class, lock: lock)
|
|
38
44
|
end
|
|
39
|
-
|
|
40
|
-
private
|
|
41
|
-
|
|
42
|
-
def _props_validate_name!(name)
|
|
43
|
-
return unless RESERVED_PROP_NAMES.include?(name)
|
|
44
|
-
|
|
45
|
-
raise ArgumentError,
|
|
46
|
-
"Property :#{name} conflicts with core Operation methods."
|
|
47
|
-
end
|
|
48
45
|
end
|
|
49
46
|
end
|
|
50
47
|
end
|
data/lib/dex/test_helpers.rb
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
module TypeCoercion
|
|
5
|
+
extend Dex::Concern
|
|
6
|
+
|
|
7
|
+
module ClassMethods
|
|
8
|
+
def _serialized_coercions
|
|
9
|
+
@_serialized_coercions ||= {
|
|
10
|
+
Time => ->(v) { v.is_a?(String) ? Time.parse(v) : v },
|
|
11
|
+
Symbol => ->(v) { v.is_a?(String) ? v.to_sym : v }
|
|
12
|
+
}.tap do |h|
|
|
13
|
+
h[Date] = ->(v) { v.is_a?(String) ? Date.parse(v) : v } if defined?(Date)
|
|
14
|
+
h[DateTime] = ->(v) { v.is_a?(String) ? DateTime.parse(v) : v } if defined?(DateTime)
|
|
15
|
+
h[BigDecimal] = ->(v) { v.is_a?(String) ? BigDecimal(v) : v } if defined?(BigDecimal)
|
|
16
|
+
end.freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def _find_ref_type(type)
|
|
22
|
+
return type if type.is_a?(Dex::RefType)
|
|
23
|
+
|
|
24
|
+
if type.respond_to?(:type)
|
|
25
|
+
inner = type.type
|
|
26
|
+
return inner if inner.is_a?(Dex::RefType)
|
|
27
|
+
end
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def _resolve_base_class(type)
|
|
32
|
+
return type.model_class if type.is_a?(Dex::RefType)
|
|
33
|
+
return _resolve_base_class(type.type) if type.respond_to?(:type)
|
|
34
|
+
|
|
35
|
+
type if type.is_a?(Class)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def _coerce_value(type, value)
|
|
39
|
+
return value unless value
|
|
40
|
+
|
|
41
|
+
if type.is_a?(Literal::Types::ArrayType)
|
|
42
|
+
return value.map { |v| _coerce_value(type.type, v) } if value.is_a?(Array)
|
|
43
|
+
|
|
44
|
+
return value
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
return _coerce_value(type.type, value) if type.is_a?(Literal::Types::NilableType)
|
|
48
|
+
|
|
49
|
+
ref = _find_ref_type(type)
|
|
50
|
+
return ref.coerce(value) if ref
|
|
51
|
+
|
|
52
|
+
base = _resolve_base_class(type)
|
|
53
|
+
coercion = _serialized_coercions[base]
|
|
54
|
+
coercion ? coercion.call(value) : value
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def _serialize_value(type, value)
|
|
58
|
+
return value unless value
|
|
59
|
+
|
|
60
|
+
if type.is_a?(Literal::Types::ArrayType) && value.is_a?(Array)
|
|
61
|
+
return value.map { |v| _serialize_value(type.type, v) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
return _serialize_value(type.type, value) if type.is_a?(Literal::Types::NilableType)
|
|
65
|
+
|
|
66
|
+
ref = _find_ref_type(type)
|
|
67
|
+
return value.id if ref
|
|
68
|
+
|
|
69
|
+
value.respond_to?(:as_json) ? value.as_json : value
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def _coerce_serialized_hash(hash)
|
|
73
|
+
return hash.transform_keys(&:to_sym) unless respond_to?(:literal_properties)
|
|
74
|
+
|
|
75
|
+
result = {}
|
|
76
|
+
literal_properties.each do |prop|
|
|
77
|
+
name = prop.name
|
|
78
|
+
raw = hash.key?(name) ? hash[name] : hash[name.to_s]
|
|
79
|
+
result[name] = _coerce_value(prop.type, raw)
|
|
80
|
+
end
|
|
81
|
+
result
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def _props_as_json
|
|
86
|
+
return {} unless self.class.respond_to?(:literal_properties)
|
|
87
|
+
|
|
88
|
+
result = {}
|
|
89
|
+
self.class.literal_properties.each do |prop|
|
|
90
|
+
value = public_send(prop.name)
|
|
91
|
+
result[prop.name.to_s] = self.class.send(:_serialize_value, prop.type, value)
|
|
92
|
+
end
|
|
93
|
+
result
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
data/lib/dex/version.rb
CHANGED
data/lib/dexkit.rb
CHANGED
|
@@ -10,21 +10,34 @@ loader.ignore("#{__dir__}/dex")
|
|
|
10
10
|
loader.setup
|
|
11
11
|
|
|
12
12
|
require_relative "dex/version"
|
|
13
|
+
require_relative "dex/concern"
|
|
13
14
|
require_relative "dex/ref_type"
|
|
15
|
+
require_relative "dex/type_coercion"
|
|
16
|
+
require_relative "dex/props_setup"
|
|
14
17
|
require_relative "dex/error"
|
|
15
18
|
require_relative "dex/operation"
|
|
19
|
+
require_relative "dex/event"
|
|
16
20
|
|
|
17
21
|
module Dex
|
|
18
22
|
class Configuration
|
|
19
|
-
attr_accessor :record_class, :transaction_adapter
|
|
23
|
+
attr_accessor :record_class, :transaction_adapter, :event_store, :event_context, :restore_event_context
|
|
20
24
|
|
|
21
25
|
def initialize
|
|
22
26
|
@record_class = nil
|
|
23
27
|
@transaction_adapter = nil
|
|
28
|
+
@event_store = nil
|
|
29
|
+
@event_context = nil
|
|
30
|
+
@restore_event_context = nil
|
|
24
31
|
end
|
|
25
32
|
end
|
|
26
33
|
|
|
27
34
|
class << self
|
|
35
|
+
def warn(message)
|
|
36
|
+
return unless defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
37
|
+
|
|
38
|
+
Rails.logger.warn("[Dex] #{message}")
|
|
39
|
+
end
|
|
40
|
+
|
|
28
41
|
def configuration
|
|
29
42
|
@configuration ||= Configuration.new
|
|
30
43
|
end
|