Using Dropbox Chooser within ActiveAdmin

We use Dropbox quite heavily in Loudcloud. I’m working on an internal CRM system and since all of our files are on Dropbox anyway, I figured it would be super helpful to just have to link to our Dropbox files.

Here are the relevant files:

# app/inputs/dropbox_input.rb
class DropboxInput < Formtastic::Inputs::FileInput
  def to_html
    input_wrapping do
      label_html <<
      builder.text_field(method, input_html_options.merge({ type: "dropbox-chooser", style: "visibility: hidden;" }))
    end
  end
end

# app/admin/invoice.rb
ActiveAdmin.register Invoice do
  form do |f|
    within @head do
      script src: "https://www.dropbox.com/static/api/1/dropins.js", id: "dropboxjs", type: "text/javascript", "data-app-key" => "APP_KEY_HERE"
    end

    # ....

    f.inputs "Official Receipt" do
      f.input :or_file_url, as: :dropbox, label: "OR File URL"
    end

    # ....
  end
end

Resources:
https://www.dropbox.com/developers/dropins/chooser/js
http://stackoverflow.com/questions/12809237/insert-specifics-js-in-the-header-of-some-pages-active-admin
https://github.com/justinfrench/formtastic/blob/master/lib/formtastic/inputs/base/html.rb
http://stackoverflow.com/questions/12465020/getting-formtastic-in-activeadmin-to-use-custom-form-input-builder

Nested forms with polymorphic association in Active Admin/Formtastic

I spent nearly the whole day making this work.

Given models:
Invoice, has_many :items
Item belongs_to :itemizable, polymorphic: true
Domain & Service has_many :items, as: :itemizable

The problem was multiple things:

  1. The automagic of Formtastic can’t detect the collection if it’s a polymorphic association
  2. Formtastic doesn’t really play well with non-existent attributes

Initially, I’ve thought of just doing:

ActiveAdmin.register Invoice do
  form do |f|    
    # ...
    f.has_many :items do |item|
      item.input :itemizable, collection: (Domain.all + Service.all)
      item.input :quantity
      item.input :price_per_piece
    end

    f.actions
  end
end

But this fails because 1) domains and service can share the same id and 2) I have no way to tell what the item was.
A few hours in and I was going nowhere. It’s surprisingly hard to look for anything related to polymorphic associations on Formtastic. This post gave me an idea however.

So, I’ve thought, why not just hold the id temporarily on an accessor attribute and just do the assignment from a callback before validation kicks in based on which attribute it went into? Raise an error if both were filled up.

It worked! I can now save new polymorphic records. (look at Item#assign_itemizable)

There’s a small problem however. The form to edit an existing record doesn’t pre-populate the corresponding select dropdowns. The solutions was rather simple, override the reader method to return the id of the itemizable if the itemizable is a member of the class.

Maintenance-wise, everything here would add overhead for every new itemizable model I would associate to item, but overall, I think it was a pretty elegant hack. *pats self at back*

Here’s the complete code:

# app/models/invoice.rb
class Invoice < ActiveRecord::Base 
  has_many :items

  accepts_nested_attributes_for :items
end

# app/models/item.rb
class Item < ActiveRecord::Base
  before_validation :assign_itemizable

  belongs_to :invoice
  belongs_to :itemizable, polymorphic: true
  
  validates :itemizable, presence: true

  attr_accessor :itemizable_domain, :itemizable_service

  def itemizable_domain
    self.itemizable.id if self.itemizable.is_a? Domain
  end

  def itemizable_service
    self.itemizable.id if self.itemizable.is_a? Service
  end

  protected
  def assign_itemizable
    if [email protected]_domain.blank? && [email protected]_service.blank?
      errors.add(:itemizable, "can't have both a domain and a service") 
    end

    unless @itemizable_domain.blank?
      self.itemizable = Domain.find(@itemizable_domain)
    end

    unless @itemizable_service.blank?
      self.itemizable = Service.find(@itemizable_service)
    end
  end
end

# app/admin/invoice.rb
ActiveAdmin.register Invoice do
  form do |f|
    f.inputs "Invoice" do 
      f.input :customer
      f.input :invoice_number
      f.input :issuing_person
      f.input :issued_on
      f.input :remarks
    end
    
    f.has_many :items do |item|
      item.input :itemizable_domain, collection: Domain.all
      item.input :itemizable_service, collection: Service.all
      item.input :quantity
      item.input :price_per_piece
    end

    f.actions
  end
end

Edit: Fixed a bug in Item because I overwrote the accessors, which were being called in the validation. This caused a sort of chicken-egg situation, and ultimately causes the validation to fail all the time. Changed .empty? to .blank? so it doesn't barf out on nils.