Skip to content

Commit 056f664

Browse files
committed
Add support for Rails 8.1
1 parent 9247193 commit 056f664

File tree

5 files changed

+196
-3
lines changed

5 files changed

+196
-3
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919

2020
steps:
2121
- name: Checkout
22-
uses: actions/checkout@v4
22+
uses: actions/checkout@v5
2323

2424
- name: Setup Ruby
2525
uses: ruby/setup-ruby@v1
@@ -57,6 +57,7 @@ jobs:
5757
- '3.2'
5858
- 'head'
5959
rails:
60+
- activerecord_8.1
6061
- activerecord_8.0
6162
- activerecord_7.2
6263

@@ -65,7 +66,7 @@ jobs:
6566

6667
steps:
6768
- name: Checkout
68-
uses: actions/checkout@v4
69+
uses: actions/checkout@v5
6970

7071
- name: Setup Ruby
7172
uses: ruby/setup-ruby@v1

Appraisals

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ end
77
appraise 'activerecord_8.0' do
88
gem 'activerecord', '~> 8.0.0'
99
end
10+
11+
appraise 'activerecord_8.1' do
12+
gem 'activerecord', '~> 8.1.0'
13+
end

gemfiles/activerecord_8.1.gemfile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# This file was generated by Appraisal
2+
3+
source "https://rubygems.org"
4+
5+
gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git"
6+
gem "pg"
7+
gem "rake"
8+
gem "rspec"
9+
gem "simplecov"
10+
gem "guard-rspec", require: false
11+
gem "rubocop", require: false
12+
gem "rubocop-rake", require: false
13+
gem "rubocop-rspec", require: false
14+
gem "activerecord", "~> 8.1.0"
15+
16+
gemspec path: "../"
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
if ActiveRecord::VERSION::STRING =~ /\A8\.1/
2+
module StoreBaseSTIClass
3+
4+
module Inheritance
5+
module ClassMethodsPatch
6+
7+
# See: https://github.com/rails/rails/blob/v8.1.0/activerecord/lib/active_record/inheritance.rb#L211
8+
def polymorphic_name
9+
ActiveRecord::Base.store_base_sti_class ? base_class.name : name
10+
end
11+
end
12+
end
13+
14+
module Associations
15+
module Preloader
16+
module ThroughAssociationPatch
17+
private
18+
19+
# See: https://github.com/rails/rails/blob/v8.1.0/activerecord/lib/active_record/associations/preloader/through_association.rb#L104
20+
def through_scope
21+
scope = through_reflection.klass.unscoped
22+
options = reflection.options
23+
24+
return scope if options[:disable_joins]
25+
26+
values = reflection_scope.values
27+
if annotations = values[:annotate]
28+
scope.annotate!(*annotations)
29+
end
30+
31+
if options[:source_type]
32+
# BEGIN PATCH
33+
# original:
34+
# scope.where! reflection.foreign_type => options[:source_type]
35+
36+
adjusted_foreign_type =
37+
if ActiveRecord::Base.store_base_sti_class
38+
options[:source_type]
39+
else
40+
([options[:source_type].constantize] + options[:source_type].constantize.descendants).map(&:to_s)
41+
end
42+
43+
scope.where! reflection.foreign_type => adjusted_foreign_type
44+
# END PATCH
45+
46+
elsif !reflection_scope.where_clause.empty?
47+
scope.where_clause = reflection_scope.where_clause
48+
49+
if includes = values[:includes]
50+
scope.includes!(source_reflection.name => includes)
51+
else
52+
scope.includes!(source_reflection.name)
53+
end
54+
55+
if values[:references] && !values[:references].empty?
56+
scope.references_values |= values[:references]
57+
else
58+
scope.references!(source_reflection.table_name)
59+
end
60+
61+
if joins = values[:joins]
62+
scope.joins!(source_reflection.name => joins)
63+
end
64+
65+
if left_outer_joins = values[:left_outer_joins]
66+
scope.left_outer_joins!(source_reflection.name => left_outer_joins)
67+
end
68+
69+
if scope.eager_loading? && order_values = values[:order]
70+
scope = scope.order(order_values)
71+
end
72+
end
73+
74+
cascade_strict_loading(scope)
75+
end
76+
end
77+
end
78+
79+
module AssociationScopePatch
80+
private
81+
82+
# See: https://github.com/rails/rails/blob/v8.1.0/activerecord/lib/active_record/associations/association_scope.rb#L81
83+
def next_chain_scope(scope, reflection, next_reflection)
84+
primary_key = Array(reflection.join_primary_key)
85+
foreign_key = Array(reflection.join_foreign_key)
86+
87+
table = reflection.aliased_table
88+
foreign_table = next_reflection.aliased_table
89+
90+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
91+
constraints = primary_key_foreign_key_pairs.map do |join_primary_key, foreign_key|
92+
table[join_primary_key].eq(foreign_table[foreign_key])
93+
end.inject(&:and)
94+
95+
if reflection.type
96+
# BEGIN PATCH
97+
# original:
98+
# value = transform_value(next_reflection.klass.polymorphic_name)
99+
# scope = apply_scope(scope, table, reflection.type, value)
100+
101+
if ActiveRecord::Base.store_base_sti_class
102+
value = transform_value(next_reflection.klass.polymorphic_name)
103+
else
104+
klass = next_reflection.klass
105+
value = ([klass] + klass.descendants).map(&:name)
106+
end
107+
scope = apply_scope(scope, table, reflection.type, value)
108+
# END PATCH
109+
end
110+
111+
scope.joins!(join(foreign_table, constraints))
112+
end
113+
end
114+
115+
module HasManyThroughAssociationPatch
116+
private
117+
118+
# See: https://github.com/rails/rails/blob/v8.1.0/activerecord/lib/active_record/associations/has_many_through_association.rb#L56
119+
def build_through_record(record)
120+
@through_records[record] ||= begin
121+
ensure_mutable
122+
123+
attributes = through_scope_attributes
124+
attributes[source_reflection.name] = record
125+
126+
through_association.build(attributes).tap do |new_record|
127+
if ActiveRecord::Base.store_base_sti_class
128+
new_record.send("#{source_reflection.foreign_type}=", options[:source_type]) if options[:source_type]
129+
end
130+
end
131+
end
132+
end
133+
end
134+
end
135+
136+
module Reflection
137+
module PolymorphicReflectionPatch
138+
private
139+
140+
# See: https://github.com/rails/rails/blob/v8.1.0/activerecord/lib/active_record/reflection.rb#L1286
141+
def source_type_scope
142+
type = @previous_reflection.foreign_type
143+
source_type = @previous_reflection.options[:source_type]
144+
145+
# START PATCH
146+
adjusted_source_type =
147+
if ActiveRecord::Base.store_base_sti_class
148+
source_type
149+
else
150+
([source_type.constantize] + source_type.constantize.descendants).map(&:to_s)
151+
end
152+
# END PATCH
153+
154+
lambda { |object| where(type => adjusted_source_type) }
155+
end
156+
end
157+
end
158+
end
159+
160+
ActiveRecord::Inheritance::ClassMethods.prepend(StoreBaseSTIClass::Inheritance::ClassMethodsPatch)
161+
ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(StoreBaseSTIClass::Associations::Preloader::ThroughAssociationPatch)
162+
ActiveRecord::Associations::AssociationScope.prepend(StoreBaseSTIClass::Associations::AssociationScopePatch)
163+
ActiveRecord::Associations::HasManyThroughAssociation.prepend(StoreBaseSTIClass::Associations::HasManyThroughAssociationPatch)
164+
ActiveRecord::Reflection::PolymorphicReflection.prepend(StoreBaseSTIClass::Reflection::PolymorphicReflectionPatch)
165+
166+
module ActiveRecord
167+
class Base
168+
class_attribute :store_base_sti_class
169+
self.store_base_sti_class = true
170+
end
171+
end
172+
end

store_base_sti_class.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@ Gem::Specification.new do |s|
2626

2727
s.files = Dir['README.md', 'LICENSE', 'lib/**/*.rb']
2828

29-
s.add_dependency('activerecord', ['>= 7.0', '< 8.1'])
29+
s.add_dependency('activerecord', ['>= 7.0', '< 8.2'])
3030
end

0 commit comments

Comments
 (0)