active_record_compose 1.0.1 → 1.1.1
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 +2 -0
- data/CHANGELOG.md +15 -0
- data/lib/active_record_compose/attributes.rb +63 -55
- data/lib/active_record_compose/callbacks.rb +30 -2
- data/lib/active_record_compose/inspectable.rb +186 -0
- data/lib/active_record_compose/model.rb +2 -270
- data/lib/active_record_compose/persistence.rb +61 -13
- data/lib/active_record_compose/railtie.rb +15 -0
- data/lib/active_record_compose/transaction_support.rb +137 -14
- data/lib/active_record_compose/validations.rb +59 -1
- data/lib/active_record_compose/version.rb +1 -1
- data/lib/active_record_compose.rb +2 -0
- data/sig/_internal/package_private.rbs +37 -18
- data/sig/active_record_compose.rbs +6 -3
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 491f46737a3744c5c5d3e0319de3ee9f64ae21a9adfab39f36cedc478bfa0e7c
|
|
4
|
+
data.tar.gz: 406f4c471e0d7c40ec7b3a0d492f8e7e6c96d93fd4b43204a128bf52c1f7ed4c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9ff0e91cfb0ca22322d1b9ca47c32f09372afa206e9fa6690c1dfe5ca30d221269ccc55fe17556627234ea8a7019118ddec8a46ad24a52c47239e694dbcac323
|
|
7
|
+
data.tar.gz: 925001a566ee81600ce91862c0c95f241dba2cd735f6ecbc3e987e83e6cd6f59d217e67afa4bed0a981f35c16645661ef7eeb00adb18df92999f9127e22116e0
|
data/.yardopts
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.1.1] - 2025-12-04
|
|
4
|
+
|
|
5
|
+
* fix: the save method would return nil instead of false.
|
|
6
|
+
* doc: We've simplified the documentation comment yard.
|
|
7
|
+
|
|
8
|
+
## [1.1.0] - 2025-11-19
|
|
9
|
+
|
|
10
|
+
* Implemented ActiveRecord-like #inspect
|
|
11
|
+
In activerecord's `#inspect`, the string is a list of attributes, and we have reproduced a similar format.
|
|
12
|
+
(https://github.com/hamajyotan/active_record_compose/pull/45)
|
|
13
|
+
* `.with_connection` `.lease_connection` and `.connection` are deprecated. Use `ActiveRecord::Base.with_connection` etc. instead.
|
|
14
|
+
(https://github.com/hamajyotan/active_record_compose/pull/46)
|
|
15
|
+
* refactor: Remove `ActiveRecord::Transactions` module dependency
|
|
16
|
+
(https://github.com/hamajyotan/active_record_compose/pull/44)
|
|
17
|
+
|
|
3
18
|
## [1.0.1] - 2025-10-17
|
|
4
19
|
|
|
5
20
|
* Removed the private interface `composite_primary_key?`
|
|
@@ -5,8 +5,6 @@ require_relative "attributes/delegation"
|
|
|
5
5
|
require_relative "attributes/querying"
|
|
6
6
|
|
|
7
7
|
module ActiveRecordCompose
|
|
8
|
-
# @private
|
|
9
|
-
#
|
|
10
8
|
# Provides attribute-related functionality for use within ActiveRecordCompose::Model.
|
|
11
9
|
#
|
|
12
10
|
# This module allows you to define attributes on your composed model, including support
|
|
@@ -56,45 +54,32 @@ module ActiveRecordCompose
|
|
|
56
54
|
include ActiveModel::Attributes
|
|
57
55
|
|
|
58
56
|
included do
|
|
57
|
+
# @type self: Class
|
|
58
|
+
|
|
59
59
|
include Querying
|
|
60
60
|
|
|
61
|
-
# @type self: Class
|
|
62
61
|
class_attribute :delegated_attributes, instance_writer: false
|
|
63
62
|
end
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
#
|
|
69
|
-
# class AccountRegistration < ActiveRecordCompose::Model
|
|
70
|
-
# def initialize(account, attributes = {})
|
|
71
|
-
# @account = account
|
|
72
|
-
# super(attributes)
|
|
73
|
-
# models.push(account)
|
|
74
|
-
# end
|
|
75
|
-
#
|
|
76
|
-
# attribute :original_attribute, :string, default: "qux"
|
|
77
|
-
# delegate_attribute :name, to: :account
|
|
78
|
-
#
|
|
79
|
-
# private
|
|
80
|
-
#
|
|
81
|
-
# attr_reader :account
|
|
82
|
-
# end
|
|
83
|
-
#
|
|
84
|
-
# account = Account.new
|
|
85
|
-
# account.name = "foo"
|
|
86
|
-
#
|
|
87
|
-
# registration = AccountRegistration.new(account)
|
|
88
|
-
# registration.name # => "foo" (delegated)
|
|
89
|
-
# registration.name? # => true (delegated attribute method + `?`)
|
|
90
|
-
#
|
|
91
|
-
# registration.name = "bar" # => updates account.name
|
|
92
|
-
# account.name # => "bar"
|
|
93
|
-
# account.name? # => true
|
|
64
|
+
# steep:ignore:start
|
|
65
|
+
|
|
66
|
+
class_methods do
|
|
67
|
+
# Provides a method of attribute access to the encapsulated model.
|
|
94
68
|
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
69
|
+
# It provides a way to access the attributes of the model it encompasses,
|
|
70
|
+
# allowing transparent access as if it had those attributes itself.
|
|
97
71
|
#
|
|
72
|
+
# @param [Array<Symbol, String>] attributes
|
|
73
|
+
# attributes A variable-length list of attribute names to delegate.
|
|
74
|
+
# @param [Symbol, String] to
|
|
75
|
+
# The target object to which attributes are delegated (keyword argument).
|
|
76
|
+
# @param [Boolean] allow_nil
|
|
77
|
+
# allow_nil Whether to allow nil values. Defaults to false.
|
|
78
|
+
# @example Basic usage
|
|
79
|
+
# delegate_attribute :name, :email, to: :profile
|
|
80
|
+
# @example Allowing nil
|
|
81
|
+
# delegate_attribute :bio, to: :profile, allow_nil: true
|
|
82
|
+
# @see Module#delegate for similar behavior in ActiveSupport
|
|
98
83
|
def delegate_attribute(*attributes, to:, allow_nil: false)
|
|
99
84
|
if to.start_with?("@")
|
|
100
85
|
raise ArgumentError, "Instance variables cannot be specified in delegate to. (#{to})"
|
|
@@ -107,45 +92,68 @@ module ActiveRecordCompose
|
|
|
107
92
|
end
|
|
108
93
|
|
|
109
94
|
# Returns a array of attribute name.
|
|
110
|
-
# Attributes declared with
|
|
95
|
+
# Attributes declared with {.delegate_attribute} are also merged.
|
|
111
96
|
#
|
|
97
|
+
# @see #attribute_names
|
|
112
98
|
# @return [Array<String>] array of attribute name.
|
|
113
99
|
def attribute_names = super + delegated_attributes.to_a.map { _1.attribute_name }
|
|
114
100
|
end
|
|
115
101
|
|
|
102
|
+
# steep:ignore:end
|
|
103
|
+
|
|
116
104
|
# Returns a array of attribute name.
|
|
117
|
-
# Attributes declared with
|
|
105
|
+
# Attributes declared with {.delegate_attribute} are also merged.
|
|
106
|
+
#
|
|
107
|
+
# class Foo < ActiveRecordCompose::Base
|
|
108
|
+
# def initialize(attributes = {})
|
|
109
|
+
# @account = Account.new
|
|
110
|
+
# super
|
|
111
|
+
# end
|
|
112
|
+
#
|
|
113
|
+
# attribute :confirmation, :boolean, default: false # plain attribute
|
|
114
|
+
# delegate_attribute :name, to: :account # delegated attribute
|
|
115
|
+
#
|
|
116
|
+
# private
|
|
117
|
+
#
|
|
118
|
+
# attr_reader :account
|
|
119
|
+
# end
|
|
118
120
|
#
|
|
121
|
+
# Foo.attribute_names # Returns the merged state of plain and delegated attributes
|
|
122
|
+
# # => ["confirmation" ,"name"]
|
|
123
|
+
#
|
|
124
|
+
# foo = Foo.new
|
|
125
|
+
# foo.attribute_names # Similar behavior for instance method version
|
|
126
|
+
# # => ["confirmation", "name"]
|
|
127
|
+
#
|
|
128
|
+
# @see #attributes
|
|
119
129
|
# @return [Array<String>] array of attribute name.
|
|
120
130
|
def attribute_names = super + delegated_attributes.to_a.map { _1.attribute_name }
|
|
121
131
|
|
|
122
132
|
# Returns a hash with the attribute name as key and the attribute value as value.
|
|
123
|
-
# Attributes declared with
|
|
133
|
+
# Attributes declared with {.delegate_attribute} are also merged.
|
|
124
134
|
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
# super(attributes)
|
|
131
|
-
# models.push(account)
|
|
132
|
-
# end
|
|
135
|
+
# class Foo < ActiveRecordCompose::Base
|
|
136
|
+
# def initialize(attributes = {})
|
|
137
|
+
# @account = Account.new
|
|
138
|
+
# super
|
|
139
|
+
# end
|
|
133
140
|
#
|
|
134
|
-
#
|
|
135
|
-
#
|
|
141
|
+
# attribute :confirmation, :boolean, default: false # plain attribute
|
|
142
|
+
# delegate_attribute :name, to: :account # delegated attribute
|
|
136
143
|
#
|
|
137
|
-
#
|
|
144
|
+
# private
|
|
138
145
|
#
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
# account = Account.new
|
|
143
|
-
# account.name = "foo"
|
|
146
|
+
# attr_reader :account
|
|
147
|
+
# end
|
|
144
148
|
#
|
|
145
|
-
#
|
|
149
|
+
# foo = Foo.new
|
|
150
|
+
# foo.name = "Alice"
|
|
151
|
+
# foo.confirmation = true
|
|
146
152
|
#
|
|
147
|
-
#
|
|
153
|
+
# foo.attributes # Returns the merged state of plain and delegated attributes
|
|
154
|
+
# # => { "confirmation" => true, "name" => "Alice" }
|
|
148
155
|
#
|
|
156
|
+
# @return [Hash<String, Object>] hash with the attribute name as key and the attribute value as value.
|
|
149
157
|
def attributes
|
|
150
158
|
super.merge(*delegated_attributes.to_a.map { _1.attribute_hash(self) })
|
|
151
159
|
end
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ActiveRecordCompose
|
|
4
|
-
# @private
|
|
5
|
-
#
|
|
6
4
|
# Provides hooks into the life cycle of an ActiveRecordCompose model,
|
|
7
5
|
# allowing you to insert custom logic before or after changes to the object's state.
|
|
8
6
|
#
|
|
@@ -21,18 +19,48 @@ module ActiveRecordCompose
|
|
|
21
19
|
include ActiveModel::Validations::Callbacks
|
|
22
20
|
|
|
23
21
|
included do
|
|
22
|
+
# @!method self.before_save(*args, &block)
|
|
23
|
+
# Registers a callback to be called before a model is saved.
|
|
24
|
+
|
|
25
|
+
# @!method self.around_save(*args, &block)
|
|
26
|
+
# Registers a callback to be called around the save of a model.
|
|
27
|
+
|
|
28
|
+
# @!method self.after_save(*args, &block)
|
|
29
|
+
# Registers a callback to be called after a model is saved.
|
|
30
|
+
|
|
24
31
|
define_model_callbacks :save
|
|
32
|
+
|
|
33
|
+
# @!method self.before_create(*args, &block)
|
|
34
|
+
# Registers a callback to be called before a model is created.
|
|
35
|
+
|
|
36
|
+
# @!method self.around_create(*args, &block)
|
|
37
|
+
# Registers a callback to be called around the creation of a model.
|
|
38
|
+
|
|
39
|
+
# @!method self.after_create(*args, &block)
|
|
40
|
+
# Registers a callback to be called after a model is created.
|
|
41
|
+
|
|
25
42
|
define_model_callbacks :create
|
|
43
|
+
|
|
44
|
+
# @!method self.before_update(*args, &block)
|
|
45
|
+
# Registers a callback to be called before a model is updated.
|
|
46
|
+
|
|
47
|
+
# @!method self.around_update(*args, &block)
|
|
48
|
+
# Registers a callback to be called around the update of a model.
|
|
49
|
+
|
|
50
|
+
# @!method self.after_update(*args, &block)
|
|
51
|
+
# Registers a callback to be called after a update is updated.
|
|
26
52
|
define_model_callbacks :update
|
|
27
53
|
end
|
|
28
54
|
|
|
29
55
|
private
|
|
30
56
|
|
|
57
|
+
# @private
|
|
31
58
|
# Evaluate while firing callbacks such as `before_save` `after_save`
|
|
32
59
|
# before and after block evaluation.
|
|
33
60
|
#
|
|
34
61
|
def with_callbacks(&block) = run_callbacks(:save) { run_callbacks(callback_context, &block) }
|
|
35
62
|
|
|
63
|
+
# @private
|
|
36
64
|
# Returns the symbol representing the callback context, which is `:create` if the record
|
|
37
65
|
# is new, or `:update` if it has been persisted.
|
|
38
66
|
#
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/parameter_filter"
|
|
4
|
+
require_relative "attributes"
|
|
5
|
+
|
|
6
|
+
module ActiveRecordCompose
|
|
7
|
+
# It provides #inspect behavior.
|
|
8
|
+
# It tries to replicate the inspect format provided by ActiveRecord as closely as possible.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# class Model < ActiveRecordCompose::Model
|
|
12
|
+
# def initialize(ar_model)
|
|
13
|
+
# @ar_model = ar_model
|
|
14
|
+
# super
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# attribute :foo, :date, default: -> { Date.today }
|
|
18
|
+
# delegate_attribute :bar, to: :ar_model
|
|
19
|
+
#
|
|
20
|
+
# private attr_reader :ar_model
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# m = Model.new(ar_model)
|
|
24
|
+
# m.inspect #=> #<Model:0x00007ff0fe75fe58 foo: "2025-11-14", bar: "bar">
|
|
25
|
+
#
|
|
26
|
+
# @example
|
|
27
|
+
# class Model < ActiveRecordCompose::Model
|
|
28
|
+
# self.filter_attributes += %i[foo]
|
|
29
|
+
#
|
|
30
|
+
# # ...
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# m = Model.new(ar_model)
|
|
34
|
+
# m.inspect #=> #<Model:0x00007ff0fe75fe58 foo: [FILTERED], bar: "bar">
|
|
35
|
+
#
|
|
36
|
+
module Inspectable
|
|
37
|
+
extend ActiveSupport::Concern
|
|
38
|
+
include ActiveRecordCompose::Attributes
|
|
39
|
+
|
|
40
|
+
# steep:ignore:start
|
|
41
|
+
|
|
42
|
+
# @private
|
|
43
|
+
FILTERED_MASK =
|
|
44
|
+
Class.new(DelegateClass(::String)) do
|
|
45
|
+
def pretty_print(pp)
|
|
46
|
+
pp.text __getobj__
|
|
47
|
+
end
|
|
48
|
+
end.new(ActiveSupport::ParameterFilter::FILTERED).freeze
|
|
49
|
+
private_constant :FILTERED_MASK
|
|
50
|
+
|
|
51
|
+
# steep:ignore:end
|
|
52
|
+
|
|
53
|
+
included do
|
|
54
|
+
self.filter_attributes = []
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# steep:ignore:start
|
|
58
|
+
|
|
59
|
+
class_methods do
|
|
60
|
+
# Returns columns not to expose when invoking {#inspect}.
|
|
61
|
+
#
|
|
62
|
+
# @return [Array<Symbol>]
|
|
63
|
+
# @see #inspect
|
|
64
|
+
def filter_attributes
|
|
65
|
+
if @filter_attributes.nil?
|
|
66
|
+
superclass.filter_attributes
|
|
67
|
+
else
|
|
68
|
+
@filter_attributes
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Specify columns not to expose when invoking {#inspect}.
|
|
73
|
+
#
|
|
74
|
+
# @param [Array<Symbol>] value
|
|
75
|
+
# @see #inspect
|
|
76
|
+
def filter_attributes=(value)
|
|
77
|
+
@inspection_filter = nil
|
|
78
|
+
@filter_attributes = value
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @private
|
|
82
|
+
def inspection_filter
|
|
83
|
+
if @filter_attributes.nil?
|
|
84
|
+
superclass.inspection_filter
|
|
85
|
+
else
|
|
86
|
+
@inspection_filter ||= ActiveSupport::ParameterFilter.new(filter_attributes, mask: FILTERED_MASK)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def inherited(subclass)
|
|
93
|
+
super
|
|
94
|
+
|
|
95
|
+
subclass.class_eval do
|
|
96
|
+
@inspection_filter = nil
|
|
97
|
+
@filter_attributes ||= nil
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# steep:ignore:end
|
|
103
|
+
|
|
104
|
+
# Returns a formatted string representation of the record's attributes.
|
|
105
|
+
# It tries to replicate the inspect format provided by ActiveRecord as closely as possible.
|
|
106
|
+
#
|
|
107
|
+
# @example
|
|
108
|
+
# class Model < ActiveRecordCompose::Model
|
|
109
|
+
# def initialize(ar_model)
|
|
110
|
+
# @ar_model = ar_model
|
|
111
|
+
# super
|
|
112
|
+
# end
|
|
113
|
+
#
|
|
114
|
+
# attribute :foo, :date, default: -> { Date.today }
|
|
115
|
+
# delegate_attribute :bar, to: :ar_model
|
|
116
|
+
#
|
|
117
|
+
# private attr_reader :ar_model
|
|
118
|
+
# end
|
|
119
|
+
#
|
|
120
|
+
# m = Model.new(ar_model)
|
|
121
|
+
# m.inspect #=> #<Model:0x00007ff0fe75fe58 foo: "2025-11-14", bar: "bar">
|
|
122
|
+
#
|
|
123
|
+
# @example use {.filter_attributes}
|
|
124
|
+
# class Model < ActiveRecordCompose::Model
|
|
125
|
+
# self.filter_attributes += %i[foo]
|
|
126
|
+
#
|
|
127
|
+
# # ...
|
|
128
|
+
# end
|
|
129
|
+
#
|
|
130
|
+
# m = Model.new(ar_model)
|
|
131
|
+
# m.inspect #=> #<Model:0x00007ff0fe75fe58 foo: [FILTERED], bar: "bar">
|
|
132
|
+
#
|
|
133
|
+
# @return [String] formatted string representation of the record's attributes.
|
|
134
|
+
# @see .filter_attributes
|
|
135
|
+
def inspect
|
|
136
|
+
inspection =
|
|
137
|
+
if @attributes
|
|
138
|
+
attributes.map { |k, v| "#{k}: #{format_for_inspect(k, v)}" }.join(", ")
|
|
139
|
+
else
|
|
140
|
+
"not initialized"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
"#<#{self.class} #{inspection}>"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# It takes a PP and pretty prints that record.
|
|
147
|
+
#
|
|
148
|
+
def pretty_print(pp)
|
|
149
|
+
pp.object_address_group(self) do
|
|
150
|
+
if @attributes
|
|
151
|
+
attrs = attributes
|
|
152
|
+
pp.seplist(attrs.keys, proc { pp.text "," }) do |attr|
|
|
153
|
+
pp.breakable " "
|
|
154
|
+
pp.group(1) do
|
|
155
|
+
pp.text attr
|
|
156
|
+
pp.text ":"
|
|
157
|
+
pp.breakable
|
|
158
|
+
pp.text format_for_inspect(attr, attrs[attr])
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
else
|
|
162
|
+
pp.breakable " "
|
|
163
|
+
pp.text "not initialized"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
|
|
170
|
+
# @private
|
|
171
|
+
def format_for_inspect(name, value)
|
|
172
|
+
return value.inspect if value.nil?
|
|
173
|
+
|
|
174
|
+
inspected_value =
|
|
175
|
+
if value.is_a?(String) && value.length > 50
|
|
176
|
+
"#{value[0, 50]}...".inspect
|
|
177
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
|
178
|
+
%("#{value.to_fs(:inspect)}")
|
|
179
|
+
else
|
|
180
|
+
value.inspect
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
self.class.inspection_filter.filter_param(name, inspected_value)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "attributes"
|
|
4
4
|
require_relative "composed_collection"
|
|
5
|
+
require_relative "inspectable"
|
|
5
6
|
require_relative "persistence"
|
|
6
7
|
require_relative "transaction_support"
|
|
7
8
|
require_relative "validations"
|
|
@@ -86,274 +87,7 @@ module ActiveRecordCompose
|
|
|
86
87
|
include ActiveRecordCompose::Persistence
|
|
87
88
|
include ActiveRecordCompose::Validations
|
|
88
89
|
include ActiveRecordCompose::TransactionSupport
|
|
89
|
-
|
|
90
|
-
begin
|
|
91
|
-
# @group Model Core
|
|
92
|
-
|
|
93
|
-
# @!method self.delegate_attribute(*attributes, to:, allow_nil: false)
|
|
94
|
-
# Provides a method of attribute access to the encapsulated model.
|
|
95
|
-
#
|
|
96
|
-
# It provides a way to access the attributes of the model it encompasses,
|
|
97
|
-
# allowing transparent access as if it had those attributes itself.
|
|
98
|
-
#
|
|
99
|
-
# @param [Array<Symbol, String>] attributes
|
|
100
|
-
# attributes A variable-length list of attribute names to delegate.
|
|
101
|
-
# @param [Symbol, String] to
|
|
102
|
-
# The target object to which attributes are delegated (keyword argument).
|
|
103
|
-
# @param [Boolean] allow_nil
|
|
104
|
-
# allow_nil Whether to allow nil values. Defaults to false.
|
|
105
|
-
# @example Basic usage
|
|
106
|
-
# delegate_attribute :name, :email, to: :profile
|
|
107
|
-
# @example Allowing nil
|
|
108
|
-
# delegate_attribute :bio, to: :profile, allow_nil: true
|
|
109
|
-
# @see Module#delegate for similar behavior in ActiveSupport
|
|
110
|
-
|
|
111
|
-
# @!method self.attribute_names
|
|
112
|
-
# Returns a array of attribute name.
|
|
113
|
-
# Attributes declared with {.delegate_attribute} are also merged.
|
|
114
|
-
#
|
|
115
|
-
# @see #attribute_names
|
|
116
|
-
# @return [Array<String>] array of attribute name.
|
|
117
|
-
|
|
118
|
-
# @!method attribute_names
|
|
119
|
-
# Returns a array of attribute name.
|
|
120
|
-
# Attributes declared with {.delegate_attribute} are also merged.
|
|
121
|
-
#
|
|
122
|
-
# class Foo < ActiveRecordCompose::Base
|
|
123
|
-
# def initialize(attributes = {})
|
|
124
|
-
# @account = Account.new
|
|
125
|
-
# super
|
|
126
|
-
# end
|
|
127
|
-
#
|
|
128
|
-
# attribute :confirmation, :boolean, default: false # plain attribute
|
|
129
|
-
# delegate_attribute :name, to: :account # delegated attribute
|
|
130
|
-
#
|
|
131
|
-
# private
|
|
132
|
-
#
|
|
133
|
-
# attr_reader :account
|
|
134
|
-
# end
|
|
135
|
-
#
|
|
136
|
-
# Foo.attribute_names # Returns the merged state of plain and delegated attributes
|
|
137
|
-
# # => ["confirmation" ,"name"]
|
|
138
|
-
#
|
|
139
|
-
# foo = Foo.new
|
|
140
|
-
# foo.attribute_names # Similar behavior for instance method version
|
|
141
|
-
# # => ["confirmation", "name"]
|
|
142
|
-
#
|
|
143
|
-
# @see #attributes
|
|
144
|
-
# @return [Array<String>] array of attribute name.
|
|
145
|
-
|
|
146
|
-
# @!method attributes
|
|
147
|
-
# Returns a hash with the attribute name as key and the attribute value as value.
|
|
148
|
-
# Attributes declared with {.delegate_attribute} are also merged.
|
|
149
|
-
#
|
|
150
|
-
# class Foo < ActiveRecordCompose::Base
|
|
151
|
-
# def initialize(attributes = {})
|
|
152
|
-
# @account = Account.new
|
|
153
|
-
# super
|
|
154
|
-
# end
|
|
155
|
-
#
|
|
156
|
-
# attribute :confirmation, :boolean, default: false # plain attribute
|
|
157
|
-
# delegate_attribute :name, to: :account # delegated attribute
|
|
158
|
-
#
|
|
159
|
-
# private
|
|
160
|
-
#
|
|
161
|
-
# attr_reader :account
|
|
162
|
-
# end
|
|
163
|
-
#
|
|
164
|
-
# foo = Foo.new
|
|
165
|
-
# foo.name = "Alice"
|
|
166
|
-
# foo.confirmation = true
|
|
167
|
-
#
|
|
168
|
-
# foo.attributes # Returns the merged state of plain and delegated attributes
|
|
169
|
-
# # => { "confirmation" => true, "name" => "Alice" }
|
|
170
|
-
#
|
|
171
|
-
# @return [Hash<String, Object>] hash with the attribute name as key and the attribute value as value.
|
|
172
|
-
|
|
173
|
-
# @!method persisted?
|
|
174
|
-
# Returns true if model is persisted.
|
|
175
|
-
#
|
|
176
|
-
# By overriding this definition, you can control the callbacks that are triggered when a save is made.
|
|
177
|
-
# For example, returning false will trigger before_create, around_create and after_create,
|
|
178
|
-
# and returning true will trigger {.before_update}, {.around_update} and {.after_update}.
|
|
179
|
-
#
|
|
180
|
-
# @return [Boolean] returns true if model is persisted.
|
|
181
|
-
# @example
|
|
182
|
-
# # A model where persistence is always false
|
|
183
|
-
# class Foo < ActiveRecordCompose::Model
|
|
184
|
-
# before_save { puts "before_save called" }
|
|
185
|
-
# before_create { puts "before_create called" }
|
|
186
|
-
# before_update { puts "before_update called" }
|
|
187
|
-
# after_update { puts "after_update called" }
|
|
188
|
-
# after_create { puts "after_create called" }
|
|
189
|
-
# after_save { puts "after_save called" }
|
|
190
|
-
#
|
|
191
|
-
# def persisted? = false
|
|
192
|
-
# end
|
|
193
|
-
#
|
|
194
|
-
# # A model where persistence is always true
|
|
195
|
-
# class Bar < Foo
|
|
196
|
-
# def persisted? = true
|
|
197
|
-
# end
|
|
198
|
-
#
|
|
199
|
-
# Foo.new.save!
|
|
200
|
-
# # before_save called
|
|
201
|
-
# # before_create called
|
|
202
|
-
# # after_create called
|
|
203
|
-
# # after_save called
|
|
204
|
-
#
|
|
205
|
-
# Bar.new.save!
|
|
206
|
-
# # before_save called
|
|
207
|
-
# # before_update called
|
|
208
|
-
# # after_update called
|
|
209
|
-
# # after_save called
|
|
210
|
-
|
|
211
|
-
# @endgroup
|
|
212
|
-
|
|
213
|
-
# @group Validations
|
|
214
|
-
|
|
215
|
-
# @!method valid?(context = nil)
|
|
216
|
-
# Runs all the validations and returns the result as true or false.
|
|
217
|
-
# @param context Validation context.
|
|
218
|
-
# @return [Boolean] true on success, false on failure.
|
|
219
|
-
|
|
220
|
-
# @!method validate(context = nil)
|
|
221
|
-
# Alias for {#valid?}
|
|
222
|
-
# @see #valid? Validation context.
|
|
223
|
-
# @param context
|
|
224
|
-
# @return [Boolean] true on success, false on failure.
|
|
225
|
-
|
|
226
|
-
# @!method validate!(context = nil)
|
|
227
|
-
# @see #valid?
|
|
228
|
-
# Runs all the validations within the specified context.
|
|
229
|
-
# no errors are found, raises `ActiveRecord::RecordInvalid` otherwise.
|
|
230
|
-
# @param context Validation context.
|
|
231
|
-
# @raise ActiveRecord::RecordInvalid
|
|
232
|
-
|
|
233
|
-
# @!method errors
|
|
234
|
-
# Returns the `ActiveModel::Errors` object that holds all information about attribute error messages.
|
|
235
|
-
#
|
|
236
|
-
# The `ActiveModel::Base` implementation itself,
|
|
237
|
-
# but also aggregates error information for objects stored in {#models} when validation is performed.
|
|
238
|
-
#
|
|
239
|
-
# class Account < ActiveRecord::Base
|
|
240
|
-
# validates :name, :email, presence: true
|
|
241
|
-
# end
|
|
242
|
-
#
|
|
243
|
-
# class AccountRegistration < ActiveRecordCompose::Model
|
|
244
|
-
# def initialize(attributes = {})
|
|
245
|
-
# @account = Account.new
|
|
246
|
-
# super(attributes)
|
|
247
|
-
# models << account
|
|
248
|
-
# end
|
|
249
|
-
#
|
|
250
|
-
# attribute :confirmation, :boolean, default: false
|
|
251
|
-
# validates :confirmation, presence: true
|
|
252
|
-
#
|
|
253
|
-
# private
|
|
254
|
-
#
|
|
255
|
-
# attr_reader :account
|
|
256
|
-
# end
|
|
257
|
-
#
|
|
258
|
-
# registration = AccountRegistration
|
|
259
|
-
# registration.valid?
|
|
260
|
-
# #=> false
|
|
261
|
-
#
|
|
262
|
-
# # In addition to the model's own validation error information (`confirmation`), also aggregates
|
|
263
|
-
# # error information for objects stored in `account` (`name`, `email`) when validation is performed.
|
|
264
|
-
#
|
|
265
|
-
# registration.errors.map { _1.attribute } #=> [:name, :email, :confirmation]
|
|
266
|
-
#
|
|
267
|
-
# @return [ActiveModel::Errors]
|
|
268
|
-
|
|
269
|
-
# @endgroup
|
|
270
|
-
|
|
271
|
-
# @group Persistences
|
|
272
|
-
|
|
273
|
-
# @!method save(**options)
|
|
274
|
-
# Save the models that exist in models.
|
|
275
|
-
# Returns false if any of the targets fail, true if all succeed.
|
|
276
|
-
#
|
|
277
|
-
# The save is performed within a single transaction.
|
|
278
|
-
#
|
|
279
|
-
# Only the `:validate` option takes effect as it is required internally.
|
|
280
|
-
# However, we do not recommend explicitly specifying `validate: false` to skip validation.
|
|
281
|
-
# Additionally, the `:context` option is not accepted.
|
|
282
|
-
# The need for such a value indicates that operations from multiple contexts are being processed.
|
|
283
|
-
# If the contexts differ, we recommend separating them into different model definitions.
|
|
284
|
-
#
|
|
285
|
-
# @params [Hash] Optional parameters.
|
|
286
|
-
# @option options [Boolean] :validate Whether to run validations.
|
|
287
|
-
# This option is intended for internal use only.
|
|
288
|
-
# Users should avoid explicitly passing <tt>validate: false</tt>,
|
|
289
|
-
# as skipping validations can lead to unexpected behavior.
|
|
290
|
-
# @return [Boolean] returns true on success, false on failure.
|
|
291
|
-
|
|
292
|
-
# @!method save!(**options)
|
|
293
|
-
# Behavior is same to {#save}, but raises an exception prematurely on failure.
|
|
294
|
-
# @see #save
|
|
295
|
-
# @raise ActiveRecord::RecordInvalid
|
|
296
|
-
# @raise ActiveRecord::RecordNotSaved
|
|
297
|
-
|
|
298
|
-
# @!method update(attributes)
|
|
299
|
-
# Assign attributes and {#save}.
|
|
300
|
-
#
|
|
301
|
-
# @param [Hash<String, Object>] attributes
|
|
302
|
-
# new attributes.
|
|
303
|
-
# @see #save
|
|
304
|
-
# @return [Boolean] returns true on success, false on failure.
|
|
305
|
-
|
|
306
|
-
# @!method update!(attributes)
|
|
307
|
-
# Behavior is same to {#update}, but raises an exception prematurely on failure.
|
|
308
|
-
#
|
|
309
|
-
# @param [Hash<String, Object>] attributes
|
|
310
|
-
# new attributes.
|
|
311
|
-
# @see #save
|
|
312
|
-
# @see #update
|
|
313
|
-
# @raise ActiveRecord::RecordInvalid
|
|
314
|
-
# @raise ActiveRecord::RecordNotSaved
|
|
315
|
-
|
|
316
|
-
# @endgroup
|
|
317
|
-
|
|
318
|
-
# @group Callbacks
|
|
319
|
-
|
|
320
|
-
# @!method self.before_save(*args, &block)
|
|
321
|
-
# Registers a callback to be called before a model is saved.
|
|
322
|
-
|
|
323
|
-
# @!method self.around_save(*args, &block)
|
|
324
|
-
# Registers a callback to be called around the save of a model.
|
|
325
|
-
|
|
326
|
-
# @!method self.after_save(*args, &block)
|
|
327
|
-
# Registers a callback to be called after a model is saved.
|
|
328
|
-
|
|
329
|
-
# @!method self.before_create(*args, &block)
|
|
330
|
-
# Registers a callback to be called before a model is created.
|
|
331
|
-
|
|
332
|
-
# @!method self.around_create(*args, &block)
|
|
333
|
-
# Registers a callback to be called around the creation of a model.
|
|
334
|
-
|
|
335
|
-
# @!method self.after_create(*args, &block)
|
|
336
|
-
# Registers a callback to be called after a model is created.
|
|
337
|
-
|
|
338
|
-
# @!method self.before_update(*args, &block)
|
|
339
|
-
# Registers a callback to be called before a model is updated.
|
|
340
|
-
|
|
341
|
-
# @!method self.around_update(*args, &block)
|
|
342
|
-
# Registers a callback to be called around the update of a model.
|
|
343
|
-
|
|
344
|
-
# @!method self.after_update(*args, &block)
|
|
345
|
-
# Registers a callback to be called after a update is updated.
|
|
346
|
-
|
|
347
|
-
# @!method self.after_commit(*args, &block)
|
|
348
|
-
# Registers a block to be called after the transaction is fully committed.
|
|
349
|
-
|
|
350
|
-
# @!method self.after_rollback(*args, &block)
|
|
351
|
-
# Registers a block to be called after the transaction is rolled back.
|
|
352
|
-
|
|
353
|
-
# @endgroup
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
# @group Model Core
|
|
90
|
+
include ActiveRecordCompose::Inspectable
|
|
357
91
|
|
|
358
92
|
def initialize(attributes = {})
|
|
359
93
|
super
|
|
@@ -394,7 +128,5 @@ module ActiveRecordCompose
|
|
|
394
128
|
# @return [ActiveRecordCompose::ComposedCollection]
|
|
395
129
|
#
|
|
396
130
|
def models = @__models ||= ActiveRecordCompose::ComposedCollection.new(self)
|
|
397
|
-
|
|
398
|
-
# @endgroup
|
|
399
131
|
end
|
|
400
132
|
end
|
|
@@ -6,7 +6,6 @@ require_relative "composed_collection"
|
|
|
6
6
|
module ActiveRecordCompose
|
|
7
7
|
using ComposedCollection::PackagePrivate
|
|
8
8
|
|
|
9
|
-
# @private
|
|
10
9
|
module Persistence
|
|
11
10
|
extend ActiveSupport::Concern
|
|
12
11
|
include ActiveRecordCompose::Callbacks
|
|
@@ -22,6 +21,11 @@ module ActiveRecordCompose
|
|
|
22
21
|
# The need for such a value indicates that operations from multiple contexts are being processed.
|
|
23
22
|
# If the contexts differ, we recommend separating them into different model definitions.
|
|
24
23
|
#
|
|
24
|
+
# @param options [Hash] parameters.
|
|
25
|
+
# @option options [Boolean] :validate Whether to run validations.
|
|
26
|
+
# This option is intended for internal use only.
|
|
27
|
+
# Users should avoid explicitly passing <tt>validate: false</tt>,
|
|
28
|
+
# as skipping validations can lead to unexpected behavior.
|
|
25
29
|
# @return [Boolean] returns true on success, false on failure.
|
|
26
30
|
def save(**options)
|
|
27
31
|
with_callbacks { save_models(**options, bang: false) }
|
|
@@ -29,38 +33,80 @@ module ActiveRecordCompose
|
|
|
29
33
|
false
|
|
30
34
|
end
|
|
31
35
|
|
|
32
|
-
#
|
|
33
|
-
# Unlike #save, an exception is raises on failure.
|
|
34
|
-
#
|
|
35
|
-
# Saving, like `#save`, is performed within a single transaction.
|
|
36
|
-
#
|
|
37
|
-
# Only the `:validate` option takes effect as it is required internally.
|
|
38
|
-
# However, we do not recommend explicitly specifying `validate: false` to skip validation.
|
|
39
|
-
# Additionally, the `:context` option is not accepted.
|
|
40
|
-
# The need for such a value indicates that operations from multiple contexts are being processed.
|
|
41
|
-
# If the contexts differ, we recommend separating them into different model definitions.
|
|
36
|
+
# Behavior is same to {#save}, but raises an exception prematurely on failure.
|
|
42
37
|
#
|
|
38
|
+
# @see #save
|
|
39
|
+
# @raise ActiveRecord::RecordInvalid
|
|
40
|
+
# @raise ActiveRecord::RecordNotSaved
|
|
43
41
|
def save!(**options)
|
|
44
42
|
with_callbacks { save_models(**options, bang: true) } || raise_on_save_error
|
|
45
43
|
end
|
|
46
44
|
|
|
47
|
-
# Assign attributes and save.
|
|
45
|
+
# Assign attributes and {#save}.
|
|
48
46
|
#
|
|
47
|
+
# @param [Hash<String, Object>] attributes
|
|
48
|
+
# new attributes.
|
|
49
|
+
# @see #save
|
|
49
50
|
# @return [Boolean] returns true on success, false on failure.
|
|
50
51
|
def update(attributes)
|
|
51
52
|
assign_attributes(attributes)
|
|
52
53
|
save
|
|
53
54
|
end
|
|
54
55
|
|
|
55
|
-
# Behavior is same to
|
|
56
|
+
# Behavior is same to {#update}, but raises an exception prematurely on failure.
|
|
56
57
|
#
|
|
58
|
+
# @param [Hash<String, Object>] attributes
|
|
59
|
+
# new attributes.
|
|
60
|
+
# @see #save
|
|
61
|
+
# @see #update
|
|
62
|
+
# @raise ActiveRecord::RecordInvalid
|
|
63
|
+
# @raise ActiveRecord::RecordNotSaved
|
|
57
64
|
def update!(attributes)
|
|
58
65
|
assign_attributes(attributes)
|
|
59
66
|
save!
|
|
60
67
|
end
|
|
61
68
|
|
|
69
|
+
# @!method persisted?
|
|
70
|
+
# Returns true if model is persisted.
|
|
71
|
+
#
|
|
72
|
+
# By overriding this definition, you can control the callbacks that are triggered when a save is made.
|
|
73
|
+
# For example, returning false will trigger before_create, around_create and after_create,
|
|
74
|
+
# and returning true will trigger {.before_update}, {.around_update} and {.after_update}.
|
|
75
|
+
#
|
|
76
|
+
# @return [Boolean] returns true if model is persisted.
|
|
77
|
+
# @example
|
|
78
|
+
# # A model where persistence is always false
|
|
79
|
+
# class Foo < ActiveRecordCompose::Model
|
|
80
|
+
# before_save { puts "before_save called" }
|
|
81
|
+
# before_create { puts "before_create called" }
|
|
82
|
+
# before_update { puts "before_update called" }
|
|
83
|
+
# after_update { puts "after_update called" }
|
|
84
|
+
# after_create { puts "after_create called" }
|
|
85
|
+
# after_save { puts "after_save called" }
|
|
86
|
+
#
|
|
87
|
+
# def persisted? = false
|
|
88
|
+
# end
|
|
89
|
+
#
|
|
90
|
+
# # A model where persistence is always true
|
|
91
|
+
# class Bar < Foo
|
|
92
|
+
# def persisted? = true
|
|
93
|
+
# end
|
|
94
|
+
#
|
|
95
|
+
# Foo.new.save!
|
|
96
|
+
# # before_save called
|
|
97
|
+
# # before_create called
|
|
98
|
+
# # after_create called
|
|
99
|
+
# # after_save called
|
|
100
|
+
#
|
|
101
|
+
# Bar.new.save!
|
|
102
|
+
# # before_save called
|
|
103
|
+
# # before_update called
|
|
104
|
+
# # after_update called
|
|
105
|
+
# # after_save called
|
|
106
|
+
|
|
62
107
|
private
|
|
63
108
|
|
|
109
|
+
# @private
|
|
64
110
|
def save_models(bang:, **options)
|
|
65
111
|
models.__wrapped_models.all? do |model|
|
|
66
112
|
if bang
|
|
@@ -71,8 +117,10 @@ module ActiveRecordCompose
|
|
|
71
117
|
end
|
|
72
118
|
end
|
|
73
119
|
|
|
120
|
+
# @private
|
|
74
121
|
def raise_on_save_error = raise ActiveRecord::RecordNotSaved.new(raise_on_save_error_message, self)
|
|
75
122
|
|
|
123
|
+
# @private
|
|
76
124
|
def raise_on_save_error_message = "Failed to save the model."
|
|
77
125
|
end
|
|
78
126
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails"
|
|
4
|
+
require "active_record_compose"
|
|
5
|
+
require "active_record/railtie"
|
|
6
|
+
|
|
7
|
+
module ActiveRecordCompose
|
|
8
|
+
class Railtie < Rails::Railtie
|
|
9
|
+
initializer "active_record_compose.set_filter_attributes", after: "active_record.set_filter_attributes" do
|
|
10
|
+
ActiveSupport.on_load(:active_record) do
|
|
11
|
+
ActiveRecordCompose::Model.filter_attributes += ActiveRecord::Base.filter_attributes
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -1,31 +1,154 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/core_ext/module"
|
|
4
|
+
|
|
3
5
|
module ActiveRecordCompose
|
|
4
|
-
# @private
|
|
5
6
|
module TransactionSupport
|
|
6
7
|
extend ActiveSupport::Concern
|
|
7
|
-
include ActiveRecord::Transactions
|
|
8
8
|
|
|
9
9
|
included do
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
define_callbacks :commit, :rollback, :before_commit, scope: [ :kind, :name ]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# steep:ignore:start
|
|
14
|
+
|
|
15
|
+
class_methods do
|
|
16
|
+
# @private
|
|
17
|
+
# @deprecated
|
|
18
|
+
def with_connection(...)
|
|
19
|
+
ActiveRecord.deprecator.warn("`with_connection` is deprecated. Use `ActiveRecord::Base.with_connection` instead.")
|
|
20
|
+
ActiveRecord::Base.with_connection(...)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @private
|
|
24
|
+
# @deprecated
|
|
25
|
+
def lease_connection(...)
|
|
26
|
+
ActiveRecord.deprecator.warn("`lease_connection` is deprecated. Use `ActiveRecord::Base.lease_connection` instead.")
|
|
27
|
+
ActiveRecord::Base.lease_connection(...)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @private
|
|
31
|
+
# @deprecated
|
|
32
|
+
def connection(...)
|
|
33
|
+
ActiveRecord.deprecator.warn("`connection` is deprecated. Use `ActiveRecord::Base.connection` instead.")
|
|
34
|
+
ActiveRecord::Base.connection(...)
|
|
35
|
+
end
|
|
15
36
|
end
|
|
16
37
|
|
|
17
|
-
|
|
18
|
-
|
|
38
|
+
# steep:ignore:end
|
|
39
|
+
|
|
40
|
+
# steep:ignore:start
|
|
41
|
+
|
|
42
|
+
class_methods do
|
|
43
|
+
# @private
|
|
44
|
+
def before_commit(*args, &block)
|
|
45
|
+
set_options_for_callbacks!(args)
|
|
46
|
+
set_callback(:before_commit, :before, *args, &block)
|
|
47
|
+
end
|
|
19
48
|
|
|
20
|
-
#
|
|
21
|
-
|
|
49
|
+
# Registers a block to be called after the transaction is fully committed.
|
|
50
|
+
#
|
|
51
|
+
def after_commit(*args, &block)
|
|
52
|
+
set_options_for_callbacks!(args, prepend_option)
|
|
53
|
+
set_callback(:commit, :after, *args, &block)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Registers a block to be called after the transaction is rolled back.
|
|
57
|
+
#
|
|
58
|
+
def after_rollback(*args, &block)
|
|
59
|
+
set_options_for_callbacks!(args, prepend_option)
|
|
60
|
+
set_callback(:rollback, :after, *args, &block)
|
|
61
|
+
end
|
|
22
62
|
|
|
23
63
|
private
|
|
24
64
|
|
|
25
|
-
|
|
65
|
+
# @private
|
|
66
|
+
def prepend_option
|
|
67
|
+
if ActiveRecord.run_after_transaction_callbacks_in_order_defined
|
|
68
|
+
{ prepend: true }
|
|
69
|
+
else
|
|
70
|
+
{}
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @private
|
|
75
|
+
def set_options_for_callbacks!(args, enforced_options = {})
|
|
76
|
+
options = args.extract_options!.merge!(enforced_options)
|
|
77
|
+
args << options
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# steep:ignore:end
|
|
82
|
+
|
|
83
|
+
concerning :SupportForActiveRecordConnectionAdaptersTransaction do
|
|
84
|
+
# @private
|
|
85
|
+
def trigger_transactional_callbacks? = true
|
|
86
|
+
|
|
87
|
+
# @private
|
|
88
|
+
def before_committed!
|
|
89
|
+
_run_before_commit_callbacks
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @private
|
|
93
|
+
def committed!(should_run_callbacks: true)
|
|
94
|
+
_run_commit_callbacks if should_run_callbacks
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @private
|
|
98
|
+
def rolledback!(force_restore_state: false, should_run_callbacks: true)
|
|
99
|
+
_run_rollback_callbacks if should_run_callbacks
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def save(**options) = with_transaction_returning_status { super }
|
|
104
|
+
|
|
105
|
+
def save!(**options) = with_transaction_returning_status { super }
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
# @private
|
|
110
|
+
def with_transaction_returning_status
|
|
111
|
+
connection_pool.with_connection do |connection|
|
|
112
|
+
with_pool_transaction_isolation_level(connection) do
|
|
113
|
+
ensure_finalize = !connection.transaction_open?
|
|
114
|
+
|
|
115
|
+
connection.transaction do
|
|
116
|
+
connection.add_transaction_record(self, ensure_finalize || has_transactional_callbacks?) # steep:ignore
|
|
117
|
+
|
|
118
|
+
yield.tap { raise ActiveRecord::Rollback unless _1 }
|
|
119
|
+
end || false
|
|
120
|
+
end
|
|
121
|
+
end
|
|
26
122
|
end
|
|
27
123
|
|
|
28
|
-
|
|
29
|
-
def
|
|
124
|
+
# @private
|
|
125
|
+
def default_ar_class = ActiveRecord::Base
|
|
126
|
+
|
|
127
|
+
# @private
|
|
128
|
+
def connection_pool(ar_class: default_ar_class)
|
|
129
|
+
connection_specification_name = ar_class.connection_specification_name
|
|
130
|
+
role = ar_class.current_role
|
|
131
|
+
shard = ar_class.current_shard # steep:ignore
|
|
132
|
+
connection_handler = ar_class.connection_handler # steep:ignore
|
|
133
|
+
retrieve_options = { role:, shard: }
|
|
134
|
+
retrieve_options[:strict] = true if ActiveRecord.gem_version.release >= Gem::Version.new("7.2.0")
|
|
135
|
+
|
|
136
|
+
connection_handler.retrieve_connection_pool(connection_specification_name, **retrieve_options)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @private
|
|
140
|
+
def with_pool_transaction_isolation_level(connection, &block)
|
|
141
|
+
if ActiveRecord.gem_version.release >= Gem::Version.new("8.1.0")
|
|
142
|
+
isolation_level = ActiveRecord.default_transaction_isolation_level # steep:ignore
|
|
143
|
+
connection.pool.with_pool_transaction_isolation_level(isolation_level, connection.transaction_open?, &block)
|
|
144
|
+
else
|
|
145
|
+
block.call
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @private
|
|
150
|
+
def has_transactional_callbacks?
|
|
151
|
+
_rollback_callbacks.present? || _commit_callbacks.present? || _before_commit_callbacks.present? # steep:ignore
|
|
152
|
+
end
|
|
30
153
|
end
|
|
31
154
|
end
|
|
@@ -5,7 +5,6 @@ require_relative "composed_collection"
|
|
|
5
5
|
module ActiveRecordCompose
|
|
6
6
|
using ComposedCollection::PackagePrivate
|
|
7
7
|
|
|
8
|
-
# @private
|
|
9
8
|
module Validations
|
|
10
9
|
extend ActiveSupport::Concern
|
|
11
10
|
include ActiveModel::Validations::Callbacks
|
|
@@ -22,27 +21,86 @@ module ActiveRecordCompose
|
|
|
22
21
|
perform_validations(options) ? super : raise_validation_error
|
|
23
22
|
end
|
|
24
23
|
|
|
24
|
+
# Runs all the validations and returns the result as true or false.
|
|
25
|
+
#
|
|
26
|
+
# @param context Validation context.
|
|
27
|
+
# @return [Boolean] true on success, false on failure.
|
|
25
28
|
def valid?(context = nil) = context_for_override_validation.with_override(context) { super }
|
|
26
29
|
|
|
30
|
+
# @!method validate(context = nil)
|
|
31
|
+
# Alias for {#valid?}
|
|
32
|
+
# @see #valid? Validation context.
|
|
33
|
+
# @param context
|
|
34
|
+
# @return [Boolean] true on success, false on failure.
|
|
35
|
+
|
|
36
|
+
# @!method validate!(context = nil)
|
|
37
|
+
# @see #valid?
|
|
38
|
+
# Runs all the validations within the specified context.
|
|
39
|
+
# no errors are found, raises `ActiveRecord::RecordInvalid` otherwise.
|
|
40
|
+
# @param context Validation context.
|
|
41
|
+
# @raise ActiveRecord::RecordInvalid
|
|
42
|
+
|
|
43
|
+
# @!method errors
|
|
44
|
+
# Returns the `ActiveModel::Errors` object that holds all information about attribute error messages.
|
|
45
|
+
#
|
|
46
|
+
# The `ActiveModel::Base` implementation itself,
|
|
47
|
+
# but also aggregates error information for objects stored in {#models} when validation is performed.
|
|
48
|
+
#
|
|
49
|
+
# class Account < ActiveRecord::Base
|
|
50
|
+
# validates :name, :email, presence: true
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# class AccountRegistration < ActiveRecordCompose::Model
|
|
54
|
+
# def initialize(attributes = {})
|
|
55
|
+
# @account = Account.new
|
|
56
|
+
# super(attributes)
|
|
57
|
+
# models << account
|
|
58
|
+
# end
|
|
59
|
+
#
|
|
60
|
+
# attribute :confirmation, :boolean, default: false
|
|
61
|
+
# validates :confirmation, presence: true
|
|
62
|
+
#
|
|
63
|
+
# private
|
|
64
|
+
#
|
|
65
|
+
# attr_reader :account
|
|
66
|
+
# end
|
|
67
|
+
#
|
|
68
|
+
# registration = AccountRegistration
|
|
69
|
+
# registration.valid?
|
|
70
|
+
# #=> false
|
|
71
|
+
#
|
|
72
|
+
# # In addition to the model's own validation error information (`confirmation`), also aggregates
|
|
73
|
+
# # error information for objects stored in `account` (`name`, `email`) when validation is performed.
|
|
74
|
+
#
|
|
75
|
+
# registration.errors.map { _1.attribute } #=> [:name, :email, :confirmation]
|
|
76
|
+
#
|
|
77
|
+
# @return [ActiveModel::Errors]
|
|
78
|
+
|
|
27
79
|
private
|
|
28
80
|
|
|
81
|
+
# @private
|
|
29
82
|
def validate_models
|
|
30
83
|
context = override_validation_context
|
|
31
84
|
models.__wrapped_models.lazy.select { _1.invalid?(context) }.each { errors.merge!(_1) }
|
|
32
85
|
end
|
|
33
86
|
|
|
87
|
+
# @private
|
|
34
88
|
def perform_validations(options)
|
|
35
89
|
options[:validate] == false || valid?(options[:context])
|
|
36
90
|
end
|
|
37
91
|
|
|
92
|
+
# @private
|
|
38
93
|
def raise_validation_error = raise ActiveRecord::RecordInvalid, self
|
|
39
94
|
|
|
95
|
+
# @private
|
|
40
96
|
def context_for_override_validation
|
|
41
97
|
@context_for_override_validation ||= OverrideValidationContext.new
|
|
42
98
|
end
|
|
43
99
|
|
|
100
|
+
# @private
|
|
44
101
|
def override_validation_context = context_for_override_validation.context
|
|
45
102
|
|
|
103
|
+
# @private
|
|
46
104
|
class OverrideValidationContext
|
|
47
105
|
attr_reader :context
|
|
48
106
|
|
|
@@ -4,16 +4,12 @@ module ActiveRecordCompose
|
|
|
4
4
|
include ActiveModel::Attributes
|
|
5
5
|
include Querying
|
|
6
6
|
|
|
7
|
+
def self.delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: bool) -> untyped
|
|
8
|
+
def self.delegated_attributes: () -> Array[Delegation]
|
|
9
|
+
def self.delegated_attributes=: (Array[Delegation]) -> untyped
|
|
7
10
|
def delegated_attributes: () -> Array[Delegation]
|
|
8
11
|
|
|
9
|
-
|
|
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
|
|
12
|
+
@attributes: untyped
|
|
17
13
|
|
|
18
14
|
class AttributePredicate
|
|
19
15
|
def initialize: (untyped value) -> void
|
|
@@ -83,11 +79,23 @@ module ActiveRecordCompose
|
|
|
83
79
|
include PackagePrivate
|
|
84
80
|
end
|
|
85
81
|
|
|
82
|
+
module Inspectable
|
|
83
|
+
extend ActiveSupport::Concern
|
|
84
|
+
include Attributes
|
|
85
|
+
|
|
86
|
+
def self.inspection_filter: () -> ActiveSupport::ParameterFilter
|
|
87
|
+
def self.filter_attributes: () -> Array[untyped]
|
|
88
|
+
def self.filter_attributes=: (Array[untyped]) -> void
|
|
89
|
+
def inspect: () -> String
|
|
90
|
+
def pretty_print: (untyped q) -> void
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
def format_for_inspect: (String name, untyped value) -> String
|
|
94
|
+
end
|
|
95
|
+
|
|
86
96
|
class Model
|
|
87
97
|
include Attributes
|
|
88
|
-
extend Attributes::ClassMethods
|
|
89
98
|
include TransactionSupport
|
|
90
|
-
extend TransactionSupport::ClassMethods
|
|
91
99
|
include Callbacks
|
|
92
100
|
|
|
93
101
|
@__models: ComposedCollection
|
|
@@ -100,21 +108,28 @@ module ActiveRecordCompose
|
|
|
100
108
|
module TransactionSupport
|
|
101
109
|
extend ActiveSupport::Concern
|
|
102
110
|
include ActiveRecord::Transactions
|
|
111
|
+
include ActiveSupport::Callbacks
|
|
112
|
+
extend ActiveSupport::Callbacks::ClassMethods
|
|
103
113
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def with_connection: [T] () { () -> T } -> T
|
|
114
|
+
def self.before_commit: (*untyped) -> untyped
|
|
115
|
+
def self.after_commit: (*untyped) -> untyped
|
|
116
|
+
def self.after_rollback: (*untyped) -> untyped
|
|
108
117
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
def save: (**untyped options) -> bool
|
|
119
|
+
def save!: (**untyped options) -> untyped
|
|
120
|
+
def _run_before_commit_callbacks: () -> untyped
|
|
121
|
+
def _run_commit_callbacks: () -> untyped
|
|
122
|
+
def _run_rollback_callbacks: () -> untyped
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
def default_ar_class: -> singleton(ActiveRecord::Base)
|
|
126
|
+
def connection_pool: (?ar_class: singleton(ActiveRecord::Base)) -> ActiveRecord::ConnectionAdapters::ConnectionPool
|
|
127
|
+
def with_pool_transaction_isolation_level: [T] (ActiveRecord::ConnectionAdapters::AbstractAdapter) { () -> T } -> T
|
|
112
128
|
end
|
|
113
129
|
|
|
114
130
|
module Persistence
|
|
115
131
|
include Callbacks
|
|
116
132
|
include TransactionSupport
|
|
117
|
-
extend TransactionSupport::ClassMethods
|
|
118
133
|
|
|
119
134
|
def save: (**untyped options) -> bool
|
|
120
135
|
def save!: (**untyped options) -> untyped
|
|
@@ -128,6 +143,10 @@ module ActiveRecordCompose
|
|
|
128
143
|
def raise_on_save_error_message: -> String
|
|
129
144
|
end
|
|
130
145
|
|
|
146
|
+
class Railtie < Rails::Railtie
|
|
147
|
+
extend Rails::Initializable::ClassMethods
|
|
148
|
+
end
|
|
149
|
+
|
|
131
150
|
module Validations : Model
|
|
132
151
|
extend ActiveSupport::Concern
|
|
133
152
|
extend ActiveModel::Validations::ClassMethods
|
|
@@ -72,9 +72,9 @@ module ActiveRecordCompose
|
|
|
72
72
|
def self.after_rollback: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
|
73
73
|
|
|
74
74
|
def self.delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: bool) -> untyped
|
|
75
|
-
|
|
76
|
-
def self.
|
|
77
|
-
def self.
|
|
75
|
+
|
|
76
|
+
def self.filter_attributes: () -> Array[untyped]
|
|
77
|
+
def self.filter_attributes=: (Array[untyped]) -> untyped
|
|
78
78
|
|
|
79
79
|
def initialize: (?Hash[attribute_name, untyped]) -> void
|
|
80
80
|
def save: (**untyped options) -> bool
|
|
@@ -84,6 +84,9 @@ module ActiveRecordCompose
|
|
|
84
84
|
|
|
85
85
|
def id: -> untyped
|
|
86
86
|
|
|
87
|
+
def inspect: () -> String
|
|
88
|
+
def pretty_print: (untyped q) -> void
|
|
89
|
+
|
|
87
90
|
private
|
|
88
91
|
def models: -> ComposedCollection
|
|
89
92
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_record_compose
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- hamajyotan
|
|
@@ -50,8 +50,10 @@ files:
|
|
|
50
50
|
- lib/active_record_compose/attributes/querying.rb
|
|
51
51
|
- lib/active_record_compose/callbacks.rb
|
|
52
52
|
- lib/active_record_compose/composed_collection.rb
|
|
53
|
+
- lib/active_record_compose/inspectable.rb
|
|
53
54
|
- lib/active_record_compose/model.rb
|
|
54
55
|
- lib/active_record_compose/persistence.rb
|
|
56
|
+
- lib/active_record_compose/railtie.rb
|
|
55
57
|
- lib/active_record_compose/transaction_support.rb
|
|
56
58
|
- lib/active_record_compose/validations.rb
|
|
57
59
|
- lib/active_record_compose/version.rb
|