Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rails 7 mvp (just get it runnable with rails 7) #146

Merged
merged 4 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion activerecord-virtual_attributes.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Gem::Specification.new do |spec|

spec.require_paths = ["lib"]

spec.add_runtime_dependency "activerecord", "~> 6.1.0"
spec.add_runtime_dependency "activerecord", ">=6.1.7.8", "<7.1"

spec.add_development_dependency "byebug"
spec.add_development_dependency "database_cleaner-active_record", "~> 2.1"
Expand Down
85 changes: 55 additions & 30 deletions lib/active_record/virtual_attributes/virtual_fields.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,33 @@ def merge_includes(hash1, hash2)
end
end

def assert_klass_has_instance_method(klass, instance_method)
klass.instance_method(instance_method)
rescue NameError => err
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}. "
raise NameError, msg
end

if ActiveRecord.version >= Gem::Version.new(7.0) # Rails 7.0 expected methods to patch
%w[
grouped_records
].each { |method| assert_klass_has_instance_method(ActiveRecord::Associations::Preloader::Branch, method) }
elsif ActiveRecord.version >= Gem::Version.new(6.1) # Rails 6.1 methods to patch
%w[
preloaders_for_reflection
preloaders_for_hash
preloaders_for_one
grouped_records
].each { |method| assert_klass_has_instance_method(ActiveRecord::Associations::Preloader, method) }
end

# Expected methods to patch on any version
%w[
build_select
arel_column
construct_join_dependency
].each { |method| assert_klass_has_instance_method(ActiveRecord::Relation, method) }

module ActiveRecord
class Base
include ActiveRecord::VirtualAttributes::VirtualFields
Expand Down Expand Up @@ -178,34 +205,42 @@ def grouped_records(orig_association, records, polymorphic_parent)
end
# rubocop:enable Style/BlockDelimiters, Lint/AmbiguousBlockAssociation, Style/MethodCallWithArgsParentheses
})
class Branch
prepend(Module.new {
def grouped_records
h = {}
polymorphic_parent = !root? && parent.polymorphic?
source_records.each do |record|
# each class can resolve virtual_{attributes,includes} differently
@association = record.class.replace_virtual_fields(association)

# 1 line optimization for single element array:
@association = association.first if association.kind_of?(Array) # && association.size == 1

case association
when Symbol, String
reflection = record.class._reflect_on_association(association)
next if polymorphic_parent && !reflection || !record.association(association).klass
when nil
next
else # need parent (preloaders_for_{hash,one}) to handle this Array/Hash
reflection = association
end
(h[reflection] ||= []) << record
end
h
end
})
end if ActiveRecord.version >= Gem::Version.new(7.0)
end
end

class Relation
def without_virtual_includes
filtered_includes = includes_values && klass.replace_virtual_fields(includes_values)
if filtered_includes != includes_values
spawn.tap { |other| other.includes_values = filtered_includes }
else
self
end
end

include(Module.new {
# From ActiveRecord::FinderMethods
def apply_join_dependency(*args, **kargs, &block)
real = without_virtual_includes
if real.equal?(self)
super
else
real.apply_join_dependency(*args, **kargs, &block)
end
end

# From ActiveRecord::QueryMethods (rails 5.2 - 6.1)
def build_select(arel)
if select_values.any?
cols = arel_columns(select_values.uniq).map do |col|
cols = arel_columns(select_values).map do |col|
# if it is a virtual attribute, then add aliases to those columns
if col.kind_of?(Arel::Nodes::Grouping) && col.name
col.as(connection.quote_column_name(col.name))
Expand Down Expand Up @@ -233,16 +268,6 @@ def construct_join_dependency(associations, join_type) # :nodoc:
associations = klass.replace_virtual_fields(associations)
super
end

# From ActiveRecord::Calculations
# introduces virtual includes support for calculate (we mostly use COUNT(*))
def calculate(operation, attribute_name)
# allow calculate to work with includes and a virtual attribute
real = without_virtual_includes
return super if real.equal?(self)

real.calculate(operation, attribute_name)
end
})
end
end