How to update "some" columns on a model/resource with Absinthe/GraphQL

I’ve been looking at the Absinthe documentation and there is an example for creating resources (https://hexdocs.pm/absinthe/complex-arguments.html#content) but I’d like to see an example of updating only a specific column on a resource.

Using the example from the docs (on this page: https://hexdocs.pm/absinthe/mutations.html#content) the code is as follows:

# schema

mutation do

  @desc "Create a post"
  field :create_post, type: :post do
    arg :title, non_null(:string)
    arg :body, non_null(:string)
    arg :published_at, :naive_datetime

    resolve &Resolvers.Content.create_post/3
  end

end

# resolvers

def create_post(_parent, args, %{context: %{current_user: user}}) do
  Blog.Content.create_post(user, args)
end

Creating the update I think would be fairly straightforward. Something like:

  @desc "Update a post"
  field :update_post, type: :post do
    arg :id, :id
    arg :title, non_null(:string)
    arg :body, non_null(:string)
    arg :published_at, :naive_datetime

    resolve &Resolvers.Content.update_post/3
  end

def update_post(_parent, %{id: post_id} = args, %{context: %{current_user: user}}) do
  post = Blog.Content.get_post!(post_id)
  
  Blog.Content.update_post(post, args)
end

The issue I have run into is that GraphQL expects all the fields to be sent. So I’d have to send title, body, and published_at even though I may only want to update the title.

What’s the idiomatic Absinthe way to address this?

Thank you

(Please keep in mind this is just the example from the guides so I haven’t tested that it works. The code I’m working with is larger/more distracting so I thought I’d go with what’s in the guides.)

Update:

I made a few modifications and the following seems to work:

  @desc "Update a post"
  field :update_post, type: :post do
    arg :id, :id
    arg :post, :post_input

    resolve &Resolvers.Content.update_post/3
  end

input_object :post_input do
  field :title, :string
  field :body, :string
  field :published_at, :naive_datetime
end


  def update_post(_parent, args, %{context: %{current_user: _current_user}}) do
    post_params = args.post
    post_id = args.id

    post = Blog.Content.get_post!(post_id)
  
    Blog.Content.update_post(post, post_params)
  end

Is this correct?

Really you can do two things.

  1. If you really want to update only one field ever, consider updating the mutation to only accept that one field. It’s much more idiomatic because you’re telling the GraphQL client which fields it can send to be updated.

  2. You can also pull the fields you want out of the args in the resolver and pass only those to your context function. It’s less idiomatic because the GraphQL client doesn’t know what’s being used, but it is a way to limit the fields.

1 Like

I think I may have found it (updated above). It appears to work by only updating the columns I send to the backend. Still not sure if this IS the way to do it.

I like being explicit as you propose in both option 1 and 2 but I would think the generalizability that GraphQL offers means that just as you can dynamically request which attributes you want to query, the same would go for a mutation.

This is still pretty new to me.

That is true but if you define your inout_object with only the fields you want to be updated, then you’ll necessarily limit what the client can send (GraphQL will throw an exception). So for my money that’s probably the best thing to do.

I’m not sure I understand (sorry). I define every attribute on the input_object, but only mutate the title in the request. Later on, I can mutate the body by sending a different mutation but not having to change anything on the backend. Doesn’t that mean I haven’t limited my backend? (Sorry if i’ve misunderstood)

That’s correct. If you don’t want the client to mutate an attribute, you should remove it. If you do, you need to keep it.

In that case doing a check in the resolver is the best way, so, as you say, you don’t have to change the inout_object.

1 Like

Thank you