active_record_compose 0.11.2 → 0.11.3
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/.yardopts +4 -0
- data/CHANGELOG.md +17 -0
- data/README.md +6 -5
- data/lib/active_record_compose/attributes/delegation.rb +40 -0
- data/lib/active_record_compose/attributes/querying.rb +69 -0
- data/lib/active_record_compose/attributes.rb +179 -0
- data/lib/active_record_compose/callbacks.rb +26 -0
- data/lib/active_record_compose/composed_collection.rb +16 -9
- data/lib/active_record_compose/model.rb +354 -18
- data/lib/active_record_compose/persistence.rb +2 -0
- data/lib/active_record_compose/transaction_support.rb +1 -0
- data/lib/active_record_compose/validations.rb +3 -0
- data/lib/active_record_compose/version.rb +1 -1
- data/lib/active_record_compose/wrapped_model.rb +4 -2
- data/lib/active_record_compose.rb +4 -0
- data/sig/_internal/package_private.rbs +42 -32
- data/sig/active_record_compose.rbs +1 -1
- metadata +8 -6
- data/lib/active_record_compose/attribute_querying.rb +0 -67
- data/lib/active_record_compose/delegate_attribute.rb +0 -95
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 154ee35310ede836e5d89ecd703efadbbc2b7fba0a6229797e9b38b5288eba47
|
4
|
+
data.tar.gz: 42c565c5093680ae089bac356541561dc83ff73759941f268af22365338126db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5a9f0a46534ea0dbb3423275a68b521543c67074d8fcd7c637d46f86fcc42f93ac6792b2d80f83b6a0aae367f6dd461e6fb4d0e06730f40da3aefd0751393e9
|
7
|
+
data.tar.gz: c5bb3aaae6e84b23bc264ce5a1ad7b369f248c1fb1f1494b2753a1a82681ecc3df8c5f10a6b68854cda319c65da59e69435dd1c4381f44ff585d81a2400b5bac
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.11.3] - 2025-07-13
|
4
|
+
|
5
|
+
- refactor: Aggregation attribute module.
|
6
|
+
(https://github.com/hamajyotan/active_record_compose/pull/24)
|
7
|
+
- Warn against specifying instance variables, etc. directly in the `:to` option of `delegate_attribute`.
|
8
|
+
- Deprecated:
|
9
|
+
```ruby
|
10
|
+
delegate_attribute :foo, to: :@model
|
11
|
+
```
|
12
|
+
- Recommended:
|
13
|
+
```ruby
|
14
|
+
delegate_attribute :foo, to: :model
|
15
|
+
private
|
16
|
+
attr_reader :model
|
17
|
+
```
|
18
|
+
- doc: Expansion of yard documentation comments.
|
19
|
+
|
3
20
|
## [0.11.2] - 2025-06-29
|
4
21
|
|
5
22
|
- `ActiveModel::Attributes.attribute_names` now takes into account attributes declared in `.delegate_attribute`
|
data/README.md
CHANGED
@@ -381,20 +381,20 @@ end
|
|
381
381
|
```ruby
|
382
382
|
r = Registration.new(name: 'foo', email: 'example@example.com', accept: false)
|
383
383
|
r.valid?
|
384
|
-
|
384
|
+
#=> true
|
385
385
|
|
386
386
|
r.valid?(:education)
|
387
|
-
|
387
|
+
#=> false
|
388
388
|
r.errors.map { [_1.attribute, _1.type] }
|
389
|
-
|
389
|
+
#=> [[:email, :invalid], [:accept, :blank]]
|
390
390
|
|
391
391
|
r.email = 'example@example.edu'
|
392
392
|
r.accept = true
|
393
393
|
|
394
394
|
r.valid?(:education)
|
395
|
-
|
395
|
+
#=> true
|
396
396
|
r.save(context: :education)
|
397
|
-
|
397
|
+
#=> true
|
398
398
|
```
|
399
399
|
|
400
400
|
## Sample application as an example
|
@@ -405,6 +405,7 @@ With Github Codespaces, it can also be run directly in the browser. Naturally, a
|
|
405
405
|
|
406
406
|
## Links
|
407
407
|
|
408
|
+
- [Document from YARD](https://hamajyotan.github.io/active_record_compose/)
|
408
409
|
- [Smart way to update multiple models simultaneously in Rails](https://dev.to/hamajyotan/smart-way-to-update-multiple-models-simultaneously-in-rails-51b6)
|
409
410
|
|
410
411
|
## Development
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordCompose
|
4
|
+
module Attributes
|
5
|
+
# @private
|
6
|
+
class Delegation
|
7
|
+
# @return [Symbol] The attribute name as symbol
|
8
|
+
attr_reader :attribute
|
9
|
+
|
10
|
+
def initialize(attribute:, to:, allow_nil: false)
|
11
|
+
@attribute = attribute.to_sym
|
12
|
+
@to = to.to_sym
|
13
|
+
@allow_nil = !!allow_nil
|
14
|
+
|
15
|
+
freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
def define_delegated_attribute(klass)
|
19
|
+
klass.delegate(reader, writer, to:, allow_nil:)
|
20
|
+
klass.define_attribute_methods(attribute)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String] The attribute name as string
|
24
|
+
def attribute_name = attribute.to_s
|
25
|
+
|
26
|
+
# @return [Hash<String, Object>]
|
27
|
+
def attribute_hash(model)
|
28
|
+
{ attribute_name => model.public_send(attribute) }
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :to, :allow_nil
|
34
|
+
|
35
|
+
def reader = attribute.to_s
|
36
|
+
|
37
|
+
def writer = "#{attribute}="
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordCompose
|
4
|
+
module Attributes
|
5
|
+
# @private
|
6
|
+
# This provides predicate methods based on the attributes.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# class AccountRegistration < ActiveRecordCompose::Model
|
10
|
+
# def initialize
|
11
|
+
# @account = Account.new
|
12
|
+
# super()
|
13
|
+
# models << account
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# attribute :original_attr
|
17
|
+
# delegate_attribute :name, :email, to: :account
|
18
|
+
#
|
19
|
+
# private
|
20
|
+
#
|
21
|
+
# attr_reader :account
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# model = AccountRegistration.new
|
25
|
+
#
|
26
|
+
# model.name #=> nil
|
27
|
+
# model.name? #=> false
|
28
|
+
# model.name = "Alice"
|
29
|
+
# model.name? #=> true
|
30
|
+
#
|
31
|
+
# model.original_attr = "Bob"
|
32
|
+
# model.original_attr? #=> true
|
33
|
+
# model.original_attr = ""
|
34
|
+
# model.original_attr? #=> false
|
35
|
+
#
|
36
|
+
# # If the value is numeric, it returns the result of checking whether it is zero or not.
|
37
|
+
# # This behavior is consistent with `ActiveRecord::AttributeMethods::Query`.
|
38
|
+
# model.original_attr = 123
|
39
|
+
# model.original_attr? #=> true
|
40
|
+
# model.original_attr = 0
|
41
|
+
# model.original_attr? #=> false
|
42
|
+
#
|
43
|
+
module Querying
|
44
|
+
extend ActiveSupport::Concern
|
45
|
+
include ActiveModel::AttributeMethods
|
46
|
+
|
47
|
+
included do
|
48
|
+
attribute_method_suffix "?", parameters: false
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def attribute?(attr_name)
|
54
|
+
value = public_send(attr_name)
|
55
|
+
|
56
|
+
case value
|
57
|
+
when true then true
|
58
|
+
when false, nil then false
|
59
|
+
else
|
60
|
+
if value.respond_to?(:zero?)
|
61
|
+
!value.zero?
|
62
|
+
else
|
63
|
+
value.present?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "attributes/delegation"
|
4
|
+
require_relative "attributes/querying"
|
5
|
+
|
6
|
+
module ActiveRecordCompose
|
7
|
+
# @private
|
8
|
+
#
|
9
|
+
# Provides attribute-related functionality for use within ActiveRecordCompose::Model.
|
10
|
+
#
|
11
|
+
# This module allows you to define attributes on your composed model, including support
|
12
|
+
# for query methods (e.g., `#attribute?`) and delegation of attributes to underlying
|
13
|
+
# ActiveRecord instances via macros.
|
14
|
+
#
|
15
|
+
# For example, `.delegate_attribute` defines attribute accessors that delegate to
|
16
|
+
# a specific model, similar to:
|
17
|
+
#
|
18
|
+
# delegate :name, :name=, to: :account
|
19
|
+
#
|
20
|
+
# Additionally, delegated attributes are included in the composed model's `#attributes`
|
21
|
+
# hash.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# class AccountRegistration < ActiveRecordCompose::Model
|
25
|
+
# def initialize(account, attributes = {})
|
26
|
+
# @account = account
|
27
|
+
# super(attributes)
|
28
|
+
# models.push(account)
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# attribute :original_attribute, :string, default: "qux"
|
32
|
+
# delegate_attribute :name, to: :account
|
33
|
+
#
|
34
|
+
# private
|
35
|
+
#
|
36
|
+
# attr_reader :account
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# account = Account.new
|
40
|
+
# account.name = "foo"
|
41
|
+
#
|
42
|
+
# registration = AccountRegistration.new(account)
|
43
|
+
# registration.name # => "foo" (delegated)
|
44
|
+
# registration.name? # => true (delegated attribute method + `?`)
|
45
|
+
#
|
46
|
+
# registration.name = "bar" # => updates account.name
|
47
|
+
# account.name # => "bar"
|
48
|
+
# account.name? # => true
|
49
|
+
#
|
50
|
+
# registration.attributes
|
51
|
+
# # => { "original_attribute" => "qux", "name" => "bar" }
|
52
|
+
#
|
53
|
+
module Attributes
|
54
|
+
extend ActiveSupport::Concern
|
55
|
+
include ActiveModel::Attributes
|
56
|
+
|
57
|
+
included do
|
58
|
+
include Querying
|
59
|
+
|
60
|
+
# @type self: Class
|
61
|
+
class_attribute :delegated_attributes, instance_writer: false
|
62
|
+
end
|
63
|
+
|
64
|
+
module ClassMethods
|
65
|
+
ALLOW_NIL_DEFAULT = Object.new.freeze # steep:ignore
|
66
|
+
private_constant :ALLOW_NIL_DEFAULT
|
67
|
+
|
68
|
+
# Defines the reader and writer for the specified attribute.
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# class AccountRegistration < ActiveRecordCompose::Model
|
72
|
+
# def initialize(account, attributes = {})
|
73
|
+
# @account = account
|
74
|
+
# super(attributes)
|
75
|
+
# models.push(account)
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# attribute :original_attribute, :string, default: "qux"
|
79
|
+
# delegate_attribute :name, to: :account
|
80
|
+
#
|
81
|
+
# private
|
82
|
+
#
|
83
|
+
# attr_reader :account
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# account = Account.new
|
87
|
+
# account.name = "foo"
|
88
|
+
#
|
89
|
+
# registration = AccountRegistration.new(account)
|
90
|
+
# registration.name # => "foo" (delegated)
|
91
|
+
# registration.name? # => true (delegated attribute method + `?`)
|
92
|
+
#
|
93
|
+
# registration.name = "bar" # => updates account.name
|
94
|
+
# account.name # => "bar"
|
95
|
+
# account.name? # => true
|
96
|
+
#
|
97
|
+
# registration.attributes
|
98
|
+
# # => { "original_attribute" => "qux", "name" => "bar" }
|
99
|
+
#
|
100
|
+
def delegate_attribute(*attributes, to:, allow_nil: ALLOW_NIL_DEFAULT) # steep:ignore
|
101
|
+
# steep:ignore:start
|
102
|
+
if to.start_with?("@")
|
103
|
+
suggested_reader_name = to.to_s.sub(/^@+/, "")
|
104
|
+
suggested_method =
|
105
|
+
if to.start_with?("@@")
|
106
|
+
"def #{suggested_reader_name} = #{to}"
|
107
|
+
else
|
108
|
+
"attr_reader :#{suggested_reader_name}"
|
109
|
+
end
|
110
|
+
|
111
|
+
message = <<~MSG
|
112
|
+
Direct use of instance or class variables in `to:` will be removed in the next minor version.
|
113
|
+
Please define a reader method (private is fine) and refer to it by name instead.
|
114
|
+
|
115
|
+
For example,
|
116
|
+
delegate_attribute #{attributes.map { ":#{_1}" }.join(", ")}, to: :#{to}#{", allow_nil: #{allow_nil}" if allow_nil != ALLOW_NIL_DEFAULT}
|
117
|
+
|
118
|
+
Instead of the above, use the following
|
119
|
+
delegate_attribute #{attributes.map { ":#{_1}" }.join(", ")}, to: :#{suggested_reader_name}#{", allow_nil: #{allow_nil}" if allow_nil != ALLOW_NIL_DEFAULT}
|
120
|
+
private
|
121
|
+
#{suggested_method}
|
122
|
+
|
123
|
+
MSG
|
124
|
+
(ActiveRecord.respond_to?(:deprecator) ? ActiveRecord.deprecator : ActiveSupport::Deprecation).warn(message)
|
125
|
+
end
|
126
|
+
allow_nil = false if allow_nil == ALLOW_NIL_DEFAULT
|
127
|
+
# steep:ignore:end
|
128
|
+
|
129
|
+
delegations = attributes.map { Delegation.new(attribute: _1, to:, allow_nil:) }
|
130
|
+
delegations.each { _1.define_delegated_attribute(self) }
|
131
|
+
|
132
|
+
self.delegated_attributes = (delegated_attributes.to_a + delegations).reverse.uniq { _1.attribute }.reverse
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns a array of attribute name.
|
136
|
+
# Attributes declared with `delegate_attribute` are also merged.
|
137
|
+
#
|
138
|
+
# @return [Array<String>] array of attribute name.
|
139
|
+
def attribute_names = super + delegated_attributes.to_a.map { _1.attribute_name }
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns a array of attribute name.
|
143
|
+
# Attributes declared with `delegate_attribute` are also merged.
|
144
|
+
#
|
145
|
+
# @return [Array<String>] array of attribute name.
|
146
|
+
def attribute_names = super + delegated_attributes.to_a.map { _1.attribute_name }
|
147
|
+
|
148
|
+
# Returns a hash with the attribute name as key and the attribute value as value.
|
149
|
+
# Attributes declared with `delegate_attribute` are also merged.
|
150
|
+
#
|
151
|
+
# @return [Hash] hash with the attribute name as key and the attribute value as value.
|
152
|
+
# @example
|
153
|
+
# class AccountRegistration < ActiveRecordCompose::Model
|
154
|
+
# def initialize(account, attributes = {})
|
155
|
+
# @account = account
|
156
|
+
# super(attributes)
|
157
|
+
# models.push(account)
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
# attribute :original_attribute, :string, default: "qux"
|
161
|
+
# delegate_attribute :name, to: :account
|
162
|
+
#
|
163
|
+
# private
|
164
|
+
#
|
165
|
+
# attr_reader :account
|
166
|
+
# end
|
167
|
+
#
|
168
|
+
# account = Account.new
|
169
|
+
# account.name = "foo"
|
170
|
+
#
|
171
|
+
# registration = AccountRegistration.new(account)
|
172
|
+
#
|
173
|
+
# registration.attributes # => { "original_attribute" => "qux", "name" => "bar" }
|
174
|
+
#
|
175
|
+
def attributes
|
176
|
+
super.merge(*delegated_attributes.to_a.map { _1.attribute_hash(self) })
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -1,11 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "validations"
|
4
|
+
|
3
5
|
module ActiveRecordCompose
|
6
|
+
# @private
|
7
|
+
#
|
8
|
+
# Provides hooks into the life cycle of an ActiveRecordCompose model,
|
9
|
+
# allowing you to insert custom logic before or after changes to the object's state.
|
10
|
+
#
|
11
|
+
# The callback flow generally follows the same structure as Active Record:
|
12
|
+
#
|
13
|
+
# * `before_validation`
|
14
|
+
# * `after_validation`
|
15
|
+
# * `before_save`
|
16
|
+
# * `before_create` (or `before_update` for update operations)
|
17
|
+
# * `after_create` (or `after_update` for update operations)
|
18
|
+
# * `after_save`
|
19
|
+
# * `after_commit` (or `after_rollback` when the transaction is rolled back)
|
20
|
+
#
|
4
21
|
module Callbacks
|
5
22
|
extend ActiveSupport::Concern
|
6
23
|
include ActiveModel::Validations::Callbacks
|
7
24
|
|
8
25
|
included do
|
26
|
+
include ActiveRecordCompose::Validations
|
27
|
+
|
9
28
|
define_model_callbacks :save
|
10
29
|
define_model_callbacks :create
|
11
30
|
define_model_callbacks :update
|
@@ -13,8 +32,15 @@ module ActiveRecordCompose
|
|
13
32
|
|
14
33
|
private
|
15
34
|
|
35
|
+
# Evaluate while firing callbacks such as `before_save` `after_save`
|
36
|
+
# before and after block evaluation.
|
37
|
+
#
|
16
38
|
def with_callbacks(&block) = run_callbacks(:save) { run_callbacks(callback_context, &block) }
|
17
39
|
|
40
|
+
# Returns the symbol representing the callback context, which is `:create` if the record
|
41
|
+
# is new, or `:update` if it has been persisted.
|
42
|
+
#
|
43
|
+
# @return [:create, :update] either `:create` if not persisted, or `:update` if persisted
|
18
44
|
def callback_context = persisted? ? :update : :create
|
19
45
|
end
|
20
46
|
end
|
@@ -5,6 +5,9 @@ require_relative "wrapped_model"
|
|
5
5
|
module ActiveRecordCompose
|
6
6
|
using WrappedModel::PackagePrivate
|
7
7
|
|
8
|
+
# Object obtained by {ActiveRecordCompose::Model#models}.
|
9
|
+
#
|
10
|
+
# It functions as a collection that contains the object to be saved.
|
8
11
|
class ComposedCollection
|
9
12
|
include Enumerable
|
10
13
|
|
@@ -15,7 +18,7 @@ module ActiveRecordCompose
|
|
15
18
|
|
16
19
|
# Enumerates model objects.
|
17
20
|
#
|
18
|
-
# @yieldparam [Object]
|
21
|
+
# @yieldparam [Object] model model instance
|
19
22
|
# @return [Enumerator] when not block given.
|
20
23
|
# @return [self] when block given, returns itself.
|
21
24
|
def each
|
@@ -27,7 +30,7 @@ module ActiveRecordCompose
|
|
27
30
|
|
28
31
|
# Appends model to collection.
|
29
32
|
#
|
30
|
-
# @param model [Object]
|
33
|
+
# @param model [Object] model instance
|
31
34
|
# @return [self] returns itself.
|
32
35
|
def <<(model)
|
33
36
|
models << wrap(model, destroy: false)
|
@@ -36,12 +39,14 @@ module ActiveRecordCompose
|
|
36
39
|
|
37
40
|
# Appends model to collection.
|
38
41
|
#
|
39
|
-
# @param model [Object]
|
40
|
-
# @param destroy [Boolean]
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# @param if [Symbol]
|
42
|
+
# @param model [Object] model instance
|
43
|
+
# @param destroy [Boolean, Proc, Symbol] Controls whether the model should be destroyed.
|
44
|
+
# - Boolean: if `true`, the model will be destroyed.
|
45
|
+
# - Proc: the model will be destroyed if the proc returns `true`.
|
46
|
+
# - Symbol: sends the symbol as a method to `owner`; if the result is truthy, the model will be destroyed.
|
47
|
+
# @param if [Proc, Symbol] Controls conditional inclusion in renewal.
|
48
|
+
# - Proc: the proc is called, and if it returns `false`, the model is excluded.
|
49
|
+
# - Symbol: sends the symbol as a method to `owner`; if the result is falsy, the model is excluded.
|
45
50
|
# @return [self] returns itself.
|
46
51
|
def push(model, destroy: false, if: nil)
|
47
52
|
models << wrap(model, destroy:, if:)
|
@@ -64,7 +69,7 @@ module ActiveRecordCompose
|
|
64
69
|
# Removes the specified model from the collection.
|
65
70
|
# Returns nil if the deletion fails, self if it succeeds.
|
66
71
|
#
|
67
|
-
# @param model [Object]
|
72
|
+
# @param model [Object] model instance
|
68
73
|
# @return [self] Successful deletion
|
69
74
|
# @return [nil] If deletion fails
|
70
75
|
def delete(model)
|
@@ -76,8 +81,10 @@ module ActiveRecordCompose
|
|
76
81
|
|
77
82
|
private
|
78
83
|
|
84
|
+
# @private
|
79
85
|
attr_reader :owner, :models
|
80
86
|
|
87
|
+
# @private
|
81
88
|
def wrap(model, destroy: false, if: nil)
|
82
89
|
if destroy.is_a?(Symbol)
|
83
90
|
method = destroy
|
@@ -1,39 +1,375 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "attributes"
|
3
4
|
require_relative "callbacks"
|
4
|
-
require_relative "attribute_querying"
|
5
5
|
require_relative "composed_collection"
|
6
|
-
require_relative "delegate_attribute"
|
7
|
-
require_relative "transaction_support"
|
8
6
|
require_relative "persistence"
|
9
|
-
require_relative "validations"
|
10
7
|
|
11
8
|
module ActiveRecordCompose
|
9
|
+
# This is the core class of {ActiveRecordCompose}.
|
10
|
+
#
|
11
|
+
# By defining subclasses of this model, you can use ActiveRecordCompose functionality in your application.
|
12
|
+
# It has the basic functionality of `ActiveModel::Model` and `ActiveModel::Attributes`,
|
13
|
+
# and also provides aggregation of multiple models and atomic updates through transaction control.
|
14
|
+
# @example Example of model registration.
|
15
|
+
# class AccountRegistration < ActiveRecordCompose::Model
|
16
|
+
# def initialize(account = Account.new, attributes = {})
|
17
|
+
# @account = account
|
18
|
+
# @profile = @account.build_profile
|
19
|
+
# models << account << profile
|
20
|
+
# super(attributes)
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# attribute :register_confirmation, :boolean, default: false
|
24
|
+
# delegate_attribute :name, :email, to: :account
|
25
|
+
# delegate_attribute :firstname, :lastname, :age, to: :profile
|
26
|
+
#
|
27
|
+
# validates :register_confirmation, presence: true
|
28
|
+
#
|
29
|
+
# private
|
30
|
+
#
|
31
|
+
# attr_reader :account, :profile
|
32
|
+
# end
|
33
|
+
# @example Multiple model update once.
|
34
|
+
# registration = AccountRegistration.new
|
35
|
+
# registration.assign_attributes(
|
36
|
+
# name: "alice-in-wonderland",
|
37
|
+
# email: "alice@example.com",
|
38
|
+
# firstname: "Alice",
|
39
|
+
# lastname: "Smith",
|
40
|
+
# age: 24,
|
41
|
+
# register_confirmation: true
|
42
|
+
# )
|
43
|
+
#
|
44
|
+
# registration.save! # Register Account and Profile models at the same time.
|
45
|
+
# Account.count # => (0 ->) 1
|
46
|
+
# Profile.count # => (0 ->) 1
|
47
|
+
# @example Attribute delegation.
|
48
|
+
# account = Account.new
|
49
|
+
# account.name = "foo"
|
50
|
+
#
|
51
|
+
# registration = AccountRegistration.new(account)
|
52
|
+
# registration.name # => "foo" (delegated)
|
53
|
+
# registration.name? # => true (delegated attribute method + `?`)
|
54
|
+
#
|
55
|
+
# registration.name = "bar" # => updates account.name
|
56
|
+
# account.name # => "bar"
|
57
|
+
# account.name? # => true
|
58
|
+
#
|
59
|
+
# registration.attributes # => { "original_attribute" => "qux", "name" => "bar" }
|
60
|
+
# @example Aggregate errors on invalid.
|
61
|
+
# registration = AccountRegistration.new
|
62
|
+
#
|
63
|
+
# registration.name = "alice-in-wonderland"
|
64
|
+
# registration.firstname = "Alice"
|
65
|
+
# registration.age = 18
|
66
|
+
#
|
67
|
+
# registration.valid?
|
68
|
+
# #=> false
|
69
|
+
#
|
70
|
+
# # The error contents of the objects stored in models are aggregated.
|
71
|
+
# # For example, direct access to errors in Account#email.
|
72
|
+
# registration.errors[:email].to_a # Account#email
|
73
|
+
# #=> ["can't be blank"]
|
74
|
+
#
|
75
|
+
# # Of course, the validation defined for itself is also working.
|
76
|
+
# registration.errors[:register_confirmation].to_a
|
77
|
+
# #=> ["can't be blank"]
|
78
|
+
#
|
79
|
+
# registration.errors.to_a
|
80
|
+
# #=> ["Email can't be blank", "Lastname can't be blank", "Register confirmation can't be blank"]
|
12
81
|
class Model
|
13
82
|
include ActiveModel::Model
|
14
|
-
include ActiveModel::Attributes
|
15
83
|
|
16
|
-
include ActiveRecordCompose::
|
17
|
-
include ActiveRecordCompose::Callbacks
|
18
|
-
include ActiveRecordCompose::DelegateAttribute
|
84
|
+
include ActiveRecordCompose::Attributes
|
19
85
|
include ActiveRecordCompose::Persistence
|
20
|
-
include ActiveRecordCompose::
|
86
|
+
include ActiveRecordCompose::Callbacks
|
87
|
+
|
88
|
+
begin
|
89
|
+
# @group Model Core
|
90
|
+
|
91
|
+
# @!method self.delegate_attribute(*attributes, to:, allow_nil: false)
|
92
|
+
# Provides a method of attribute access to the encapsulated model.
|
93
|
+
#
|
94
|
+
# It provides a way to access the attributes of the model it encompasses,
|
95
|
+
# allowing transparent access as if it had those attributes itself.
|
96
|
+
#
|
97
|
+
# @param [Array<Symbol, String>] attributes
|
98
|
+
# attributes A variable-length list of attribute names to delegate.
|
99
|
+
# @param [Symbol, String] to
|
100
|
+
# The target object to which attributes are delegated (keyword argument).
|
101
|
+
# @param [Boolean] allow_nil
|
102
|
+
# allow_nil Whether to allow nil values. Defaults to false.
|
103
|
+
# @example Basic usage
|
104
|
+
# delegate_attribute :name, :email, to: :profile
|
105
|
+
# @example Allowing nil
|
106
|
+
# delegate_attribute :bio, to: :profile, allow_nil: true
|
107
|
+
# @see Module#delegate for similar behavior in ActiveSupport
|
108
|
+
|
109
|
+
# @!method self.attribute_names
|
110
|
+
# Returns a array of attribute name.
|
111
|
+
# Attributes declared with {.delegate_attribute} are also merged.
|
112
|
+
#
|
113
|
+
# @see #attribute_names
|
114
|
+
# @return [Array<String>] array of attribute name.
|
115
|
+
|
116
|
+
# @!method attribute_names
|
117
|
+
# Returns a array of attribute name.
|
118
|
+
# Attributes declared with {.delegate_attribute} are also merged.
|
119
|
+
#
|
120
|
+
# class Foo < ActiveRecordCompose::Base
|
121
|
+
# def initialize(attributes = {})
|
122
|
+
# @account = Account.new
|
123
|
+
# super
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# attribute :confirmation, :boolean, default: false # plain attribute
|
127
|
+
# delegate_attribute :name, to: :account # delegated attribute
|
128
|
+
#
|
129
|
+
# private
|
130
|
+
#
|
131
|
+
# attr_reader :account
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# Foo.attribute_names # Returns the merged state of plain and delegated attributes
|
135
|
+
# # => ["confirmation" ,"name"]
|
136
|
+
#
|
137
|
+
# foo = Foo.new
|
138
|
+
# foo.attribute_names # Similar behavior for instance method version
|
139
|
+
# # => ["confirmation", "name"]
|
140
|
+
#
|
141
|
+
# @see #attributes
|
142
|
+
# @return [Array<String>] array of attribute name.
|
143
|
+
|
144
|
+
# @!method attributes
|
145
|
+
# Returns a hash with the attribute name as key and the attribute value as value.
|
146
|
+
# Attributes declared with {.delegate_attribute} are also merged.
|
147
|
+
#
|
148
|
+
# class Foo < ActiveRecordCompose::Base
|
149
|
+
# def initialize(attributes = {})
|
150
|
+
# @account = Account.new
|
151
|
+
# super
|
152
|
+
# end
|
153
|
+
#
|
154
|
+
# attribute :confirmation, :boolean, default: false # plain attribute
|
155
|
+
# delegate_attribute :name, to: :account # delegated attribute
|
156
|
+
#
|
157
|
+
# private
|
158
|
+
#
|
159
|
+
# attr_reader :account
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# foo = Foo.new
|
163
|
+
# foo.name = "Alice"
|
164
|
+
# foo.confirmation = true
|
165
|
+
#
|
166
|
+
# foo.attributes # Returns the merged state of plain and delegated attributes
|
167
|
+
# # => { "confirmation" => true, "name" => "Alice" }
|
168
|
+
#
|
169
|
+
# @return [Hash<String, Object>] hash with the attribute name as key and the attribute value as value.
|
170
|
+
|
171
|
+
# @!method persisted?
|
172
|
+
# Returns true if model is persisted.
|
173
|
+
#
|
174
|
+
# By overriding this definition, you can control the callbacks that are triggered when a save is made.
|
175
|
+
# For example, returning false will trigger before_create, around_create and after_create,
|
176
|
+
# and returning true will trigger {.before_update}, {.around_update} and {.after_update}.
|
177
|
+
#
|
178
|
+
# @return [Boolean] returns true if model is persisted.
|
179
|
+
# @example
|
180
|
+
# # A model where persistence is always false
|
181
|
+
# class Foo < ActiveRecordCompose::Model
|
182
|
+
# before_save { puts "before_save called" }
|
183
|
+
# before_create { puts "before_create called" }
|
184
|
+
# before_update { puts "before_update called" }
|
185
|
+
# after_update { puts "after_update called" }
|
186
|
+
# after_create { puts "after_create called" }
|
187
|
+
# after_save { puts "after_save called" }
|
188
|
+
#
|
189
|
+
# def persisted? = false
|
190
|
+
# end
|
191
|
+
#
|
192
|
+
# # A model where persistence is always true
|
193
|
+
# class Bar < Foo
|
194
|
+
# def persisted? = true
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
# Foo.new.save!
|
198
|
+
# # before_save called
|
199
|
+
# # before_create called
|
200
|
+
# # after_create called
|
201
|
+
# # after_save called
|
202
|
+
#
|
203
|
+
# Bar.new.save!
|
204
|
+
# # before_save called
|
205
|
+
# # before_update called
|
206
|
+
# # after_update called
|
207
|
+
# # after_save called
|
208
|
+
|
209
|
+
# @endgroup
|
210
|
+
|
211
|
+
# @group Validations
|
212
|
+
|
213
|
+
# @!method valid?(context = nil)
|
214
|
+
# Runs all the validations and returns the result as true or false.
|
215
|
+
# @param context Validation context.
|
216
|
+
# @return [Boolean] true on success, false on failure.
|
217
|
+
|
218
|
+
# @!method validate(context = nil)
|
219
|
+
# Alias for {#valid?}
|
220
|
+
# @see #valid? Validation context.
|
221
|
+
# @param context
|
222
|
+
# @return [Boolean] true on success, false on failure.
|
223
|
+
|
224
|
+
# @!method validate!(context = nil)
|
225
|
+
# @see #valid?
|
226
|
+
# Runs all the validations within the specified context.
|
227
|
+
# no errors are found, raises `ActiveRecord::RecordInvalid` otherwise.
|
228
|
+
# @param context Validation context.
|
229
|
+
# @raise ActiveRecord::RecordInvalid
|
230
|
+
|
231
|
+
# @!method errors
|
232
|
+
# Returns the `ActiveModel::Errors` object that holds all information about attribute error messages.
|
233
|
+
#
|
234
|
+
# The `ActiveModel::Base` implementation itself,
|
235
|
+
# but also aggregates error information for objects stored in {#models} when validation is performed.
|
236
|
+
#
|
237
|
+
# class Account < ActiveRecord::Base
|
238
|
+
# validates :name, :email, presence: true
|
239
|
+
# end
|
240
|
+
#
|
241
|
+
# class AccountRegistration < ActiveRecordCompose::Model
|
242
|
+
# def initialize(attributes = {})
|
243
|
+
# @account = Account.new
|
244
|
+
# super(attributes)
|
245
|
+
# models << account
|
246
|
+
# end
|
247
|
+
#
|
248
|
+
# attribute :confirmation, :boolean, default: false
|
249
|
+
# validates :confirmation, presence: true
|
250
|
+
#
|
251
|
+
# private
|
252
|
+
#
|
253
|
+
# attr_reader :account
|
254
|
+
# end
|
255
|
+
#
|
256
|
+
# registration = AccountRegistration
|
257
|
+
# registration.valid?
|
258
|
+
# #=> false
|
259
|
+
#
|
260
|
+
# # In addition to the model's own validation error information (`confirmation`), also aggregates
|
261
|
+
# # error information for objects stored in `account` (`name`, `email`) when validation is performed.
|
262
|
+
#
|
263
|
+
# registration.errors.map { _1.attribute } #=> [:name, :email, :confirmation]
|
264
|
+
#
|
265
|
+
# @return [ActiveModel::Errors]
|
266
|
+
|
267
|
+
# @endgroup
|
268
|
+
|
269
|
+
# @group Persistences
|
270
|
+
|
271
|
+
# @!method save(**options)
|
272
|
+
# Save the models that exist in models.
|
273
|
+
# Returns false if any of the targets fail, true if all succeed.
|
274
|
+
#
|
275
|
+
# The save is performed within a single transaction.
|
276
|
+
#
|
277
|
+
# Only the `:validate` option takes effect as it is required internally.
|
278
|
+
# However, we do not recommend explicitly specifying `validate: false` to skip validation.
|
279
|
+
# Additionally, the `:context` option is not accepted.
|
280
|
+
# The need for such a value indicates that operations from multiple contexts are being processed.
|
281
|
+
# If the contexts differ, we recommend separating them into different model definitions.
|
282
|
+
#
|
283
|
+
# @params [Hash] Optional parameters.
|
284
|
+
# @option options [Boolean] :validate Whether to run validations.
|
285
|
+
# This option is intended for internal use only.
|
286
|
+
# Users should avoid explicitly passing <tt>validate: false</tt>,
|
287
|
+
# as skipping validations can lead to unexpected behavior.
|
288
|
+
# @return [Boolean] returns true on success, false on failure.
|
289
|
+
|
290
|
+
# @!method save!(**options)
|
291
|
+
# Behavior is same to {#save}, but raises an exception prematurely on failure.
|
292
|
+
# @see #save
|
293
|
+
# @raise ActiveRecord::RecordInvalid
|
294
|
+
# @raise ActiveRecord::RecordNotSaved
|
295
|
+
|
296
|
+
# @!method update(attributes)
|
297
|
+
# Assign attributes and {#save}.
|
298
|
+
#
|
299
|
+
# @param [Hash<String, Object>] attributes
|
300
|
+
# new attributes.
|
301
|
+
# @see #save
|
302
|
+
# @return [Boolean] returns true on success, false on failure.
|
303
|
+
|
304
|
+
# @!method update!(attributes)
|
305
|
+
# Behavior is same to {#update}, but raises an exception prematurely on failure.
|
306
|
+
#
|
307
|
+
# @param [Hash<String, Object>] attributes
|
308
|
+
# new attributes.
|
309
|
+
# @see #save
|
310
|
+
# @see #update
|
311
|
+
# @raise ActiveRecord::RecordInvalid
|
312
|
+
# @raise ActiveRecord::RecordNotSaved
|
313
|
+
|
314
|
+
# @endgroup
|
315
|
+
|
316
|
+
# @group Callbacks
|
317
|
+
|
318
|
+
# @!method self.before_save(*args, &block)
|
319
|
+
# Registers a callback to be called before a model is saved.
|
320
|
+
|
321
|
+
# @!method self.around_save(*args, &block)
|
322
|
+
# Registers a callback to be called around the save of a model.
|
323
|
+
|
324
|
+
# @!method self.after_save(*args, &block)
|
325
|
+
# Registers a callback to be called after a model is saved.
|
326
|
+
|
327
|
+
# @!method self.before_create(*args, &block)
|
328
|
+
# Registers a callback to be called before a model is created.
|
329
|
+
|
330
|
+
# @!method self.around_create(*args, &block)
|
331
|
+
# Registers a callback to be called around the creation of a model.
|
332
|
+
|
333
|
+
# @!method self.after_create(*args, &block)
|
334
|
+
# Registers a callback to be called after a model is created.
|
335
|
+
|
336
|
+
# @!method self.before_update(*args, &block)
|
337
|
+
# Registers a callback to be called before a model is updated.
|
338
|
+
|
339
|
+
# @!method self.around_update(*args, &block)
|
340
|
+
# Registers a callback to be called around the update of a model.
|
341
|
+
|
342
|
+
# @!method self.after_update(*args, &block)
|
343
|
+
# Registers a callback to be called after a update is updated.
|
344
|
+
|
345
|
+
# @!method self.after_commit(*args, &block)
|
346
|
+
# Registers a block to be called after the transaction is fully committed.
|
347
|
+
|
348
|
+
# @!method self.after_rollback(*args, &block)
|
349
|
+
# Registers a block to be called after the transaction is rolled back.
|
350
|
+
|
351
|
+
# @endgroup
|
352
|
+
end
|
353
|
+
|
354
|
+
# @group Model Core
|
21
355
|
|
22
356
|
def initialize(attributes = {})
|
23
357
|
super
|
24
358
|
end
|
25
359
|
|
26
|
-
# Returns true if model is persisted.
|
27
|
-
#
|
28
|
-
# By overriding this definition, you can control the callbacks that are triggered when a save is made.
|
29
|
-
# For example, returning false will trigger before_create, around_create and after_create,
|
30
|
-
# and returning true will trigger before_update, around_update and after_update.
|
31
|
-
#
|
32
|
-
# @return [Boolean] returns true if model is persisted.
|
33
|
-
def persisted? = super
|
34
|
-
|
35
360
|
private
|
36
361
|
|
362
|
+
# Returns a collection of model elements to encapsulate.
|
363
|
+
# @example Adding models
|
364
|
+
# models << inner_model_a << inner_model_b
|
365
|
+
# models.push(inner_model_c)
|
366
|
+
# @example `#push` can have `:destroy` `:if` options
|
367
|
+
# models.push(profile, destroy: :blank_profile?)
|
368
|
+
# models.push(profile, destroy: -> { blank_profile? })
|
369
|
+
# @return [ActiveRecordCompose::ComposedCollection]
|
370
|
+
#
|
37
371
|
def models = @__models ||= ActiveRecordCompose::ComposedCollection.new(self)
|
372
|
+
|
373
|
+
# @endgroup
|
38
374
|
end
|
39
375
|
end
|
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "composed_collection"
|
4
|
+
require_relative "transaction_support"
|
4
5
|
|
5
6
|
module ActiveRecordCompose
|
6
7
|
using ComposedCollection::PackagePrivate
|
7
8
|
|
9
|
+
# @private
|
8
10
|
module Persistence
|
9
11
|
extend ActiveSupport::Concern
|
10
12
|
include ActiveRecordCompose::TransactionSupport
|
@@ -3,10 +3,12 @@
|
|
3
3
|
require "active_support/core_ext/object"
|
4
4
|
|
5
5
|
module ActiveRecordCompose
|
6
|
+
# @private
|
6
7
|
class WrappedModel
|
7
8
|
# @param model [Object] the model instance.
|
8
|
-
# @param destroy [Boolean]
|
9
|
-
#
|
9
|
+
# @param destroy [Boolean, Proc, Symbol] Controls whether the model should be destroyed.
|
10
|
+
# - Boolean: if `true`, the model will be destroyed.
|
11
|
+
# - Proc: the model will be destroyed if the proc returns `true`.
|
10
12
|
# @param if [Proc] evaluation result is false, it will not be included in the renewal.
|
11
13
|
def initialize(model, destroy: false, if: nil)
|
12
14
|
@model = model
|
@@ -5,5 +5,9 @@ require "active_record"
|
|
5
5
|
require_relative "active_record_compose/version"
|
6
6
|
require_relative "active_record_compose/model"
|
7
7
|
|
8
|
+
# namespaces in gem `active_record_compose`.
|
9
|
+
#
|
10
|
+
# Most of the functionality resides in {ActiveRecordCompose::Model}.
|
11
|
+
#
|
8
12
|
module ActiveRecordCompose
|
9
13
|
end
|
@@ -1,11 +1,46 @@
|
|
1
1
|
module ActiveRecordCompose
|
2
|
-
module
|
3
|
-
include ActiveModel::AttributeMethods
|
2
|
+
module Attributes
|
4
3
|
extend ActiveSupport::Concern
|
5
|
-
|
4
|
+
include ActiveModel::Attributes
|
5
|
+
include Querying
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
def delegated_attributes: () -> Array[Delegation]
|
8
|
+
|
9
|
+
module ClassMethods : Module
|
10
|
+
include ActiveModel::Attributes::ClassMethods
|
11
|
+
include ActiveModel::AttributeMethods::ClassMethods
|
12
|
+
|
13
|
+
def delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: bool) -> untyped
|
14
|
+
def delegated_attributes: () -> Array[Delegation]
|
15
|
+
def delegated_attributes=: (Array[Delegation]) -> untyped
|
16
|
+
end
|
17
|
+
|
18
|
+
class Delegation
|
19
|
+
def initialize: (attribute: String, to: Symbol, ?allow_nil: bool) -> void
|
20
|
+
def attribute: () -> Symbol
|
21
|
+
def attribute_name: () -> String
|
22
|
+
def attribute_hash: (Object model) -> Hash[String, untyped]
|
23
|
+
def define_delegated_attribute: ((Module & ActiveModel::AttributeMethods::ClassMethods) klass) -> void
|
24
|
+
|
25
|
+
@attribute: Symbol
|
26
|
+
@to: Symbol
|
27
|
+
@allow_nil: bool
|
28
|
+
|
29
|
+
private
|
30
|
+
def to: () -> Symbol
|
31
|
+
def allow_nil: () -> bool
|
32
|
+
def reader: () -> String
|
33
|
+
def writer: () -> String
|
34
|
+
end
|
35
|
+
|
36
|
+
module Querying
|
37
|
+
include ActiveModel::AttributeMethods
|
38
|
+
extend ActiveSupport::Concern
|
39
|
+
extend ActiveModel::AttributeMethods::ClassMethods
|
40
|
+
|
41
|
+
private
|
42
|
+
def attribute?: (attribute_name) -> untyped
|
43
|
+
end
|
9
44
|
end
|
10
45
|
|
11
46
|
module Callbacks
|
@@ -37,34 +72,9 @@ module ActiveRecordCompose
|
|
37
72
|
include PackagePrivate
|
38
73
|
end
|
39
74
|
|
40
|
-
module DelegateAttribute
|
41
|
-
include ActiveModel::Attributes
|
42
|
-
extend ActiveSupport::Concern
|
43
|
-
|
44
|
-
class Delegation
|
45
|
-
def initialize: (attribute: String, to: Symbol, allow_nil: boolish) -> void
|
46
|
-
def attribute: () -> String
|
47
|
-
def to: () -> Symbol
|
48
|
-
def allow_nil: () -> boolish
|
49
|
-
def reader: () -> String
|
50
|
-
def writer: () -> String
|
51
|
-
end
|
52
|
-
|
53
|
-
def delegated_attributes: () -> Array[Delegation]
|
54
|
-
|
55
|
-
module ClassMethods : Module
|
56
|
-
include ActiveModel::Attributes::ClassMethods
|
57
|
-
include ActiveModel::AttributeMethods::ClassMethods
|
58
|
-
|
59
|
-
def delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: untyped?) -> untyped
|
60
|
-
def delegated_attributes: () -> Array[Delegation]
|
61
|
-
def delegated_attributes=: (Array[Delegation]) -> untyped
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
75
|
class Model
|
66
|
-
include
|
67
|
-
extend
|
76
|
+
include Attributes
|
77
|
+
extend Attributes::ClassMethods
|
68
78
|
include TransactionSupport
|
69
79
|
extend TransactionSupport::ClassMethods
|
70
80
|
include Callbacks
|
@@ -71,7 +71,7 @@ module ActiveRecordCompose
|
|
71
71
|
def self.after_commit: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
72
72
|
def self.after_rollback: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
73
73
|
|
74
|
-
def self.delegate_attribute: (*untyped methods, to: untyped, ?allow_nil:
|
74
|
+
def self.delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: bool) -> untyped
|
75
75
|
def self.connection: -> ActiveRecord::ConnectionAdapters::AbstractAdapter
|
76
76
|
def self.lease_connection: -> ActiveRecord::ConnectionAdapters::AbstractAdapter
|
77
77
|
def self.with_connection: [T] () { () -> T } -> T
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_compose
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.11.
|
4
|
+
version: 0.11.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- hamajyotan
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activerecord
|
@@ -38,15 +38,17 @@ extensions: []
|
|
38
38
|
extra_rdoc_files: []
|
39
39
|
files:
|
40
40
|
- ".rubocop.yml"
|
41
|
+
- ".yardopts"
|
41
42
|
- CHANGELOG.md
|
42
43
|
- CODE_OF_CONDUCT.md
|
43
44
|
- LICENSE.txt
|
44
45
|
- README.md
|
45
46
|
- lib/active_record_compose.rb
|
46
|
-
- lib/active_record_compose/
|
47
|
+
- lib/active_record_compose/attributes.rb
|
48
|
+
- lib/active_record_compose/attributes/delegation.rb
|
49
|
+
- lib/active_record_compose/attributes/querying.rb
|
47
50
|
- lib/active_record_compose/callbacks.rb
|
48
51
|
- lib/active_record_compose/composed_collection.rb
|
49
|
-
- lib/active_record_compose/delegate_attribute.rb
|
50
52
|
- lib/active_record_compose/model.rb
|
51
53
|
- lib/active_record_compose/persistence.rb
|
52
54
|
- lib/active_record_compose/transaction_support.rb
|
@@ -62,7 +64,7 @@ metadata:
|
|
62
64
|
homepage_uri: https://github.com/hamajyotan/active_record_compose
|
63
65
|
source_code_uri: https://github.com/hamajyotan/active_record_compose
|
64
66
|
changelog_uri: https://github.com/hamajyotan/active_record_compose/blob/main/CHANGELOG.md
|
65
|
-
documentation_uri: https://
|
67
|
+
documentation_uri: https://hamajyotan.github.io/active_record_compose/
|
66
68
|
rubygems_mfa_required: 'true'
|
67
69
|
rdoc_options: []
|
68
70
|
require_paths:
|
@@ -78,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
80
|
- !ruby/object:Gem::Version
|
79
81
|
version: '0'
|
80
82
|
requirements: []
|
81
|
-
rubygems_version: 3.6.
|
83
|
+
rubygems_version: 3.6.7
|
82
84
|
specification_version: 4
|
83
85
|
summary: activemodel form object pattern
|
84
86
|
test_files: []
|
@@ -1,67 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActiveRecordCompose
|
4
|
-
# = Attribute \Querying
|
5
|
-
#
|
6
|
-
# This provides predicate methods based on the attributes.
|
7
|
-
#
|
8
|
-
# class AccountRegistration < ActiveRecordCompose::Model
|
9
|
-
# def initialize
|
10
|
-
# @account = Account.new
|
11
|
-
# super()
|
12
|
-
# models << account
|
13
|
-
# end
|
14
|
-
#
|
15
|
-
# attribute :original_attr
|
16
|
-
# delegate_attribute :name, :email, to: :account
|
17
|
-
#
|
18
|
-
# private
|
19
|
-
#
|
20
|
-
# attr_reader :account
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
# model = AccountRegistration.new
|
24
|
-
#
|
25
|
-
# model.name #=> nil
|
26
|
-
# model.name? #=> false
|
27
|
-
# model.name = 'Alice'
|
28
|
-
# model.name? #=> true
|
29
|
-
#
|
30
|
-
# model.original_attr = "Bob"
|
31
|
-
# model.original_attr? #=> true
|
32
|
-
# model.original_attr = ""
|
33
|
-
# model.original_attr? #=> false
|
34
|
-
#
|
35
|
-
# # If the value is numeric, it returns the result of checking whether it is zero or not.
|
36
|
-
# # This behavior is consistent with `ActiveRecord::AttributeMethods::Query`.
|
37
|
-
# model.original_attr = 123
|
38
|
-
# model.original_attr? #=> true
|
39
|
-
# model.original_attr = 0
|
40
|
-
# model.original_attr? #=> false
|
41
|
-
#
|
42
|
-
module AttributeQuerying
|
43
|
-
extend ActiveSupport::Concern
|
44
|
-
include ActiveModel::AttributeMethods
|
45
|
-
|
46
|
-
included do
|
47
|
-
attribute_method_suffix "?", parameters: false
|
48
|
-
end
|
49
|
-
|
50
|
-
private
|
51
|
-
|
52
|
-
def attribute?(attr_name)
|
53
|
-
value = public_send(attr_name)
|
54
|
-
|
55
|
-
case value
|
56
|
-
when true then true
|
57
|
-
when false, nil then false
|
58
|
-
else
|
59
|
-
if value.respond_to?(:zero?)
|
60
|
-
!value.zero?
|
61
|
-
else
|
62
|
-
value.present?
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
@@ -1,95 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActiveRecordCompose
|
4
|
-
# = Delegate \Attribute
|
5
|
-
#
|
6
|
-
# It provides a macro description that expresses access to the attributes of the AR model through delegation.
|
7
|
-
#
|
8
|
-
# class AccountRegistration < ActiveRecordCompose::Model
|
9
|
-
# def initialize(account, attributes = {})
|
10
|
-
# @account = account
|
11
|
-
# super(attributes)
|
12
|
-
# models.push(account)
|
13
|
-
# end
|
14
|
-
#
|
15
|
-
# attribute :original_attribute, :string, default: 'qux'
|
16
|
-
#
|
17
|
-
# # like a `delegate :name, :name=, to: :account`
|
18
|
-
# delegate_attribute :name, to: :account
|
19
|
-
#
|
20
|
-
# private
|
21
|
-
#
|
22
|
-
# attr_reader :account
|
23
|
-
# end
|
24
|
-
#
|
25
|
-
# account = Account.new
|
26
|
-
# account.name = 'foo'
|
27
|
-
#
|
28
|
-
# registration = AccountRegistration.new(account)
|
29
|
-
# registration.name #=> 'foo' # delegate to account#name
|
30
|
-
#
|
31
|
-
# registration.name = 'bar' # delegate to account#name=
|
32
|
-
# account.name #=> 'bar'
|
33
|
-
#
|
34
|
-
# # Attributes defined in delegate_attribute will be included in the original `#attributes`.
|
35
|
-
# registration.attributes #=> { 'original_attribute' => 'qux', 'name' => 'bar' }
|
36
|
-
#
|
37
|
-
module DelegateAttribute
|
38
|
-
extend ActiveSupport::Concern
|
39
|
-
include ActiveModel::Attributes
|
40
|
-
|
41
|
-
# steep:ignore:start
|
42
|
-
if defined?(Data)
|
43
|
-
Delegation = Data.define(:attribute, :to, :allow_nil) do
|
44
|
-
def reader = attribute.to_s
|
45
|
-
def writer = "#{attribute}="
|
46
|
-
end
|
47
|
-
else
|
48
|
-
Delegation = Struct.new(:attribute, :to, :allow_nil, keyword_init: true) do
|
49
|
-
def reader = attribute.to_s
|
50
|
-
def writer = "#{attribute}="
|
51
|
-
end
|
52
|
-
end
|
53
|
-
# steep:ignore:end
|
54
|
-
|
55
|
-
included do
|
56
|
-
# @type self: Class
|
57
|
-
class_attribute :delegated_attributes, instance_writer: false
|
58
|
-
end
|
59
|
-
|
60
|
-
module ClassMethods
|
61
|
-
# Defines the reader and writer for the specified attribute.
|
62
|
-
#
|
63
|
-
def delegate_attribute(*attributes, to:, allow_nil: nil)
|
64
|
-
delegations = attributes.map { Delegation.new(attribute: _1.to_s, to:, allow_nil:) }
|
65
|
-
|
66
|
-
delegations.map do |delegation|
|
67
|
-
delegate delegation.reader, delegation.writer, to: delegation.to, allow_nil: delegation.allow_nil
|
68
|
-
define_attribute_methods delegation.attribute
|
69
|
-
end
|
70
|
-
|
71
|
-
self.delegated_attributes = (delegated_attributes.to_a + delegations).reverse.uniq { _1.attribute }.reverse
|
72
|
-
end
|
73
|
-
|
74
|
-
# Returns a array of attribute name.
|
75
|
-
# Attributes declared with `delegate_attribute` are also merged.
|
76
|
-
#
|
77
|
-
# @return [Array<String>] array of attribute name.
|
78
|
-
def attribute_names = super + delegated_attributes.to_a.map { _1.attribute }
|
79
|
-
end
|
80
|
-
|
81
|
-
# Returns a array of attribute name.
|
82
|
-
# Attributes declared with `delegate_attribute` are also merged.
|
83
|
-
#
|
84
|
-
# @return [Array<String>] array of attribute name.
|
85
|
-
def attribute_names = super + delegated_attributes.to_a.map { _1.attribute }
|
86
|
-
|
87
|
-
# Returns a hash with the attribute name as key and the attribute value as value.
|
88
|
-
# Attributes declared with `delegate_attribute` are also merged.
|
89
|
-
#
|
90
|
-
# @return [Hash] hash with the attribute name as key and the attribute value as value.
|
91
|
-
def attributes
|
92
|
-
super.merge(delegated_attributes.to_a.map { _1.attribute }.to_h { [ _1, public_send(_1) ] })
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|