How to use URL placeholders in action?

Given I have a nested resource with articles having many comments, how do I create an API endpoint for posting comments.
I want my API for creating comments at POST /articles/:id/comments. How do I create this nested API structure?
My resource for Articles and Comments are shown below:

Article resource:

defmodule Account.Blog.Article do use Ash.Resource, domain: Account.Blog, data_layer: AshPostgres.DataLayer, extensions: [AshJsonApi.Resource] alias Account.Blog.Comment postgres do table "articles" repo Account.Repo end json_api do type "article" end actions do defaults [:read] create :article do accept [:name] end end attributes do uuid_primary_key :id attribute :name, :string end relationships do has_many :comments, Comment end end 

Comments resource

defmodule Account.Blog.Comment do use Ash.Resource, domain: Account.Blog, data_layer: AshPostgres.DataLayer, extensions: [AshJsonApi.Resource] alias Account.Blog.Article postgres do table "comments" repo Account.Repo end json_api do type "comment" end actions do defaults [:read] create :comment do accept [:comment, :article_id] end end attributes do uuid_primary_key :id attribute :comment, :string end relationships do belongs_to :article, Article end end 

Domain:

defmodule Account.Blog do use Ash.Domain, extensions: [AshJsonApi.Domain] alias Account.Blog.{Article, Comment} resources do resource Article resource Comment end json_api do routes do base_route "/articles", Article do get(:read) post(:article) end base_route "/comments", Comment do get(:read) post(:comment) end end end end 

I am currently giving the request at POST /comments with the article_id to mention which article the comments belongs to.

Example:

{ "data": { "type": "comment", "attributes": { "comment": "Good", "article_id": "article_id" } } } 

You should be able to do that by including an article_id in the route params for your post.

# include at top level post Post, :create, route: "/articles/:article_id/comments" 

Your post pointed out to me that being able to nest base_routes could clean this up greatly, so I’m pushing a release w/ that ability soon. Then it would look like this:

defmodule Account.Blog do use Ash.Domain, extensions: [AshJsonApi.Domain] alias Account.Blog.{Article, Comment} resources do resource Article resource Comment end json_api do routes do base_route "/articles", Article do get(:read) post(:article) base_route "/:article_id", Comment do post :comment # would call the `:comment` create action on comment end end base_route "/comments", Comment do get(:read) post(:comment) end end end end 
2 Likes

wow @zachdaniel that’s super fast! You comment here is 18hours ago and I see this commit implementing this feature17hrs ago! improvement: support nested `base_route`s · ash-project/ash_json_api@bf77d4d · GitHub

This is something I was also looking for as well. However, I would like to understand the difference between using related or post_to_relationship macro inside the routes vs what you have proposed as a new solution here:

For eg., can the following do the same as what you have proposed as nested base_route for creating comment?

 json_api do routes do base_route "/articles", Article do post_to_relationship :comments end end 
1 Like

@zachdaniel Thank you so much for the quick reply :smiley:. Updated to the newer version and applied the solution.

related is a route for reading the records of a relationship, i.e

/posts/:id/comments

It can be thought of as an index route that only returns the related records.

post_to_relationship is for “linking” a record or records to another via a relationship. It accepts a very specific input (something called “linkage”), and is typically only used to connect existing things together.

You can see more here: JSON:API — Latest Specification (v1.1)

2 Likes

Just to clarify, please check if my understanding is correct:

1. related option on routes

Use this only for GET requests for related records. Doesn’t work for any other methods.

If Article resource has many Tag relationship, this related option is used to fetch only the associated tags of a specific article.

In this case, the Ash action for the api is placed in the Article resource.

2. post_to_relationship option on routes

Use this option for any other methods other than GET for related records. The related record must already exist. In the case of Article and Tag relationships, both the records must exist already. post_to_relationship is used to create the linkage in ArticleTags resource.

The same applies for patch_relationship, delete_from_relationship to update or delete the link ArticleTags resource record.

In this case, the Ash action for the api is placed in the Article resource.

3. Nested base_route as explained in this post by @zachdaniel

Use this option to do any of the above actions with the main differences as below:

  1. Ash action for the nested route is present in the nested resource rather than in the parent resource as in the above options. i.e., GET /articles/:id/tags will be responded by an action in the Tag resource.
  2. In the nested resource, one has to take care of creating/updating/deleting all the links manually.

Is my understanding correct?

1 Like

Yep! Looks right to me :slight_smile:

1 Like