ActiveAdminにCarrierwave用のフォームを自動出力する

# はじめに

ActiveAdminもCarrierwaveも便利でいいですよね。

でもActiveAdminにはCarrierwave用の処理が入ってないので、```app/admin/*rb```に毎回色々書かなきゃいけなくなって大変です。
また、ファイルのアップロードとその上書きアップロードはできるけど、削除はそのままではできないので、これも毎回削除用のチェックボックスを追加しないといけなくて大変です。

せっかくのActiveAdminなので、カラムの種類に応じて勝手に必要な表示とフォームが出力されるようにしたいです。

ということで、とりあえずCarrierwaveのカラムについて勝手に必要な処理をしてくれるようにしました。
他の特殊な型のカラムでも同じように出来るので、今後のためにもメモしておこうかと思います。

## 環境

```Gemfile.lockから抜粋
rails (4.2.0)
activeadmin (1.0.0.pre2)
carrierwave (0.10.0)
```

# 完成サンプル

## 一覧画面、閲覧画面

* サムネイルがない場合 ・・・ 実ファイルのURLへリンクしたファイル名を表示
* 画像アップロードがされていない（or削除された）場合 ・・・ 空欄
* サムネイルがある場合 ・・・ サムネイル表示

![image](https://qiita-image-store.s3.amazonaws.com/0/64098/3d5a491e-96ba-98ec-2c3c-87770647819c.png)

## 編集画面

* サムネイルがある場合

![image](https://qiita-image-store.s3.amazonaws.com/0/64098/4274a5d0-a1d6-407f-aa73-9270a53ab2e2.png)

* サムネイルがない場合

![image](https://qiita-image-store.s3.amazonaws.com/0/64098/420d3255-2685-1b81-dc16-784f47ffd992.png)


# ソースサンプル

## まとめてダウンロード

[ActiveAdmin+Carrierwave [Gist]](https://gist.github.com/goosys/0071a19f122d89ae7028bd2f7d2a2228)

## 一覧画面、閲覧画面用

```ruby:config/initializers/extensions/active_admin/view_helpers/display_helper.rb
#
# https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/view_helpers/display_helper.rb
#
module ActiveAdmin
  module ViewHelpers
    module DisplayHelper

      def pretty_format_with_pretty_other_format(object)
        pretty_uploader_format(object) ||
          pretty_jsonb_format(object) ||
          pretty_format_without_pretty_other_format(object)
      end
      alias_method_chain :pretty_format, :pretty_other_format

      def pretty_uploader_format(object)
        if object.class.ancestors.include?(CarrierWave::Uploader::Base)
          if object.present?
            if object.version_exists?(:thumb) && object.thumb.file.exists?
              content_tag(:p,
                image_tag(object.url(:thumb)),
                {class: 'attachment_wrap attachment_img'}
              )
            else
              link_to object.file.identifier, object.url
            end
          end
        end
      end

      def pretty_jsonb_format(object)
        if object.class.ancestors.include?(Hash)
          content_tag(:pre,
            JSON.pretty_generate(object).gsub(":", " =>")
          )
        end
      end

    end
  end
end
```

Railsでのパッチの方法がまだよく分からないのでそこらへんは適当ですが、
元の[pretty_format](https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/view_helpers/display_helper.rb#L63)メソッドにカラム型毎に処理を追加していけばよさそうでした。
引数の```object```にはModelのAttributeが入ってきてたので、その型なり値なりを見て分岐していけばよさそうです。

Carrierwaveの場合は、

```ruby:app/models/animal.rb
class Animal < ActiveRecord::Base
  mount_uploader :image, DefaultUploader
```

```ruby:app/uploaders/default_uploader.rb
class DefaultUploader < CarrierWave::Uploader::Base
```

このように定義されていると、imageカラムのときの```object```には```DefaultUploader```のインスタンスが渡ってきます。
デフォルト以外のアップローダーもどんどん定義されていくと思うので、どのアップローダーでも共通に表示されるように```CarrierWave::Uploader::Base```を継承しているクラスかどうかで判断しています。

### サムネイル

あとは、サムネイル表示できるようにアップローダーの```version```に```thumb```を定義しておけばOKです。
画像以外のアップローダーの場合でも、キャプチャ画像などをthumbに保存するようにしておけば色々捗りそうな予感がしますね。

```ruby:app/uploaders/default_uploader.rb
  version :thumb do
    process :resize_to_fit => [100, 100]
  end
```

thumbというversionが存在しないアップローダーの場合はファイル名の表示になります。

### ちなみに、
参考までに、jsonbカラムの場合の処理も掲載しました。
jsonbカラムの場合は```object```に```Hash```が渡ってきたので、そのままJSON文字列にして表示しています。
長いJSONが想定される場合は適宜substrするなりして対応すればよいかと思います。

## 編集画面用

```ruby:app/inputs/file_input.rb
class FileInput < Formtastic::Inputs::FileInput
  def image_html_options
    {:class => 'attachment_wrap attachment_img'}.merge(options[:image_html] || {})
  end
  def image_plain_html_options
    {:class => 'attachment_wrap attachment_plain'}.merge(options[:image_html] || {})
  end

  def to_html
    input_wrapping do
      label_html <<
      builder.file_field(method, input_html_options) <<
      image_html
    end
  end

  protected

    def image_html
      return "".html_safe if builder.object.new_record?

      case options[:image]
      when Symbol
        builder.object.send(options[:image])
      when Proc
        options[:image].call(builder.object)
      when String
        options[:image].to_s
      else
        att = builder.object.public_send(method)
        if att.present?
          if att.version_exists?(:thumb) && att.thumb.file.exists?
            builder.template.content_tag :div, nil, image_html_options, true do
              builder.template.image_tag(att.url(:thumb) ) +
              builder.check_box("remove_#{method.to_s}".to_sym, {}, true, false)
            end
          else
            builder.template.content_tag :div, nil, image_plain_html_options, true do
              builder.template.content_tag(:span, att.url() ) +
              builder.check_box("remove_#{method.to_s}".to_sym, {}, true, false)
            end
          end
        else
          "".html_safe
        end
      end

    end
end
```

※どこかから拝借したソースをベースにしてますが、URLを紛失してしまったので、探し当てられたら改めて参照を掲載しますm(_ _)m

参照したソースでは、

```ruby
f.input :image, as: :file, :image => proc { |o| o.image.url(:thumb) }
```

こんな風に書けば好きな文字を表示できるようにしたZE！HAHAHA！ みたいな感じでしたが、
できれば```app/admin/*rb```にはなるべく何も書きたくなかったので、何も書かなければデフォルトでサムネイルかファイル名表示するようにしました。

```ruby:app/admin/*.rb
  form do |f|
    f.input :image, as: :file
    f.input :image #imageはCarriewaveのuploaderなのでデフォルトで as: :fileになる
    f.inputs #全てのカラムをデフォルトで表示
```

これらのどの書き方でも自動的にサムネイル表示されます。
全カラムがデフォルトだけでよい場合は、```form```のブロックすら不要です。

### 削除

削除機能自体はCarrierwaveにきちんと搭載されているので、そのお作法に倣えば簡単です。
既にチェックボックスは出力していますが、そのチェックボックスはこのように出力されているはずです。

```html
<input type="file" name="animal[remove_image]"> /* remove_カラム名 */
```

この```remove_カラム名```がtrue（チェックされた状態）で送信されればCarrierwaveがきちんと削除処理してくれます。
このチェックボックスをCarrierwaveに到達させるにはpermissionの指定が必要なので、追加しておきます。

```ruby:app/admin/animal.rb
ActiveAdmin.register Animal do
  permit_params do
    Animal.column_names - ["id","created_at","updated_at"] + ["remove_image"]
  end
```

これで、上の画像のように勝手にチェックボックスが出てきて、チェックすると削除されるところまで実装できちゃいました。

アップローダーを増やしたり```mount_uploader```したカラムを増やしたりしても、管理画面には```remove_*```を追記するだけでいいので楽ですね。
※ほんとはここも自動化したいけど、associationとか絡んでくるとpermit_paramsはごちゃごちゃ書くことになりそうで、そこまでサポートできないのでやめました。

### 注意

今気付きましたが、```Formtastic::Inputs::FileInput```を拡張しているので、これをActiveAdmin以外でも使っていると影響を受けちゃいますね。当然ですよね。。
それはそれでいい場合もありますが、例えばメールフォーム等の一回きりの送信しかしない画面であれば、画像削除は不要かもです。
その場合は、それ用の処理を追記するとか、```:image```オプションに何もしないブロックを渡すとかしてみてください。
```semantic_form_for```なんかを使っている場合はご注意を。

## CSS

```css:app/assets/stylesheets/active_admin/bass.scss
/**
 * FileInput < Formtastic::Inputs::FileInput
 */
.attachment_wrap {
    @extend p.inline-hints;
    font-style: default;
    display: block;

    &.attachment_img img {
        height: 80px;
    }

    &.attachment_plain {
    }

    [name*="[remove_"] {
        display: inline-block;
        margin-left: 5px;
    }
}
```

サムネイルやチェックボックスのスタイルなどを。

# おわり

いやー素晴らしいですね。ActiveAdminもCarrierwaveもRubyもRailsも。