All threads / How do I do upserts and/or conditional saves?

Ask A Question


You’re not receiving notifications from this thread.

How do I do upserts and/or conditional saves?

Morgan asked in Ruby
I have an external API that I am parsing and saving to the database via a scheduled job and I am having trouble working out how to:

A: How to only save the parsed object if it has changed.
B. How to only save the related images if the post object has changed.

Luckily every object from the API comes with a mod_time field which is updated whenever something changes.

Below is a simplified version of what I have now which is currently just saving straight to the database.

module SomeApi
  class ImportPosts < Api

    def self.import
      response = client.get

      response.body['posts'].each do |post|
        post_body = {
          external_id: post['external_id'],
          mod_time: post['mod_time'],
          status: post['status'],
          external_post_id: post['post_id'],
          title: post['title'],
          body:  post['body']

        # This works as it should with no dublicates
        @new_post = Post.where(external_id: post_body[:external_id]).first_or_create(post_body)

        # This will always run
        post['objects']['img'].each do |media|

            external_id: media['external_id'],
            mod_time: media['mod_time'],
            url: media['url'],
            format: media['format']

    def client

My models are pretty standard:

class Post < ApplicationRecord
  has_many :images

  accepts_nested_attributes_for :images

class Image < ApplicationRecord
  belongs_to :post

As you can see, the Post will only save if the external_id does not exist but I think I need to first check if the mod_time is newer but I can only do that if the post object has been saved to compare.

The other issue which I have found surprisingly difficult, is how to only run the associated images if the parent post is ran? I tried wrapping the image code in a first_or_create block but it only saves the posts external_id.

Hey Morgan, 

Your first_or_create method allows you to check attributes on the found or created object, you just need to remove the post_body params and assign them later to do what you want:

@new_post = Post.where(external_id: post_body[:external_id]).first_or_create

if @new_post.mod_time.present? #update record
  # do whatever updating you want
else #it's a new record
  post['objects']['img'].each do |media|
        external_id: media['external_id'],
    mod_time: media['mod_time'],
    url: media['url'],
    format: media['format']
  # you may be able to get away with just doing @new_post.update(post_params) but I can't recall if it
  # will save your newly created association as well, so you'll want to tinker with it some.

This might take care of your last issue as well, but I'm not 100% sure I understand what the issue is there so play with this some and if it doesn't then post back. 
Thanks again Jacob!

I originally had the first_or_create as a block with the image code inside it which would save the post objects with only the external_id so I was missing the crucial @new_post.assign_attributes(post_body)

Excellent! Glad you were able to get it working! :)
Join the discussion

Want to stay up-to-date with Ruby on Rails?

Join 34,674+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.

    logo Created with Sketch.

    Ruby on Rails tutorials, guides, and screencasts for web developers learning Ruby, Rails, Javascript, Turbolinks, Stimulus.js, Vue.js, and more. Icons by Icons8

    © 2020 GoRails, LLC. All rights reserved.