composable-core 0.0.4 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/composable/core/attribute_dsl.rb +91 -0
- data/lib/composable/core/callbacks.rb +15 -0
- data/lib/composable/core/command.rb +57 -0
- data/lib/composable/core/composable_dsl.rb +221 -0
- data/lib/composable/core/gem_version.rb +1 -1
- data/lib/composable/core/inheritable_attributes.rb +66 -0
- data/lib/composable/core/record_invalid.rb +43 -0
- data/lib/composable/core.rb +11 -0
- metadata +38 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7d56d2c41394ea626296bdcfef8a547e272e7202c872972d6ea4b749ebcff538
|
4
|
+
data.tar.gz: b9f1d63e63db227c16cb45d5e702d32df51c1774fff1dc8c7ed8e002fae36f8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e0f79489b8c6c33dfe0d0bdb8d76fe8ee08be37a00749ebb5fe4c7050ff4d336e3f3f7af56224f049f1334d9933095e3a5e23e458aca516e5b047ac7f2018614
|
7
|
+
data.tar.gz: 415dfb4b81b6c069a9fb8c80b0a53b3c52be68423f9063e3da35e2d98e768227b134ba5ffc708aadf444fb1c6b832c316b468c4d1bb6896268ea4812505fabac
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Composable
|
4
|
+
module Core
|
5
|
+
module AttributeDSL
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include InheritableAttributes
|
8
|
+
|
9
|
+
included do
|
10
|
+
inheritable_attributes :attributes, default: Set.new
|
11
|
+
inheritable_attributes :_attribute_options, default: {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(options = {})
|
15
|
+
unless options.respond_to?(:has_key?)
|
16
|
+
raise ArgumentError, "When assigning attributes, you must pass a hash" \
|
17
|
+
" as an argument, #{options.class} passed."
|
18
|
+
end
|
19
|
+
|
20
|
+
attributes.each do |attribute|
|
21
|
+
# Try a `string` key if `symbol` key does not exist
|
22
|
+
value = options.fetch(attribute.to_sym, options[attribute.to_s])
|
23
|
+
send("#{attribute}=", value) unless value.nil?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def attributes
|
28
|
+
self.class.attributes
|
29
|
+
end
|
30
|
+
|
31
|
+
def params
|
32
|
+
attributes.each_with_object({}) do |attribute, hash|
|
33
|
+
hash[attribute] = send(attribute)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module ClassMethods
|
38
|
+
def attribute(*attrs)
|
39
|
+
options = attrs.extract_options!
|
40
|
+
|
41
|
+
return attributes if attrs.empty?
|
42
|
+
|
43
|
+
attrs.each do |attribute|
|
44
|
+
_save_options_for(attribute, **options)
|
45
|
+
|
46
|
+
next if attributes.include?(attribute.to_sym)
|
47
|
+
|
48
|
+
_define_getter(attribute)
|
49
|
+
_define_setter(attribute)
|
50
|
+
_define_question_mark_method(attribute)
|
51
|
+
attributes << attribute.to_sym
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def _define_getter(attribute)
|
58
|
+
define_method(attribute) do
|
59
|
+
options = self.class._attribute_options.fetch(attribute.to_sym, {})
|
60
|
+
value = if instance_variable_defined?("@#{attribute}")
|
61
|
+
instance_variable_get("@#{attribute}")
|
62
|
+
else
|
63
|
+
instance_variable_set("@#{attribute}", options[:default].deep_dup)
|
64
|
+
end
|
65
|
+
|
66
|
+
options[:type] ? options[:type].cast(value) : value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def _define_setter(attribute)
|
71
|
+
define_method("#{attribute}=") do |value|
|
72
|
+
options = self.class._attribute_options.fetch(attribute.to_sym, {})
|
73
|
+
instance_variable_set("@#{attribute}", options[:type] ? options[:type].cast(value) : value)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def _define_question_mark_method(attribute)
|
78
|
+
define_method("#{attribute}?") do
|
79
|
+
send(attribute).present?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def _save_options_for(attribute, type: nil, default: nil, **typed_options)
|
84
|
+
options = _attribute_options[attribute.to_sym] ||= {}
|
85
|
+
options[:default] = default unless default.nil?
|
86
|
+
options[:type] = ActiveModel::Type.lookup(type, **typed_options) unless type.nil?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Composable
|
4
|
+
module Core
|
5
|
+
module Command
|
6
|
+
class NotImplementedError < ::StandardError; end
|
7
|
+
|
8
|
+
attr_reader :result
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def call(*args, **kwargs)
|
12
|
+
new(*args, **kwargs).call
|
13
|
+
end
|
14
|
+
|
15
|
+
def call!(*args, **kwargs)
|
16
|
+
new(*args, **kwargs).call(raise_exception: true)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.prepended(base)
|
21
|
+
base.extend ClassMethods
|
22
|
+
|
23
|
+
base.include ActiveModel::Validations
|
24
|
+
base.include ActiveSupport::Rescuable
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(raise_exception: false)
|
28
|
+
fail NotImplementedError unless defined?(super)
|
29
|
+
|
30
|
+
@called = true
|
31
|
+
@result = super()
|
32
|
+
|
33
|
+
raise Error, errors.full_messages.to_sentence if raise_exception && failure?
|
34
|
+
|
35
|
+
self
|
36
|
+
rescue Exception => exception
|
37
|
+
raise if raise_exception || !rescue_with_handler(exception)
|
38
|
+
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def success?
|
43
|
+
called? && !failure?
|
44
|
+
end
|
45
|
+
|
46
|
+
def failure?
|
47
|
+
called? && errors.any?
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def called?
|
53
|
+
@called ||= false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Composable
|
4
|
+
module Core
|
5
|
+
module ComposableDSL
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include InheritableAttributes
|
8
|
+
|
9
|
+
included do
|
10
|
+
inheritable_attributes :composables, default: {}
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def composable(attribute, **options, &block)
|
15
|
+
composables[attribute.to_sym] ||= Composable.new(attribute)
|
16
|
+
composables[attribute.to_sym].evaluate(**options, &block)
|
17
|
+
|
18
|
+
return if attributes.include?(attribute.to_sym)
|
19
|
+
|
20
|
+
attribute attribute
|
21
|
+
|
22
|
+
alias_method "#{attribute}_original_setter=", "#{attribute}="
|
23
|
+
|
24
|
+
define_method("#{attribute}=") do |value|
|
25
|
+
send("#{attribute}_original_setter=", value)
|
26
|
+
composables[attribute.to_sym].sync_attributes(
|
27
|
+
self, composable_record_for(attribute), reverse: true
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# In order to prevent attribute values passed in at initialize from being
|
34
|
+
# overridden by composable_record values, composable_record mapping methods
|
35
|
+
# have to be synced first.
|
36
|
+
def initialize(options = {})
|
37
|
+
composable_keys.each do |attribute|
|
38
|
+
value = options.delete(attribute) || options.delete(attribute.to_s)
|
39
|
+
send("#{attribute}=", value) if value
|
40
|
+
end
|
41
|
+
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
def composables
|
46
|
+
self.class.composables
|
47
|
+
end
|
48
|
+
|
49
|
+
def save_composables
|
50
|
+
multi_db_transaction do
|
51
|
+
sync_composable_records
|
52
|
+
save_composable_records
|
53
|
+
|
54
|
+
yield if block_given?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def sync_composable_records
|
61
|
+
composables.each do |attribute, composable|
|
62
|
+
record = composable_record_for(attribute)
|
63
|
+
composable.sync_attributes(self, record) if composable.valid?(self, record)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def save_composable_records
|
68
|
+
composables.each do |attribute, composable|
|
69
|
+
record = composable_record_for(attribute)
|
70
|
+
record.save! if composable.valid?(self, record)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Ensures `save!` queries are wrapped within multiple db transactions.
|
75
|
+
# It might be the case that our service is working with several composable
|
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.
|
78
|
+
def multi_db_transaction(attribute: composable_keys.first, &block)
|
79
|
+
return yield if attribute.nil?
|
80
|
+
|
81
|
+
index = composable_keys.index(attribute)
|
82
|
+
record = composable_record_for(attribute)
|
83
|
+
|
84
|
+
multi_db_transaction(attribute: composable_keys[index + 1]) do
|
85
|
+
record.respond_to?(:transaction) ? record.transaction(&block) : yield
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def composable_keys
|
90
|
+
@composable_keys ||= composables.keys.freeze
|
91
|
+
end
|
92
|
+
|
93
|
+
def composable_record_for(attribute)
|
94
|
+
send(attribute) or
|
95
|
+
raise ArgumentError, "required composable argument: #{attribute}"
|
96
|
+
end
|
97
|
+
|
98
|
+
class Composable
|
99
|
+
attr_reader :attribute, :mapping, :conditions
|
100
|
+
|
101
|
+
def initialize(attribute)
|
102
|
+
@attribute = attribute
|
103
|
+
@mapping = {}
|
104
|
+
@conditions = Conditions.new
|
105
|
+
end
|
106
|
+
|
107
|
+
def initialize_copy(original_object)
|
108
|
+
@mapping = original_object.mapping.deep_dup
|
109
|
+
@conditions = original_object.conditions.deep_dup
|
110
|
+
end
|
111
|
+
|
112
|
+
def evaluate(**options, &block)
|
113
|
+
conditions.merge(**options)
|
114
|
+
|
115
|
+
instance_eval(&block) if block
|
116
|
+
end
|
117
|
+
|
118
|
+
def sync_attributes(form, record, reverse: false)
|
119
|
+
mapping.each_value do |instance|
|
120
|
+
instance.sync(form, record, reverse: reverse)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def valid?(...)
|
125
|
+
conditions.valid?(...)
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
# DSL method
|
131
|
+
def sync(*args, to: nil, **options)
|
132
|
+
args.extract_options!
|
133
|
+
|
134
|
+
args.compact.uniq.each do |name|
|
135
|
+
mapping[name.to_sym] ||= Sync.new(from: name)
|
136
|
+
mapping[name.to_sym].merge(to: (to || name), **options)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class Sync
|
141
|
+
attr_reader :from, :to, :conditions
|
142
|
+
|
143
|
+
def initialize(from:)
|
144
|
+
@from = from.to_sym
|
145
|
+
@conditions = Conditions.new
|
146
|
+
end
|
147
|
+
|
148
|
+
def initialize_copy(original_object)
|
149
|
+
@conditions = original_object.conditions.deep_dup
|
150
|
+
end
|
151
|
+
|
152
|
+
def merge(to:, **options)
|
153
|
+
@to = to.to_sym
|
154
|
+
|
155
|
+
conditions.merge(**options)
|
156
|
+
end
|
157
|
+
|
158
|
+
def sync(form, record, reverse: false)
|
159
|
+
if reverse
|
160
|
+
form.send("#{from}=", record.send(to))
|
161
|
+
else
|
162
|
+
value = form.send(from)
|
163
|
+
record.send("#{to}=", value) if conditions.valid?(form, record)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class Conditions
|
169
|
+
attr_reader :conditions
|
170
|
+
|
171
|
+
def initialize
|
172
|
+
@conditions = {}
|
173
|
+
end
|
174
|
+
|
175
|
+
def initialize_copy(original_object)
|
176
|
+
@conditions = original_object.conditions.deep_dup
|
177
|
+
end
|
178
|
+
|
179
|
+
def merge(**options)
|
180
|
+
options.symbolize_keys.slice(:if, :unless).each do |statement, condition|
|
181
|
+
conditions[statement] = Condition.new(statement, condition)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def valid?(form, record)
|
186
|
+
conditions.values.all? { |condition| condition.call(form, record) }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class Condition
|
191
|
+
attr_reader :condition, :statement, :callable
|
192
|
+
|
193
|
+
def initialize(statement, condition)
|
194
|
+
@statement = statement.to_sym
|
195
|
+
@condition = condition
|
196
|
+
@callable = make_lambda
|
197
|
+
end
|
198
|
+
|
199
|
+
def call(form, record)
|
200
|
+
callable.call(form, record).eql?(statement == :if)
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
def make_lambda
|
206
|
+
case condition
|
207
|
+
when Symbol
|
208
|
+
->(form, _record) { form.send(condition) }
|
209
|
+
when ::Proc
|
210
|
+
lambda { |form, record|
|
211
|
+
form.instance_exec(*[record][0...condition.arity], &condition)
|
212
|
+
}
|
213
|
+
else
|
214
|
+
->(*) { true }
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Composable
|
4
|
+
module Core
|
5
|
+
module InheritableAttributes
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def inheritable_attributes(*attributes)
|
10
|
+
options = attributes.extract_options!
|
11
|
+
|
12
|
+
@inheritable_attributes ||= [{attribute: :inheritable_attributes}]
|
13
|
+
attributes.each do |attribute|
|
14
|
+
@inheritable_attributes << {attribute: attribute, default: options[:default]}
|
15
|
+
|
16
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
17
|
+
class << self; attr_accessor :#{attribute} end
|
18
|
+
RUBY
|
19
|
+
send("#{attribute}=", options[:default]) unless options[:default].nil?
|
20
|
+
end
|
21
|
+
@inheritable_attributes
|
22
|
+
end
|
23
|
+
|
24
|
+
def uninheritable_attributes(*attributes)
|
25
|
+
options = attributes.extract_options!
|
26
|
+
|
27
|
+
@uninheritable_attributes ||= [{attribute: :uninheritable_attributes}]
|
28
|
+
|
29
|
+
attributes.each do |attribute|
|
30
|
+
@uninheritable_attributes << {attribute: attribute, default: options[:default]}
|
31
|
+
|
32
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
33
|
+
class << self; attr_accessor :#{attribute} end
|
34
|
+
RUBY
|
35
|
+
send("#{attribute}=", options[:default]) unless options[:default].nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
@uninheritable_attributes
|
39
|
+
end
|
40
|
+
|
41
|
+
def inherited(subclass)
|
42
|
+
super
|
43
|
+
|
44
|
+
Array(@inheritable_attributes).each do |hash|
|
45
|
+
inheritable_attribute = hash[:attribute]
|
46
|
+
instance_name = "@#{inheritable_attribute}"
|
47
|
+
instance_value = instance_variable_get(instance_name).deep_dup
|
48
|
+
subclass.instance_variable_set(instance_name, instance_value || hash[:default].deep_dup)
|
49
|
+
end
|
50
|
+
|
51
|
+
Array(@uninheritable_attributes).each do |hash|
|
52
|
+
uninheritable_attribute = hash[:attribute]
|
53
|
+
instance_name = "@#{uninheritable_attribute}"
|
54
|
+
|
55
|
+
if instance_name == "@uninheritable_attributes"
|
56
|
+
instance_value = instance_variable_get(instance_name).deep_dup
|
57
|
+
subclass.instance_variable_set(instance_name, instance_value)
|
58
|
+
else
|
59
|
+
subclass.instance_variable_set(instance_name, hash[:default].deep_dup)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Composable
|
4
|
+
module Core
|
5
|
+
module RecordInvalid
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include ActiveSupport::Rescuable
|
8
|
+
|
9
|
+
class ErrorWithRecord < Exception
|
10
|
+
attr_reader :record
|
11
|
+
|
12
|
+
def initialize(record)
|
13
|
+
@record = record
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
included do
|
18
|
+
prepend ComposableDSLExt
|
19
|
+
|
20
|
+
rescue_from(ErrorWithRecord) do |exception|
|
21
|
+
exception.record.errors.delete(:base)&.each { |m| errors.add(:base, m) }
|
22
|
+
exception.record.errors.each do |error|
|
23
|
+
if error.attribute.to_sym.in?(attributes)
|
24
|
+
errors.add(error.attribute, error.message)
|
25
|
+
else
|
26
|
+
errors.add(:base, exception.record.errors.full_message(error.attribute, error.message))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module ComposableDSLExt
|
33
|
+
def save_composable_records
|
34
|
+
super
|
35
|
+
rescue ActiveRecord::RecordInvalid => exception
|
36
|
+
raise ErrorWithRecord.new(exception.record)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
Composable::Core::ComposableDSL.include Composable::Core::RecordInvalid
|
data/lib/composable/core.rb
CHANGED
@@ -1,9 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "core/version"
|
4
|
+
require "active_support"
|
5
|
+
require "active_support/core_ext/object/deep_dup"
|
6
|
+
require "active_model"
|
4
7
|
|
5
8
|
module Composable
|
6
9
|
module Core
|
7
10
|
class Error < StandardError; end
|
11
|
+
|
12
|
+
autoload :AttributeDSL, "composable/core/attribute_dsl"
|
13
|
+
autoload :Callbacks, "composable/core/callbacks"
|
14
|
+
autoload :Command, "composable/core/command"
|
15
|
+
autoload :ComposableDSL, "composable/core/composable_dsl"
|
16
|
+
autoload :InheritableAttributes, "composable/core/inheritable_attributes"
|
8
17
|
end
|
9
18
|
end
|
19
|
+
|
20
|
+
require "composable/core/record_invalid" if defined?(ActiveRecord)
|
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
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.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jairo Vazquez
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
12
|
-
dependencies:
|
11
|
+
date: 2022-08-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activemodel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '6.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '6.1'
|
13
41
|
description: Core composable object to work with ruby services and forms
|
14
42
|
email:
|
15
43
|
- jairovm20@gmail.com
|
@@ -21,7 +49,13 @@ files:
|
|
21
49
|
- LICENSE.txt
|
22
50
|
- README.md
|
23
51
|
- lib/composable/core.rb
|
52
|
+
- lib/composable/core/attribute_dsl.rb
|
53
|
+
- lib/composable/core/callbacks.rb
|
54
|
+
- lib/composable/core/command.rb
|
55
|
+
- lib/composable/core/composable_dsl.rb
|
24
56
|
- lib/composable/core/gem_version.rb
|
57
|
+
- lib/composable/core/inheritable_attributes.rb
|
58
|
+
- lib/composable/core/record_invalid.rb
|
25
59
|
- lib/composable/core/version.rb
|
26
60
|
homepage: https://github.com/jairovm/composables
|
27
61
|
licenses:
|
@@ -45,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
79
|
- !ruby/object:Gem::Version
|
46
80
|
version: '0'
|
47
81
|
requirements: []
|
48
|
-
rubygems_version: 3.
|
82
|
+
rubygems_version: 3.3.3
|
49
83
|
signing_key:
|
50
84
|
specification_version: 4
|
51
85
|
summary: Core Composable Object
|