user2936314
user2936314

Reputation: 1792

Rails POSTing JSON for accepts_nested_attributes

I am playing around with Rails and Angular and trying to get a simple association to update via a JSON PUT request from the frontend.

Association: Article has_many :categories, through: :article_categories

Article model:

class Article < ActiveRecord::Base
    validates :title, presence: true, uniqueness: true
    validates :body, presence: true
    has_many :article_categories
    has_many :categories, through: :article_categories

    accepts_nested_attributes_for :categories
end

I've got no issues updating the title and body, but I cannot update the article's categories.

Here are the relevant controller parts

        def update
            @article = Article.find(params[:id])
            if @article.update(article_params)
                render json: @article, status: 200
            end
        end

        private
          def article_params
            params.require(:article).permit(:title, :body,
             categories_attributes: [:name, :id])
          end

My incoming JSON looks like this, spaced out to make it more readable:

Started PUT "/api/v1/articles/6" for 127.0.0.1 at 2014-06-01 17:53:04 +0900
Processing by Api::V1::ArticlesController#update as HTML
  Parameters: {"title"=>"new title", "body"=>"blablabla", "id"=>"6", "categories"=>
[{"name"=>"Ruby", "id"=>1}, {"name"=>"Javascript", "id"=>2}, {"name"=>"HTML", "id"=>3},
{"name"=>"CSS", "id"=>4}],

"categories_attributes"=>[{"name"=>"Ruby", "id"=>1},
{"name"=>"Javascript", "id"=>2}, {"name"=>"HTML", "id"=>3}, {"name"=>"CSS", "id"=>4}],

"article"=>{"id"=>"6", "title"=>"new title", "body"=>"blablabla"}}

The only feedback I get is that article id isn't a whitelisted param. Isn't the categories_attributes what Rails looks for when it takes nested attributes? Why isn't it complaining about the categories params not being whitelisted?

Upvotes: 1

Views: 221

Answers (2)

user2936314
user2936314

Reputation: 1792

There were a couple issues here:

My json format was incorrect. The categories were not nested in article and that's why rails wasn't throwing validation errors. I changed the angular frontend to post this:

{"article"=>{"title"=>"sdfsd", "body"=>"sdf", "category_ids"=>[1, 2, 3]}}

My angular $scope contained both the category ID and name, so I had to write a function to parse out the IDs and dump them in an array. Annoying.

Next, creating an article with this JSON format was failing because of the validations on ArticleCategory. I added the inverse_of to my models as described here https://github.com/rails/rails/issues/5178 and then the validations would pass when creating a new article with categories and bypassing the join model. If I understand things correctly, this is an alternative solution to Richard Peck's answer.

The final models looked like this:

class Article < ActiveRecord::Base
  validates :title, presence: true, uniqueness: true
  validates :body, presence: true
  has_many :article_categories, inverse_of: :article
  has_many :categories, through: :article_categories
end

class Category < ActiveRecord::Base
  validates :name, presence: true, uniqueness: true
  has_many :article_categories, inverse_of: :category
  has_many :articles, through: :article_categories
end


class ArticleCategory < ActiveRecord::Base
  belongs_to :article, inverse_of: :article_categories
  belongs_to :category, inverse_of: :article_categories
  validates :category, :article, presence: true
end

Upvotes: 0

Richard Peck
Richard Peck

Reputation: 76784

We've had this problem before - you're basically bypassing the join model, which is preventing your application from working correctly.

Nested Association

Basically, you need to pass your associated data to your article_categories model before passing the categories data:

#app/models/article.rb
Class Article < ActiveRecord::Base
    ...
    accepts_nested_attributes_for :article_categories
end

#app/models/article_category.rb
Class ArticleCategory < ActiveRecord::Base
    belongs_to :category
    belongs_to :article
    accepts_nested_attributes_for :category
end

#app/controllers/articles_controller.rb
def new
   @article = Article.new
   @article.article_categories.build.build_category
end

def create
   @article = Article.new(article_params)
   @article.save
end

private

def article_params
     params.require(:article).permit(:article, :attributes, article_categories_attributes: [categories_attributes: [:category, :options]] )
end

#app/view/articles/new.html.erb
<%= form_for @article do |f| %>
    <%= f.fiels_for :article_categories do |ac| %>
        <%= ac.fields_For :categories do |c| %>
            <%= c.text_field :your_field &>
        <% end %>
    <% end %>
<% end %>

Upvotes: 1

Related Questions