Skip to content
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

Empty record when conditional finds none #128

Open
hrdwdmrbl opened this issue May 12, 2022 · 1 comment
Open

Empty record when conditional finds none #128

hrdwdmrbl opened this issue May 12, 2022 · 1 comment

Comments

@hrdwdmrbl
Copy link

Context: The problem is basically https://stackoverflow.com/questions/34708509/how-to-use-returning-with-on-conflict-in-postgresql/37543015

Problem: If you have a conditional upsert, record.upsert!({arel_condition: upsert_condition}), and the condition returns no rows, then the record object will be empty.

My situation: In my case, my condition is about timestamps. self.class.arel_table[:shopify_updated_at].lteq(send(:shopify_updated_at)) where Shopify is an external source of data for my app. I only want to upsert if the current record is newer (has a later updated_at).

Proposed solution: There are solutions listed in the Stack Overflow discussion. I might suggest a simple solution which is to follow up the INSERT with a subsequent read when nothing is returned. The read should also use the upsert_keys if given. This is necessary because otherwise it is not know how to find a matching record.

For me, the upsert_keys is usually an external ID column. Something like shopify_order_id. I don't want duplicate orders, so I upsert on conflict of duplicate shopify_order_id. If a conflict is found, and the shopify_updated_at of what I'm trying to upsert is older, then by the way Postgres works, no row will be returned! So the only thing to do is to either remove the condition, modify the INSERT query, or fetch again when empty.

For example purposes only:

    def _upsert_record(upsert_attribute_names = changed, arel_condition = nil, opts = {})
        existing_attribute_names = attributes_for_create(attributes.keys)
        existing_attributes = attributes_with_values(existing_attribute_names)
        values = self.class._upsert_record(existing_attributes, upsert_attribute_names, [arel_condition].compact, opts)

        values = if values.first.to_h.empty? && opts[:upsert_keys].present?
          find_by_conditions = opts[:upsert_keys].each_with_object({}) do |upsert_key, unique_attributes|
            unique_attributes[upsert_key] = send(upsert_key)
          end
          existing_record = self.class.find_by(find_by_conditions)
          existing_record.attributes
        else
          values.first.to_h
        end

        @attributes = self.class.attributes_builder.build_from_database(values)
        @new_record = false
        changes_applied
        values
      end
@hrdwdmrbl
Copy link
Author

Perhaps a do-no-harm short-term change could be to not overwrite @attributes when values.first.to_h is .empty? ? It won't really help fix anything but at least it's less data-destructive

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant