Skip to content

Commit a289545

Browse files
authored
Merge pull request #150 from kbrock/allow_rails_7
Add rails 7.0 support and drop rails 6.1
2 parents 6826ea7 + 2905c1c commit a289545

File tree

7 files changed

+81
-106
lines changed

7 files changed

+81
-106
lines changed

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ The versioning of this gem follows ActiveRecord versioning, and does not follow
44

55
## [Unreleased]
66

7+
## [7.0.0] - 2024-08-01
8+
9+
* Use Arel.literal [#154](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/154)
10+
* drop attribute_builder [#153](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/153)
11+
* dropped virtual_aggregate [#152](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/152)
12+
* resolve rubocops (also fix bin/console) [#151](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/151)
13+
* Rails 7.0 support / dropping 6.1 [#150](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/150)
14+
* condense includes produced by replace_virtual_fields [#149](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/149)
15+
* fix bin/console [#148](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/148)
16+
* Rails 7.0 support pt1 [#146](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/146)
17+
* Fix sqlite3 v2 and rails [#140](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/140)
18+
* Use custom Arel node [#114](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/114)
19+
720
## [6.1.2] - 2023-10-26
821

922
* Fix bind variables for joins with static strings [#124](https://github.com/ManageIQ/activerecord-virtual_attributes/pull/124)
@@ -93,7 +106,8 @@ The versioning of this gem follows ActiveRecord versioning, and does not follow
93106
* Initial Release
94107
* Extracted from ManageIQ/manageiq
95108

96-
[Unreleased]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v6.1.2...HEAD
109+
[Unreleased]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v7.0.0...HEAD
110+
[7.0.0]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v6.1.2...v7.0.0
97111
[6.1.2]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v6.1.1...v6.1.2
98112
[6.1.1]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v6.1.0...v6.1.1
99113
[6.1.0]: https://github.com/ManageIQ/activerecord-virtual_attributes/compare/v3.0.0...v6.1.0

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
source "https://rubygems.org"
44

5-
gem "activerecord", "~> 6.1.7"
5+
gem "activerecord", "~>7.0.8"
66
gem "mysql2"
77
gem "pg"
88
gem "sqlite3", "< 2"

activerecord-virtual_attributes.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
2828

2929
spec.require_paths = ["lib"]
3030

31-
spec.add_runtime_dependency "activerecord", ">=6.1.7.8", "<7.1"
31+
spec.add_runtime_dependency "activerecord", "~> 7.0"
3232

3333
spec.add_development_dependency "byebug"
3434
spec.add_development_dependency "database_cleaner-active_record", "~> 2.1"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module ActiveRecord
22
module VirtualAttributes
3-
VERSION = "6.1.2".freeze
3+
VERSION = "7.0.0".freeze
44
end
55
end

lib/active_record/virtual_attributes/virtual_fields.rb

Lines changed: 56 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -124,20 +124,12 @@ def assert_klass_has_instance_method(klass, instance_method)
124124
raise NameError, msg
125125
end
126126

127-
if ActiveRecord.version >= Gem::Version.new(7.0) # Rails 7.0 expected methods to patch
128-
%w[
129-
grouped_records
130-
].each { |method| assert_klass_has_instance_method(ActiveRecord::Associations::Preloader::Branch, method) }
131-
elsif ActiveRecord.version >= Gem::Version.new(6.1) # Rails 6.1 methods to patch
132-
%w[
133-
preloaders_for_reflection
134-
preloaders_for_hash
135-
preloaders_for_one
136-
grouped_records
137-
].each { |method| assert_klass_has_instance_method(ActiveRecord::Associations::Preloader, method) }
138-
end
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) }
139132

140-
# Expected methods to patch on any version
141133
%w[
142134
build_select
143135
arel_column
@@ -152,102 +144,70 @@ class Base
152144
module Associations
153145
class Preloader
154146
prepend(Module.new {
155-
# preloader.rb active record 6.0
156-
# changed:
157-
# since grouped_records can return a hash/array, we need to handle those 2 new cases
158-
def preloaders_for_reflection(reflection, records, scope, polymorphic_parent)
159-
case reflection
160-
when Array
161-
reflection.flat_map { |ref| preloaders_on(ref, records, scope, polymorphic_parent) }
162-
when Hash
163-
preloaders_on(reflection, records, scope, polymorphic_parent)
164-
else
165-
super(reflection, records, scope)
166-
end
167-
end
168-
169-
# rubocop:disable Style/BlockDelimiters, Lint/AmbiguousBlockAssociation, Style/MethodCallWithArgsParentheses
170-
# preloader.rb active record 6.0
171-
# changed:
172-
# passing polymorphic around (and makes 5.2 more similar to 6.0)
173-
def preloaders_for_hash(association, records, scope, polymorphic_parent)
174-
association.flat_map { |parent, child|
175-
grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records|
176-
loaders = preloaders_for_reflection(reflection, reflection_records, scope, polymorphic_parent)
177-
recs = loaders.flat_map(&:preloaded_records).uniq
178-
child_polymorphic_parent = reflection && reflection.respond_to?(:options) && reflection.options[:polymorphic]
179-
loaders.concat Array.wrap(child).flat_map { |assoc|
180-
preloaders_on assoc, recs, scope, child_polymorphic_parent
181-
}
182-
loaders
147+
# preloader is called with virtual attributes - need to resolve
148+
def call
149+
# Possibly overkill since all records probably have the same class and associations
150+
# use a cache so we only convert includes once per base class
151+
assoc_cache = Hash.new { |h, klass| h[klass] = klass.replace_virtual_fields(associations) }
152+
153+
# convert the includes with virtual attributes to includes with proper associations
154+
records_by_assoc = records.group_by { |rec| assoc_cache[rec.class] }
155+
# if these are the same includes, then do the preloader work
156+
return super if records_by_assoc.size == 1 && records_by_assoc.keys.first == associations
157+
158+
# for each of the associations, run a preloader
159+
records_by_assoc.each do |klass_associations, klass_records|
160+
next if klass_associations.blank?
161+
162+
Array[klass_associations].each do |klass_association| # rubocop:disable Style/RedundantArrayConstructor
163+
# this calls back into itself, but it will take the short circuit
164+
Preloader.new(:records => klass_records, :associations => klass_association, :scope => scope).call
183165
end
184-
}
185-
end
186-
187-
# preloader.rb active record 6.0
188-
# changed:
189-
# passing polymorphic_parent to preloaders_for_reflection
190-
def preloaders_for_one(association, records, scope, polymorphic_parent)
191-
grouped_records(association, records, polymorphic_parent)
192-
.flat_map do |reflection, reflection_records|
193-
preloaders_for_reflection(reflection, reflection_records, scope, polymorphic_parent)
194-
end
195-
end
196-
197-
# preloader.rb active record 6.0, 6.1
198-
def grouped_records(orig_association, records, polymorphic_parent)
199-
h = {}
200-
records.each do |record|
201-
# The virtual_field lookup can return Symbol/Nil/Other (typically a Hash)
202-
# so the case statement and the cases for Nil/Other are new
203-
204-
# each class can resolve virtual_{attributes,includes} differently
205-
association = record.class.replace_virtual_fields(orig_association)
206-
# 1 line optimization for single element array:
207-
association = association.first if association.kind_of?(Array) && association.size == 1
208-
209-
case association
210-
when Symbol, String
211-
reflection = record.class._reflect_on_association(association)
212-
next if polymorphic_parent && !reflection || !record.association(association).klass
213-
when nil
214-
next
215-
else # need parent (preloaders_for_{hash,one}) to handle this Array/Hash
216-
reflection = association
217-
end
218-
(h[reflection] ||= []) << record
219166
end
220-
h
221167
end
222-
# rubocop:enable Style/BlockDelimiters, Lint/AmbiguousBlockAssociation, Style/MethodCallWithArgsParentheses
223168
})
169+
224170
class Branch
225171
prepend(Module.new {
172+
# from branched.rb 7.0
226173
def grouped_records
227174
h = {}
228175
polymorphic_parent = !root? && parent.polymorphic?
229176
source_records.each do |record|
230-
# each class can resolve virtual_{attributes,includes} differently
231-
@association = record.class.replace_virtual_fields(association)
232-
233-
# 1 line optimization for single element array:
234-
@association = association.first if association.kind_of?(Array) # && association.size == 1
235-
236-
case association
237-
when Symbol, String
238-
reflection = record.class._reflect_on_association(association)
239-
next if polymorphic_parent && !reflection || !record.association(association).klass
240-
when nil
241-
next
242-
else # need parent (preloaders_for_{hash,one}) to handle this Array/Hash
243-
reflection = association
244-
end
177+
# begin virtual_attributes changes
178+
association = record.class.replace_virtual_fields(self.association)
179+
# end virtual_attributes changes
180+
181+
reflection = record.class._reflect_on_association(association)
182+
next if polymorphic_parent && !reflection || !record.association(association).klass
245183
(h[reflection] ||= []) << record
246184
end
247185
h
248186
end
187+
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
194+
195+
klass = record.association(needed_association).klass
196+
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)
207+
end
208+
end
249209
})
250-
end if ActiveRecord.version >= Gem::Version.new(7.0)
210+
end
251211
end
252212
end
253213

@@ -270,9 +230,8 @@ def build_select(arel)
270230
end
271231
end
272232

273-
# from ActiveRecord::QueryMethods (rails 5.2 - 6.0)
274-
# TODO: remove from rails 7.0
275-
def arel_column(field, &block)
233+
# from ActiveRecord::QueryMethods (rails 5.2 - 7.0)
234+
def arel_column(field)
276235
if virtual_attribute?(field) && (arel = table[field])
277236
arel
278237
else

spec/spec_helper.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,7 @@
4141
end
4242
end
4343
end
44+
45+
require "active_record"
46+
puts
47+
puts "\e[93mUsing ActiveRecord #{ActiveRecord.version}\e[0m"

spec/virtual_includes_spec.rb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,7 @@
243243
expect(books.last.author).to be_nil # the book just created does not have an author
244244

245245
# the second time preloading throws an error
246-
preloader = ActiveRecord::Associations::Preloader.new
247-
preloader.preload(books, :author => :books)
248-
246+
preloaded(books, :author => :books)
249247
expect(books.size).to be(4)
250248
end
251249
end
@@ -548,8 +546,8 @@
548546
end
549547

550548
def preloaded(records, associations, preload_scope = nil)
551-
preloader = ActiveRecord::Associations::Preloader.new
552-
preloader.preload(records, associations, preload_scope)
549+
# Rails 7+ interface, see rails commit: e3b9779cb701c63012bc1af007c71dc5a888d35a
550+
ActiveRecord::Associations::Preloader.new(:records => records, :associations => associations, :scope => preload_scope).call
553551
records
554552
end
555553
end

0 commit comments

Comments
 (0)