Search code examples
ruby-on-railsunit-testingember.jsstrong-parametersjson-api

Rails Ember strong parameters clarification


What should the strong parameters for my chapters_controller be if I have a Book entity and a Chapter entity?

Note: I am using JSON API.

In my chapters_controller, should my strong parameters be:

:title, :order, :content, :published, :book, :picture

Or should it be:

:title, :order, :content, :published, :book_id, :picture

If I use :book instead of :book_id, then in my Ember application, when I go to create a new chapter, I am able to create it and associate this chapter to the parent book, however, my test fails:

def setup
  @book = books(:one)

  @new_chapter = {
    title: "Cooked Wolf Dinner",
    order: 4,
    published: false,
    content: "The bad wolf was very mad. He was determined to eat the little pig so he climbed down the chimney.",
    book: @book
  }
end

def format_jsonapi(params)
  params = {
    data: {
      type: "books",
      attributes: params
    }
  }
  return params
end

...

test "chapter create - should create new chapter assigned to an existing book" do
  assert_difference "Chapter.count", +1 do
    post chapters_path, params: format_jsonapi(@new_chapter), headers: user_authenticated_header(@jim)
    assert_response :created

    json = JSON.parse(response.body)

    attributes = json['data']['attributes']

    assert_equal "Cooked Wolf Dinner", attributes['title']
    assert_equal 4, attributes['order']
    assert_equal false, attributes['published']
    assert_equal @book.title, attributes['book']['title']
  end
end

I get error in my console saying Association type mismatch.

Perhaps my line:

book: @book 

is causing it?

Either way, gut feeling is telling me I should be using :book in my chapters_controller strong parameters.

It's just my test isn't passing, and I am not sure how to write the parameter hash for my test to pass.


Solution

  • After a few more hours of struggle and looking at the JSON API docs:

    http://jsonapi.org/format/#crud-creating

    It has come to my attention, in order to set a belongsTo relationship to an entity with JSON API, we need do this:

    POST /photos HTTP/1.1
    Content-Type: application/vnd.api+json
    Accept: application/vnd.api+json
    
    {
      "data": {
        "type": "photos",
        "attributes": {
          "title": "Ember Hamster",
          "src": "http://example.com/images/productivity.png"
        },
        "relationships": {
          "photographer": {
            "data": { "type": "people", "id": "9" }
          }
        }
      }
    }
    

    This also led me to fixing another problem I had in the past which I couldn't fix. Books can be created with multiple genres.

    The JSON API structure for assigning an array of Genre to a Book entity, we replace the data hash with a data array in the relationship part like this:

    "data": [
        { "type": "comments", "id": "5" },
        { "type": "comments", "id": "12" }
    ]
    

    Additonally, in my controllers, anything strong parameters like so:

    :title, :content, genre_ids: []
    

    Becomes

    :title, :content, :genres
    

    To comply with JSON API.

    So for my new test sample datas I now have:

    def setup
      ...
      @new_chapter = {
        title: "Cooked Wolf Dinner",
        order: 4,
        published: false,
        content: "The bad wolf was very mad. He was determined to eat the little pig so he climbed down the chimney.",
      }
      ...
    end
    
    def format_jsonapi(params, book_id = nil)
      params = {
        data: {
          type: "chapters",
          attributes: params
        }
      }
    
      if book_id != nil
        params[:data][:relationships] = {
          book: {
            data: {
              type: "books",
              id: book_id
            }
          }
        }
      end
    
      return params
    end
    

    Special note on the relationship settings - only add relationships to params if there is a relationship, otherwise, setting it to nil is telling JSON API to remove that relationship, instead of ignoring it.

    Then I can call my test like so:

    test "chapter create - should create new chapter assigned to an existing book" do
      assert_difference "Chapter.count", +1 do
      post chapters_path, params: format_jsonapi(@new_chapter, @book.id), headers: user_authenticated_header(@jim)
      assert_response :created
    
      json = JSON.parse(response.body)
    
      attributes = json['data']['attributes']
    
      assert_equal "Cooked Wolf Dinner", attributes['title']
      assert_equal 4, attributes['order']
      assert_equal false, attributes['published']
      assert_equal @book.id, json['data']['relationships']['book']['data']['id'].to_i
    end