Excerpt

Rails 6 provides connection switching to allow for applications with multiple databases. One of the supported behaviors is a primary/replica setup where the replica database is read-only. Devise users will run into some issues with the default Rails 6 connection switching, as it restricts writes to non-GET/HEAD/OPTIONS requests.
As a result, some requests will trigger ActiveRecord::ReadOnlyError as a result of hooks firing, etc. Some of the Devise methods can be worked around directly in models like this:
Unfortunately some of the cases where writes inside a GET request don't seem quite as straightforward to work around. Some of the Warden after_sign_in hooks call #save directly on the model. Since Warden hooks are additive, it's not easy to replace the hook with a monkey-patched one. We're currently experiencing some failures for lockable that we (believe we) are not able to code around without forking Devise. Example:
I'd like to propose a pull request to make the Rails 6 primary/replica functionality work without requiring any changes for users who are on older versions of Rails or who aren't leveraging ActiveRecord's connected_to functionality. There are two ways I can see to approach this:
1.
Require users to wrap their model methods in a connected_to clause around super as described above. hooks/lockable would need to be updated to push its call to #save to a model method which could then be wrapped. Then we would document that this is the recommended approach for users leveraging primary/replica setups. This would be the smallest change, but would be less convenient for multiple-database users in Rails 6.
2.
Add a new configuration option, something named like devise_database_role. If that role is set, then all model saves in model modules or hooks would get wrapped in a connected_to block using that role. If the setting is not defined, then all saves happen directly as now. This approach would be more comprehensive and be a one-stop change for primary/replica setups vs. needing to patch each relevant method, but requires more invasive changes across several model modules.
I'm excited to tackle either of these changes, but looking for feedback on the preferred approach.
GuillaumeGillet
commented
on Aug 12, 2021
So based on what I read, I end up doing in my user model :
def update_tracked_fields!(request)
User.connected_to(role: :writing) do
super
end
end
def update_tracked_fields(request)
User.connected_to(role: :writing) do
super
end
end
def remember_me!
User.connected_to(role: :writing) do
super
end
end
def forget_me!
User.connected_to(role: :writing) do
super
end
end
So, so far, there is no global hook to do that. Am I right?
vitobotta
commented
on Sep 23, 2019
Hi @colinross , in the meantime I ended up doing something similar with an around filter for all the Devise actions just to be sure:
around_action :ensure_primary_database, if: :devise_controller?
...
def ensure_primary_database
ActiveRecord::Base.connected_to(role: :writing) do
ActiveRecord::Base.connection_handler.while_preventing_writes(false) do
yield
end
end
end
This solved my original problem, so I had forgotten about
it. I agree with what you said about Devise, perhaps it's a non-issue so
I'm closing. Thank you for your reply.