How to use params.expect with accepts_nested_attributes_for delegated_type?

Since the parameters can differ between the delegated types (sort of the whole point of using them) this does not seem to be possible - unless there is a way to define on the individual (sub)models which parameters the controller should expect?

I mean, it works if I add all the parameters used by all my delegated types to params.expect(delegator: [ delegateee: [ :all, :possible, :paramaters, :that, :a, :delegatee, :can, :possibly, :use]]) but that just doesn’t feel right. The only attribute they share is :id and I’m not even using that since I set update_only: true.

I have worked with delegated types in different projects a fair bit, and each type has always had a different controller.

What use-case do you have that it needs to be one controller; maybe with that insight I can help better.

An analogous example would be the old Animal one:

# models/animal.rb
class Animal < ApplicationRecord
  delegated_type :animalable, types: ["Animal::Cow", "Animal::Sheep"], inverse_of: :animal, dependent: :destroy
  accepts_nested_attributes_for :animalable, update_only: true
end

# models/animal/cow.rb
class Animal::Cow < ApplicationRecord
  has_one :animal, as: :animalable, inverse_of: :animalable, touch: true
end

# models/animal/sheep.rb
class Animal::Sheep < ApplicationRecord
  has_one :animal, as: :animalable, inverse_of: :animalable, touch: true
end

# db/schema.rb
  create_table "animals", force: :cascade do |t|
    t.string "name"
    t.bigint "animalable_id"
    t.string "animalable_type"
  end

  create_table "animal_cow", force: :cascade do |t|
    t.boolean "milked"
  end

  create_table "animal_sheep", force: :cascade do |t|
    t.boolean "shorn"
  end

# views/animals/new.html.erb
<%= form_with(model: @animal) do |form| %>
	<%= form.text_field(:name) %>
	<%= render partial: "animals/#{@animal.animalable_type.demodulize}/new", locals: {form: form} %>
	<%= form.submit %>
<% end %>

# views/animals/goat/_new.html.erb
<%= form.fields_for :animalable, include_id: false do |animalable| %>
	<%= animalable.checkbox(:shorn) %>
<% end %>

# views/animals/cow/_new.html.erb
<%= form.fields_for :animalable, include_id: false do |animalable| %>
	<%= animalable.checkbox(:milked) %>
<% end %>

Got it. How about something like…

PERMITTED_ATTRIBUTES = {
    'cow' => [:milked],
    'sheep' => [:shorn]
  }.freeze

  private

  def animal_params
    params.require(:animal).permit(
      :id,
      animalable_attributes: [
        :id,
        :animalable_type,
        *PERMITTED_ATTRIBUTES[params[:animal][:animalable_type]]
      ]
    )
  end
1 Like

Seconding this. The happy path OP is looking for is to have a controller corresponding to each type. Then each controller is only responsible for defining strong params for its corresponding type, and the shared params can be provided through a parent controller or a concern.