Nested strong parameters in rails - AssociationTypeMismatch MYMODEL expected, got ActionController::Parameters()
Asked Answered
K

6

11

I'm rendering a model and it's children Books in JSON like so:

{"id":2,"complete":false,"private":false, "books" [{ "id":2,"name":"Some Book"},.....

I then come to update this model by passing the same JSON back to my controller and I get the following error:

ActiveRecord::AssociationTypeMismatch (Book (#2245089560) expected, got ActionController::Parameters(#2153445460))

In my controller I'm using the following to update:

@project.update_attributes!(project_params)

private

def project_params
    params.permit(:id, { books: [:id] } )
end

No matter which attributes I whitelist in permit I can't seem to save the child model.

Am I missing something obvious?

Update - another example:

Controller:

def create
    @model = Model.new(model_params)
end
def model_params
    params.fetch(:model, {}).permit(:child_model => [:name, :other])
end

Request:

post 'api.address/model', :model => { :child_model => { :name => "some name" } }

Model:

accepts_nested_attributes_for :child_model

Error:

expected ChildModel, got ActionController::Parameters

Tried this method to no avail: http://www.rubyexperiments.com/using-strong-parameters-with-nested-forms/

Kodak answered 31/1, 2014 at 21:19 Comment(0)
U
9

I'm using Angular.js & Rails & Rails serializer, and this worked for me:

Model:

  • has_many :features
  • accepts_nested_attributes_for :features

ModelSerializer:

  • has_many :features, root: :features_attributes

Controller:

  • params.permit features_attributes: [:id, :enabled]

AngularJS:

  • ng-repeat="feature in model.features_attributes track by feature.id
Unitive answered 5/6, 2014 at 18:20 Comment(2)
What is ModelSerializer?Cellular
@Cellular I imagine he's probably talking about this.Expressage
S
13

Are you using accepts_nested_attributes_for :books on your project model? If so, instead of "books", the key should be "books_attributes".

def project_params
  params.permit(:id, :complete, :false, :private, books_attributes: [:id, :name])
end
Septuagint answered 2/2, 2014 at 5:1 Comment(6)
Thanks Josh - I'm using accepts_nested_attributes_for :books but I'm not using a nested form when updating the objects, I'm using the json output. When I change the key to: books_attributes it doesn't seem to update the books and specifies books as an unpermitted parameter.Kodak
@AlanH, you need to edit your form, like this: f.fields_for :book, book do |b|Lethe
Thanks @Lethe but I'm passing the data via AngularJS and don't have a form.Kodak
@AlanH Did you ever find a solution for this? I'm facing the exact same issue.Jodijodie
@Jodijodie - No I ended up posting the models separately as I couldn't find a good solution. I've updated the question with another example.Kodak
@Jodijodie - try this? #17584642Kodak
U
9

I'm using Angular.js & Rails & Rails serializer, and this worked for me:

Model:

  • has_many :features
  • accepts_nested_attributes_for :features

ModelSerializer:

  • has_many :features, root: :features_attributes

Controller:

  • params.permit features_attributes: [:id, :enabled]

AngularJS:

  • ng-repeat="feature in model.features_attributes track by feature.id
Unitive answered 5/6, 2014 at 18:20 Comment(2)
What is ModelSerializer?Cellular
@Cellular I imagine he's probably talking about this.Expressage
D
7

My solution to this using ember.js was setting the books_attributes mannualy.

In controller:

def project_params      
  params[:project][:books_attributes] = params[:project][:books_or_whatever_name_relationships_have] if params[:project][:books_or_whatever_name_relationships_have]
  params.require(:project).permit(:attr1, :attr2,...., books_attributes: [:book_attr1, :book_attr2, ....])

end

So rails checks and filters the nested attributes as it expected them to come

Doralyn answered 11/6, 2014 at 11:42 Comment(0)
P
0

This worked for me. My parent model was an Artist and the child model was a Url.

class ArtistsController < ApplicationController

  def update
    artist = Artist.find(params[:id].to_i)
    artist.update_attributes(artist_params)
    render json: artist
  end

private

  def artist_params
    remap_urls(params.permit(:name, :description, urls: [:id, :url, :title, :_destroy]))
  end

  def remap_urls(hash)
    urls = hash[:urls]
    return hash unless urls
    hash.reject{|k,v| k == 'urls' }.merge(:urls_attributes => urls)
  end
end

class Artist < ActiveRecord::Base
  has_many :urls, dependent: :destroy
  accepts_nested_attributes_for :urls, allow_destroy: true
end

class Url < ActiveRecord::Base
  belongs_to :artist
end

... and in coffeescript (to handle deletions):

  @ArtistCtrl = ($scope, $routeParams, $location, API) ->

    $scope.destroyUrls = []

    $scope.update = (artist) ->
      artist.urls.push({id: id, _destroy: true}) for id in $scope.destroyUrls
      artist.$update(redirectToShow, artistError)

    $scope.deleteURL = (artist,url) ->
      artist.urls.splice(artist.urls.indexOf(url),1)
      $scope.destroyUrls.push(url.id)
Petersham answered 28/9, 2014 at 3:28 Comment(0)
K
0

Something is missing from all of the answers, which is the inputs for fields_for in the form.

The form works if you do this:

f.fields_for @model.submodel do ..

However, the form is sent as model[submodel], but that's what causes the error others have mentioned in their answers. If you try to do model.update(model_params), Rails will raise an error that it's expecting a Submodel type.

To fix this, make sure you follow the :name, value format:

f.fields_for :submodel, @model.submodel do ...

Then in the controller, make sure you put _attributes on your params:

def model_params
  params.require(:model).permit(submodel_attributes: [:field])
end

Now the save, update, etc. will work fine.

Kulturkampf answered 20/12, 2019 at 18:22 Comment(0)
B
-3

Wasted several days trying to figure out how to use accepts_nested_attributes with Angular, and the issue is always the same: Rails whitelist will not allow the variables into the params hash. I've tried every single different whitelisting syntax that everyone said on SO and other blogs, tried using :inverse, tried using habtm and mas_many_through, tried manually rolling my own solution but that wont work if the whitelist wont allow params through, tried doing what http://guides.rubyonrails.org says about 'Outside the Scope of Strong Parameters', tried removing whitelisting all together which isnt really an option but it causes other problems anyways. Not sure why rails 4 strong parameter whitelisting wont allow arbitrary data thru, thats a huge problem especially if accepts_nested_attributes doesn't work either.... I guess we are left to just create/delete all associations on a separate page/form/controller and look like an idiot making my end users use several forms/pages to do something that should be easily doable on 1 page with 1 form. Ya know, usually I expect Angular to screw me, but this time Angular worked quite well and it was actually Rails 4 that screwed me twice on 1 issue that should be very straightforward.

Bailar answered 11/4, 2015 at 21:14 Comment(2)
This is not really answering the question directly. Can you edit your post so it directly answers the question posted?Harrietharriett
I agree with the last comment but I sympathize with this response since it appears the accepted answer doesn't work as of Rails 4.2.0. I've gone through a similar circus of scouring the internet for an solution and trying every available permit pattern. I've had luck setting up a separate json array which accepts the model object's attributes which I parse out and then manually set active record associations for but this makes both client and server side code super ugly.Aviva

© 2022 - 2024 — McMap. All rights reserved.