Conversation
| ) | ||
| Db::ProofingComponent::Add.call(user_id, :document_check, session_doc_auth_vendor) | ||
| Db::ProofingComponent::Add.call(user_id, :document_type, 'state_id') | ||
| return unless liveness_checking_enabled? | ||
| Db::ProofingComponent::Add.call(user_id, :liveness_check, session_doc_auth_vendor) | ||
|
|
||
| proofing_component = current_user.proofing_component || current_user.build_proofing_component | ||
| component_attributes = { | ||
| document_check: session_doc_auth_vendor, | ||
| document_type: 'state_id', | ||
| } | ||
| if liveness_checking_enabled? | ||
| component_attributes[:liveness_check] = session_doc_auth_vendor | ||
| end | ||
| proofing_component.update(component_attributes) | ||
| end |
There was a problem hiding this comment.
in the old way, each of these 3 separate attributes resulted in a find-or-create + save, so that's like 2-3 queries x 3 attributes, and this is now 1 find + 1 save
| return unless user_id | ||
| return unless TOKEN_ALLOWLIST.index(token.to_sym) | ||
|
|
||
| proofing_cost = ::ProofingComponent.create_or_find_by(user_id: user_id) |
There was a problem hiding this comment.
So arguably changing this to the user.proofing_component || user.build_proofing_component is more sensitive to race conditions than this existing code, I was not sure how to leverage the associations to do this same behavior
There was a problem hiding this comment.
Another option: user.proofing_component || user.create_proofing_component which shortens the time between queries
There was a problem hiding this comment.
I'm not entirely sure I follow what practical impact we might expect from building vs. creating, but to your alternative, maybe we don't bother update-ing at all if the entry doesn't exist?
if current_user.proofing_component
current_user.proofing_component.update(component_attributes)
else
current_user.create_proofing_component(component_attributes)
endOr assign with an upsert ?
current_user.proofing_component = ProofingComponent.upsert(
user: current_user,
**component_attributes,
)There was a problem hiding this comment.
The previous implementation was resilient to a race-condition where two conflicting records were created at the same time, and I think would be fine in the update case since we're only typically updating a column or two at a time. The current implementation I don't think accounts for either case.
Upsert would make the most sense imo both for performance and race-condition reasons.
There was a problem hiding this comment.
I like that idea!
A few small snags:
- from the upsert docs, it skips ActiveRecord callbacks, which means no
updated_atand nocreated_at
[3] pry(main)> ProofingComponent.upsert(user_id: 1000)
D, [2021-12-21T09:25:36.713454 #44575] DEBUG -- : ProofingComponent Upsert (16.5ms) INSERT INTO "proofing_components" ("user_id") VALUES (1000) ON CONFLICT ("id") DO UPDATE SET updated_at=(CASE WHEN ("proofing_components"."user_id" IS NOT DISTINCT FROM excluded."user_id") THEN "proofing_components".updated_at ELSE CURRENT_TIMESTAMP END),"user_id"=excluded."user_id" RETURNING "id"
ActiveRecord::NotNullViolation: PG::NotNullViolation: ERROR: null value in column "created_at" of relation "proofing_components" violates not-null constraint
DETAIL: Failing row contains (6, 1000, null, null, null, null, null, null, null, null, null).- passing
created_attoupsertwould override an existing created_at which we don't want... - it doesn't return an AR model, so the
current_user.proofing_component =part wouldn't work but we don't really need that
There was a problem hiding this comment.
I'm going to keep noodling on this and see if I can find a way to do what we want
There was a problem hiding this comment.
updated in b238e99
I had a few ideas:
- switch the assocation to
has_many :proofing_components(even though there's only one) because then we can douser.proofing_components.first_or_create - unfortunately that is not the same as
create_or_find_bywhich still require theuserto be passed in, so I just went with that
| end | ||
|
|
||
| def save_proofing_components | ||
| return unless current_user |
There was a problem hiding this comment.
Do we know when this would be the case? I was wondering about hybrid flow, where in other places we use the EffectiveUser mixin to manage the user associated with the anonymous session. But are we ever actually calling save_proofing_components in that hybrid session (i.e. do we need to bother with that mixin?)?
There was a problem hiding this comment.
I am hoping it was just an artifact of how sloppily the individual feature tests for steps were done, it doesn't make sense why we need this check
There was a problem hiding this comment.
Curious if it's still needed with the recent round of revisions. Thinking the ProofingComponent.create_or_find_by would be more tolerant to the absence of current_user.
There was a problem hiding this comment.
There's a null constraint on the column so this blows up:
[1] pry(main)> ProofingComponent.create_or_find_by(user: nil)
TRANSACTION (0.1ms) BEGIN
ProofingComponent Create (2.4ms) INSERT INTO "proofing_components" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", "2021-12-21 22:05:23.908635"], ["updated_at", "2021-12-21 22:05:23.908635"]]
TRANSACTION (0.2ms) ROLLBACK
ActiveRecord::NotNullViolation: PG::NotNullViolation: ERROR: null value in column "user_id" of relation "proofing_components" violates not-null constraint
DETAIL: Failing row contains (9, null, null, null, null, null, null, null, 2021-12-21 22:05:23.908635, 2021-12-21 22:05:23.908635, null).| return unless user_id | ||
| return unless TOKEN_ALLOWLIST.index(token.to_sym) | ||
|
|
||
| proofing_cost = ::ProofingComponent.create_or_find_by(user_id: user_id) |
There was a problem hiding this comment.
I'm not entirely sure I follow what practical impact we might expect from building vs. creating, but to your alternative, maybe we don't bother update-ing at all if the entry doesn't exist?
if current_user.proofing_component
current_user.proofing_component.update(component_attributes)
else
current_user.create_proofing_component(component_attributes)
endOr assign with an upsert ?
current_user.proofing_component = ProofingComponent.upsert(
user: current_user,
**component_attributes,
)|
@mitchellhenke @aduth updated from comments! PTAL when you can |
| end | ||
|
|
||
| def save_proofing_components | ||
| return unless current_user |
There was a problem hiding this comment.
Curious if it's still needed with the recent round of revisions. Thinking the ProofingComponent.create_or_find_by would be more tolerant to the absence of current_user.
* Inline Db::ProofingComponent::DeleteAll, use ActiveRecord methods * Inline Db::ProofingComponent::Add, use AR methods
So as part of #5721, I looked at some of our proofing component code, and I think we can simplify it and use ActiveRecord built-in methods for some similar validation (such as checking if columns exist or not), and some slight query optimizations
This will conflict with #5721, so let's not merge this until that is wrapped up, but I did want to start a convo about it