Skip to content

Commit 0983e1b

Browse files
committed
Active Record's railtie can now eager load tenanted models at boot
1 parent 0f952de commit 0983e1b

File tree

5 files changed

+53
-6
lines changed

5 files changed

+53
-6
lines changed

GUIDE.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Documentation outline:
5454
- app.config.hosts
5555
- example TenantSelector proc
5656
- explain Tenanted options "connection_class" and "tenanted_rails_records"
57+
- explain why we pin `active_record.use_schema_cache_dump = true` and `active_record.check_schema_cache_dump_version = false`
5758

5859
TODO:
5960

@@ -83,8 +84,8 @@ TODO:
8384
- [x] all the creation and schema migration complications (we have existing tests for this)
8485
- [x] read and write to the schema dump file
8586
- [x] write to the schema cache dump file
86-
- [ ] make sure we read from the schema cache dump file when untenanted
87-
- [ ] test production eager loading of the schema cache from dump files
87+
- [x] make sure we read from the schema cache dump file when untenanted
88+
- [x] test production eager loading of the schema cache from dump files
8889
- [ ] feature to turn off automatic creation/migration
8990
- pay attention to Rails.config.active_record.migration_error when we turn off auto-migrating
9091
- [ ] UntenantedConnectionPool should peek at its stack and if it happened during schema cache load, output a friendly message to let people know what to do

lib/active_record/tenanted/base.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@ def subtenant_of(class_name)
3636
def tenanted?
3737
false
3838
end
39+
40+
# TODO: This monkey patch shouldn't be necessary after 8.1 lands and the need for a
41+
# connection is removed. For details see https://github.com/rails/rails/pull/54348
42+
def _default_attributes # :nodoc:
43+
@default_attributes ||= begin
44+
# I've removed the `with_connection` block here.
45+
nil_connection = nil
46+
attributes_hash = columns_hash.transform_values do |column|
47+
ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(nil_connection, column))
48+
end
49+
50+
attribute_set = ActiveModel::AttributeSet.new(attributes_hash)
51+
apply_pending_attribute_modifications(attribute_set)
52+
attribute_set
53+
end
54+
end
3955
end
4056
end
4157
end

lib/active_record/tenanted/railtie.rb

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,23 @@ class Railtie < ::Rails::Railtie
3939
end
4040
end
4141

42+
initializer("active_record_tenanted.active_record_schema_cache",
43+
before: "active_record.copy_schema_cache_config") do
44+
# Rails must be able to load the schema for a tenanted model without a database connection
45+
# (e.g., boot-time eager loading, or calling User.new to build a form). This gem relies on
46+
# reading from the schema cache dump to do that.
47+
#
48+
# Rails defaults use_schema_cache_dump to true, but we explicitly re-set it here because if
49+
# this is ever turned off, Rails will not work as expected.
50+
Rails.application.config.active_record.use_schema_cache_dump = true
51+
52+
# The schema cache version check needs to query the database, which isn't always possible
53+
# for tenanted models.
54+
Rails.application.config.active_record.check_schema_cache_dump_version = false
55+
end
56+
4257
initializer "active_record-tenanted.monkey_patches" do
4358
ActiveSupport.on_load(:active_record) do
44-
# require "rails/generators/active_record/migration.rb"
45-
# ActiveRecord::Generators::Migration.prepend(ActiveRecord::Tenanted::Patches::Migration)
4659
ActiveRecord::Tasks::DatabaseTasks.prepend(ActiveRecord::Tenanted::Patches::DatabaseTasks)
4760
end
4861

lib/active_record/tenanted/untenanted_connection_pool.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ def initialize(db_config, model)
1919
@model = model
2020
end
2121

22-
def schema_cache
22+
def schema_reflection
2323
schema_cache_path = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(db_config)
24-
schema_reflection = ActiveRecord::ConnectionAdapters::SchemaReflection.new(schema_cache_path)
24+
ActiveRecord::ConnectionAdapters::SchemaReflection.new(schema_cache_path)
25+
end
26+
27+
def schema_cache
2528
ActiveRecord::ConnectionAdapters::BoundSchemaReflection.new(schema_reflection, self)
2629
end
2730

test/unit/base_test.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@
7878
User.schema_cache.columns("users")
7979
end
8080
end
81+
82+
test "the active record railtie will fail to eager load the schema" do
83+
# the code here should mirror the "active_record.define_attribute_methods" initializer
84+
assert_not(User.connection_pool.schema_reflection.cached?(User.table_name))
85+
end
8186
end
8287

8388
describe "when schema cache dump file exists" do
@@ -96,6 +101,15 @@
96101
assert_equal([ "id", "email", "created_at", "updated_at" ],
97102
User.schema_cache.columns("users")&.map(&:name))
98103
end
104+
105+
test "the active record railtie will eager load the schema" do
106+
# the code here should mirror the "active_record.define_attribute_methods" initializer
107+
assert(User.connection_pool.schema_reflection.cached?(User.table_name))
108+
assert(User.define_attribute_methods)
109+
110+
assert_equal([ "id", "email", "created_at", "updated_at" ],
111+
User.schema_cache.columns("users")&.map(&:name))
112+
end
99113
end
100114
end
101115
end

0 commit comments

Comments
 (0)