ActiveAdminを対象のクラスの詳細を見ずにドバリと設定したい。
ActiveAdmin
はポン付けでそれなりに動く管理画面Gemとして有名ですが、ちょっとだけ手を入れるというのがわりとめんどくさい面もあります。
しかしポン付けを希望している以上、必要以上に対象モデルの詳細を読んだりしたくないので、ActiveAdmin.register
内で取得できる情報でなんとかやっていきたいと思います。
駆け足で調べたので、浅い実装に対するポン付けにしか対応していない可能性があります(polymorphic
とか試していない)。
- Rails 4.2.6
- ActiveAdmin 1.0.0.pre1
その前に
ActiveAdmin
で使用されている記法は他のGem由来のものが多いので、ActiveAdmin
自体のドキュメントを追うよりも、依存しているGemを追ったほうが早い場合があります。
検索フィールドの機微に関してはRansack
activerecord-hackery/ransackが参考になります。
作成、編集フォームの描画メソッドについてはFormtastic
justinfrench/formtasticが参考になります。
filter
関連に関する検索セレクトボックスを消す
一見便利な機能ですが、レコードの数が増えてくるとそれを全てセレクトボックス内に描画してしまうため、結構重くなるようです。
ActiveAdmin.register ModelA do
ModelA.reflections.keys.each { |key| remove_filter(key) }
end
消えました。
関連に関する検索はしたいんですが
hoge_id
を用いた検索ならばセレクトボックスが出てきませんので、DBへのアクセスや描画などの心配もありません。
filter
をそのまま使うと、filter
で設定した分の検索フィールドしか表示しません。 preserve_default_filters!
を用いれば、もとの検索フィールドはそのままに追加できます。
ActiveAdmin.register ModelA do
preserve_default_filters!
ModelA.reflections.each_pair do |name, config|
remove_filter name
if config.macro == :belongs_to
filter config.foreign_key, label: config.foreign_key
else
through = config.options[:through]
if !!through
filter "#{through}_#{name.singularize}_id", label: "#{name}_id", as: :numeric
else
filter "#{name}_id", label: "#{name}_id", as: :numeric
end
end
end
end
has_many
のthrough
あたりは実際に描画された検索フィールドから勘で書いているので、もっと適切な引数があるかもしれません。
form
出てこないフィールドがあるんですけど
ActiveAdmin
は編集フィールドの作成の元ネタとして、ActiveRecord::ModelSchema.content_columns
を使用しています。これは
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
# and columns used for single table inheritance have been removed.
def content_columns
@content_columns ||= columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
end
ということで、_id
や_count
末尾のカラムのフォームは表示してくれません。inheritance_column
は、デフォルトでは値がtype
です。誰もがつまづく噂のアイツですね。
さて、本来ならばこれでも問題のないところですが、時々間違えて_count
カラムを作って出てこないとか、そういうことがあるみたいです。
というわけで全部入りのやつでフォームを作り直しましょう。
つくる
content_columns
の元ネタになっている生columns
を使います。
上述のように、名称からはそれが関連のためのカラムであるかどうか、確信できません。
そこでremove_filter
でも用いたreflections
を参照して、関連に使用されているカラムであれば、カラム名ではなく関連名を与えます。
ActiveAdmin.register ModelA do
selectable = ModelA.reflections.inject({}) { |a, (name, config)| a.merge!(config.foreign_key => name) }
columns = ModelA.columns.map(&:name).tap { |names|
names.delete(ModelA.primary_key)
}.map { |name|
if selectable.key?(name)
selectable[name]
else
name
end
}
form do |f|
f.inputs '', *columns
end
end
index
と同じく、関連セレクトボックスが不要
ActiveAdmin.register ModelA do
selectable = ModelA.reflections.inject({}) { |a, (name, config)| a.merge!(config.foreign_key => name) }
columns = ModelA.columns.map(&:name).tap { |names|
names.delete(ModelA.primary_key)
}.select { |name|
name unless selectable.key?(name)
}
form do |f|
f.inputs '', *columns
end
end
_id
で略
ActiveAdmin.register ModelA do
columns = ModelA.columns.map(&:name).tap do |names|
names.delete(ModelA.primary_key)
end
form do |f|
f.inputs '', *columns
end
end
乱暴にpermit_params
controller {}
内でparams.permit!
というのもありましたが、さすがにブルータルすぎるのでやめました。
ActiveAdmin.register ModelA do
columns = ModelA.columns.map(&:name).tap do |names|
names.delete(ModelA.primary_key)
end
permit_params columns
end
おまけ: THE 手作業
ActiveAdmin
の関連セレクトボックスは、先のクラスにname
系のメソッドなどがないと"#<AdminUser:0x007fd6302cf318>"
などと言いだしてだるいことで有名です。
class_eval
でかましていく
app/admin/dashboard.rb
などで先のクラスにname
メソッドをつっこんでやりましょう。
AdminUser.class_eval do
return if method_defined?(:name)
def name
"AdminUser #{email}"
end
end
先のクラスにActiveAdmin
のためのコードが侵入しないのはいいですが、行儀が悪いですね。ただ、filter
とform
両方をさわる必要がないので、早いといえば早い。
セレクトボックス個別にちゃんとやる
filter
にはpreserve_default_filters!
があるので、これをかました後該当セレクトボックスをremove_filter
、あとはfilter
でアレします。
一方form
はform
を呼んだが最後、全部消えます。幸い引数なしのf.inputs
で全てのフィールドを描画、except
オプションで除外設定が出来ます。
残念ながらつけたすオプションは見つけられなかったので、見た目的に別のボックスになっちゃいますが以下のとおりです。
ActiveAdmin.register ModelA do
form do |f|
f.inputs do
f.input :admin_user, as: :select, collection: AdminUser.pluck(:email, :id)
end
f.inputs except: 'admin_user'
f.actions
end
end
こつは、except
の値をString
で渡すことです。びっくりした(関連以外だとSymbol
で消える)。
これで大体いける
管理で雑に値を入れたり消したりする分には、たぶんこれで十分です。こういうササッと終わらせる場所に時間や労力を使わずに、やっていきましょう。
一応今回のは全部まとめてmmmpa/active_admin_relation_removerにしました。
# インデックスの関連を`id`化
ActiveAdmin.register ModelA do
ActiveAdminRelationRemover.prepare(self) do
filter!
end
end
# フォームの関連を`id`化
ActiveAdmin.register ModelB do
ActiveAdminRelationRemover.prepare(self) do
form!
permit!
end
end
# 全部
ActiveAdmin.register ModelC do
ActiveAdminRelationRemover.prepare(self).brutal!
end
ノーテストなので動いたり動かなかったりします。
勢いまかせで設定する参考程度にしてください。