Routes.rb for supporting an Upsert

Hi all, Our team is increasingly hitting issues where we need to have an upsert operation in the API that will basically find_or_create on the parameters and not just an ID. The HTTP spec basically defines PUT as being compatible with the concept of an upsert.

I have started manually adding this with custom actions in many of our resources, but I wanted to raise this discussion here because I think a lot of other folks might be hitting this and silently working around it, and then many newbies are likely going to GraphQL or violating REST over needs like this. Baking it into the standard resources routes helper so that PUT to the index path does an upsert might be a great future default.

1 Like

Do you have an idea how that would look in the routes api? If yes put it into a gem. Document it propely and if it gets milions of downloads migt eventually be added to rails core. Thats what i would do.

1 Like

Hey @noahhorton how did you go about using upsert? I got this far:

        resources :images, except: [:create, :update] do
          collection do
            match "/", action: :upsert, via: [:put, :post]
          end
          member do
            match "/", action: :upsert, via: [:put, :patch]
          end
        end

Is there a better way? It’d be nice if you could just do something like

resources :images, with: :upsert

Hey - sorry for not replying earlier.

I basically over-rode CREATE to do upsert since CREATE had all the logic needed to know which case it was. But I definitely felt a bit dirty.

I wonder if you could dry that up with a route concern. So:

concern :upsert do
  collection do
    match "/", action: :upsert, via: [:put, :post]
  end
  member do
    match "/", action: :upsert, via: [:put, :patch]
  end
end

Then

resources :images, except: %i[create update], concerns: :upsert

If you had a whole bunch of resources like this you could perhaps use with_options to dry it up more. I.E.

with_options except: %i[create update], concerns: :upsert do
  resources :images
  resources :videos
  resources :songs
end

Of course if all those are going to be the same (no further customizations) you could just do:

resources *%i[images videos songs],
  except: %i[create update], concerns: :upsert

I think I’ve typically just used create previously for upserts and include the id with the params if I want to upsert. It may not be 100% the intention of the HTTP spec but it feels like the pragmatic approach. I’m find loosing the purity.

1 Like