activerecord-virtual_attributes 6.1.2 → 7.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e19652ea9b4883f4acae661b63dcc69b4fa4b0bdd0b286a0eb8b17a87892278e
4
- data.tar.gz: 2ba220783720e1bc5b67e0af787c1396127b3fef2f2e0813deb7e34b766579b8
3
+ metadata.gz: e4a4b9dd1a4f51cfef16c50545afe8c064f1727a4580016b400b78afe4279a15
4
+ data.tar.gz: 8a4c28698b8191586fb25dfe0885b54300306ac2f5a3866e7731bc789bd85654
5
5
  SHA512:
6
- metadata.gz: 2b60fcf44b86210f1dbe3ab9be5585158c027bc9c81c0f2ccad77a0079a77b0918bc58feca99f550f4ad2aff4e61defaf7229b125f6091dd770df0242af645fe
7
- data.tar.gz: e691ea9679a9fda6db72fc3f057dd7d5aee163e9fd1d4aff035292da1e03c23eb711920bad8cf79ad41054bb28c2ba29ac9a03a4a38d7b44a52b9ca322245e16
6
+ metadata.gz: 0b06ebb3ad544e2c19f4b0a103ec54cd471fa9a187cee566399b985446244baadac66d35a343dc7311b7b73963bb55a15d87a3968287ea434f1093595073c974
7
+ data.tar.gz: e904ebef2e7594f37055971d738a20343c546834ec7597d10816949a005f1ba6dbd979cc422b1ba1b99a079e699c9219b97907e798d9eee5e3fbfa7ce114e1f6
data/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ The versioning of this gem follows ActiveRecord versioning, and does not follow
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [7.1.0] - 2025-02-19
8
+
9
+ * Use TableAlias for table aliasing [#168](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/168)
10
+ * Rails 7.1 support
11
+
12
+ ## [7.0.0] - 2024-08-01
13
+
14
+ * Use Arel.literal [#154](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/154)
15
+ * drop attribute_builder [#153](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/153)
16
+ * dropped virtual_aggregate [#152](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/152)
17
+ * resolve rubocops (also fix bin/console) [#151](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/151)
18
+ * Rails 7.0 support / dropping 6.1 [#150](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/150)
19
+ * condense includes produced by replace_virtual_fields [#149](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/149)
20
+ * fix bin/console [#148](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/148)
21
+ * Rails 7.0 support pt1 [#146](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/146)
22
+ * Fix sqlite3 v2 and rails [#140](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/140)
23
+ * Use custom Arel node [#114](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/114)
24
+
7
25
  ## [6.1.2] - 2023-10-26
8
26
 
9
27
  * Fix bind variables for joins with static strings [#124](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/124)
@@ -93,7 +111,9 @@ The versioning of this gem follows ActiveRecord versioning, and does not follow
93
111
  * Initial Release
94
112
  * Extracted from ManageIQ/manageiq
95
113
 
96
- [Unreleased]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v6.1.2...HEAD
114
+ [Unreleased]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v7.1.0...HEAD
115
+ [7.1.0]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v7.0.0...v7.1.0
116
+ [7.0.0]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v6.1.2...v7.0.0
97
117
  [6.1.2]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v6.1.1...v6.1.2
98
118
  [6.1.1]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v6.1.0...v6.1.1
99
119
  [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.1.5"
6
6
  gem "mysql2"
7
7
  gem "pg"
8
- gem "sqlite3"
8
+ gem "sqlite3", "< 2"
9
9
 
10
10
  gemspec
@@ -9,29 +9,37 @@ 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
23
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|bin)/}) || f.match(/^(\.)|renovate.json/) }
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_dependency "concurrent-ruby", "< 1.3.5" # Temporary pin down as concurrent-ruby 1.3.5 breaks Rails 7.0, and rails-core doesn't
32
+ # plan to ship a new 7.0 to fix it. See https://github.com/rails/rails/pull/54264
33
+ spec.add_runtime_dependency "activerecord", "~> 7.0", ">=7.0.8.7"
29
34
 
30
35
  spec.add_development_dependency "byebug"
31
36
  spec.add_development_dependency "database_cleaner-active_record", "~> 2.1"
32
37
  spec.add_development_dependency "db-query-matchers"
33
- spec.add_development_dependency "manageiq-style"
38
+ spec.add_development_dependency "manageiq-style", ">= 1.5.3"
39
+ spec.add_development_dependency "mysql2"
40
+ spec.add_development_dependency "pg"
34
41
  spec.add_development_dependency "rake", "~> 13.0"
35
42
  spec.add_development_dependency "rspec", "~> 3.0"
36
43
  spec.add_development_dependency "simplecov", ">= 0.21.2"
44
+ spec.add_development_dependency "sqlite3"
37
45
  end
@@ -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.1.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
@@ -270,11 +271,9 @@ module ActiveRecord
270
271
  def self.select_from_alias_table(to_klass, src_relation)
271
272
  to_table = to_klass.arel_table
272
273
  # if a self join, alias the second table to a different name
273
- if to_table.table_name == src_relation.table_name
274
- # use a dup to not modify the primary table in the model
275
- to_table = to_table.dup
274
+ if to_table.name == src_relation.name
276
275
  # use a table alias to not conflict with table name in the primary query
277
- to_table.table_alias = "#{to_table.table_name}_sub"
276
+ to_table = to_table.alias("#{to_table.name}_sub")
278
277
  end
279
278
  to_table
280
279
  end
@@ -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.wrap(klass_associations).each do |klass_association|
163
+ # this calls back into itself, but it will take the short circuit
164
+ Preloader.new(:records => klass_records, :associations => klass_association, :scope => scope, :available_records => @available_records, :associate_by_default => @associate_by_default).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!
metadata CHANGED
@@ -1,29 +1,49 @@
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.1.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: 2025-02-19 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "<"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.3.5
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "<"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.3.5
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: activerecord
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
31
  - - "~>"
18
32
  - !ruby/object:Gem::Version
19
- version: 6.1.0
33
+ version: '7.0'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 7.0.8.7
20
37
  type: :runtime
21
38
  prerelease: false
22
39
  version_requirements: !ruby/object:Gem::Requirement
23
40
  requirements:
24
41
  - - "~>"
25
42
  - !ruby/object:Gem::Version
26
- version: 6.1.0
43
+ version: '7.0'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 7.0.8.7
27
47
  - !ruby/object:Gem::Dependency
28
48
  name: byebug
29
49
  requirement: !ruby/object:Gem::Requirement
@@ -68,6 +88,34 @@ dependencies:
68
88
  version: '0'
69
89
  - !ruby/object:Gem::Dependency
70
90
  name: manageiq-style
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: 1.5.3
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 1.5.3
103
+ - !ruby/object:Gem::Dependency
104
+ name: mysql2
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: pg
71
119
  requirement: !ruby/object:Gem::Requirement
72
120
  requirements:
73
121
  - - ">="
@@ -122,6 +170,20 @@ dependencies:
122
170
  - - ">="
123
171
  - !ruby/object:Gem::Version
124
172
  version: 0.21.2
173
+ - !ruby/object:Gem::Dependency
174
+ name: sqlite3
175
+ requirement: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ type: :development
181
+ prerelease: false
182
+ version_requirements: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
125
187
  description: Define attributes in arel
126
188
  email:
127
189
  - keenan@thebrocks.net
@@ -129,23 +191,12 @@ executables: []
129
191
  extensions: []
130
192
  extra_rdoc_files: []
131
193
  files:
132
- - ".codeclimate.yml"
133
- - ".github/workflows/ci.yaml"
134
- - ".gitignore"
135
- - ".rspec"
136
- - ".rubocop.yml"
137
- - ".rubocop_cc.yml"
138
- - ".rubocop_local.yml"
139
- - ".whitesource"
140
- - ".yamllint"
141
194
  - CHANGELOG.md
142
195
  - Gemfile
143
196
  - LICENSE.txt
144
197
  - README.md
145
198
  - Rakefile
146
199
  - activerecord-virtual_attributes.gemspec
147
- - bin/console
148
- - bin/setup
149
200
  - init.rb
150
201
  - lib/active_record-virtual_attributes.rb
151
202
  - lib/active_record/virtual_attributes.rb
@@ -166,6 +217,7 @@ metadata:
166
217
  homepage_uri: https://github.com/ManageIQ/activerecord-virtual_attributes
167
218
  source_code_uri: https://github.com/ManageIQ/activerecord-virtual_attributes
168
219
  changelog_uri: https://github.com/ManageIQ/activerecord-virtual_attributes/blob/master/CHANGELOG.md
220
+ rubygems_mfa_required: 'true'
169
221
  post_install_message:
170
222
  rdoc_options: []
171
223
  require_paths:
@@ -181,7 +233,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
181
233
  - !ruby/object:Gem::Version
182
234
  version: '0'
183
235
  requirements: []
184
- rubygems_version: 3.2.33
236
+ rubygems_version: 3.5.22
185
237
  signing_key:
186
238
  specification_version: 4
187
239
  summary: Access non-sql attributes from sql
data/.codeclimate.yml DELETED
@@ -1,34 +0,0 @@
1
- version: '2'
2
- prepare:
3
- fetch:
4
- - url: https://raw.githubusercontent.com/ManageIQ/manageiq-style/master/.rubocop_base.yml
5
- path: ".rubocop_base.yml"
6
- - url: https://raw.githubusercontent.com/ManageIQ/manageiq-style/master/.rubocop_cc_base.yml
7
- path: ".rubocop_cc_base.yml"
8
- - url: https://raw.githubusercontent.com/ManageIQ/manageiq-style/master/styles/base.yml
9
- path: styles/base.yml
10
- - url: https://raw.githubusercontent.com/ManageIQ/manageiq-style/master/styles/cc_base.yml
11
- path: styles/cc_base.yml
12
- checks:
13
- argument-count:
14
- enabled: false
15
- complex-logic:
16
- enabled: false
17
- file-lines:
18
- enabled: false
19
- method-complexity:
20
- config:
21
- threshold: 11
22
- method-count:
23
- enabled: false
24
- method-lines:
25
- enabled: false
26
- nested-control-flow:
27
- enabled: false
28
- return-statements:
29
- enabled: false
30
- plugins:
31
- rubocop:
32
- enabled: true
33
- config: ".rubocop_cc.yml"
34
- channel: rubocop-0-82
@@ -1,71 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- pull_request:
6
- schedule:
7
- - cron: '0 0 * * 0'
8
-
9
- jobs:
10
- ci:
11
- runs-on: ubuntu-latest
12
- strategy:
13
- matrix:
14
- ruby-version:
15
- - '2.7'
16
- - '3.0'
17
- - '3.1'
18
- services:
19
- postgres:
20
- image: postgres:13
21
- env:
22
- POSTGRES_PASSWORD: password
23
- POSTGRES_DB: virtual_attributes
24
- options: --health-cmd pg_isready --health-interval 2s --health-timeout 5s --health-retries 5
25
- ports:
26
- - 5432:5432
27
- mysql:
28
- image: mysql:8.0
29
- env:
30
- MYSQL_ROOT_PASSWORD: password
31
- MYSQL_DATABASE: virtual_attributes
32
- options: --health-cmd="mysqladmin ping -h 127.0.0.1 -P 3306 --silent" --health-interval 10s --health-timeout 5s --health-retries 3
33
- ports:
34
- - 3306:3306
35
- env:
36
- CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
37
- # for the pg cli (psql, pg_isready) and rails
38
- PGHOST: localhost
39
- PGPORT: 5432
40
- PGUSER: postgres
41
- PGPASSWORD: password
42
- # for the mysql cli (mysql, mysqladmin)
43
- MYSQL_HOST: 127.0.0.1
44
- MYSQL_PWD: password
45
- steps:
46
- - uses: actions/checkout@v4
47
- - name: Set up Ruby
48
- uses: ruby/setup-ruby@v1
49
- with:
50
- ruby-version: ${{ matrix.ruby-version }}
51
- bundler-cache: true
52
- timeout-minutes: 30
53
- - name: Run SQLite tests
54
- env:
55
- DB: sqlite3
56
- run: bundle exec rake
57
- - name: Run PostgreSQL tests
58
- env:
59
- DB: postgresql
60
- COLLATE_SYMBOLS: false
61
- run: bundle exec rake
62
- - name: Run MySQL tests
63
- env:
64
- DB: mysql2
65
- run: bundle exec rake
66
- - name: Report code coverage
67
- if: ${{ github.ref == 'refs/heads/master' && matrix.ruby-version == '3.1' }}
68
- continue-on-error: true
69
- uses: paambaati/codeclimate-action@v5
70
- env:
71
- CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
data/.gitignore DELETED
@@ -1,17 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- test/database.yml
11
- **/*.log
12
- **/*.db
13
- Gemfile.lock
14
- gemfiles/*.lock
15
-
16
- # rspec failure tracking
17
- .rspec_status
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,4 +0,0 @@
1
- inherit_from:
2
- - ".rubocop_local.yml"
3
- inherit_gem:
4
- manageiq-style: ".rubocop_base.yml"
data/.rubocop_cc.yml DELETED
@@ -1,4 +0,0 @@
1
- inherit_from:
2
- - ".rubocop_base.yml"
3
- - ".rubocop_cc_base.yml"
4
- - ".rubocop_local.yml"
data/.rubocop_local.yml DELETED
File without changes
data/.whitesource DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "settingsInheritedFrom": "ManageIQ/whitesource-config@master"
3
- }
data/.yamllint DELETED
@@ -1,8 +0,0 @@
1
- ---
2
- extends: relaxed
3
-
4
- rules:
5
- indentation:
6
- indent-sequences: false
7
- line-length:
8
- max: 1000
data/bin/console DELETED
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "active_record-virtual_attributes"
5
-
6
- # models for local testing
7
- require "rspec"
8
- Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
9
-
10
- require "irb"
11
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,13 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -e
5
-
6
- bundle install
7
- echo
8
-
9
- echo "Setting up the postgres database for specs..."
10
- echo "SELECT 'CREATE DATABASE virtual_attributes' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'virtual_attributes')\gexec" | psql -U postgres
11
-
12
- echo "Setting up the mysql database for specs..."
13
- mysql -u root -e 'CREATE SCHEMA IF NOT EXISTS 'virtual_attributes';'