LoginSignup
3
4

More than 3 years have passed since last update.

ActiveAdminのTips& Bugs

Last updated at Posted at 2020-02-20

最近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)の値でフィルター機能を作りたい

ソースコード

app/models/shop.rb
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
app/admin/shops.rb
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はこちらの記事を参照させていただきました.

参照1
参照2

画面とまとめ

スクリーンショット 2020-02-20 16.50.57.png

これで親(Shop)にはない,子(Job)の要素(publishedカラム)でfilterができました.

参照

カスタムフィルター②(Group byの引数動的変更もカスタムフィルターを使用する)

経理系のデータなどデータの粒度を動的に変更して, 集計値をみたいというケースが多いと思います.
それもカスタムフィルターで行います.(最初, controllerのscoped_collectionメソッドでscopeを変えて,
画面からパラメーターを指定するというやり方を、採用したのですが、一覧画面からは(id以外の)parameterが渡ってこない、もしくはそのやり方がわからないので苦労しました)

app/admin/bills.rb
ActiveAdmin.register Bill do
  index do
    # 省略
  end

  # カスタムフィルター
  # 引数に数字を渡すとバグになるので, 日本語で対応
  filter :sql_group_by, label: "GROUP", as: :select, collection: proc {[%w[年月+会社+仕事 年月会社仕事], %w[年月+会社+仕事+職種 年月会社仕事職種], %w[年月+会社 年月会社]]}
end
app/models/bill_entry_and_recruit.rb
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とカスタムフィルターは共存できないバグがあります.

app/models/company.rb
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(一般管理者)

Gemfile
## 権限管理
gem 'cancancan'
gem 'devise'
gem 'rolify', '~> 4.1.1'

スクリーンショット 2020-03-09 17.16.11.png

BUG: 閲覧リンクが表示されない

やりたいこと: 一般管理者は他の管理者の情報を閲覧できないようにする.(但し, 自分の閲覧は表示したい.)
実際: 一覧(index)画面では, 閲覧リンクが表示されない.

app/models/ability.rb
  def general_admin
    cannot :destroy, :all
    can :update, AdminUser
    # 第3引数移行に, パラメーターを渡すとfilter機能が働く
    can :read, AdminUser, id: @admin_user
  end

私の画面では, :readと指定すれば動くのですが、 :show, :indexと指定したときには動きません.(他の人の記事を見たら動くのもあるのに....このヴァージョンのバグかも?)
画像見たらわかるように, 閲覧リンクが非表示です.(BUG)

BUG:役割に応じて, フィルターを非表示にすることができない.

BUGというよりは, 仕様かもしれません.

app/admin/admin_user.rb
# こちらはエラーとなる
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はすぐに見つかるのですが、 テキストフォームは意外と少ないです.また, 実際の画面のキャプチャーが欲しいところです.
公式参照

app/admin/contract.rb
  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も設定していますが, 実際の画面を見てみると
スクリーンショット 2020-03-10 11.51.23.png
このように, Agent_margin_rateが日本語化されていないバグがあります.

カスタムソート(CustomSort)

複合カラムでソート

前提: year , monthが別々のカラムに分かれており, 年月ソートを降順でDefault設定したい.

app/admin/bill.rb
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"
が代入されるようだ..

画面キャプチャをみると, ちゃんと年/月で降順ソートがされています.
スクリーンショット 2020-03-10 15.23.38.png

参照1
参照2

詳細画面(Show)

if文の挙動にBUGあり

特定のカラムに値があったら, 表示する.
なければ表示しない. という単純な仕様

app/admin/shops.rb
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だけが表示されているのが正しい挙動です.

実際の画面を見ると

スクリーンショット 2020-02-20 10.53.44.png
写真1 写真4 => 非表示
写真2 写真3 => 表示
(上下のメイン写真コメントと写真5はここでは無関係)

ここで
写真3は写真1同様に非表示とならなければなりません.同様に
写真4は写真2同様に表示とならなければなりません.
ここでバグがありました.

結論

:シンボル使用した時と, resouceから呼び出した時で, 挙動が正反対になっています.
正しい挙動はresouceを使用した時です.
ifの引数にシンボリックの使用は推奨されません.

編集画面(Edit)

タブ(tab)が使えないバグ

公式ドキュメント
ドキュメント

タブの機能を使おうとすると, 2重(もしくはそれ以上)で表示されてしまう.
公式のドキュメントに書いてるくせに, 使えないようだ.

かなり古い記事だが
これによると, 最新のGemではなく, 以下のように指定すると使えるようだが....
今後, 消される可能性あるのを導入してもなー....

gem "activeadmin", github: "gregbell/active_admin"

columnで代用できるようだ(公式ドキュメント)にその記述はないが

公式ドキュメントにはないのですが, 詳細画面同様に, columnは使えるようだ

app/admin/shop.rb
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を指定したとき, 特に日付変更付近が意図した日時になっているか確認した方がいいでしょう

config/application.rb
class Application < Rails::Application
  config.time_zone = 'Tokyo'
end

実際のDBの値
2017-05-27 23:25:38
スクリーンショット 2020-06-19 14.37.12.png

実際の画面
2017年05月28日 (DBとズレる)
スクリーンショット 2020-06-19 14.39.03.png

ここで, 上記のTImeZoneの設定を外すと

config/application.rb
class Application < Rails::Application
-  config.time_zone = 'Tokyo'
# 下はあってもなくても, 結果は同じ
+  config.active_record.default_timezone = :local
end

画面
2017年05月27日 (DBと同じである)
スクリーンショット 2020-06-19 14.38.23.png

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4