Accepting Nested Attributes in Batman.js

69 Views Asked by At

I have this relationship working using HTML and HTTP, but I'm completely lost as to make this work with Batman.js. I've made a plethora of attempts, but none have been successful. I've also attempted to implement the steps in this guide with no luck. Can anyone point me in the right direction?

My routes and models are the Batman.js equivalent of the rails files, aside from the controller, which is where I anticipate I will need to implement some fancy Batman.js

Routes

resources :tasks do
  resources :task_entries
end

Models

class Task < ActiveRecord::Base
  has_many :task_entries

  accepts_nested_attributes_for :task_entries

  # must have at least one entry, built in controller
  validates :task_entries, presence: true
end


class TaskEntry < ActiveRecord::Base
  belongs_to :task
  has_one :item

  accepts_nested_attributes_for :item

  # must have an item, built in controller
  validates :item, :task, presence: true
end

class Item < ActiveRecord::Base
  belongs_to :parent, polymorphic: true

  validates :title, presence: true
end        

Controller

class TasksController < ApplicationController
  def new
    @task = Task.new
    @task.task_entries.build(item: Item.new)
  end

  def task_params
    returned_params = params.require(:task).permit(
      task_entries_attributes: [
        :status, :due_at, :defer_until, :estimated_duration, :completed_at, 
        item_attributes: [
          :title, :description, :flagged, :visibility, :difficulty
        ]
      ]
    )
  end
end

Form that works

tasks/_form.html.slim
= form_for @task do |f|
  = f.fields_for :task_entries do |e|
    = e.fields_for :item do |i|
      = i.text_field :title
      = i.text_area :description
      = i.check_box :flagged
      = i.select :visibility, Item.visibility.options
      = i.select :difficulty, Item.difficulty.options
    = e.select :status, TaskEntry.status.options
    = e.datetime_select :due_at
    = e.datetime_select :defer_until
    = e.number_field :estimated_duration
    = e.datetime_select :completed_at
  = f.submit

HTTP example that works

POST /tasks HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: localhost:3000

task[task_entries_attributes][0][item_attributes][title]=help+me+stack+overflow%2C+you%27re+my+only+hope
2

There are 2 best solutions below

0
rmosolgo On BEST ANSWER

First, I'd say until this PR is merged, I wouldn't count on batman.js to encode your attributes. Instead, let batman.js encode the data its own way, then transform it in your Rails controller. (That's how we do it in Planning Center Check-Ins.)

To transform the params in your Rails controller, update the task_params method:

  def task_params
    # first, permit the params without the _attributes suffix:
    returned_params = params.require(:task).permit(
      task_entries: [
        :status, :due_at, :defer_until, :estimated_duration, :completed_at, 
        items: [
          :title, :description, :flagged, :visibility, :difficulty
        ]
      ]
    )

    # then, reassign them to different keys in the params hash (and delete the old keys)
    if returned_params[:task_entries].present?
      task_entries_params = returned_params.delete(:task_entries)
      returned_params[:task_entries_attributes] = task_entries_params
      # do the same for the items params
      if returned_params[:task_entries_attributes][:items].present?
        items_params = returned_params[:task_entries_attributes].delete(:items)
        returned_params[:task_entries_attributes][:items_attributes] = items_params
      end
    end
  end

This is assuming batman.js models that look like this:

class MyApp.Task extends Batman.Model 
  # ...
  @hasMany "task_entries", saveInline: true 

class MyApp.TaskEntry extends Batman.Model 
  # ...
  @encode "status", "due_at", "defer_until", "estimated_duration", "completed_at"
  @hasOne "item", saveInline: true, polymorphic: true, as: "parent"

class MyApp.Item extends Batman.Model 
  # ...
  @encode "title", "description", "flagged", "visibility", "difficulty"
  @belongsTo "parent", polymorphic: true

I'd agree that transforming the params isn't ideal. Hopefully that will be cleared up in batman.js 0.17.

1
allenwei On

The form seems not correct, item belongs to task entry, not task

= form_for @task do |f| = f.fields_for :task_entries do |e| = e.fields_for :item do |i| = i.text_field :title = i.text_area :description = i.check_box :flagged = i.select :visibility, Item.visibility.options = i.select :difficulty, Item.difficulty.options = e.select :status, TaskEntry.status.options = e.datetime_select :due_at = e.datetime_select :defer_until = e.number_field :estimated_duration = e.datetime_select :completed_at = f.submit