activerecord-virtual_attributes 6.1.2 → 7.1.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: 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';'