Skip to content
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 0.13.3 - 2026-08-18

- Stop loading `ActiveRecord::Base` when the gem is required. The engine now
loads `SolidObjects::Record` from an `ActiveSupport.on_load(:active_record)`
hook, so a host application keeps the normal timing of its own
`on_load(:active_record)` and `on_load(:active_record_encryption)` hooks. An
application that assigns its Active Record encryption keys in
`config/initializers` no longer loses them.
- Apply the configured `connects_to` in the record class body, so the
connection follows the class through a development reload.

## 0.13.2 - 2026-08-17

- Accept a `key:` on `schedule`, naming a reminder for the item it is waiting
Expand Down
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
solid_objects (0.13.2)
solid_objects (0.13.3)
actioncable (>= 8.0)
actionpack (>= 8.0)
actionview (>= 8.0)
Expand Down Expand Up @@ -384,7 +384,7 @@ CHECKSUMS
rubocop-rails-omakase (1.1.0) sha256=2af73ac8ee5852de2919abbd2618af9c15c19b512c4cfc1f9a5d3b6ef009109d
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
solid_objects (0.13.2)
solid_objects (0.13.3)
sqlite3 (2.9.5-aarch64-linux-gnu) sha256=78075b6337d3d182c6d2b4691049ed45cd220826160c9ea18946bf6a1de200dc
sqlite3 (2.9.5-aarch64-linux-musl) sha256=18c801185deb4adc01ddb281e8f672a39e3d1729979ca91e39439cd3eac0402d
sqlite3 (2.9.5-arm-linux-gnu) sha256=1bdfca0c7d63998c60b0f4a8e3c8df2d33800ccc4abd2d612eddbbbc92a4c48b
Expand Down
2 changes: 2 additions & 0 deletions app/models/solid_objects/record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ def configure_connection
connects_to(**connection_configuration)
end
end

configure_connection
end
end
8 changes: 5 additions & 3 deletions lib/solid_objects/engine.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# rbs_inline: enabled

require_relative "../../app/models/solid_objects/record"

module SolidObjects
class Engine < ::Rails::Engine
RECORD_PATH = File.expand_path("../../app/models/solid_objects/record.rb", __dir__)

isolate_namespace SolidObjects

config.generators do |generators|
Expand All @@ -16,7 +16,9 @@ class Engine < ::Rails::Engine
end

initializer "solid_objects.database", after: :load_config_initializers do
SolidObjects::Record.configure_connection
ActiveSupport.on_load(:active_record) do
require RECORD_PATH
end
end

initializer "solid_objects.helpers" do
Expand Down
2 changes: 1 addition & 1 deletion lib/solid_objects/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# rbs_inline: enabled

module SolidObjects
VERSION = "0.13.2"
VERSION = "0.13.3"
end
2 changes: 2 additions & 0 deletions sig/generated/lib/solid_objects/engine.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

module SolidObjects
class Engine < ::Rails::Engine
RECORD_PATH: untyped

include SolidObjects::ActorHelper
end
end
8 changes: 8 additions & 0 deletions test/dummy/config/initializers/encryption_keys.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

Rails.application.config.active_record.encryption.primary_key =
"solid-objects-dummy-primary-key"
Rails.application.config.active_record.encryption.deterministic_key =
"solid-objects-dummy-deterministic-key"
Rails.application.config.active_record.encryption.key_derivation_salt =
"solid-objects-dummy-key-derivation-salt"
7 changes: 7 additions & 0 deletions test/dummy/config/initializers/solid_objects_connects_to.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

if ENV["SOLID_OBJECTS_DUMMY_CONNECTS_TO"]
SolidObjects.configure do |config|
config.connects_to = { database: { writing: :primary } }
end
end
10 changes: 10 additions & 0 deletions test/dummy/connects_to_check.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

ENV["RAILS_ENV"] = "test"
ENV["SOLID_OBJECTS_DUMMY_CONNECTS_TO"] = "1"

require_relative "config/environment"

Rails.application.eager_load!

puts ActiveRecord::Base.connection_handler.connection_pool_names.sort.join(" ")
7 changes: 7 additions & 0 deletions test/dummy/encryption_configuration_check.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

ENV["RAILS_ENV"] = "test"

require_relative "config/environment"

puts ActiveRecord::Encryption.config.primary_key
62 changes: 62 additions & 0 deletions test/integration/active_record_load_timing_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

require "test_helper"
require "open3"

# Requiring the gem must not load ActiveRecord::Base. A host application and
# other gems register `ActiveSupport.on_load(:active_record)` hooks from a
# railtie initializer. A hook that is registered after the constant is already
# loaded runs at once, before the `config/initializers` files that supply its
# configuration, so the timing of the load decides whether that configuration
# arrives at all.
class ActiveRecordLoadTimingTest < ActiveSupport::TestCase
test "requiring the gem leaves the active record load hooks deferred" do
assert_equal "deferred", probe_result,
"the gem loads ActiveRecord::Base at require time, so host application hooks run too early"
end

# The reported breakage: an application that assigns its encryption keys in
# `config/initializers` lost them, because the railtie hook that reads them
# had already run.
test "an application initializer configures Active Record encryption" do
output, error_output, status = Open3.capture3(
Gem.ruby,
File.expand_path("../dummy/encryption_configuration_check.rb", __dir__)
)

assert status.success?, error_output
assert_equal "solid-objects-dummy-primary-key", output.strip
end

private

# Computed in a fresh process, because this process loaded Active Record long
# before the question was asked.
def probe_result
output, error_output, status = Open3.capture3(
{ "BUNDLE_GEMFILE" => gem_root("Gemfile") },
Gem.ruby,
"-e",
probe,
chdir: gem_root(".")
)
assert status.success?, error_output
output.strip
end

def probe
<<~RUBY
require "rails"
require "active_record/railtie"
require "solid_objects"

fired = false
ActiveSupport.on_load(:active_record) { fired = true }
puts(fired ? "immediate" : "deferred")
RUBY
end

def gem_root(path)
File.expand_path("../../#{path}", __dir__)
end
end
14 changes: 14 additions & 0 deletions test/integration/engine_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ class EngineTest < ActiveSupport::TestCase
assert_equal "/solid_objects/components", output.strip
end

# The configuration arrives in `config/initializers`, and the record class
# reads it when it loads, which is after those files run.
test "connects the record class to the configured database" do
command = [
Gem.ruby,
File.expand_path("../dummy/connects_to_check.rb", __dir__)
]

output, error_output, status = Open3.capture3(*command)

assert status.success?, error_output
assert_equal "ActiveRecord::Base SolidObjects::Record", output.strip
end

test "packages the morph refresh browser module" do
specification = Gem::Specification.load(
File.expand_path("../../solid_objects.gemspec", __dir__)
Expand Down
Loading