composable-core 0.0.13 → 0.0.14
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 +7 -0
- data/README.md +161 -22
- data/lib/composable/core/attribute_dsl.rb +19 -9
- data/lib/composable/core/command.rb +1 -1
- data/lib/composable/core/composable_dsl.rb +30 -21
- data/lib/composable/core/gem_version.rb +1 -1
- data/lib/composable/core/inheritable_attributes.rb +5 -9
- data/lib/composable/core/record_invalid.rb +7 -5
- data/lib/composable/core.rb +2 -1
- metadata +7 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f4df88ab1a3ea8dfe4a54166f06d63608f480683efe0bc31beea1f2ca0b02e9
|
|
4
|
+
data.tar.gz: 307e555872f0f2a58ad7861cf9919643f66cf5d629ac45ba0667e935f02885af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 265a79bd34dd978dd60005f4c474d43a42730270f4a4173e690739933526a02e601c9417254e9508752433aee321ba4363cd8c5c860f40c123deaf6f818b57ba
|
|
7
|
+
data.tar.gz: d769eb62f1232b101a9339f2a3951d8aa13f1b6fa54210d65ace8c0b92efb4011a5cd79cbddeea5c3db5ec34d90155b1f6cc1c5a63ff99276a3abaa8dd5cebf7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
- Evaluate composable eligibility once before synchronization and reuse it for persistence.
|
|
4
|
+
- Preserve reverse synchronization for predeclared record attributes.
|
|
5
|
+
- Copy constructor inputs and preserve explicit nil and symbol-key precedence.
|
|
6
|
+
- Deep-copy inheritance defaults and replace interpolated accessor generation.
|
|
7
|
+
- Preserve process-control exceptions and handle record validation without mutating errors, including late ActiveRecord loading.
|
|
8
|
+
- Replace placeholder tests and enforce complete line/branch coverage.
|
|
9
|
+
|
|
3
10
|
## [0.0.1] - 2022-01-20
|
|
4
11
|
|
|
5
12
|
- Initial release
|
data/README.md
CHANGED
|
@@ -1,43 +1,182 @@
|
|
|
1
|
-
#
|
|
1
|
+
# composable-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Reusable concerns for attributes, command execution, callbacks, composed records,
|
|
4
|
+
and class configuration. Requires Ruby 3.2+, ActiveModel and ActiveSupport 7.2+.
|
|
5
|
+
Install with `gem "composable-core"` and require `composable/core`.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
## Attributes
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
```ruby
|
|
10
|
+
require "composable/core"
|
|
11
|
+
|
|
12
|
+
class MessageInput
|
|
13
|
+
include Composable::Core::AttributeDSL
|
|
14
|
+
|
|
15
|
+
attribute :title, type: :string
|
|
16
|
+
attribute :count, type: :integer, default: 1
|
|
17
|
+
attribute :labels, default: []
|
|
18
|
+
attribute :requested_at, default: -> { Time.current }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
input = {"title" => "Example", "count" => "2", class: "notice"}.freeze
|
|
22
|
+
message = MessageInput.new(input)
|
|
23
|
+
message.count # => 2
|
|
24
|
+
message.params[:title] # => "Example"
|
|
25
|
+
message.rest # => { class: "notice" }
|
|
26
|
+
```
|
|
8
27
|
|
|
9
|
-
|
|
28
|
+
`attribute` declares readers, writers and `name?` predicates using Rails
|
|
29
|
+
`present?`. `attributes`/`attribute` without arguments return the declared Set;
|
|
30
|
+
`params` returns declared values; `rest` contains unknown keys. Unknown keys are
|
|
31
|
+
retained rather than assigned to methods.
|
|
32
|
+
|
|
33
|
+
Inputs must be hashes or hash-like objects exposing `each_pair` and `to_h`.
|
|
34
|
+
Permitted Rails parameters are accepted; unpermitted parameters raise through
|
|
35
|
+
Rails. Initialization copies the input, supports string and symbol keys and gives
|
|
36
|
+
the symbol key precedence. Explicit `nil` replaces a default. Mutable defaults
|
|
37
|
+
are deep-copied per instance; callable defaults run lazily without arguments.
|
|
38
|
+
Types are resolved through `ActiveModel::Type.lookup`, including registered
|
|
39
|
+
application types and their keyword options.
|
|
40
|
+
|
|
41
|
+
## Commands and callbacks
|
|
42
|
+
|
|
43
|
+
Prefer the service/form/client bases when applicable. A custom command can
|
|
44
|
+
prepend `Composable::Core::Command` and implement `call`. The wrapper adds
|
|
45
|
+
ActiveModel validation and `rescue_from`, class-level `.call`/`.call!`, and
|
|
46
|
+
instance-level status/result methods. A custom implementation must call `valid?`
|
|
47
|
+
itself; the provided bases already do so. A missing implementation raises
|
|
48
|
+
`Composable::Core::Command::NotImplementedError`.
|
|
49
|
+
|
|
50
|
+
Handled exceptions return the command; handlers must record failure in `errors`.
|
|
51
|
+
`.call!` raises `Composable::Core::Error` when errors exist, and propagates raised
|
|
52
|
+
exceptions without invoking handlers. `Interrupt`, `SystemExit` and other
|
|
53
|
+
exceptions outside `StandardError` are never swallowed.
|
|
54
|
+
|
|
55
|
+
`Composable::Core::Callbacks` defines the `save` callback chain (`before_save`,
|
|
56
|
+
`around_save`, `after_save`). It does not execute that chain on its own.
|
|
57
|
+
|
|
58
|
+
## Record composition
|
|
59
|
+
|
|
60
|
+
`ComposableDSL` pairs with `AttributeDSL`. Declare a record with
|
|
61
|
+
`composable(:project) { sync :name; sync :display_name, to: :name }`.
|
|
62
|
+
Assigning the record copies mapped values into the command, including when the
|
|
63
|
+
record attribute was declared before `composable`. Existing attribute type/default
|
|
64
|
+
configuration is retained. During initialization,
|
|
65
|
+
record values are copied first so explicit command attributes take precedence.
|
|
66
|
+
`save_composables` copies command values into records, calls `save!` on eligible
|
|
67
|
+
records, and executes an optional block.
|
|
68
|
+
|
|
69
|
+
Both a composable and an individual `sync` accept `if:` and `unless:` conditions.
|
|
70
|
+
Composable eligibility is evaluated once per save, before any record mappings
|
|
71
|
+
are applied. The same selected records are then synchronized and saved; a
|
|
72
|
+
condition depending on a record's old value is not re-evaluated after that value
|
|
73
|
+
changes. Individual `sync` conditions still run when their field is synchronized.
|
|
74
|
+
Use a method symbol or a Proc: zero-argument blocks run on the command; blocks
|
|
75
|
+
with an argument receive the record. Conditions use Ruby truthiness. Reverse
|
|
76
|
+
mapping on record assignment always initializes the command's values. Conditional
|
|
77
|
+
saving does not make a declared record optional: a missing record raises
|
|
78
|
+
`ArgumentError`.
|
|
79
|
+
|
|
80
|
+
Records exposing `transaction` are wrapped in nested transactions. Exceptions
|
|
81
|
+
roll back work on a shared connection. Separate databases do not provide a
|
|
82
|
+
distributed atomic commit; a commit failure can leave partial persistence.
|
|
83
|
+
Adding command errors alone does not roll back writes. With ActiveRecord loaded,
|
|
84
|
+
record validation errors are copied into command errors without changing the
|
|
85
|
+
record's original errors; record error names matching declared command attributes
|
|
86
|
+
are copied to those fields, while other names become base errors. Renamed sync
|
|
87
|
+
mappings do not rename validation-error fields automatically.
|
|
88
|
+
|
|
89
|
+
## Inheritable configuration
|
|
90
|
+
|
|
91
|
+
Include `InheritableAttributes` and declare
|
|
92
|
+
`inheritable_attributes :settings, default: {}` for a deep copy of the parent's
|
|
93
|
+
current value in each subclass. `uninheritable_attributes :cache, default: []`
|
|
94
|
+
resets the subclass to a fresh copy of the declared default. False and nil values
|
|
95
|
+
are preserved. These are class settings, not request-local storage.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
## Build a custom command
|
|
99
|
+
|
|
100
|
+
Use the concerns directly when the service/form bases do not fit your lifecycle.
|
|
101
|
+
This standalone command validates before producing its result:
|
|
10
102
|
|
|
11
103
|
```ruby
|
|
12
|
-
|
|
13
|
-
|
|
104
|
+
class FormatMessage
|
|
105
|
+
include Composable::Core::AttributeDSL
|
|
106
|
+
prepend Composable::Core::Command
|
|
107
|
+
|
|
108
|
+
attribute :text, type: :string
|
|
109
|
+
validates :text, presence: true
|
|
110
|
+
|
|
111
|
+
def call
|
|
112
|
+
return unless valid?
|
|
14
113
|
|
|
15
|
-
|
|
114
|
+
text.strip.upcase
|
|
115
|
+
end
|
|
116
|
+
end
|
|
16
117
|
|
|
17
|
-
|
|
118
|
+
command = FormatMessage.call(text: " hello ")
|
|
119
|
+
command.result # => "HELLO"
|
|
120
|
+
command.success? # => true
|
|
18
121
|
|
|
19
|
-
|
|
122
|
+
invalid = FormatMessage.call(text: "")
|
|
123
|
+
invalid.failure? # => true
|
|
124
|
+
invalid.errors.full_messages # => ["Text can't be blank"]
|
|
125
|
+
```
|
|
20
126
|
|
|
21
|
-
|
|
127
|
+
A module prepended to a base class wraps that base's implementation. A subclass
|
|
128
|
+
that overrides `call` can bypass the wrapper. For services/forms implement `save`,
|
|
129
|
+
and for HTTP clients implement `request`, to preserve the inherited lifecycle.
|
|
22
130
|
|
|
23
|
-
##
|
|
131
|
+
## Override defaults in a subclass
|
|
24
132
|
|
|
25
|
-
|
|
133
|
+
Attribute definitions are inherited independently; changing a subclass's default
|
|
134
|
+
does not change its parent. Mutable defaults are also independent per instance.
|
|
26
135
|
|
|
27
|
-
|
|
136
|
+
```ruby
|
|
137
|
+
class BatchInput
|
|
138
|
+
include Composable::Core::AttributeDSL
|
|
139
|
+
attribute :limit, type: :integer, default: 10
|
|
140
|
+
attribute :labels, default: []
|
|
141
|
+
end
|
|
28
142
|
|
|
29
|
-
|
|
143
|
+
class SmallBatchInput < BatchInput
|
|
144
|
+
attribute :limit, default: 2
|
|
145
|
+
end
|
|
30
146
|
|
|
31
|
-
|
|
147
|
+
BatchInput.new.limit # => 10
|
|
148
|
+
SmallBatchInput.new.limit # => 2
|
|
32
149
|
|
|
33
|
-
|
|
150
|
+
first = BatchInput.new
|
|
151
|
+
second = BatchInput.new
|
|
152
|
+
first.labels << "priority"
|
|
153
|
+
second.labels # => []
|
|
34
154
|
|
|
35
|
-
|
|
155
|
+
BatchInput.new(limit: nil).limit # => nil, rather than the default
|
|
156
|
+
```
|
|
36
157
|
|
|
37
|
-
##
|
|
158
|
+
## Copy or reset class configuration
|
|
38
159
|
|
|
39
|
-
|
|
160
|
+
```ruby
|
|
161
|
+
class Processor
|
|
162
|
+
include Composable::Core::InheritableAttributes
|
|
163
|
+
inheritable_attributes :settings, default: { retries: 2, tags: [] }
|
|
164
|
+
uninheritable_attributes :cache, default: {}
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
Processor.settings[:tags] << "parent"
|
|
168
|
+
Processor.cache[:last_run] = "example"
|
|
169
|
+
|
|
170
|
+
class SpecializedProcessor < Processor
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
SpecializedProcessor.settings[:tags] # => ["parent"]
|
|
174
|
+
SpecializedProcessor.cache # => {}
|
|
175
|
+
SpecializedProcessor.settings[:tags] << "child"
|
|
176
|
+
Processor.settings[:tags] # => ["parent"]
|
|
177
|
+
```
|
|
40
178
|
|
|
41
|
-
|
|
179
|
+
Subclass configuration is copied when the subclass is created. Later parent
|
|
180
|
+
changes do not update existing subclasses automatically.
|
|
42
181
|
|
|
43
|
-
|
|
182
|
+
See [testing](../docs/testing.md) for the isolated suite and coverage gates.
|
|
@@ -12,16 +12,15 @@ module Composable
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def initialize(options = {})
|
|
15
|
-
|
|
16
|
-
raise ArgumentError, "When assigning attributes, you must pass a hash" \
|
|
17
|
-
" as an argument, #{options.class} passed."
|
|
18
|
-
end
|
|
19
|
-
|
|
15
|
+
options = normalize_attributes(options)
|
|
20
16
|
attributes.each do |attribute|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
if options.key?(attribute)
|
|
18
|
+
value = options.delete(attribute)
|
|
19
|
+
options.delete(attribute.to_s)
|
|
20
|
+
public_send("#{attribute}=", value)
|
|
21
|
+
elsif options.key?(attribute.to_s)
|
|
22
|
+
public_send("#{attribute}=", options.delete(attribute.to_s))
|
|
23
|
+
end
|
|
25
24
|
end
|
|
26
25
|
|
|
27
26
|
@rest = options
|
|
@@ -41,6 +40,17 @@ module Composable
|
|
|
41
40
|
@rest
|
|
42
41
|
end
|
|
43
42
|
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def normalize_attributes(options)
|
|
46
|
+
unless options.respond_to?(:each_pair) && options.respond_to?(:to_h)
|
|
47
|
+
raise ArgumentError, "When assigning attributes, you must pass a hash"
|
|
48
|
+
end
|
|
49
|
+
options.to_h.dup
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
public
|
|
53
|
+
|
|
44
54
|
module ClassMethods
|
|
45
55
|
def attribute(*attrs)
|
|
46
56
|
options = attrs.extract_options!
|
|
@@ -15,9 +15,9 @@ module Composable
|
|
|
15
15
|
composables[attribute.to_sym] ||= Composable.new(attribute)
|
|
16
16
|
composables[attribute.to_sym].evaluate(**options, &block)
|
|
17
17
|
|
|
18
|
-
return if
|
|
18
|
+
return if method_defined?("#{attribute}_original_setter=")
|
|
19
19
|
|
|
20
|
-
attribute attribute
|
|
20
|
+
attribute(attribute) unless attributes.include?(attribute.to_sym)
|
|
21
21
|
|
|
22
22
|
alias_method "#{attribute}_original_setter=", "#{attribute}="
|
|
23
23
|
|
|
@@ -34,9 +34,15 @@ module Composable
|
|
|
34
34
|
# overridden by composable_record values, composable_record mapping methods
|
|
35
35
|
# have to be synced first.
|
|
36
36
|
def initialize(options = {})
|
|
37
|
+
options = normalize_attributes(options)
|
|
37
38
|
composable_keys.each do |attribute|
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
if options.key?(attribute)
|
|
40
|
+
value = options.delete(attribute)
|
|
41
|
+
options.delete(attribute.to_s)
|
|
42
|
+
public_send("#{attribute}=", value)
|
|
43
|
+
elsif options.key?(attribute.to_s)
|
|
44
|
+
public_send("#{attribute}=", options.delete(attribute.to_s))
|
|
45
|
+
end
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
super
|
|
@@ -48,8 +54,11 @@ module Composable
|
|
|
48
54
|
|
|
49
55
|
def save_composables
|
|
50
56
|
multi_db_transaction do
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
eligible_composables = composables.select do |attribute, composable|
|
|
58
|
+
composable.valid?(self, composable_record_for(attribute))
|
|
59
|
+
end
|
|
60
|
+
sync_composable_records(eligible_composables)
|
|
61
|
+
save_composable_records(eligible_composables)
|
|
53
62
|
|
|
54
63
|
yield if block_given?
|
|
55
64
|
end
|
|
@@ -57,24 +66,22 @@ module Composable
|
|
|
57
66
|
|
|
58
67
|
private
|
|
59
68
|
|
|
60
|
-
def sync_composable_records
|
|
61
|
-
|
|
69
|
+
def sync_composable_records(eligible_composables)
|
|
70
|
+
eligible_composables.each do |attribute, composable|
|
|
62
71
|
record = composable_record_for(attribute)
|
|
63
|
-
composable.sync_attributes(self, record)
|
|
72
|
+
composable.sync_attributes(self, record)
|
|
64
73
|
end
|
|
65
74
|
end
|
|
66
75
|
|
|
67
|
-
def save_composable_records
|
|
68
|
-
|
|
76
|
+
def save_composable_records(eligible_composables)
|
|
77
|
+
eligible_composables.each_key do |attribute|
|
|
69
78
|
record = composable_record_for(attribute)
|
|
70
|
-
record.save!
|
|
79
|
+
record.save!
|
|
71
80
|
end
|
|
72
81
|
end
|
|
73
82
|
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
# objects that are gonna be saved into diffent databases, if there is an
|
|
77
|
-
# error, all transactions are gonna be rollback regardless of the DB.
|
|
83
|
+
# Nest transactions for composable records. Transactions across distinct
|
|
84
|
+
# database connections are not a distributed atomic commit.
|
|
78
85
|
def multi_db_transaction(attribute: composable_keys.first, &block)
|
|
79
86
|
return yield if attribute.nil?
|
|
80
87
|
|
|
@@ -129,8 +136,6 @@ module Composable
|
|
|
129
136
|
|
|
130
137
|
# DSL method
|
|
131
138
|
def sync(*args, to: nil, **options)
|
|
132
|
-
args.extract_options!
|
|
133
|
-
|
|
134
139
|
args.compact.uniq.each do |name|
|
|
135
140
|
mapping[name.to_sym] ||= Sync.new(from: name)
|
|
136
141
|
mapping[name.to_sym].merge(to: (to || name), **options)
|
|
@@ -197,7 +202,7 @@ module Composable
|
|
|
197
202
|
end
|
|
198
203
|
|
|
199
204
|
def call(form, record)
|
|
200
|
-
callable.call(form, record)
|
|
205
|
+
!!callable.call(form, record) == (statement == :if)
|
|
201
206
|
end
|
|
202
207
|
|
|
203
208
|
private
|
|
@@ -208,10 +213,14 @@ module Composable
|
|
|
208
213
|
->(form, _record) { form.send(condition) }
|
|
209
214
|
when ::Proc
|
|
210
215
|
lambda { |form, record|
|
|
211
|
-
|
|
216
|
+
if condition.arity == 0
|
|
217
|
+
form.instance_exec(&condition)
|
|
218
|
+
else
|
|
219
|
+
form.instance_exec(record, &condition)
|
|
220
|
+
end
|
|
212
221
|
}
|
|
213
222
|
else
|
|
214
|
-
|
|
223
|
+
raise ArgumentError, "condition must be a Symbol or Proc"
|
|
215
224
|
end
|
|
216
225
|
end
|
|
217
226
|
end
|
|
@@ -13,10 +13,8 @@ module Composable
|
|
|
13
13
|
attributes.each do |attribute|
|
|
14
14
|
@inheritable_attributes << {attribute: attribute, default: options[:default]}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
RUBY
|
|
19
|
-
send("#{attribute}=", options[:default]) unless options[:default].nil?
|
|
16
|
+
singleton_class.attr_accessor(attribute)
|
|
17
|
+
send("#{attribute}=", options[:default].deep_dup) unless options[:default].nil?
|
|
20
18
|
end
|
|
21
19
|
@inheritable_attributes
|
|
22
20
|
end
|
|
@@ -29,10 +27,8 @@ module Composable
|
|
|
29
27
|
attributes.each do |attribute|
|
|
30
28
|
@uninheritable_attributes << {attribute: attribute, default: options[:default]}
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
RUBY
|
|
35
|
-
send("#{attribute}=", options[:default]) unless options[:default].nil?
|
|
30
|
+
singleton_class.attr_accessor(attribute)
|
|
31
|
+
send("#{attribute}=", options[:default].deep_dup) unless options[:default].nil?
|
|
36
32
|
end
|
|
37
33
|
|
|
38
34
|
@uninheritable_attributes
|
|
@@ -45,7 +41,7 @@ module Composable
|
|
|
45
41
|
inheritable_attribute = hash[:attribute]
|
|
46
42
|
instance_name = "@#{inheritable_attribute}"
|
|
47
43
|
instance_value = instance_variable_get(instance_name).deep_dup
|
|
48
|
-
subclass.instance_variable_set(instance_name, instance_value
|
|
44
|
+
subclass.instance_variable_set(instance_name, instance_value)
|
|
49
45
|
end
|
|
50
46
|
|
|
51
47
|
Array(@uninheritable_attributes).each do |hash|
|
|
@@ -6,11 +6,12 @@ module Composable
|
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
include ActiveSupport::Rescuable
|
|
8
8
|
|
|
9
|
-
class ErrorWithRecord <
|
|
9
|
+
class ErrorWithRecord < StandardError
|
|
10
10
|
attr_reader :record
|
|
11
11
|
|
|
12
12
|
def initialize(record)
|
|
13
13
|
@record = record
|
|
14
|
+
super(record.errors.full_messages.to_sentence)
|
|
14
15
|
end
|
|
15
16
|
end
|
|
16
17
|
|
|
@@ -18,9 +19,8 @@ module Composable
|
|
|
18
19
|
prepend ComposableDSLExt
|
|
19
20
|
|
|
20
21
|
rescue_from(ErrorWithRecord) do |exception|
|
|
21
|
-
exception.record.errors.delete(:base)&.each { |m| errors.add(:base, m) }
|
|
22
22
|
exception.record.errors.each do |error|
|
|
23
|
-
if error.attribute.to_sym.in?(attributes)
|
|
23
|
+
if error.attribute == :base || error.attribute.to_sym.in?(attributes)
|
|
24
24
|
errors.add(error.attribute, error.message)
|
|
25
25
|
else
|
|
26
26
|
errors.add(:base, exception.record.errors.full_message(error.attribute, error.message))
|
|
@@ -30,9 +30,11 @@ module Composable
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
module ComposableDSLExt
|
|
33
|
-
def save_composable_records
|
|
33
|
+
def save_composable_records(...)
|
|
34
34
|
super
|
|
35
|
-
rescue
|
|
35
|
+
rescue StandardError => exception
|
|
36
|
+
raise unless defined?(ActiveRecord::RecordInvalid) && exception.is_a?(ActiveRecord::RecordInvalid)
|
|
37
|
+
|
|
36
38
|
raise ErrorWithRecord.new(exception.record)
|
|
37
39
|
end
|
|
38
40
|
end
|
data/lib/composable/core.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "core/version"
|
|
4
|
+
require "set"
|
|
4
5
|
require "active_support"
|
|
5
6
|
require "active_support/core_ext/object/deep_dup"
|
|
6
7
|
require "active_model"
|
|
@@ -17,4 +18,4 @@ module Composable
|
|
|
17
18
|
end
|
|
18
19
|
end
|
|
19
20
|
|
|
20
|
-
require "composable/core/record_invalid"
|
|
21
|
+
require "composable/core/record_invalid"
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: composable-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.14
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jairo Vazquez
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-10-07 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activesupport
|
|
@@ -15,28 +15,28 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '7.2'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '7.2'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: activemodel
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
32
|
+
version: '7.2'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
39
|
+
version: '7.2'
|
|
40
40
|
description: Core composable object to work with ruby services and forms
|
|
41
41
|
email:
|
|
42
42
|
- jairovm20@gmail.com
|
|
@@ -70,7 +70,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
70
70
|
requirements:
|
|
71
71
|
- - ">="
|
|
72
72
|
- !ruby/object:Gem::Version
|
|
73
|
-
version: 2.
|
|
73
|
+
version: 3.2.0
|
|
74
74
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
75
|
requirements:
|
|
76
76
|
- - ">="
|