activerecord-virtual_attributes 6.1.2 → 7.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: e19652ea9b4883f4acae661b63dcc69b4fa4b0bdd0b286a0eb8b17a87892278e
4
- data.tar.gz: 2ba220783720e1bc5b67e0af787c1396127b3fef2f2e0813deb7e34b766579b8
3
+ metadata.gz: cf8f363320713146ad286d76999f5640a9fc44f3e139f6e5377ac8220e476217
4
+ data.tar.gz: cbabad277bfef0e01da4d14b8942671770eb283c9692ef9eb7a07cbac5f25b68
5
5
  SHA512:
6
- metadata.gz: 2b60fcf44b86210f1dbe3ab9be5585158c027bc9c81c0f2ccad77a0079a77b0918bc58feca99f550f4ad2aff4e61defaf7229b125f6091dd770df0242af645fe
7
- data.tar.gz: e691ea9679a9fda6db72fc3f057dd7d5aee163e9fd1d4aff035292da1e03c23eb711920bad8cf79ad41054bb28c2ba29ac9a03a4a38d7b44a52b9ca322245e16
6
+ metadata.gz: 10295799b997217903b155c7ed84e9ee82bad2cf6c208b6aa140dc94e66df9b4ed8fbcf13418a2feadf075e7f93b2c550d7a94b698aae0c08b6d29a4d5ebe159
7
+ data.tar.gz: 1663fd7ab8af93e3419b6a1ae608c94d55df957b45d904e38b31a3b5f93667159e6ee10e667f560174b12cc2b66e936e20bcadc4ac98a45953ed3923f21cac94
data/.codeclimate.yml CHANGED
@@ -31,4 +31,4 @@ plugins:
31
31
  rubocop:
32
32
  enabled: true
33
33
  config: ".rubocop_cc.yml"
34
- channel: rubocop-0-82
34
+ channel: rubocop-1-56-3
@@ -25,7 +25,7 @@ jobs:
25
25
  ports:
26
26
  - 5432:5432
27
27
  mysql:
28
- image: mysql:8.0
28
+ image: mysql:8.4
29
29
  env:
30
30
  MYSQL_ROOT_PASSWORD: password
31
31
  MYSQL_DATABASE: virtual_attributes
@@ -66,6 +66,6 @@ jobs:
66
66
  - name: Report code coverage
67
67
  if: ${{ github.ref == 'refs/heads/master' && matrix.ruby-version == '3.1' }}
68
68
  continue-on-error: true
69
- uses: paambaati/codeclimate-action@v5
69
+ uses: paambaati/codeclimate-action@v8
70
70
  env:
71
71
  CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
