How do I do upserts and/or conditional saves?
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.
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| @new_post.images.build( external_id: media['external_id'], mod_time: media['mod_time'], url: media['url'], format: media['format'] ).save end end end def client Api.client end end end
My models are pretty standard:
Post
class Post < ApplicationRecord has_many :images accepts_nested_attributes_for :images end
Image
class Image < ApplicationRecord belongs_to :post end
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| @new_post.images.build( external_id: media['external_id'], mod_time: media['mod_time'], url: media['url'], format: media['format'] ) end @new_post.assign_attributes(post_params) @new_post.save # 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. end
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)
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)