How to create an object of a STI subclass using ActiveAdmin
Asked Answered
L

3

5

Given the following setup(which is not working currently)

class Employee < ActiveRecord::Base
end

class Manager < Employee
end

ActiveAdmin.register Employee do
  form do |f|
    f.input :name
    f.input :joining_date
    f.input :salary
    f.input :type, as: select, collection: Employee.descendants.map(&:name)
  end
end

I would like to have a single "new" form for all employees and be able to select the STI type of the employee in the form. I am able to see the select box for "type" as intended but when I hit the "Create" button, I get the following error:

ActiveModel::MassAssignmentSecurity::Error in Admin::EmployeesController#create

Can't mass-assign protected attributes: type

Now, I am aware of the way protected attributes work in Rails and I have a couple of workarounds such as defining Employee.attributes_protected_by_default but that is lowering the security and too hack-y.

I want to be able to do this using some feature in ActiveAdmin but I can't find one. I do not want to have to create a custom controller action as the example I showed is highly simplified and contrived.

I wish that somehow the controller generated by ActiveAdmin would identify type and do Manager.create instead of Employee.create

Does anyone know a workaround?

Loup answered 16/7, 2012 at 1:48 Comment(0)
H
10

You can customize the controller yourself. Read ActiveAdmin Doc on Customizing Controllers. Here is a quick example:

ActiveAdmin.register Post do
  controller do
    alias_method :create_user, :create

    def create
      # do what you need to here
      # then call create_user alias
      # which calls the original create
      create_user
      # or do the create yourself and don't
      # call create_user
    end
  end
end
Hite answered 5/8, 2012 at 16:39 Comment(0)
M
4

Newer versions of the inherited_resources gem have a BaseHelpers module. You can override its methods to change how the model is altered, while still maintaining all of the surrounding controller code. It's a little cleaner than alias_method, and they have hooks for all the standard REST actions:

controller do
  # overrides InheritedResources::BaseHelpers#create_resource
  def create_resource(object)
    object.do_some_cool_stuff_and_save
  end

  # overrides InheritedResources::BaseHelpers#destroy_resource
  def destroy_resource(object)
    object.soft_delete
  end
end
Median answered 25/9, 2020 at 1:1 Comment(0)
H
0

If you want to use Employee.descendants in development environment, you should set config.eager_load = true inside development.rb. By default Rails not preload all classes in development to keep it faster.

Or you can preload sub-class manually by adding require_dependency:

class Employee < ApplicationRecord
end
require_dependency 'models/manager'
$ Employee.descendants
=> ['Manager']

Anyway, here is my solution for creating/updating STI records:

accounts.rb

Don't forget to add :type to permit_params:

  ActiveAdmin.register AccountModule::Account, as: 'Account' do

    permit_params :first_name, :last_name, :email, :password, 
                :password_confirmation, :type

    form do |f|
      f.semantic_errors

      inputs do
        f.input :first_name
        f.input :last_name
        f.input :email
        f.input :password
        f.input :password_confirmation
        if f.object.new_record?
          f.input :type, as: :select, collection: ['EmailAccount', 'SocialAccount'], include_blank: false
        end
      end
      f.actions
    end
  ...

Also we should add custom create/update actions:

  controller do
    def create
      resource_type = permitted_params.dig(:account, :type)
      @resource = "AccountModule::#{resource_type}".constantize.new(permitted_params[:account])

      if @resource.save
        flash[:notice] = 'Account was successfully created.'
        redirect_to admin_account_path(@resource.id)
      else
        render action: :new
      end
    end

    def update
      @resource = AccountModule::Account.find(permitted_params[:id])

      if @resource.update(permitted_params[:account])
        flash[:notice] = 'Account was successfully updated.'
        redirect_to admin_account_path(@resource.id)
      else
        render action: :edit
      end
    end
  end
Hauser answered 2/7 at 14:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.