data/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ The versioning of this gem follows ActiveRecord versioning, and does not follow
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [7.0.0] - 2024-08-01
8
+
9
+ * Use Arel.literal [#154](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/154)
10
+ * drop attribute_builder [#153](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/153)
11
+ * dropped virtual_aggregate [#152](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/152)
12
+ * resolve rubocops (also fix bin/console) [#151](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/151)
13
+ * Rails 7.0 support / dropping 6.1 [#150](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/150)
14
+ * condense includes produced by replace_virtual_fields [#149](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/149)
15
+ * fix bin/console [#148](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/148)
16
+ * Rails 7.0 support pt1 [#146](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/146)
17
+ * Fix sqlite3 v2 and rails [#140](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/140)
18
+ * Use custom Arel node [#114](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/114)
19
+
7
20
  ## [6.1.2] - 2023-10-26
8
21
 
9
22
  * Fix bind variables for joins with static strings [#124](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/124)
@@ -93,7 +106,8 @@ The versioning of this gem follows ActiveRecord versioning, and does not follow
93
106
  * Initial Release
94
107
  * Extracted from ManageIQ/manageiq
95
108
 
96
- [Unreleased]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v6.1.2...HEAD
109
+ [Unreleased]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v7.0.0...HEAD
110
+ [7.0.0]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v6.1.2...v7.0.0
97
111
  [6.1.2]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v6.1.1...v6.1.2
98
112
  [6.1.1]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v6.1.0...v6.1.1
99
113
  [6.1.0]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v3.0.0...v6.1.0
data/Gemfile CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activerecord", "~> 6.1.4"
5
+ gem "activerecord", "~>7.0.8"
6
6
  gem "mysql2"
7
7
  gem "pg"
8
- gem "sqlite3"
8
+ gem "sqlite3", "< 2"
9
9
 
10
10
  gemspec
@@ -9,29 +9,35 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Keenan Brock"]
10
10
  spec.email = ["keenan@thebrocks.net"]
11
11
 
12
- spec.summary = %q{Access non-sql attributes from sql}
13
- spec.description = %q{Define attributes in arel}
12
+ spec.summary = "Access non-sql attributes from sql"
13
+ spec.description = "Define attributes in arel"
14
14
  spec.homepage = "https://github.com/ManageIQ/activerecord-virtual_attributes"
15
15
  spec.license = "Apache 2.0"
16
- spec.metadata["homepage_uri"] = spec.homepage
17
- spec.metadata["source_code_uri"] = "https://github.com/ManageIQ/activerecord-virtual_attributes"
18
- spec.metadata["changelog_uri"] = "https://github.com/ManageIQ/activerecord-virtual_attributes/blob/master/CHANGELOG.md"
16
+ spec.metadata = {
17
+ "homepage_uri" => spec.homepage,
18
+ "source_code_uri" => "https://github.com/ManageIQ/activerecord-virtual_attributes",
19
+ "changelog_uri" => "https://github.com/ManageIQ/activerecord-virtual_attributes/blob/master/CHANGELOG.md",
20
+ "rubygems_mfa_required" => "true"
21
+ }
19
22
 
20
23
  # Specify which files should be added to the gem when it is released.
21
24
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
25
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
26
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
27
  end
25
28
 
26
29
  spec.require_paths = ["lib"]
27
30
 
28
- spec.add_runtime_dependency "activerecord", "~> 6.1.0"
31
+ spec.add_runtime_dependency "activerecord", "~> 7.0"
29
32
 
30
33
  spec.add_development_dependency "byebug"
31
34
  spec.add_development_dependency "database_cleaner-active_record", "~> 2.1"
32
35
  spec.add_development_dependency "db-query-matchers"
33
36
  spec.add_development_dependency "manageiq-style"
37
+ spec.add_development_dependency "mysql2"
38
+ spec.add_development_dependency "pg"
34
39
  spec.add_development_dependency "rake", "~> 13.0"
35
40
  spec.add_development_dependency "rspec", "~> 3.0"
36
41
  spec.add_development_dependency "simplecov", ">= 0.21.2"
42
+ spec.add_development_dependency "sqlite3"
37
43
  end
data/bin/console CHANGED
@@ -3,9 +3,12 @@
3
3
  require "bundler/setup"
4
4
  require "active_record-virtual_attributes"
5
5
 
6
+ # any helper that is not rspec specific
7
+ Dir['./spec/support/**/*.rb'].sort.select { |f| !File.read(f).include?("RSpec") }.each { |f| require f }
8
+
6
9
  # models for local testing
7
- require "rspec"
8
- Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
10
+ Database.new.setup.migrate
11
+ require_relative "../seed"
9
12
 
10
13
  require "irb"
11
14
  IRB.start(__FILE__)
@@ -1,4 +1,4 @@
1
- RSpec::Matchers.define :have_virtual_attribute do |name, type|
1
+ RSpec::Matchers.define(:have_virtual_attribute) do |name, type|
2
2
  match do |klass|
3
3
  expect(klass.has_attribute?(name)).to be_truthy
4
4
  expect(klass.virtual_attribute?(name)).to be_truthy
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module VirtualAttributes
3
- VERSION = "6.1.2".freeze
3
+ VERSION = "7.0.0".freeze
4
4
  end
5
5
  end
@@ -10,8 +10,13 @@ module ActiveRecord
10
10
  # Model.select(Model.arel_table.grouping(Model.arel_table[:field2]).as(:field))
11
11
  # Model.attribute_supported_by_sql?(:field) # => true
12
12
 
13
- # in essence, this is our Arel::Nodes::VirtualAttribute
14
- class Arel::Nodes::Grouping
13
+ class VirtualAttribute < Arel::Nodes::Grouping
14
+ def initialize(arel, name = nil, relation = nil)
15
+ super(arel)
16
+ @name = name
17
+ @relation = relation
18
+ end
19
+
15
20
  attr_accessor :name, :relation
16
21
 
17
22
  # methods from Arel::Nodes::Attribute
@@ -95,10 +100,12 @@ module ActiveRecord
95
100
  return unless arel_lambda
96
101
 
97
102
  arel = arel_lambda.call(table)
98
- arel = Arel::Nodes::Grouping.new(arel) unless arel.kind_of?(Arel::Nodes::Grouping)
99
- arel.name = column_name
100
- arel.relation = table
101
- arel
103
+ # By convention, all attributes are defined with a grouping.
104
+ # Since we're adding a VirtualAttribute node, which is essentially a
105
+ # grouping, there is no need to keep both and end up with double parens
106
+ arel = arel.expr if arel.kind_of?(Arel::Nodes::Grouping)
107
+
108
+ VirtualAttribute.new(arel, column_name, table)
102
109
  end
103
110
 
104
111
  private
@@ -111,6 +118,8 @@ module ActiveRecord
111
118
  end
112
119
  end
113
120
 
121
+ # fixed in https://github.com/rails/rails/pull/45642
122
+ if ActiveRecord.version < Gem::Version.new(7.1)
114
123
  module Arel # :nodoc: all
115
124
  # rubocop:disable Naming/MethodName
116
125
  # rubocop:disable Naming/MethodParameterName
@@ -150,3 +159,4 @@ module Arel # :nodoc: all
150
159
  # rubocop:enable Naming/MethodParameterName
151
160
  # rubocop:enable Style/ConditionalAssignment
152
161
  end
162
+ end
@@ -74,12 +74,13 @@ module ActiveRecord
74
74
  type = options[:type] || to_ref.klass.type_for_attribute(col)
75
75
  type = ActiveRecord::Type.lookup(type) if type.kind_of?(Symbol)
76
76
  raise "unknown attribute #{to}##{col} referenced in #{name}" unless type
77
+
77
78
  arel = virtual_delegate_arel(col, to_ref)
78
79
  define_virtual_attribute(method_name, type, :uses => (options[:uses] || to), :arel => arel)
79
80
  end
80
81
 
81
82
  # see activesupport module/delegation.rb
82
- def define_delegate(method_name, method, to: nil, allow_nil: nil, default: nil)
83
+ def define_delegate(method_name, method, to: nil, allow_nil: nil, default: nil) # rubocop:disable Naming/MethodParameterName
83
84
  location = caller_locations(2, 1).first
84
85
  file, line = location.path, location.lineno
85
86
 
@@ -125,7 +126,7 @@ module ActiveRecord
125
126
  module_eval(method_def, file, line)
126
127
  end
127
128
 
128
- def virtual_delegate_name_prefix(prefix, to)
129
+ def virtual_delegate_name_prefix(prefix, to) # rubocop:disable Naming/MethodParameterName
129
130
  "#{prefix == true ? to : prefix}_" if prefix
130
131
  end
131
132
 
@@ -260,7 +261,7 @@ module ActiveRecord
260
261
 
261
262
  yield arel if block_given?
262
263
 
263
- ::Arel::Nodes::Grouping.new(arel)
264
+ arel
264
265
  end
265
266
 
266
267
  # determine table reference to use for a sub query
@@ -5,6 +5,7 @@ module ActiveRecord
5
5
  include ActiveRecord::VirtualAttributes
6
6
  include ActiveRecord::VirtualAttributes::VirtualReflections
7
7
 
8
+ # rubocop:disable Style/SingleLineMethods
8
9
  module NonARModels
9
10
  def dangerous_attribute_method?(_); false; end
10
11
 
@@ -14,6 +15,7 @@ module ActiveRecord
14
15
 
15
16
  def belongs_to_required_by_default; false; end
16
17
  end
18
+ # rubocop:enable Style/SingleLineMethods
17
19
 
18
20
  included do
19
21
  unless respond_to?(:dangerous_attribute_method?)
@@ -31,18 +33,20 @@ module ActiveRecord
31
33
  end
32
34
 
33
35
  def replace_virtual_fields(associations)
34
- return associations if associations.blank?
35
-
36
- case associations
37
- when String, Symbol
38
- virtual_field?(associations) ? replace_virtual_fields(virtual_includes(associations)) : associations.to_sym
39
- when Array
40
- associations.collect { |association| replace_virtual_fields(association) }.compact
41
- when Hash
42
- replace_virtual_field_hash(associations)
43
- else
44
- associations
45
- end
36
+ return nil if associations.blank?
37
+
38
+ ret =
39
+ case associations
40
+ when String, Symbol
41
+ virtual_field?(associations) ? replace_virtual_fields(virtual_includes(associations)) : associations.to_sym
42
+ when Array
43
+ associations.filter_map { |association| replace_virtual_fields(association) }
44
+ when Hash
45
+ replace_virtual_field_hash(associations)
46
+ else
47
+ associations
48
+ end
49
+ simplify_includes(ret)
46
50
  end
47
51
 
48
52
  def replace_virtual_field_hash(associations)
@@ -96,11 +100,42 @@ module ActiveRecord
96
100
  merge_includes(include_to_hash(v1), v2)
97
101
  end
98
102
  end
103
+
104
+ # @param [Hash|Array|Symbol|nil]
105
+ def simplify_includes(ret)
106
+ case ret
107
+ when Hash
108
+ ret.size <= 1 && ret.values.first.blank? ? ret.keys.first : ret
109
+ when Array
110
+ ret.size <= 1 ? ret.first : ret
111
+ else
112
+ ret
113
+ end
114
+ end
99
115
  end
100
116
  end
101
117
  end
102
118
  end
103
119
 
120
+ def assert_klass_has_instance_method(klass, instance_method)
121
+ klass.instance_method(instance_method)
122
+ rescue NameError => err
123
+ msg = "#{klass} is missing the method our prepended code is expecting to patch. Was the undefined method removed or renamed upstream?\nSee: #{__FILE__}.\nThe NameError was: #{err}. "
124
+ raise NameError, msg
125
+ end
126
+
127
+ # Expect these methods to exist. (Otherwise we are patching the wrong methods)
128
+ %w[
129
+ grouped_records
130
+ preloaders_for_reflection
131
+ ].each { |method| assert_klass_has_instance_method(ActiveRecord::Associations::Preloader::Branch, method) }
132
+
133
+ %w[
134
+ build_select
135
+ arel_column
136
+ construct_join_dependency
137
+ ].each { |method| assert_klass_has_instance_method(ActiveRecord::Relation, method) }
138
+
104
139
  module ActiveRecord
105
140
  class Base
106
141
  include ActiveRecord::VirtualAttributes::VirtualFields
@@ -109,105 +144,81 @@ module ActiveRecord
109
144
  module Associations
110
145
  class Preloader
111
146
  prepend(Module.new {
112
- # preloader.rb active record 6.0
113
- # changed:
114
- # since grouped_records can return a hash/array, we need to handle those 2 new cases
115
- def preloaders_for_reflection(reflection, records, scope, polymorphic_parent)
116
- case reflection
117
- when Array
118
- reflection.flat_map { |ref| preloaders_on(ref, records, scope, polymorphic_parent) }
119
- when Hash
120
- preloaders_on(reflection, records, scope, polymorphic_parent)
121
- else
122
- super(reflection, records, scope)
147
+ # preloader is called with virtual attributes - need to resolve
148
+ def call
149
+ # Possibly overkill since all records probably have the same class and associations
150
+ # use a cache so we only convert includes once per base class
151
+ assoc_cache = Hash.new { |h, klass| h[klass] = klass.replace_virtual_fields(associations) }
152
+
153
+ # convert the includes with virtual attributes to includes with proper associations
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
157
+
158
+ # for each of the associations, run a preloader
159
+ records_by_assoc.each do |klass_associations, klass_records|
160
+ next if klass_associations.blank?
161
+
162
+ Array[klass_associations].each do |klass_association| # rubocop:disable Style/RedundantArrayConstructor
163
+ # this calls back into itself, but it will take the short circuit
164
+ Preloader.new(:records => klass_records, :associations => klass_association, :scope => scope).call
165
+ end
123
166
  end
124
167
  end
168
+ })
125
169
 
126
- # rubocop:disable Style/BlockDelimiters, Lint/AmbiguousBlockAssociation, Style/MethodCallWithArgsParentheses
127
- # preloader.rb active record 6.0
128
- # changed:
129
- # passing polymorphic around (and makes 5.2 more similar to 6.0)
130
- def preloaders_for_hash(association, records, scope, polymorphic_parent)
131
- association.flat_map { |parent, child|
132
- grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records|
133
- loaders = preloaders_for_reflection(reflection, reflection_records, scope, polymorphic_parent)
134
- recs = loaders.flat_map(&:preloaded_records).uniq
135
- child_polymorphic_parent = reflection && reflection.respond_to?(:options) && reflection.options[:polymorphic]
136
- loaders.concat Array.wrap(child).flat_map { |assoc|
137
- preloaders_on assoc, recs, scope, child_polymorphic_parent
138
- }
139
- loaders
140
- end
141
- }
142
- end
170
+ class Branch
171
+ prepend(Module.new {
172
+ # from branched.rb 7.0
173
+ def grouped_records
174
+ h = {}
175
+ polymorphic_parent = !root? && parent.polymorphic?
176
+ source_records.each do |record|
177
+ # begin virtual_attributes changes
178
+ association = record.class.replace_virtual_fields(self.association)
179
+ # end virtual_attributes changes
143
180
 
144
- # preloader.rb active record 6.0
145
- # changed:
146
- # passing polymorphic_parent to preloaders_for_reflection
147
- def preloaders_for_one(association, records, scope, polymorphic_parent)
148
- grouped_records(association, records, polymorphic_parent)
149
- .flat_map do |reflection, reflection_records|
150
- preloaders_for_reflection(reflection, reflection_records, scope, polymorphic_parent)
181
+ reflection = record.class._reflect_on_association(association)
182
+ next if polymorphic_parent && !reflection || !record.association(association).klass
183
+ (h[reflection] ||= []) << record
151
184
  end
152
- end
185
+ h
186
+ end
153
187
 
154
- # preloader.rb active record 6.0, 6.1
155
- def grouped_records(orig_association, records, polymorphic_parent)
156
- h = {}
157
- records.each do |record|
158
- # The virtual_field lookup can return Symbol/Nil/Other (typically a Hash)
159
- # so the case statement and the cases for Nil/Other are new
188
+ # branched.rb 7.0
189
+ def preloaders_for_reflection(reflection, reflection_records)
190
+ reflection_records.group_by do |record|
191
+ # begin virtual_attributes changes
192
+ needed_association = record.class.replace_virtual_fields(association)
193
+ # end virtual_attributes changes
160
194
 
161
- # each class can resolve virtual_{attributes,includes} differently
162
- association = record.class.replace_virtual_fields(orig_association)
163
- # 1 line optimization for single element array:
164
- association = association.first if association.kind_of?(Array) && association.size == 1
195
+ klass = record.association(needed_association).klass
165
196
 
166
- case association
167
- when Symbol, String
168
- reflection = record.class._reflect_on_association(association)
169
- next if polymorphic_parent && !reflection || !record.association(association).klass
170
- when nil
171
- next
172
- else # need parent (preloaders_for_{hash,one}) to handle this Array/Hash
173
- reflection = association
197
+ if reflection.scope && reflection.scope.arity != 0
198
+ # For instance dependent scopes, the scope is potentially
199
+ # different for each record. To allow this we'll group each
200
+ # object separately into its own preloader
201
+ reflection_scope = reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass, record).inject(&:merge!)
202
+ end
203
+
204
+ [klass, reflection_scope]
205
+ end.map do |(rhs_klass, reflection_scope), rs|
206
+ preloader_for(reflection).new(rhs_klass, rs, reflection, scope, reflection_scope, associate_by_default)
174
207
  end
175
- (h[reflection] ||= []) << record
176
208
  end
177
- h
178
- end
179
- # rubocop:enable Style/BlockDelimiters, Lint/AmbiguousBlockAssociation, Style/MethodCallWithArgsParentheses
180
- })
209
+ })
210
+ end
181
211
  end
182
212
  end
183
213
 
184
214
  class Relation
185
- def without_virtual_includes
186
- filtered_includes = includes_values && klass.replace_virtual_fields(includes_values)
187
- if filtered_includes != includes_values
188
- spawn.tap { |other| other.includes_values = filtered_includes }
189
- else
190
- self
191
- end
192
- end
193
-
194
215
  include(Module.new {
195
- # From ActiveRecord::FinderMethods
196
- def apply_join_dependency(*args, **kargs, &block)
197
- real = without_virtual_includes
198
- if real.equal?(self)
199
- super
200
- else
201
- real.apply_join_dependency(*args, **kargs, &block)
202
- end
203
- end
204
-
205
216
  # From ActiveRecord::QueryMethods (rails 5.2 - 6.1)
206
217
  def build_select(arel)
207
218
  if select_values.any?
208
- cols = arel_columns(select_values.uniq).map do |col|
219
+ cols = arel_columns(select_values).map do |col|
209
220
  # if it is a virtual attribute, then add aliases to those columns
210
- if col.kind_of?(Arel::Nodes::Grouping) && col.name
221
+ if col.kind_of?(VirtualAttributes::VirtualAttribute)
211
222
  col.as(connection.quote_column_name(col.name))
212
223
  else
213
224
  col
@@ -219,9 +230,8 @@ module ActiveRecord
219
230
  end
220
231
  end
221
232
 
222
- # from ActiveRecord::QueryMethods (rails 5.2 - 6.0)
223
- # TODO: remove from rails 7.0
224
- def arel_column(field, &block)
233
+ # from ActiveRecord::QueryMethods (rails 5.2 - 7.0)
234
+ def arel_column(field)
225
235
  if virtual_attribute?(field) && (arel = table[field])
226
236
  arel
227
237
  else
@@ -230,19 +240,9 @@ module ActiveRecord
230
240
  end
231
241
 
232
242
  def construct_join_dependency(associations, join_type) # :nodoc:
233
- associations = klass.replace_virtual_fields(associations)
243
+ associations = klass.replace_virtual_fields(associations) || {}
234
244
  super
235
245
  end
236
-
237
- # From ActiveRecord::Calculations
238
- # introduces virtual includes support for calculate (we mostly use COUNT(*))
239
- def calculate(operation, attribute_name)
240
- # allow calculate to work with includes and a virtual attribute
241
- real = without_virtual_includes
242
- return super if real.equal?(self)
243
-
244
- real.calculate(operation, attribute_name)
245
- end
246
246
  })
247
247
  end
248
248
  end
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  end
18
18
 
19
19
  def virtual_has_many(name, options = {})
20
- define_method("#{name.to_s.singularize}_ids") do
20
+ define_method(:"#{name.to_s.singularize}_ids") do
21
21
  records = send(name)
22
22
  records.respond_to?(:ids) ? records.ids : records.collect(&:id)
23
23
  end
@@ -57,11 +57,11 @@ module ActiveRecord
57
57
  end
58
58
 
59
59
  def follow_associations(association_names)
60
- association_names.inject(self) { |klass, name| klass.try!(:reflect_on_association, name).try!(:klass) }
60
+ association_names.inject(self) { |klass, name| klass&.reflect_on_association(name)&.klass }
61
61
  end
62
62
 
63
63
  def follow_associations_with_virtual(association_names)
64
- association_names.inject(self) { |klass, name| klass.try!(:reflection_with_virtual, name).try!(:klass) }
64
+ association_names.inject(self) { |klass, name| klass&.reflection_with_virtual(name)&.klass }
65
65
  end
66
66
 
67
67
  # invalid associations return a nil
@@ -97,6 +97,7 @@ module ActiveRecord
97
97
 
98
98
  def add_virtual_reflection(reflection, name, uses, _options)
99
99
  raise ArgumentError, "macro must be specified" unless reflection
100
+
100
101
  reset_virtual_reflection_information
101
102
  _virtual_reflections[name.to_sym] = reflection
102
103
  define_virtual_include(name.to_s, uses)
@@ -49,35 +49,6 @@ module VirtualAttributes
49
49
  define_virtual_aggregate_method(name, relation, column, :average) { |values| values.count == 0 ? 0 : values.sum / values.count }
50
50
  end
51
51
 
52
- # @param method_name
53
- # :count :average :minimum :maximum :sum
54
- #
55
- # example:
56
- #
57
- # class Hardware
58
- # has_many :disks
59
- # virtual_sum :allocated_disk_storage, :disks, :size
60
- # end
61
- #
62
- # generates:
63
- #
64
- # def allocated_disk_storage
65
- # if disks.loaded?
66
- # disks.map(&:size).compact.sum
67
- # else
68
- # disks.sum(:size) || 0
69
- # end
70
- # end
71
- #
72
- # virtual_attribute :allocated_disk_storage, :integer, :uses => :disks, :arel => ...
73
- #
74
- # # arel => (SELECT sum("disks"."size") where "hardware"."id" = "disks"."hardware_id")
75
-
76
- def virtual_aggregate(name, relation, method_name = :sum, column = nil, options = {})
77
- return virtual_total(name, relation, options) if method_name == :size
78
- return virtual_sum(name, relation, column, options) if method_name == :sum
79
- end
80
-
81
52
  def define_virtual_aggregate_attribute(name, relation, method_name, column, options)
82
53
  reflection = reflect_on_association(relation)
83
54
 
@@ -101,7 +72,7 @@ module VirtualAttributes
101
72
  if has_attribute?(name)
102
73
  self[name] || 0
103
74
  elsif (rel = send(relation)).loaded?
104
- values = rel.map { |t| t.send(column) }.compact
75
+ values = rel.filter_map { |t| t.send(column) }
105
76
  if block_given?
106
77
  yield values
107
78
  else
@@ -138,7 +109,7 @@ module VirtualAttributes
138
109
  query.where(join.right.expr)
139
110
 
140
111
  # add coalesce to ensure correct value comes out
141
- t.grouping(Arel::Nodes::NamedFunction.new('COALESCE', [t.grouping(query), Arel::Nodes::SqlLiteral.new("0")]))
112
+ Arel::Nodes::NamedFunction.new('COALESCE', [t.grouping(query), Arel.sql("0")])
142
113
  end
143
114
  end
144
115
  end
@@ -83,14 +83,6 @@ module ActiveRecord
83
83
  end
84
84
  end
85
85
 
86
- def attributes_builder # :nodoc:
87
- unless defined?(@attributes_builder) && @attributes_builder
88
- defaults = _default_attributes.except(*(column_names - [primary_key]))
89
- @attributes_builder = ActiveModel::AttributeSet::Builder.new(attribute_types, defaults)
90
- end
91
- @attributes_builder
92
- end
93
-
94
86
  private
95
87
 
96
88
  def load_schema!
data/renovate.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "config:recommended"
5
+ ]
6
+ }
data/seed.rb ADDED
@@ -0,0 +1,3 @@
1
+ Author.create_with_books(3)
2
+ Author.create_with_books(4)
3
+ Author.create_with_books(2)
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: 6.1.2
4
+ version: 7.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: 2023-10-26 00:00:00.000000000 Z
11
+ date: 2024-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 6.1.0
19
+ version: '7.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 6.1.0
26
+ version: '7.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: byebug
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mysql2
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pg
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: rake
85
113
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +150,20 @@ dependencies:
122
150
  - - ">="
123
151
  - !ruby/object:Gem::Version
124
152
  version: 0.21.2
153
+ - !ruby/object:Gem::Dependency
154
+ name: sqlite3
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
125
167
  description: Define attributes in arel
126
168
  email:
127
169
  - keenan@thebrocks.net
@@ -159,6 +201,8 @@ files:
159
201
  - lib/active_record/virtual_attributes/virtual_reflections.rb
160
202
  - lib/active_record/virtual_attributes/virtual_total.rb
161
203
  - lib/activerecord-virtual_attributes.rb
204
+ - renovate.json
205
+ - seed.rb
162
206
  homepage: https://github.com/ManageIQ/activerecord-virtual_attributes
163
207
  licenses:
164
208
  - Apache 2.0
@@ -166,6 +210,7 @@ metadata:
166
210
  homepage_uri: https://github.com/ManageIQ/activerecord-virtual_attributes
167
211
  source_code_uri: https://github.com/ManageIQ/activerecord-virtual_attributes
168
212
  changelog_uri: https://github.com/ManageIQ/activerecord-virtual_attributes/blob/master/CHANGELOG.md
213
+ rubygems_mfa_required: 'true'
169
214
  post_install_message:
170
215
  rdoc_options: []
171
216
  require_paths:
@@ -181,7 +226,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
181
226
  - !ruby/object:Gem::Version
182
227
  version: '0'
183
228
  requirements: []
184
- rubygems_version: 3.2.33
229
+ rubygems_version: 3.5.9
185
230
  signing_key:
186
231
  specification_version: 4
187
232
  summary: Access non-sql attributes from sql