-
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
Updating User model with GET requests is not compatible with Rails 6 read/write splitting #5133
Comments
I don't necessarily want to get into a discussion about the rails 6 read/write stuff, but I don't (personally) think that is it that black and white, especially when dealing with the meta-record like the logged in user, assuming you want to track their actions. I would also argue that we are talking about RESTful semantics, and not HTTP. Specifically with the confirm request, you can't do a POST from inside an email, at least directly. Also, semantically, it is the show action of the 'confirmation' resource. That all said, I believe the cleanest way of to overcome this situation would be to always use the write db for devise actions, or maybe just the ones you know have problems. Again, I'm not sure of how granular you can configure the Alternatively you could override the pertinent methods on the model (pseudo code, I don't know what the final DSL for read/write looked like at the moment being an RC)
I don't think this is a bug with devise, at a certain point there is a difference between 'written for rails' and 'written to support every possible configuration of rails'. It is clearly a personal opinion, but I would prefer devise to be a solid, tested, and customizable 90% solution rather than muck it up with large amounts of functionality not required by 95+% of installations. |
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. |
Sounds good, although for the next person looking at this, I think that your filter overmatches a bit. It seems would prevent using the read replica even for application-level defined controller actions (in the case that you are extending a devise controller). YYMV |
OK, I can use that thing only where needed. Apart from the login and confirmation actions, is there anywhere else Devise writes with GET requests? |
I wouldn't' want to speak authoritatively on that, as it depends on what
modules you have enabled and your configuration. Is there a specific
exception that gets thrown when this is attempted that you/we could
rescue/track?
…On Mon, Sep 23, 2019 at 12:09 PM Vito Botta ***@***.***> wrote:
OK, I can use that thing only where needed. Apart from the login and
confirmation actions, is there anywhere else Devise writes with GET
requests?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#5133>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AABNFT3XYUYE2T7BINIQZYLQLEH5DANCNFSM4ITVSEHQ>
.
|
It complains that it can't write to the database while in read only mode, because the GET request forces the usage of the replicas. Luckily Rails prevents writes from going to the replicas :) |
Just spitballing here, but could you run the test suite under the same type
of read/write configuration?
…On Mon, Sep 23, 2019 at 12:25 PM Vito Botta ***@***.***> wrote:
It complains that it can't write to the database while in read only mode,
because the GET request forces the usage of the replicas. Luckily Rails
prevents writes from going to the replicas :)
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#5133>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AABNFTYT5YZXDPWBBMD2DBDQLEJ3NANCNFSM4ITVSEHQ>
.
|
@colinross it's extremely difficult to test read/write behavior in a typical Rails test suite because any suite that uses transactions to wrap all the tests won't use a replica database (I think to avoid sync issues). We've had a lot of trouble trying to write application-level tests that verify reader or writer use authoritatively. |
I found this thread last night after having the same issue with Devise. I realised that the
While @vitobotta's general solution above did not work for me, I managed to solve the problem by forcing the write connection in the following methods: class User < ApplicationRecord
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
...
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
...
end I suspect the second patch isn't needed as |
@nodunayo we did something similar, but also patched |
Running into an issue with this now with the |
@geoffharcourt A few days ago I decommissioned my readonly follower DB, so I no longer have this issue at all. Hope you manage to solve this for the long-term. I suspect we will eventually set up a follower again. |
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
Environment
Current behavior
Not sure of where to post this. I am trying to set up read/writing splitting of SQL queries with Rails 6 since it now has support for this thanks to the multiple database features, and am having problems with some Devise actions. Rails' new auto connection switching sends writes to a primary database and reads to replicas and whether or not writes are allowed depends on the HTTP request. If it's a GET request for example, only reads are allowed by default and sent to the replicas, while for POST/PUT/DELETE queries are sent to the master/primary.
In some cases Devise performs writes even with GET requests, for example when user follows the link in the confirmation email which confirms the user using a token. In this case Devise updates
confirmed_at
andupdated_at
columns of the user but because it's a GET request, the write is blocked by Rails 6 using the automatic connection switching based on the HTTP verb.Expected behavior
In theory Devise should follow HTTP semantics and not make changes to data with GET requests, but I am not sure of how to work around this. I am thinking that I could load the confirmation page and then submit a POST request with JavaScript that actually confirms the user, but there are other cases where Devise makes changes with GET requests.
Any help/idea much appreciated.
The text was updated successfully, but these errors were encountered: