I have researched this question very much, with no solution specifically for deeply nested models. I have come up with the following fix, wondering if there is a better way.
I've tried to cut this down to be nearly as simple as I can make it to explain the issue and my solution.
The issue is that there exist the following ActiveRecord models in rails:
Deeply Nested Object Tree
class Template
has_many :sections, dependent: :destroy
accepts_nested_attributes_for :sections
has_many :columns, through: :sections, dependent: :destroy
has_many :fields, through: :columns, dependent: :destroy
class Section
belongs_to :template
has_many :columns, dependent: :destroy
accepts_nested_attributes_for :columns
class Column
belongs_to :section
has_many :fields, dependent: :destroy
accepts_nested_attributes_for :fields
class Field
belongs_to :column
and in angular, the goal is to send a single ngResource $resource call to 'templates/:id' and update the entire chain of children. (Each piece of the chain is created prior, in the template creation process. The need for a unified update occurs when the template is finalized.)
### ClassFactory.coffee ###
#
# Create a connection to the API
# ...
return $resource('api/:class/:id', { format: 'json' }, {...})
# ...
### EditTemplateController.coffee ###
#
# Create angular template resource, save it, and redirect to editing view
# ...
$scope.template = new ClassFactory({class: 'templates'})
$scope.template.$save({class: 'templates'}, (res)->
$location.path('templates/'+res.id+'/edit')
)
#
# Update the angular object
# ...
$scope.saveTemplate = ->
$scope.template.$update({class: 'templates', id: $scope.template.id})
#...
### templates_controller.rb ###
#
# Create a single DB interaction with deliberately declared parameters and nested *_attributes parameters
# ...
def update
params[:template][:sections_attributes] = params[:sections]
params[:template][:sections_attributes].each do |paramSection|
paramSection[:columns_attributes] = paramSection[:columns]
paramSection[:columns_attributes].each do |paramColumn|
paramColumn[:fields_attributes] = paramColumn[:fields]
end
end
template = current_user.templates.find(params[:id])
template.update_attributes(allowed_params)
head :no_content
end
private
def allowed_params
params.require(:template).permit(
:name, sections_attributes: [
:id, :name, columns_attributes: [
:id, :fields_attributes: [
:id, :name, :value
]
]
]
end
# ...
As far as I have worked out, the fix is to declare *_attributes as shown above:
params[:template][:sections_attributes] = params[:sections]
because of angular's inability to send the format of parameters that rails is looking for.
This obviously feels like a hacky solution. Is there no better way to handle deeply nested rails models while using angularjs?