activerecord-virtual_attributes 7.1.1 → 7.2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26048751ad2fe218660a4b62fa6c89d6467e7d33404c463154a041cf638ff43c
4
- data.tar.gz: 8c2302076104393e4e4eac994e854a951ee6c0588b3fbfefc6c95f3d8d905861
3
+ metadata.gz: ef69f0683d1cf4b95da626b2407fe42fcd2e6564053bcb424ea8c867365e6c94
4
+ data.tar.gz: 28d0d1506872044eae2cb1d9e84cd0044e9dae43f849facabd44024c9f1ed676
5
5
  SHA512:
6
- metadata.gz: 34b460bb1293c44743f26c0b91d62d857f53158022f2777ffb29f97a2279b6a9bbd3d19e4cd83dafaee035061093da669da4ac2bb749f1524258ae5db8d5577c
7
- data.tar.gz: 2b7876b8d6f5c0eeafaf2f2f7740943ead1cc25b0c211ee84e44365609d86c659c328103c432d0c01184d1c09f13f2af5226c9d05614ac7c875392e503ae981a
6
+ metadata.gz: 2c26a6ea644c42aa0317bcf3848f31ae79d49a86f80fa2c0832642652c9195171f52fe23b52789544fd7045efbb51a398d911e39fc2f4645e2e88a2945fa1555
7
+ data.tar.gz: 3d59e61f9b22e9037237c1e1f8e8be91ee36dc165c54a101bf742f4a5b88cc9da6b0a13fef44dbe18dda4f0af299028179e5e37c001e7f3419f6cd988679e044
data/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ The versioning of this gem follows ActiveRecord versioning, and does not follow
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [7.2.0.0] - 2025-06-25
8
+
9
+ * virtual_delegate requires type [#185](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/185)
10
+ * Rails 7.2 support [#187](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/187)
11
+
12
+ ## [7.1.2] - 2025-06-20
13
+
14
+ * Introduce virtual_has_many :through [#191](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/191)
15
+ * Use kwargs for virtual_delegate and virtual_column [#190](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/190)
16
+
7
17
  ## [7.1.1] - 2025-06-18
8
18
 
9
19
  * Deprecate virtual_delegate without a type [#188](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/188)
@@ -115,7 +125,9 @@ The versioning of this gem follows ActiveRecord versioning, and does not follow
115
125
  * Initial Release
116
126
  * Extracted from ManageIQ/manageiq
117
127
 
118
- [Unreleased]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v7.1.1...HEAD
128
+ [Unreleased]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v7.2.0.0...HEAD
129
+ [7.2.0.0]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v7.1.2...v7.2.0.0
130
+ [7.1.2]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v7.1.1...v7.1.2
119
131
  [7.1.1]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v7.1.0...v7.1.1
120
132
  [7.1.0]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v7.0.0...v7.1.0
121
133
  [7.0.0]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v6.1.2...v7.0.0
data/Gemfile CHANGED
@@ -2,9 +2,4 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activerecord", "~>7.1.5"
6
- gem "mysql2"
7
- gem "pg"
8
- gem "sqlite3", "< 2"
9
-
10
5
  gemspec
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
 
29
29
  spec.require_paths = ["lib"]
30
30
 
31
- spec.add_runtime_dependency "activerecord", "~> 7.1", ">=7.1.5.1"
31
+ spec.add_runtime_dependency "activerecord", "~> 7.2.2", ">=7.2.2.1"
32
32
 
33
33
  spec.add_development_dependency "byebug"
34
34
  spec.add_development_dependency "database_cleaner-active_record", "~> 2.1"
@@ -40,5 +40,5 @@ Gem::Specification.new do |spec|
40
40
  spec.add_development_dependency "rake", "~> 13.0"
41
41
  spec.add_development_dependency "rspec", "~> 3.0"
42
42
  spec.add_development_dependency "simplecov", ">= 0.21.2"
43
- spec.add_development_dependency "sqlite3"
43
+ spec.add_development_dependency "sqlite3", "< 2"
44
44
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module VirtualAttributes
3
- VERSION = "7.1.1".freeze
3
+ VERSION = "7.2.0.0".freeze
4
4
  end
5
5
  end
@@ -111,10 +111,9 @@ module ActiveRecord
111
111
  private
112
112
 
113
113
  def define_virtual_arel(name, arel) # :nodoc:
114
- self._virtual_arel = _virtual_arel.merge(name => arel)
114
+ self._virtual_arel = _virtual_arel.merge(name.to_s => arel) unless arel.nil?
115
115
  end
116
116
  end
117
117
  end
118
118
  end
119
119
  end
120
-
@@ -9,80 +9,38 @@ module ActiveRecord
9
9
  module VirtualDelegates
10
10
  extend ActiveSupport::Concern
11
11
 
12
- included do
13
- class_attribute :virtual_delegates_to_define, :instance_accessor => false, :default => {}
14
- end
15
-
16
12
  module ClassMethods
17
13
  #
18
14
  # Definition
19
15
  #
20
16
 
21
- def virtual_delegate(*methods)
22
- options = methods.extract_options!
23
- unless (to = options[:to])
24
- raise ArgumentError, 'Delegation needs an association. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
25
- end
26
-
27
- unless options[:type]
28
- ActiveRecord::VirtualAttributes.deprecator.warn("Calling virtual_delegate without :type is now deprecated", caller)
29
- end
30
-
17
+ def virtual_delegate(*methods, to:, type:, prefix: nil, allow_nil: nil, default: nil, uses: nil, **options) # rubocop:disable Naming/MethodParameterName
31
18
  to = to.to_s
32
- if to.include?(".") && methods.size > 1
33
- raise ArgumentError, 'Delegation only supports specifying a method name when defining a single virtual method'
19
+ if to.include?(".") && (methods.size > 1 || prefix)
20
+ raise ArgumentError, 'Delegation only supports specifying a target method name when defining a single virtual method with no prefix'
34
21
  end
35
22
 
36
23
  if to.count(".") > 1
37
- raise ArgumentError, 'Delegation needs a single association. Supply an option hash with a :to key with only 1 period (e.g. delegate :hello, to: "greeter.greeting")'
24
+ raise ArgumentError, 'Delegation needs a single association. Supply keyword :to with only 1 period (e.g. delegate :hello, to: "greeter.greeting")'
38
25
  end
39
26
 
40
- allow_nil = options[:allow_nil]
41
- default = options[:default]
42
-
43
27
  # put method entry per method name.
44
28
  # This better supports reloading of the class and changing the definitions
45
29
  methods.each do |method|
46
- method_prefix = virtual_delegate_name_prefix(options[:prefix], to)
47
- method_name = "#{method_prefix}#{method}"
48
- if to.include?(".") # to => "target.method"
49
- to, method = to.split(".").map(&:to_sym)
50
- options[:to] = to
30
+ method_name, to, method = determine_method_names(method, to, prefix)
31
+ unless (to_ref = reflection_with_virtual(to))
32
+ raise ArgumentError, "Delegation needs an association. Association #{to} does not exist"
51
33
  end
52
34
 
53
35
  define_delegate(method_name, method, :to => to, :allow_nil => allow_nil, :default => default)
54
-
55
- self.virtual_delegates_to_define =
56
- virtual_delegates_to_define.merge(method_name => [method, options])
36
+ virtual_attribute(method_name, type, :uses => (uses || to), :arel => virtual_delegate_arel(method, to_ref), **options)
57
37
  end
58
38
  end
59
39
 
60
40
  private
61
41
 
62
- # define virtual_attribute for delegates
63
- #
64
- # this is called at schema load time (and not at class definition time)
65
- #
66
- # @param method_name [Symbol] name of the attribute on the source class to be defined
67
- # @param col [Symbol] name of the attribute on the associated class to be referenced
68
- # @option options :to [Symbol] name of the association from the source class to be referenced
69
- # @option options :arel [Proc] (optional and not common)
70
- # @option options :uses [Array|Symbol|Hash] sql includes hash. (default: to)
71
- def define_virtual_delegate(method_name, col, options)
72
- unless (to = options[:to]) && (to_ref = reflection_with_virtual(to.to_s))
73
- raise ArgumentError, 'Delegation needs an association. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
74
- end
75
-
76
- col = col.to_s
77
- type = options[:type] || to_ref.klass.type_for_attribute(col)
78
- type = ActiveRecord::Type.lookup(type) if type.kind_of?(Symbol)
79
- raise "unknown attribute #{to}##{col} referenced in #{name}" unless type
80
-
81
- arel = virtual_delegate_arel(col, to_ref)
82
- define_virtual_attribute(method_name, type, :uses => (options[:uses] || to), :arel => arel)
83
- end
84
-
85
42
  # see activesupport module/delegation.rb
43
+ # rubocop:disable Style/TernaryParentheses
86
44
  def define_delegate(method_name, method, to: nil, allow_nil: nil, default: nil) # rubocop:disable Naming/MethodParameterName
87
45
  location = caller_locations(2, 1).first
88
46
  file, line = location.path, location.lineno
@@ -98,6 +56,7 @@ module ActiveRecord
98
56
  # On the other hand it could be that the target has side-effects,
99
57
  # whereas conceptually, from the user point of view, the delegator should
100
58
  # be doing one call.
59
+ # NOTE: This is based upon ActiveSupport 6.0 delegate.rb, but we added has_attribute? and default
101
60
  if allow_nil
102
61
  method_def = <<-METHOD
103
62
  def #{method_name}(#{definition})
@@ -128,9 +87,27 @@ module ActiveRecord
128
87
  method_def = method_def.split("\n").map(&:strip).join(';')
129
88
  module_eval(method_def, file, line)
130
89
  end
90
+ # rubocop:enable Style/TernaryParentheses
91
+
92
+ # Sometimes the `to` contains the column name target.column, split it up to the source method_name and target column
93
+ # If `to` does specify the column name, `to` becomes the target (i.e.: association)
94
+ #
95
+ # @param column [Symbol|String] the name of the column
96
+ # @param to [Symbol|String]
97
+ # @param prefix [Boolean|Nil|Symbol]
98
+ # @return [Symbol, Symbol, Symbol] method_name, relation, relation's column name
99
+ def determine_method_names(column, to, prefix) # rubocop:disable Naming/MethodParameterName
100
+ method_name = column = column.to_sym
101
+
102
+ tos = to.to_s
103
+ if tos.include?(".") # to => "target.method"
104
+ to, column = tos.split(".").map(&:to_sym)
105
+ end
106
+
107
+ method_prefix = "#{prefix == true ? to : prefix}_" if prefix
108
+ method_name = "#{method_prefix}#{method_name}".to_sym
131
109
 
132
- def virtual_delegate_name_prefix(prefix, to) # rubocop:disable Naming/MethodParameterName
133
- "#{prefix == true ? to : prefix}_" if prefix
110
+ [method_name, to.to_sym, column]
134
111
  end
135
112
 
136
113
  # @param col [String] attribute name
@@ -143,7 +143,7 @@ module ActiveRecord
143
143
 
144
144
  module Associations
145
145
  class Preloader
146
- prepend(Module.new {
146
+ prepend(Module.new do
147
147
  # preloader is called with virtual attributes - need to resolve
148
148
  def call
149
149
  # Possibly overkill since all records probably have the same class and associations
@@ -152,8 +152,12 @@ module ActiveRecord
152
152
 
153
153
  # convert the includes with virtual attributes to includes with proper associations
154
154
  records_by_assoc = records.group_by { |rec| assoc_cache[rec.class] }
155
- # if these are the same includes, then do the preloader work
156
- return super if records_by_assoc.size == 1 && records_by_assoc.keys.first == associations
155
+ # If the association were already translated, then short circuit / do the standard preloader work.
156
+ # When replace_virtual_fields removes the outer array, match that too.
157
+ if records_by_assoc.size == 1 &&
158
+ (associations == records_by_assoc.keys.first || associations == [records_by_assoc.keys.first])
159
+ return super
160
+ end
157
161
 
158
162
  # for each of the associations, run a preloader
159
163
  records_by_assoc.each do |klass_associations, klass_records|
@@ -165,13 +169,14 @@ module ActiveRecord
165
169
  end
166
170
  end
167
171
  end
168
- })
172
+ end)
169
173
 
170
174
  class Branch
171
- prepend(Module.new {
175
+ prepend(Module.new do
172
176
  # from branched.rb 7.0
173
177
  # not going to modify rails code for rubocops
174
178
  # rubocop:disable Lint/AmbiguousOperatorPrecedence
179
+ # rubocop:disable Layout/EmptyLineAfterGuardClause
175
180
  def grouped_records
176
181
  h = {}
177
182
  polymorphic_parent = !root? && parent.polymorphic?
@@ -186,9 +191,11 @@ module ActiveRecord
186
191
  end
187
192
  h
188
193
  end
194
+ # rubocop:enable Layout/EmptyLineAfterGuardClause
189
195
  # rubocop:enable Lint/AmbiguousOperatorPrecedence
190
196
 
191
197
  # branched.rb 7.0
198
+ # rubocop:disable Style/MultilineBlockChain
192
199
  def preloaders_for_reflection(reflection, reflection_records)
193
200
  reflection_records.group_by do |record|
194
201
  # begin virtual_attributes changes
@@ -209,13 +216,14 @@ module ActiveRecord
209
216
  preloader_for(reflection).new(rhs_klass, rs, reflection, scope, reflection_scope, associate_by_default)
210
217
  end
211
218
  end
212
- })
219
+ end)
220
+ # rubocop:enable Style/MultilineBlockChain
213
221
  end
214
222
  end
215
223
  end
216
224
 
217
225
  class Relation
218
- include(Module.new {
226
+ include(Module.new do
219
227
  # From ActiveRecord::QueryMethods (rails 5.2 - 6.1)
220
228
  def build_select(arel)
221
229
  if select_values.any?
@@ -246,6 +254,6 @@ module ActiveRecord
246
254
  associations = klass.replace_virtual_fields(associations) || {}
247
255
  super
248
256
  end
249
- })
257
+ end)
250
258
  end
251
259
  end
@@ -25,7 +25,7 @@ module ActiveRecord
25
25
  private
26
26
 
27
27
  def define_virtual_include(name, uses)
28
- self._virtual_includes = _virtual_includes.merge(name => uses)
28
+ self._virtual_includes = _virtual_includes.merge(name.to_s => uses) unless uses.nil?
29
29
  end
30
30
  end
31
31
  end
@@ -5,31 +5,28 @@ module ActiveRecord
5
5
  include ActiveRecord::VirtualAttributes::VirtualIncludes
6
6
 
7
7
  module ClassMethods
8
-
9
8
  #
10
9
  # Definition
11
10
  #
12
11
 
13
- def virtual_has_one(name, options = {})
14
- uses = options.delete(:uses)
12
+ def virtual_has_one(name, uses: nil, **options)
15
13
  reflection = ActiveRecord::Associations::Builder::HasOne.build(self, name, nil, options)
16
- add_virtual_reflection(reflection, name, uses, options)
14
+ add_virtual_reflection(reflection, name, uses)
17
15
  end
18
16
 
19
- def virtual_has_many(name, options = {})
17
+ def virtual_has_many(name, uses: nil, source: name, through: nil, **options)
20
18
  define_method(:"#{name.to_s.singularize}_ids") do
21
19
  records = send(name)
22
20
  records.respond_to?(:ids) ? records.ids : records.collect(&:id)
23
21
  end
24
- uses = options.delete(:uses)
22
+ define_delegate(name, source, :to => through, :allow_nil => true, :default => []) if through
25
23
  reflection = ActiveRecord::Associations::Builder::HasMany.build(self, name, nil, options)
26
- add_virtual_reflection(reflection, name, uses, options)
24
+ add_virtual_reflection(reflection, name, uses)
27
25
  end
28
26
 
29
- def virtual_belongs_to(name, options = {})
30
- uses = options.delete(:uses)
27
+ def virtual_belongs_to(name, uses: nil, **options)
31
28
  reflection = ActiveRecord::Associations::Builder::BelongsTo.build(self, name, nil, options)
32
- add_virtual_reflection(reflection, name, uses, options)
29
+ add_virtual_reflection(reflection, name, uses)
33
30
  end
34
31
 
35
32
  def virtual_reflection?(name)
@@ -95,7 +92,7 @@ module ActiveRecord
95
92
 
96
93
  private
97
94
 
98
- def add_virtual_reflection(reflection, name, uses, _options)
95
+ def add_virtual_reflection(reflection, name, uses)
99
96
  raise ArgumentError, "macro must be specified" unless reflection
100
97
 
101
98
  reset_virtual_reflection_information
@@ -52,19 +52,19 @@ module ActiveRecord
52
52
  #
53
53
 
54
54
  # Compatibility method: `virtual_attribute` is a more accurate name
55
- def virtual_column(name, **options)
56
- type = options.delete(:type)
57
- raise ArgumentError, "missing :type attribute" unless type
58
-
55
+ def virtual_column(name, type:, **options)
59
56
  virtual_attribute(name, type, **options)
60
57
  end
61
58
 
62
- def virtual_attribute(name, type, **options)
59
+ def virtual_attribute(name, type, uses: nil, arel: nil, **options)
63
60
  name = name.to_s
64
61
  reload_schema_from_cache
65
62
 
66
63
  self.virtual_attributes_to_define =
67
64
  virtual_attributes_to_define.merge(name => [type, options])
65
+
66
+ define_virtual_include(name, uses)
67
+ define_virtual_arel(name, arel)
68
68
  end
69
69
 
70
70
  #
@@ -87,27 +87,18 @@ module ActiveRecord
87
87
  end
88
88
  end
89
89
 
90
- private
91
-
92
- def load_schema!
93
- super
94
-
95
- virtual_attributes_to_define.each do |name, (type, options)|
96
- type = type.call if type.respond_to?(:call)
97
- type = ActiveRecord::Type.lookup(type, **options.except(:uses, :arel)) if type.kind_of?(Symbol)
98
-
99
- define_virtual_attribute(name, type, **options.slice(:uses, :arel))
100
- end
101
-
102
- virtual_delegates_to_define.each do |method_name, (method, options)|
103
- define_virtual_delegate(method_name, method, options)
90
+ def attribute_types
91
+ @attribute_types || super.tap do |hash|
92
+ virtual_attributes_to_define.each do |name, (type, options)|
93
+ type = type.call if type.respond_to?(:call)
94
+ type = ActiveRecord::Type.lookup(type, **options) if type.kind_of?(Symbol)
95
+ hash[name] = type
96
+ end
104
97
  end
105
98
  end
106
99
 
107
- def define_virtual_attribute(name, cast_type, uses: nil, arel: nil)
108
- attribute_types[name] = cast_type
109
- define_virtual_include(name, uses) if uses
110
- define_virtual_arel(name, arel) if arel
100
+ def define_virtual_attribute(name, cast_type)
101
+ attribute_types[name.to_s] = cast_type
111
102
  end
112
103
  end
113
104
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-virtual_attributes
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.1
4
+ version: 7.2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keenan Brock
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-06-17 00:00:00.000000000 Z
11
+ date: 2025-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '7.1'
19
+ version: 7.2.2
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 7.1.5.1
22
+ version: 7.2.2.1
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: '7.1'
29
+ version: 7.2.2
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 7.1.5.1
32
+ version: 7.2.2.1
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: byebug
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -160,16 +160,16 @@ dependencies:
160
160
  name: sqlite3
161
161
  requirement: !ruby/object:Gem::Requirement
162
162
  requirements:
163
- - - ">="
163
+ - - "<"
164
164
  - !ruby/object:Gem::Version
165
- version: '0'
165
+ version: '2'
166
166
  type: :development
167
167
  prerelease: false
168
168
  version_requirements: !ruby/object:Gem::Requirement
169
169
  requirements:
170
- - - ">="
170
+ - - "<"
171
171
  - !ruby/object:Gem::Version
172
- version: '0'
172
+ version: '2'
173
173
  description: Define attributes in arel
174
174
  email:
175
175
  - keenan@thebrocks.net