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 +4 -4
- data/CHANGELOG.md +21 -1
- data/Gemfile +2 -2
- data/activerecord-virtual_attributes.gemspec +17 -9
- data/lib/active_record/virtual_attributes/rspec/have_virtual_attribute.rb +1 -1
- data/lib/active_record/virtual_attributes/version.rb +1 -1
- data/lib/active_record/virtual_attributes/virtual_arel.rb +16 -6
- data/lib/active_record/virtual_attributes/virtual_delegates.rb +6 -7
- data/lib/active_record/virtual_attributes/virtual_fields.rb +106 -106
- data/lib/active_record/virtual_attributes/virtual_reflections.rb +4 -3
- data/lib/active_record/virtual_attributes/virtual_total.rb +2 -31
- data/lib/active_record/virtual_attributes.rb +0 -8
- metadata +68 -16
- data/.codeclimate.yml +0 -34
- data/.github/workflows/ci.yaml +0 -71
- data/.gitignore +0 -17
- data/.rspec +0 -3
- data/.rubocop.yml +0 -4
- data/.rubocop_cc.yml +0 -4
- data/.rubocop_local.yml +0 -0
- data/.whitesource +0 -3
- data/.yamllint +0 -8
- data/bin/console +0 -11
- data/bin/setup +0 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e4a4b9dd1a4f51cfef16c50545afe8c064f1727a4580016b400b78afe4279a15
|
|
4
|
+
data.tar.gz: 8a4c28698b8191586fb25dfe0885b54300306ac2f5a3866e7731bc789bd85654
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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/
|
|
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
|
@@ -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 =
|
|
13
|
-
spec.description =
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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.
|
|
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
|
|
@@ -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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
185
|
+
h
|
|
186
|
+
end
|
|
153
187
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
178
|
-
|
|
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
|
|
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?(
|
|
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 -
|
|
223
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
data/.github/workflows/ci.yaml
DELETED
|
@@ -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
data/.rspec
DELETED
data/.rubocop.yml
DELETED
data/.rubocop_cc.yml
DELETED
data/.rubocop_local.yml
DELETED
|
File without changes
|
data/.whitesource
DELETED
data/.yamllint
DELETED
data/bin/console
DELETED
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';'
|