How to structure this post api

I have a User and Role Schema.

User - has_many role
Role - belongs to user

Now I’m creating a user with simple data

def create_user(attrs) do
    %User{}
    |> User(attrs)
    |> Repo.insert()
    |> case do
      {:error, _changeset} = changeset ->
        changeset

      {:ok, user} ->
        user 
    end
 end

but now I have to create a user on one condition. So Role has a number with only that number I can create this user. So when I’m creating a user my api should look like this

http://localhost:4000/api/v1/user/1234526/role

So I thought of using cast_assoc and use the role changeset in the user changeset and while creating the user I just fetch the role number and create the user.

I just want to know if this approach is right or we can do something else also?

Hmm, can’t you first create the Role and then put that number into the User attrs?

how? Sorry didn’t understand

I thought cast_assoc will give me the changeset with the role’s number.

It is not usual to have this, it is usually the opposite. And it is the belongs_to which holds the link information.

Can You expand on this? I don’t understand too. Is that some constraint? If so, You should show the changeset.

This is just for example.

Association is correct. I just changed the context of it.

So I just want to use another changeset which is in this case is role in the user changeset. Does api help?

Not really… because it should look like.

/api/vx/users/:id/roles

Note the plural form. It is not specified which method it is (GET or POST)

This form indicates user has_many roles, not role has_many users.

I read this like

  • list all roles for user with id (GET)
  • or create a new role for user with id (POST)

okay I have another example

I have a order → has_many line_item
and line_item → belongs to order

here is the changeset for line_item

 def line_item_changeset(order_item, attrs \\ %{}) do
    order_item
    |> cast(attrs, [
      :cost_price,
      :price,
      :quantity,
      :order_id
    ])
  end

and here is the order one

def order_changeset(order, attrs \\ %{}) do
    order
    |> cast(attrs, [
      :number,
      :state,
      :completed_at,
       ])
 
  end

Now Order can have many line_item.

But while creating a line_item. I want to create a api like this.
Post API
http://localhost:4000/orders/1234526/line_item

I have changed the context. I hope this help

If You create a resource with mix phx.gen.json You might see it’s the plural form which is used.

If You want to use the singular form, You might have to map your routes manually.

If You use this, it means create a line_item for a given order, but the order should already exists.

And this example is the opposite of User/Role example, because it’s the order which has many line_items.

Yeah. But now how do I create this api. Can you give me some idea on that

I would start like this…

$ mix phx.new maxtonx --no-html --no-webpack --no-dashboard
$ cd maxtonx
$ mix phx.gen.json Shop Order orders
...
Add the resource to your :api scope in lib/maxtonx_web/router.ex:

    resources "/orders", OrderController, except: [:new, :edit]
...
$ mix phx.gen.context Shop LineItem line_items order_id:references:orders

Then I would add the line_item_controller.ex and update the router…

  scope "/api", MaxtonxWeb do
    pipe_through :api

    resources "/orders", OrderController, except: [:new, :edit] do
      resources "/line_items", LineItemController, except: [:new, :edit]
    end
  end

and will get those routes.

$ mix phx.routes
Compiling 19 files (.ex)
Generated maxtonx app
          order_path  GET     /api/orders                           MaxtonxWeb.OrderController :index
          order_path  GET     /api/orders/:id                       MaxtonxWeb.OrderController :show
          order_path  POST    /api/orders                           MaxtonxWeb.OrderController :create
          order_path  PATCH   /api/orders/:id                       MaxtonxWeb.OrderController :update
                      PUT     /api/orders/:id                       MaxtonxWeb.OrderController :update
          order_path  DELETE  /api/orders/:id                       MaxtonxWeb.OrderController :delete
order_line_item_path  GET     /api/orders/:order_id/line_items      MaxtonxWeb.LineItemController :index
order_line_item_path  GET     /api/orders/:order_id/line_items/:id  MaxtonxWeb.LineItemController :show
order_line_item_path  POST    /api/orders/:order_id/line_items      MaxtonxWeb.LineItemController :create
order_line_item_path  PATCH   /api/orders/:order_id/line_items/:id  MaxtonxWeb.LineItemController :update
                      PUT     /api/orders/:order_id/line_items/:id  MaxtonxWeb.LineItemController :update
order_line_item_path  DELETE  /api/orders/:order_id/line_items/:id  MaxtonxWeb.LineItemController :delete
           websocket  WS      /live/websocket                       Phoenix.LiveView.Socket
            longpoll  GET     /live/longpoll                        Phoenix.LiveView.Socket
            longpoll  POST    /live/longpoll                        Phoenix.LiveView.Socket
           websocket  WS      /socket/websocket                     MaxtonxWeb.UserSocket

Still a lot to go, but api looks fine.

Excellent replies by others. I’ll only add that if you want to add both a User and a Role at the same time (which doesn’t make much sense to me, roles should be in the DB before users) then you should use Ecto.Multi to do so.