RailsのDBのCRUD操作を簡単に実装するためのライブラリ。 一覧の検索、ソート、ページネーションや各アクションの認可機能も備える。
事前にyarnを実行できる環境を構築しておくこと。
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.10/install.sh | bash
※nvmのインストールは最新の情報をサイトで確認
nvm install --lts
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update
sudo apt-get install --no-install-recommends yarn
※yarnのインストールは最新の情報をサイトで確認
git clone git@bitbucket.org:aselab/crud.git
cd crud
bundle install
cd spec/dummy
bin/yarn install
bin/rails db:migrate
bin/rails s
crudは今のところ vendor/gems/crud に置くことを前提とする。 Webpackerと Bootstrap 4 が前提なので別途インストールしておく。
git submoduleで運用する場合
git submodule add git@bitbucket.org:aselab/crud.git vendor/gems/crud
git submodule update --init
# 特定のバージョンを使いたい場合はsubmoduleに移動してcheckoutする
ファイルだけ追加する場合
mkdir -p vendor/gems/crud
git archive --remote=git@bitbucket.org:aselab/crud.git [master or バージョン指定など] | tar -x -C vendor/gems/crud
以下をGemfileに追加してbundle install
gem 'crud', path: 'vendor/gems/crud'
# API modeの場合はこちら、以降の設定は不要
gem 'crud_api', path: 'vendor/gems/crud'
yarnでパッケージ追加
bin/yarn add vendor/gems/crud/webpacker
bin/yarn add @fortawesome/fontawesome @fortawesome/fontawesome-free-solid
# 以下はinputsを使う時に必要なものだけ
bin/yarn add select2 select2-bootstrap4-theme
Webpackerのエントリーポイントに含める app/javascript/packs/application.js
import 'crud'
import 'crud/fontawesome'
import 'crud/coreui'
// inputsは使うものだけを個別にimportしてもよい
import 'crud/inputs'
layoutなどにbootstrap_flash_messagesを追加
config/initializers/crud.rb に以下のように書く
Crud.configure do |config|
# https://github.com/plataformatec/simple_form/pull/1553 を利用するかどうか
config.simple_form.use_valid_class = false
config.icon.search = "fas fa-search"
end
設定値は lib/crud/config.rb を参照。
モデルのCRUD機能を提供するコントローラの基底クラス。 Rails標準のRESTful routesに則ったアクションを提供する。
アクション名 | 内容 |
---|---|
index | 一覧表示 |
show | 詳細表示 |
new | 新規作成画面表示 |
edit | 編集画面表示 |
create | 新規作成処理 |
update | 更新処理 |
destroy | 削除処理 |
それぞれのアクションが実行される時に、set_defaults, before_(アクション名), do_(アクション名) メソッドが呼び出される仕様になっている。 何も実装しなくてもデフォルトルールに従って動作するように作られており、動作をカスタマイズしたい場合だけprotectedのメソッドを定義したり、 各メソッドをオーバーライドして実装すればよい。 最小のコード例:
class UsersController < Crud::ApplicationController
permit_keys :name, :age
end
indexアクションについては提供する機能が多いので、do_index内で以下のメソッドを呼び出しており、個別にオーバーライドできるようにしている。
メソッド名 | 機能 | デフォルト処理 |
---|---|---|
do_filter | フィルター処理 | params[:except_ids]を除外 |
do_query | 検索ソート処理 | キーワード検索, 詳細検索, ソートの項を参照 |
do_page | ページネーション | params[:per] (デフォルト25)件毎のparams[:page]ページ目を検索 |
crudのコントローラに対応するモデルはmodel, indexアクションで表示するデータはresources、その他アクションで扱うデータはresourceを利用する。 これらはhelperメソッドとして定義しているため、controllers, helpers, viewsどこでも使えるようになっている。
デフォルトではmodelはコントローラ名から解決される。例えばUsersControllerならばUserモデルを扱う。 命名規約に反するモデルを扱いたい場合はメソッドをオーバーライドして明示的に指定する。
resourcesにはデフォルトではmodel.allが設定されている。 例えば検索対象をモデル全体ではなく特定の範囲に絞り込みたい場合などは、do_filterメソッドをオーバーライドして絞り込んだ結果を返すように実装する。 resourceにはmodel.find(params[:id])の結果が設定されている。
class FoosController < Crud::ApplicationController
protected
# デフォルトではFooモデルが扱われるが、別のモデルを扱いたい場合は定義
def model
User
end
# 一覧で扱う対象を特定のscopeに絞り込みたい場合
def do_filter
resources.active unless params[:all]
end
# 論理削除にしたい
def do_destroy
resource.update_attribute(:deleted_at, DateTime.now)
end
end
params[:term]の値で各カラムをOR条件で検索する。 デフォルトでは文字列、数値型、列挙型の一覧表示カラムを検索する。 文字列はlike検索、その他は完全一致で検索する。 ActiveRecordの場合は関連の検索もサポートしている。詳細は検索条件の指定を参照。
params[:operator]またはparams[:op]で各カラムの検索方法を、 params[:value]またはparams[:v]またはparams直下で各カラムの検索値を指定する。
オペレータ名 | 別名 | 検索値の数 |
---|---|---|
equals | = | 1 |
not_equals | != | 1 |
contains | ~ | 1 |
not_contains | !~ | 1 |
any_of | in | * |
greater_or_equal | >= | 1 |
less_or_equal | <= | 1 |
between | <> | 2 |
any | * | 0 |
none | !* | 0 |
operatorを指定しない場合のデフォルトはequalsである。
# nameにuserが含まれるusersを検索
/users?operator[name]=contains&value[name]=user
# ageが20から30のusersを検索
/users?op[age]=between&v[age][]=20&v[age][]=30
# name: user1, age: 18のusersを検索
/users?name=user1&age=18
params[:sort_key]にカラム名を、params[:sort_order]にソート順(asc, desc)を指定する。 ソート順を指定しない場合のデフォルトは昇順である。
permit_keysで更新対象のカラムを指定する。StrongParametersと同様の指定方法。 全アクション共通の対象カラムはmodel_columnsメソッドで定義する。デフォルトはpermit_keysと同じ。 各アクション毎の対象カラムはcolumns_for_(index, show, create, update)で定義する。デフォルトはmodel_columnsと同じ。 一覧表示の各フォーマット毎の出力対象カラムはcolumns_for_(MIMEタイプ)で定義する。デフォルトはcolumns_for_indexと同じ。 検索対象カラムはcolumns_for_searchで定義する。デフォルトはcolumns_for_indexのうち文字列、数値型、列挙型のカラム。 詳細検索対象のカラムはcolumns_for_advanced_searchで定義する。デフォルトはcolumns_for_indexと同じ。
class FoosController < Crud::ApplicationController
permit_keys :column1, :column2, array_column: [], hash_column: [:nested_column1, :nested_column2]
protected
def model_columns
[:column1, :column2]
end
# 一覧画面の表示項目
def columns_for_index
[:column1, :virtual_column]
end
# 新規作成画面の表示項目
def columns_for_create
[:column1]
end
# CSV出力時の項目
def columns_for_csv
model_columns
end
# 検索対象のカラム
def columns_for_search
[:column1]
end
# 詳細検索対象のカラム
def columns_for_advanced_search
[:column1]
end
end
index_actionsメソッドで一覧画面の各行ごとのアクションを定義できる。デフォルトは:show, :edit, :destroy
各アクションのリンク表示を変更したい場合、以下の優先順でメソッドを定義してカスタマイズできる。
- helperに #{controller_name}link_to#{action} という名前のメソッドを実装
- helperに link_to_#{action} という名前のメソッドを実装
- helperに #{controller_name}link_to#{action}_options という名前のメソッドを実装
- helperに link_to_#{action}_options という名前のメソッドを実装
一覧や詳細画面で表示されるカラムの内容は、デフォルトではsimple_formatで出力される。 以下の優先順でメソッドを定義して表示内容をカスタマイズすることができる。
- helperに #{controller_name}_#{column_name}_html という名前のメソッドを実装
- helperに #{column_name}_html という名前のメソッドを実装
- modelに #{column_name}_label という名前のメソッドを実装
module UsersHelper
def users_name_html(resource, value)
link_to value, resource
end
end
resourceにはモデルのインスタンスが、valueにはそのカラムに対応する値が渡される。 この例の場合 resource: Userインスタンス, value: nameの値
# helperに実装
module UsersHelper
def users_birth_date_html(resource, value)
value.try(:strftime, "%Y/%m/%d")
end
end
# modelに実装
class User < ApplicationRecord
def birth_date_label
birth_date.try(:strftime, "%Y/%m/%d")
end
end
編集画面で表示されるカラムの内容は、デフォルトはsimple_formと同じだが、日付型はピッカーで表示される。 以下の優先順でhelperメソッドを定義して入力フォームをカスタマイズすることができる。
- #{controller_name}_#{column_name}_input という名前のメソッドを実装
- #{column_name}_input という名前のメソッドを実装
- #{controller_name}_#{column_name}_input_options という名前のメソッドを実装
- #{column_name}_input_options という名前のメソッドを実装
module UsersHelper
def users_sex_input_options
{ as: :select, collection: [:male, :female] }
end
end
simple_formのinputに渡すオプション値を返すように実装する。
module UsersHelper
def users_column1_input(f)
content_tag(:div) do
f.text_field(:column1) + javascript_tag("...")
end
end
end
- :bootstrap_datepicker
- :bootstrap_timepicker
- :bootstrap_datetimepicker
- :bootstrap_filestyle
- :select2
デフォルトでは一覧に表示されている文字列または数値、列挙型のカラムが検索対象となる。 検索対象のカラムを変更したい場合はコントローラにcolumns_for_searchメソッドを定義する。
デフォルトでは文字列カラムはlike検索、それ以外は完全一致で検索される。 検索条件を変更したい場合や、DBに存在しない仮想カラムに検索条件を持たせる場合などには、 search_by_#{column_name} というメソッドをコントローラに定義する。 引数には検索キーワードが渡されるので、それを使って検索条件を返すように実装すればよい。 このメソッドを定義せず、後述の詳細検索用のメソッドを定義するだけでもよい。
class User < ApplicationRecord
def full_name
"#{last_name} #{first_name}"
end
end
class UsersController < Crud::ApplicationController
protected
def columns_for_search
[:full_name, :email]
end
def search_by_full_name(term)
["last_name LIKE :term OR first_name LIKE :term", term: "%#{term}%"]
end
end
ActiveRecordの場合は関連も検索できる。 関連モデルのsearch_fieldという名前のクラスメソッドを定義すると、 関連テーブルをjoinしてそのカラム名で検索する。 search_fieldを指定しない場合は、デフォルトでnameまたはtitleというカラムが用いられる。
検索対象のカラムを変更したい場合はコントローラにcolumns_for_advanced_searchメソッドを定義する。
検索処理を変更したい場合は、advanced_search_by_#{column_name} というメソッドをコントローラに定義するか、 モデルのクラスメソッドとして定義する。 引数にはoperatorと検索値が渡される。検索値の個数は可変なので、可変長引数で定義するとよい。
実装例は spec/dummy/app/models/ar/user.rb を参照。
詳細検索フォームを変更したい場合は、以下の優先順でhelperメソッドを定義してカスタマイズすることができる。
- #{controller_name}_#{column_name}_search_input
- #{column_name}_search_input
- #{controller_name}_#{column_name}_search_input_options
- #{column_name}_search_input_options
- #{controller_name}_#{column_name}_input_options
- #{column_name}_input_options
search_inputメソッドの引数にはsimple_form_forのbuilder, operator, 検索値が渡される。検索値の個数は可変なので、可変長引数で定義する。 search_input_optionsとinput_optionsメソッドは引数なしで、input_optionsメソッドは編集フォームでも使われる。
詳細検索のオペレータを変更したい場合は、以下の優先順でhelperメソッドを定義してカスタマイズすることができる。
- #{controller_name}_#{column_name}_search_operator_options
- #{column_name}_search_operator_options
詳細検索フォームカスタマイズの実装例は spec/dummy/app/helpers/users_helper.rb を参照。
デフォルトのソート条件やページネーション件数を指定することができる。 ページネーション件数はデフォルト25件。
class SampleController < Crud::ApplicationController
default_sort_key :name
default_sort_order :desc
default_paginates_per 10
end
カラム毎にソート条件をカスタマイズしたい場合、sort_by_#{column_name} というメソッドをコントローラに定義する。 引数には:ascまたは:descが渡されるので、それを使ってソート条件を返すように実装すればよい。
class UsersController < Crud::ApplicationController
protected
def sort_by_full_name(order)
"last_name #{order}, first_name #{order}"
end
end
権限制御をしたい場合はapp/authorizationsにコントローラと同名のAuthorizationクラスを定義する。 各アクションの実行を許可するかどうかをメソッド定義してtrue/falseを返すように実装すればよい。
特殊なアクションとして、manageを定義するとcreate, update, destroyの権限をまとめて制御できる。 アクションに対応するメソッドを定義しない場合のデフォルト値はtrueである。
# 暗黙的にUsersControllerで用いられる
class UsersAuthorization < Crud::Authorization::Default
def create?(user)
false
end
def manage?(user)
user == current_user
end
end
メソッドの引数にはアクションを実行しようとしている対象のレコードが渡される。 また、コントローラのcurrent_userが渡されるため、ログインユーザによる制御も可能。
コントローラにAuthorizationという名前のインナークラスを定義してもよい。
class UsersController < Crud::ApplicationController
class Authorization < Crud::Authorization::Default
def create?(user)
false
end
def manage?(user)
user == current_user
end
end
end
リソース毎に権限を細かく設定したい場合に使うライブラリ。
rails generate permissible:install
権限制御したいモデルごとに任意のロール名と値をビットで定義し、包含関係を作ることができる。
class Article < ApplicationRecord
acts_as_permissible(admin: 0b111, write: 0b011, read: 0b001, default: 0b001)
end
上記の場合、write権限を持つロールはadmin, writeで、read権限を持つロールはadmin, write, readになる。
TODO
結果のフォーマットはhtml, js, jsonに対応。indexのみcsvにも対応。 htmlとjsはviews/crud/applicationのテンプレートが用いられるため、これをcontrollerに対応するviewとしてコピーして編集すればよい。 jsonはactive_model_serializersを用いて出力される。 app/serializersにモデルに対応するシリアライザーを定義すればそれが用いられる。
特定のフォーマットの動作自体を書き換えたい場合、以下のようにアクションのメソッドをオーバーライドしてsuperにブロックを渡すことができる。
def create
super do |format|
format.html { ... }
end
end
編集画面のキャンセル、作成/更新時のリダイレクト先を変更したい場合、cancel_pathをオーバーライドするとよい。デフォルトでは一覧画面に戻る。 削除時はrequest.refererに戻る。
# モデル + crudコントローラ生成
rails generate crud:scaffold User group:references name age:integer
# crudコントローラ生成
rails generate crud:controller Users group_id name age
# crudのviewをapp/views/crud/applicationにコピー
rails generate crud:application_views
# crudのviewをコントローラ単位でコピー
rails generate crud:views users
include Crud::ModalPickerController
f.input as: :modal_picker, url: picker_controllers_path
TODO
include Crud::Wizard
TODO