はじめに
皆さんは、ActiveAdmin使ってますか?
ActiveAdmin、サッと管理画面作るには素晴らしい反面、
ちょっとでも基本的な実装から外れると、途端につまる、、
そんな経験をされた方も多いかと思います。
そして往々として、業務では基本的な実装から外れるものですよね。
そんな格闘の中で得た、「ActiveAdminでちょっと凝ったことをするのに使える(かもしれない)方法」を、
「ActiveAdmin 力技集」 としてシェアしようと思います。
なお、この方法が正しいというわけではなく、こう乗り切ったよ、くらいの感覚で見ていただけるとありがたいです。
もっといい方法があれば、ぜひコメントをお願いします!
環境
Ruby 2.5.5
Rails 5.2.2.1
activeadmin 1.3.1
ActiveAdmin 力技集
Model
顧客がいて、接点があって、請求と明細がある
class Customer < ApplicationRecord
has_many :contacts
has_many :invoices
end
class Contact < ApplicationRecord
belongs_to :customer
end
class Invoice < ApplicationRecord
belongs_to :customer
has_many :items, class_name: 'InvoiceItem'
end
class InvoiceItem < ApplicationRecord
belongs_to :invoice
end
絞り込まれた一覧へのリンク
例えば顧客の詳細画面に、その顧客との接点一覧へのボタンを置きたい時。
button_to '接点履歴一覧へ', admin_customer_contacts_path, method: :get, params: { q: { customer_id_equals: customer.id } }
検索条件がparams[:q]
に入るのがキモ。
filter
で検索を実装して実際に検索してみて、そのパラメータを再現すると手っ取り早い。
なお、このパラメータの使用に際して、filter
で実装されている必要はない。
別のカラムでソート
ActiveAdmin.register Customer do
index do
column '顧客ID', :id
# sortable: にソートしたいカラム名を記述
column '顧客名', :name, sortable: :kana
end
end
このように漢字を表示しつつ読み仮名でのソートができる。
formでjsを使う
例えばformで顧客を選択したいがselectだと厳しいので、オートコンプリートを実装したい時などに。
ActiveAdmin.register Contact do
# ...
form partial: 'form'
end
<script language="JavaScript">
// @contactが暗黙的に渡ってくる。
var contact = <%= raw @contact.to_json %>
// ...
</script>
当然、この例では@contact
の中身がブラウザで丸見えになるので、セキュリティ等問題がないユースケースか、検証が必要。
関連先によるソート・絞り込み
例えば顧客との接点の一覧画面で、顧客によってソート・絞り込みをしたい時。
ActiveAdmin.register Contact do
filter :date, label: '年月日'
# 関連先の型までは見てくれないようなので、as: :string などの指定が必要
filter :customer_name, as: :string, label: '顧客名'
index do
column '年月日', :date
column '顧客ID', :customer_id
# sortableには発行されるSQLで使えるカラム名を渡す。今回はcustomersテーブルをjoinしているので、customers.kana
# なお、関連名を渡すといい感じにリンク化してくれる
column '顧客名', :customer, sortable: :'customers.kana'
column '内容', :memo
end
controller do
def scoped_collection
# 関連先でソートを行う場合はjoinが必要
end_of_association_chain.joins(:customer)
end
end
end
index
の表はscoped_collection
で発行されたSQLの結果を使っている、と考えるとイメージしやすい。
デフォルト検索条件
必ず「ある年月の一覧」を表示したい場合はこのように。
ActiveAdmin.register Invoice do
# ...
controller do
# 必ず年月による絞り込みがかかるよう、indexページ表示前にparamsの不足を補う
before_action only: :index do
# 最初に表示されるのは先月分。
prev_month = Time.current.prev_month
if params[:q].present?
params[:q].merge!(year_eq: prev_month.year, month_eq: prev_month.month) if params[:q][:year_eq].blank? || params[:q][:month_eq].blank?
else
params.merge!(q: { year_eq: prev_month.year, month_eq: prev_month.month })
end
end
end
end
他の検索条件を邪魔しないように注意が必要。
GROUP BYした結果の一覧を作る
例えば版管理を行う請求書の一覧などで、版違いは同じ行でまとめたい時。
ActiveAdmin.register Invoice do
index do
# ...
column 'バージョン', class: 'minWidth100' do |latest_invoice|
Invoice
.where(customer_id: latest_invoice.customer)
.where(year: latest_invoice.year)
.where(month: latest_invoice.month)
.each do |invoice|
div { span link_to "第#{invoice.version}版", admin_invoice_path(invoice) }
end
nil # nilを返さないと、上の行の結果が出力されてしまう
end
end
controller do
# ...
def scoped_collection
# 一覧には顧客ごとの請求書を出したいのだが、返るレコード数分、表の行が作られてしまう。
# そのため、orderした上でgroup、顧客ごとに最新の請求書だけを取得
# 参考: https://qiita.com/ryo-ishii/items/36b878cf2d0bd8ef7e07
Invoice
.group(:customer_id, :year, :month)
.from(
end_of_association_chain
.select('invoices.*, customers.name, customers.kana, CONCAT(year, LPAD(month, 2, 0)) AS ym')
.joins(:customer)
.order(version: :desc),
:invoices
)
end
end
end
まとめ
ActiveAdminの良いところでもあり辛いところは、暗黙的に定義されている変数が多いところだと思います。
それらをいかに把握して差し込んでいくかが、ActiveAdminで力技をする際に重要になります。
それ以前に、力技を使わなくても済むような要件、データモデルの定義を心がけていきたいですね。