-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Proposal: a way to handle multi-DB ActiveRecord setups #5264
Comments
I think the solution 2 makes sense. But devise is ORM agnostic, so I think we should probably take care of those connection switching methods should be in the active record specific adapter. |
I suppose we could make the configuration option a Proc or lambda that would wrap all the saves, then folks could do anything they needed to for any ORM inside the block. |
@geoffharcourt have you made progress on this? Happy to help as well... |
I have a working change with a configurable proc (default is current behavior and existing tests pass), but I can't find any existing hooks tests and I'm not 100% sure how to build a test harness for those. |
I only have bad ideas about that like inspecting |
For applications using a primary/replica multiple database setup, Rails 6's default connection switching can lead to the `Lockable` after sign-in hook firing on `GET` requests, getting the read-only database, and then attempting to write while zeroing out the user's failed attempts. This change adds a customizable wrapper proc that can allow applications to customize this behavior. For a primary/replica setup using ActiveRecord's default connection switching, it might look like this: ```ruby Devise.setup do |config| config.warden_hook_save_wrapper = Proc.new { # Use the writable DB connection no matter what ApplicationRecord.connected_to(role: :writing) do yield end } end ``` Fix heartcombo#5264
Hi @trevorrjohn take a peek here: #5310 |
For applications using a primary/replica multiple database setup, Rails 6's default connection switching can lead to the `Lockable` after sign-in hook firing on `GET` requests, getting the read-only database, and then attempting to write while zeroing out the user's failed attempts. This change adds a customizable wrapper proc that can allow applications to customize this behavior. For a primary/replica setup using ActiveRecord's default connection switching, it might look like this: ```ruby Devise.setup do |config| config.warden_hook_save_wrapper = Proc.new { # Use the writable DB connection no matter what ApplicationRecord.connected_to(role: :writing) do yield end } end ``` Fix heartcombo#5264
For applications using a primary/replica multiple database setup, Rails 6's default connection switching can lead to the `Lockable` after sign-in hook firing on `GET` requests, getting the read-only database, and then attempting to write while zeroing out the user's failed attempts. This change adds a customizable wrapper proc that can allow applications to customize this behavior. For a primary/replica setup using ActiveRecord's default connection switching, it might look like this: ```ruby class ConnectionWrapper def process(&block) # Use the writable DB connection no matter what ApplicationRecord.connected_to(role: :writing) do block.call end end end Devise.setup do |config| config.warden_hook_save_wrapper = ConnectionWrapper.new end ``` Fix heartcombo#5264
For applications using a primary/replica multiple database setup, Rails 6's default connection switching can lead to the `Lockable` after sign-in hook firing on `GET` requests, getting the read-only database, and then attempting to write while zeroing out the user's failed attempts. This change adds a customizable wrapper proc that can allow applications to customize this behavior. For a primary/replica setup using ActiveRecord's default connection switching, it might look like this: ```ruby Devise.setup do |config| config.warden_hook_save_wrapper = Proc.new do |hook| ApplicationRecord.connected_to(role: :writing) do hook.call end end end ``` Fix heartcombo#5264
For applications using a primary/replica multiple database setup, Rails 6's default connection switching can lead to the `Lockable` after sign-in hook firing on `GET` requests, getting the read-only database, and then attempting to write while zeroing out the user's failed attempts. This change adds a customizable wrapper proc that can allow applications to customize this behavior. For a primary/replica setup using ActiveRecord's default connection switching, it might look like this: ```ruby Devise.setup do |config| config.warden_hook_save_wrapper = Proc.new do |hook| ApplicationRecord.connected_to(role: :writing) do hook.call end end end ``` Fix heartcombo#5264
For applications using a primary/replica multiple database setup, Rails 6's default connection switching can lead to the `Lockable` after sign-in hook firing on `GET` requests, getting the read-only database, and then attempting to write while zeroing out the user's failed attempts. This change adds a customizable wrapper proc that can allow applications to customize this behavior. For a primary/replica setup using ActiveRecord's default connection switching, it might look like this: ```ruby Devise.setup do |config| config.warden_hook_save_wrapper = Proc.new do |hook| ApplicationRecord.connected_to(role: :writing) do hook.call end end end ``` Fix heartcombo#5264
Resetting failed attempts after sign in happened inside a warden hook specific for the lockable module, but that was hidden inside the hook implementation and didn't allow any user customization. One such customization needed for example is to direct these updates to a write DB when using a multi-DB setup. With the logic hidden in the warden hook this wasn't possible, now that it's exposed in a model method much like trackable, we can override the model method to wrap it in a connection switch block for example, point to a write DB, and simply call `super`. Closes #5310 Related to #5264 and #5133
Resetting failed attempts after sign in happened inside a warden hook specific for the lockable module, but that was hidden inside the hook implementation and didn't allow any user customization. One such customization needed for example is to direct these updates to a write DB when using a multi-DB setup. With the logic hidden in the warden hook this wasn't possible, now that it's exposed in a model method much like trackable, we can override the model method to wrap it in a connection switch block for example, point to a write DB, and simply call `super`. Closes #5310 Related to #5264 and #5133
Resetting failed attempts after sign in happened inside a warden hook specific for the lockable module, but that was hidden inside the hook implementation and didn't allow any user customization. One such customization needed for example is to direct these updates to a write DB when using a multi-DB setup. With the logic hidden in the warden hook this wasn't possible, now that it's exposed in a model method much like trackable, we can override the model method to wrap it in a connection switch block for example, point to a write DB, and simply call `super`. Closes #5310 Related to #5264 and #5133
Resetting failed attempts after sign in happened inside a warden hook specific for the lockable module, but that was hidden inside the hook implementation and didn't allow any user customization. One such customization needed for example is to direct these updates to a write DB when using a multi-DB setup. With the logic hidden in the warden hook this wasn't possible, now that it's exposed in a model method much like trackable, we can override the model method to wrap it in a connection switch block for example, point to a write DB, and simply call `super`. Closes #5310 Related to #5264 and #5133
Resetting failed attempts after sign in happened inside a warden hook specific for the lockable module, but that was hidden inside the hook implementation and didn't allow any user customization. One such customization needed for example is to direct these updates to a write DB when using a multi-DB setup. With the logic hidden in the warden hook this wasn't possible, now that it's exposed in a model method much like trackable, we can override the model method to wrap it in a connection switch block for example, point to a write DB, and simply call `super`. Closes #5310 Related to #5264 and #5133
Resetting failed attempts after sign in happened inside a warden hook specific for the lockable module, but that was hidden inside the hook implementation and didn't allow any user customization. One such customization needed for example is to direct these updates to a write DB when using a multi-DB setup. With the logic hidden in the warden hook this wasn't possible, now that it's exposed in a model method much like trackable, we can override the model method to wrap it in a connection switch block for example, point to a write DB, and simply call `super`. Closes #5310 Related to #5264 and #5133
Resetting failed attempts after sign in happened inside a warden hook specific for the lockable module, but that was hidden inside the hook implementation and didn't allow any user customization. One such customization needed for example is to direct these updates to a write DB when using a multi-DB setup. With the logic hidden in the warden hook this wasn't possible, now that it's exposed in a model method much like trackable, we can override the model method to wrap it in a connection switch block for example, point to a write DB, and simply call `super`. Closes #5310 Related to #5264 and #5133
This change has been accomplished via 5d5636f. Thanks @carlosantoniodasilva! |
Hi, facing the same issue, I struggle to understand how this patch have solved the multi-db issue. Is there any example or doc I can refer? |
So nothing out of the box just works. I have a method in my user that takes
a block and connects to the primary db (for writes) which I use, but mainly
I wrote an around action which also connects to the primary db and use that
in controllers that write to the db on GET requests.
…On Thu, Aug 12, 2021, 5:13 AM GuillaumeGillet ***@***.***> wrote:
Hi, facing the same issue, I struggle to understand how this patch have
solved the multi-db issue. Is there any example or doc I can refer?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#5264 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAFEQLQ54QAZWCSLTVZ3IN3T4OGENANCNFSM4OR5B63A>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&utm_campaign=notification-email>
.
|
That's right, at the moment Devise doesn't have a way to automatically switch to a write connection (I have some ideas for that), so you have to override these hooks in your model and switch it yourself to the connection you want, then call |
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? |
Yes, this is what I have too. Unless devise has added something in the past
few months I don't believe there is another way.
…On Thu, Aug 12, 2021, 7:51 AM GuillaumeGillet ***@***.***> wrote:
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?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#5264 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAFEQLVOOXLXEHIOHYMMMRLT4OYTXANCNFSM4OR5B63A>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&utm_campaign=notification-email>
.
|
Ok, thx, I thought I was missing something. |
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 Wardenafter_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 forlockable
that we (believe we) are not able to code around without forking Devise. Example:devise/lib/devise/hooks/lockable.rb
Lines 5 to 12 in f39c6fd
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:Require users to wrap their model methods in a
connected_to
clause aroundsuper
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.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 aconnected_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.
Related: #5133
The text was updated successfully, but these errors were encountered: