0

I am trying to 'debug' the update method of my TasksController's such that it can automatically update the related 'tags' that are sent over http using JSON API. I am not sure what is the correct way for doing this.

Here are my models:

class Task < ApplicationRecord
  has_many :task_tags
  has_many :tags, through: :task_tags
  validates :title, presence: true
  validates :title, uniqueness: true
end

class Tag < ApplicationRecord
  has_many :task_tags
  has_many :tasks, through: :task_tags
  validates :title, presence: true
  validates :title, uniqueness: true
end

class TaskTag < ApplicationRecord
  belongs_to :task
  belongs_to :tag
  validates :task_id, presence: true
  validates :tag_id, presence: true
end

So the Task model and Tag model are related to each other in a many-to-many relationship, which is resolved via TaskTag model. Nothing profound.

The data is sent to the model via JSON API using Postman, which I cannot change. Here is the data being sent in the Postman's PATCH action in the 'body' is as shown below:

{"data":
    {   "type":"tasks",
        "id":"2",

        "attributes":{
            "title":"Updated Task Title",
            "tags": ["Urgent", "Home"]
        }
    }
}

My ActiveModel Serializer is working correctly for the JSON API and therefore, I believe that I am receiving the correct data in my TasksController's 'update' action shown below:

class Api::V1::TasksController < ApplicationController
   ...
  def update
    task = Task.find(params[:id])
    byebug
    if task.update_attributes(task_params)
      render json: task, status: 201
    else
      render json: { errors: task.errors.full_messages }, status: 422
    end
  end
  ...
  private

  def task_params
    ActiveModelSerializers::Deserialization.jsonapi_parse(params)
  end
end

See the 'byebug' statement in my update method? The program breaks correctly there. At the byebug prompt in the terminal, I typed in 'task_params' to see what is returned by it. Here is the output:

[21, 30] in /Users/bharat/scout/todo_api_app/app/controllers/api/v1/tasks_controller.rb
   21:   end
   22: 
   23:   def update
   24:     task = Task.find(params[:id])
   25:     byebug
=> 26:     if task.update_attributes(task_params)
   27:       render json: task, status: 201
   28:     else
   29:       render json: { errors: task.errors.full_messages }, status: 422
   30:     end
(byebug) task_params
{:title=>"Updated Task Title", :tags=>["Urgent", "Home"], :id=>"2"}
(byebug) 

So it boils down to receiving the hash with the key ':tags' that has an array of tag title values. My question is: what is the Rails way of automatically creating the tags? I can always extract the :tags key value array and programmatically do it. In the past, I have done this sort of thing using accepts_nested_attributes_for when using the HTML forms as front end. But this is the first time that I am dealing with the JSON API. I suspect this is a more generic question though than the JSON API since serialization/de-serialization is working correctly.

Sorry for the long winded question, but there was no other way that I could think of to shorten it.

Bharat
  • 2,409
  • 6
  • 32
  • 57
  • If you can't change JSON structure you can't create tags automatically. For `accepts_nested_attributes_for` you need very specific JSON structure: `tags_attributes: [ { title: "Urgent" }, { id: 1, title: "Home" } ]` – Vasilisa Dec 12 '18 at 08:50
  • @Bharat "Rails way of automatically creating the tags"... because your params: `tags: [...]` is an Array of strings, the Rails way would assume then that `tags` is an attribute of type Array (i.e. through `serialize :tags, Array` or through a Postgresql Array type of column. But because your `some_task.tags` are of type `Tag` association-records and not an array of strings, then the Rails way format would need to be something like `update(tags: [tag1, tag2])` where `tag1` and `tag2` are instances of `Tag` . Regarding your problem though, I'm not sure if there's an automatic way for this. – Jay-Ar Polidario Dec 12 '18 at 10:43
  • @Bharat Unless I'm missing something that's already part of Rails, I think I'll do just like that what you've said: to use nested attributes. – Jay-Ar Polidario Dec 12 '18 at 10:51
  • @Bharat on a related note, I answered this [SO question](https://stackoverflow.com/questions/53722061/change-the-name-of-nested-keys-received-as-parameter/53723950#53723950) yesterday which you might find useful to convert your `task_params` Hash values to automatically "deep" include `*_attributes` appended so that the returned "modified" Hash value is immediately compatible to be used with `accepts_nested_attributes` – Jay-Ar Polidario Dec 12 '18 at 10:53

0 Answers0