最近ActiveAdminを使用しているのですが
便利な反面, ドキュメントがわかりづらく, バグでは?と思うことが多いです.
また, Qiitaにある記事も帯に短しで, 目的の用途の記事が見つからなかったので,
私が躓いたところをいろいろ追加して行こうと思います.
前提環境
activeadmin (1.4.3)
画面に関係なく共通のBUG
resourceが動かない!!
私の場合, 開発環境では動いたのですが
本番環境がでは,resourceを利用している箇所は全て動かなくなり, root_pathへリダイレクトされました.
理由はわかりませんが..
両者ともruby,rails,gemが同じversionです.
違うのが
vagrant+docker上にCentos7
# cat /etc/redhat-release
CentOS Linux release 7.7.1908 (Core)
AWSのインスタンスでCentOS7
# cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)
違いはここだけです.もしかしたら, そのせいで動かないのかも??
全てにのresourceに置いてこのような現象になるわけではありません!!!(うーん, ここがよくわからない)
理由はわかりませんが, resourceの使用は控えましょう
一覧画面(Index)
カスタムフィルター①(子テーブルの要素をFilter対象とする)
###前提
親(Shop) : 子(Job) = 1 : N 関係の時,
親要素に子要素をJOINして, DBのビューのような使い方をしている.
###実現したいこと
子(Job)の特定のカラム(ここでは:published)の値でフィルター機能を作りたい
###ソースコード
class Shop < ActiveRecord::Base
has_many :jobs, dependent: :destroy
# ActiveAdmin用のscope
scope :join_jobs, -> { joins(:jobs)}
scope :active_admin_custom_filter, -> (filter) {
# gem ransackerのバグのため、ここで数値へ変換
# filter = filter == "掲載中" ? 1 :0
self.where('jobs.published = ? ',filter)
}
# 別にprivateでなくても良いが
private
# ActiveAdmin用のfilterカスタマイズ
def self.ransackable_scopes(auth_object = nil)
[:active_admin_custom_filter]
end
ActiveAdmin.register Shop do
# gemのransackerにバグがあり, 自作scopeに数値を渡せない. initilizarの要修正
filter :active_admin_custom_filter, label: "掲載(求)", as: :select, collection: proc{ [%(掲載中 1), %w(掲載停止 0)] }
# initilizarを修正していない時はこっち
# filter :active_admin_custom_filter, label: "掲載(求)", as: :select, collection: proc{ [%(掲載中 掲載中), %w(掲載停止 掲載停止)] }
# カスタムフィルターだと, as: を指定する必要がある. よって, 下では表示すらされない
# filter :active_admin_custom_filter, label: "掲載(求)
# override
controller do
def scoped_collection
end_of_association_chain.join_jobs
end
end
end
gem ranscakerのBUGはこちらの記事を参照させていただきました.
画面とまとめ
これで親(Shop)にはない,子(Job)の要素(publishedカラム)でfilterができました.
カスタムフィルター②(Group byの引数動的変更もカスタムフィルターを使用する)
経理系のデータなどデータの粒度を動的に変更して, 集計値をみたいというケースが多いと思います.
それもカスタムフィルターで行います.(最初, controllerのscoped_collectionメソッドでscopeを変えて,
画面からパラメーターを指定するというやり方を、採用したのですが、一覧画面からは(id以外の)parameterが渡ってこない、もしくはそのやり方がわからないので苦労しました)
ActiveAdmin.register Bill do
index do
# 省略
end
# カスタムフィルター
# 引数に数字を渡すとバグになるので, 日本語で対応
filter :sql_group_by, label: "GROUP", as: :select, collection: proc {[%w[年月+会社+仕事 年月会社仕事], %w[年月+会社+仕事+職種 年月会社仕事職種], %w[年月+会社 年月会社]]}
end
class Bill < ActiveRecord::Base
# activeadmin用のカスタムフイルター用のscope
scope :sql_group_by, ->(argument) {
# 集計対象のカラム
aggreate_columns = ["entry_count", "entry_sum_amount", 略, "gift_sum_amount"]
# activerecordのsqlベタ書きするのが大変なので
select_argument = aggreate_columns.map {|column| "sum(" + column + ")" + " as " + column}.join(", ")
# 画面からの引数により,scopeを変える
case argument
when "年月会社仕事" then
# 集計以外のカラムを追加する
select_argument << ", company_id, bill_target_yyyymm, job_id"
# SQLのGROUPの引数を指定する
group_by = "company_id, bill_target_yyyymm, job_id"
self.group(group_by).select(select_argument)
when
# あとは同様に
end
}
def self.ransackable_scopes(auth_object = nil)
%i[sql_group_by]
end
end
英語にも日本語でも記事が見つからなかったですが、フィルターで十分でできるようです.頭を柔らかく.
カスタムフィルター③(DeafulScopeとの兼ね合いについて)
DefaultScopeとカスタムフィルターは共存できないバグがあります.
class Company < ActiveRecord::Base
default_scope { where('companies.deleted_at is null') }
# activeadmin用のfilter
# default_scope の影響でうまく稼働しない
formatter = -> boolean {
# default_scopeを使用時, trueの条件式の時,ログでは取れているのだが,画面に表示されない(activeadmin側のbug.defaut_scopを外すと稼働する
boolean == "true" ? Company.unscope(where: :deleted_at).where('companies.deleted_at is not null').pluck(:id) : Company.pluck(:id)
}
ransacker(:deleted_at, formatter: formatter) { |parent| parent.table[:id] }
end
ここでdefalut_scopeを削除するとカスタムフィルターも動きます
admin_user画面(権限によって、表示を切替)
super_admin(スーパー管理者)とgeneral_admin(一般管理者)
## 権限管理
gem 'cancancan'
gem 'devise'
gem 'rolify', '~> 4.1.1'
BUG: 閲覧リンクが表示されない
やりたいこと: 一般管理者は他の管理者の情報を閲覧できないようにする.(但し, 自分の閲覧は表示したい.)
実際: 一覧(index)画面では, 閲覧リンクが表示されない.
def general_admin
cannot :destroy, :all
can :update, AdminUser
# 第3引数移行に, パラメーターを渡すとfilter機能が働く
can :read, AdminUser, id: @admin_user
end
私の画面では, :readと指定すれば動くのですが、 :show, :indexと指定したときには動きません.(他の人の記事を見たら動くのもあるのに....このヴァージョンのバグかも?)
画像見たらわかるように, 閲覧リンクが非表示です.(BUG)
BUG:役割に応じて, フィルターを非表示にすることができない.
BUGというよりは, 仕様かもしれません.
# こちらはエラーとなる
if current_admin_user.roles.pluck(:id).include?(4)
filter :id
filter :email
filter :roles, as: :select, collection: -> { Role.accessible_by(current_ability,:index) }
end
# こちらなら非表示になるが、 ボタンは残ってしまうバグがある.
filter :id, if: proc { current_user.roles.pluck(:id).include?(4) }
filter :email, if: proc { current_user.roles.pluck(:id).include?(4) }
filter :roles, as: :select, collection: -> { Role.accessible_by(current_ability,:index) }, if: proc { current_user.roles.pluck(:id).include?(4)}
proc以降が複雑になっているのは, 1ユーザ複数権限の設定を可能にしているからである.
一括操作(batch_action)のテキスト入力の際、 日本語変換されないBUG
ステータス更新などはのTipsはすぐに見つかるのですが、 テキストフォームは意外と少ないです.また, 実際の画面のキャプチャーが欲しいところです.
公式参照
batch_action "マージン率の一括更新", form: {agent_margin_rate: :text} do |ids, inputs|
batch_action_collection.find(ids).each do |record|
record.agent_margin_rate = inputs["agent_margin_rate"]
# validation skip
record.update_columns(agent_margin_rate: inputs["agent_margin_rate"])
end
redirect_to collection_path, notice: [ids, inputs].to_s + "更新成功."
end
ja.ymlも設定していますが, 実際の画面を見てみると
このように, Agent_margin_rateが日本語化されていないバグがあります.
カスタムソート(CustomSort)
複合カラムでソート
前提: year , monthが別々のカラムに分かれており, 年月ソートを降順でDefault設定したい.
ActiveAdmin.register Bill do
config.per_page = [50, 500]
menu priority: 3, label: proc {"請求"}, parent: "企業"
# カラムが一つの時は単純に設定するだけ
# config.sort_order = "year_desc"
# 複数カラムを設定する時は?
# 以下のケースはエラーとなる(以前は動いていたかもしれない)
# config.sort_order = "year_desc, month_desc"
# 正しくは,カスタムフィルターを利用する
config.sort_order = "year_month_desc"
order_by(:year_month) do |order_clause|
%w[year month].map { |column|
"#{column} #{order_clause.order}"
}.join(", ")
end
ちなみに
order_clause.order は config.sort_orderで指定した"desc(asc)"
order_clause.order は config.sort_orderのソート以外, このケースだと"year_month"
が代入されるようだ..
画面キャプチャをみると, ちゃんと年/月で降順ソートがされています.
詳細画面(Show)
if文の挙動にBUGあり
特定のカラムに値があったら, 表示する.
なければ表示しない. という単純な仕様
ActiveAdmin.register Shop do
show do
panel "求人(Job)情報" do
attributes_table_for shop.job do
#① idがあれば表示
if resource.job.job_img1_id.present?
row :job_img1_id
end
#② idがなければ表示
if resource.job.job_img2_id.nil?
row :job_img2_id
end
#③ idがあれば表示 (ここ挙動がおかしい!!バグ!!)
if :job_img3_id.present?
row :job_img3_id
end
#④ idがなければ表示 (ここ挙動がおかしい!!バグ!!)
if :job_img4_id.nil?
row :job_img4_id
end
end
end
end
end
今 job_img[1-4]_id は値がないnull(nil)です.
つまり、写真2と写真4だけが表示されているのが正しい挙動です.
実際の画面を見ると
写真1 写真4 => 非表示
写真2 写真3 => 表示
(上下のメイン写真コメントと写真5はここでは無関係)
ここで
写真3は写真1同様に非表示とならなければなりません.同様に
写真4は写真2同様に表示とならなければなりません.
ここでバグがありました.
結論
:シンボル使用した時と, resouceから呼び出した時で, 挙動が正反対になっています.
正しい挙動はresouceを使用した時です.
ifの引数にシンボリックの使用は推奨されません.
編集画面(Edit)
タブ(tab)が使えないバグ
タブの機能を使おうとすると, 2重(もしくはそれ以上)で表示されてしまう.
公式のドキュメントに書いてるくせに, 使えないようだ.
かなり古い記事だが
これによると, 最新のGemではなく, 以下のように指定すると使えるようだが....
今後, 消される可能性あるのを導入してもなー....
gem "activeadmin", github: "gregbell/active_admin"
columnで代用できるようだ(公式ドキュメント)にその記述はないが
公式ドキュメントにはないのですが, 詳細画面同様に, columnは使えるようだ
ActiveAdmin.register Shop do
# 全て変更可能
permit_params{ Shop.attribute_names.map(&:to_sym) }
columns do
colum do
f.inputs "基本" do
f.input :aaa
end
end
colum do
f.inputs "補足1" do
f.input :aaa
end
f.inputs "補足2" do
f.input :bbb
end
end
end
公式ドキュメントにないので不安はあるが,
カラムが多すぎて見辛かったテーブルも少しは見やすなるでしょう.
その他
[Bug]TimeZoneのについて
TimeZoneを指定したとき, 特に日付変更付近が意図した日時になっているか確認した方がいいでしょう
class Application < Rails::Application
config.time_zone = 'Tokyo'
end
ここで, 上記のTImeZoneの設定を外すと
class Application < Rails::Application
- config.time_zone = 'Tokyo'
# 下はあってもなくても, 結果は同じ
+ config.active_record.default_timezone = :local
end