railsじゃないサービスの管理/集計画面を作りたくなる事があったのでメモ。on AWS。
前提
- 管理対象:例えばec2にアプリケーションサーバ(java)、rdsのDB(mysql)で構築されている。
- 管理画面:別のec2と別のrds(同じrdsでもDB分ければいいけど)に作る。
- 開発環境の構築は省略。
デプロイ先のシステム構成と主なgem
- centOS 6.5
- ruby 2.2.2
- rails 4.1.6
- nginx 1.9.11
- unicorn 5.0
- activeadmin 1.0.0pre2
- devise 3.5.5
- cancancan 1.13
- whenever 0.9
開発
管理対象と管理画面自体のDBが異なるけどどうするか?
コードで接続先を分ける事が必要。それぞれの接続先を定義したdatabase.ymlを作った上で、どちらに接続するかをModel単位で分けるイメージ。具体的には、
default: &default
adapter: mysql2
encoding: utf8
pool: 5
username: {ユーザ}
password: {パスワード}
socket: /tmp/mysql.sock
# 管理/集計画面用DBの接続先
development:
<<: *default
host: {管理・集計画面用DB/開発環境のエンドポイント}
production:
<<: *default
host: {管理・集計画面用DB/本番環境のエンドポイント}
database: {DB名}
username: {ユーザ}
password: {パスワード}
# 集計対象DBの接続先
target_development:
<<: *default
host: {集計対象DB/開発環境のエンドポイント}
target_production:
<<: *default
host: {集計対象DB/本番環境のエンドポイント}
database: {DB名}
username: {ユーザ}
password: {パスワード}
とした場合、集計対象用のDBのModelを次のクラスを継承して作る。
class Target < ActiveRecord::Base
self.abstract_class = true
if Rails.env == 'production'
establish_connection(:target_production)
else
establish_connection(:target_development)
end
end
管理・集計用のDBは普通にActiveRecord::Base
を継承して作ればOK。
認証機能が欲しい
認証機能はdeviseで簡単にできる。
activeadminインストールで自動生成されたadmin_user含む3つのモデルについて、マイグレーションとモデルを作成する。admin_userは管理/集計画面のユーザ、admin_roleは権限、admin_user_admin_roleは中間テーブル。
class DeviseCreateAdminUsers < ActiveRecord::Migration
def change
create_table(:admin_users) do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
t.string :unlock_token # Only if unlock strategy is :email or :both
t.datetime :locked_at
t.timestamps null: false
end
add_index :admin_users, :email, unique: true
add_index :admin_users, :reset_password_token, unique: true
add_index :admin_users, :confirmation_token, unique: true
add_index :admin_users, :unlock_token, unique: true
end
end
class AdminUser < ActiveRecord::Base
has_many :admin_user_admin_roles
has_many :admin_roles, through: :admin_user_admin_roles
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable
def has_role?(admin_role_sym)
admin_roles.any? { |r| r.name.underscore.downcase.to_sym == admin_role_sym }
end
end
class CreateAdminRoles < ActiveRecord::Migration
def change
create_table :admin_roles do |t|
t.string :name
t.timestamps
end
add_index :admin_roles, :name, :unique => true
end
end
class AdminRole < ActiveRecord::Base
has_many :admin_user_admin_roles
has_many :admin_users, through: :admin_user_admin_roles
end
class CreateAdminUserAdminRoles < ActiveRecord::Migration
def change
create_table :admin_user_admin_roles do |t|
t.integer :admin_role_id
t.integer :admin_user_id
t.timestamps
end
add_index(:admin_user_admin_roles, :admin_role_id)
add_index(:admin_user_admin_roles, :admin_user_id, unique: true)
end
end
class AdminUserAdminRole < ActiveRecord::Base
belongs_to :admin_user
belongs_to :admin_role
end
admin_user.rbの様々な機能は、activeadminインストール時にコメントアウトされている属性を有効にすると使えるようになる。confirmableとか。合わせてmigrationも有効にしておく。
また、confirmableなどでメール送信できるようにしとかなければいけないのでそれもやっておく。AWSなのでSES使ってaction_mailerでメール送る、など。
以上でmodelが出来上がったので、ユーザの追加編集削除が出来るようadmin_user.rbを好みでカスタマイズして使う。例えば下記は初期パスワードを自動で作ってメールで送信するようになっている。
ActiveAdmin.register AdminUser do
permit_params :email, :password, :password_confirmation, admin_role_ids: []
index :download_links => false do
selectable_column
id_column
column :admin_roles do |f|
f.admin_roles.size > 0 ? f.admin_roles.map { |r| r.name }.join(", ") : ''
end
column :email
column :current_sign_in_at
column :sign_in_count
column :created_at
actions
end
filter :email
filter :admin_role
filter :current_sign_in_at
filter :sign_in_count
filter :created_at
form do |f|
f.inputs "Admin Details" do
f.input :email
f.input :admin_roles
end
f.actions
end
controller do
def create
if params[:admin_user][:password] == params[:admin_user][:email]
redirect_to new_admin_admin_user_path, alert: 'メールアドレスと同じ文字列をパスワードに設定する事はできません'
return
end
generated_password = Devise.friendly_token.first(9)
params[:admin_user][:password] = generated_password
params[:admin_user][:password_confirmation] = generated_password
InitialPassword.send_generated_password(params[:admin_user][:email], generated_password).deliver
super
end
def update
if params[:admin_user][:password] == params[:admin_user][:email]
redirect_to edit_admin_admin_user_path, alert: 'メールアドレスと同じ文字列をパスワードに設定する事はできません'
return
end
if params[:admin_user][:password].blank?
params[:admin_user].delete("password")
params[:admin_user].delete("password_confirmation")
end
super
end
end
end
class InitialPassword < ActionMailer::Base
default from: 'hogehoge@example.com'
default reply_to: 'fugafuga@example.com'
def send_generated_password(address, generated_password)
@generated_password = generated_password
mail to: address, subject: '初期パスワード'
end
end
権限管理がしたい
cancancanで出来る。rails g cancancan:ability
で作ったファイルを、super(全操作可能)/admin(ユーザと権限の追加・編集・削除だけNG)/power(generalで閲覧できるページに加えて秘匿性の高いページの閲覧可能)/general(ダッシュボードなど一部のページの閲覧のみ可能)の権限で分ける場合は次のようにする。
class Ability
include CanCan::Ability
def initialize(user)
user || AdminUser.new
send(user.admin_roles.first.name.downcase)
end
def super
can :manage, :all
end
def admin
can :manage, :all
cannot :manage, AdminUser
cannot :manage, AdminRole
end
def power
general
can :manage, ActiveAdmin::Page, name: "秘匿性の高いページ1"
can :manage, ActiveAdmin::Page, name: "秘匿性の高いページ2"
end
def general
can :manage, ActiveAdmin::Page, name: "Dashboard"
can :manage, ActiveAdmin::Page, name: "秘匿性の低いページ1"
can :manage, ActiveAdmin::Page, name: "秘匿性の低いページ2"
end
end
ちなみに閲覧のみで良いのにpower/generalにmanage権限をつけている理由はcsvダウンロードを許可したかったから。
また、各権限のレコードををadmin_rolesにインサートしておくのを忘れない事。
バッチで定期的に集計値を保存したい
whenever使ったら簡単にできる。集計用テーブルaggregationsを作った上で、データ作成するコードといつ集計するかを決めるコードを作る。
namespace :aggregation do
desc 'aggregate basic data'
task :basic => :environment do
{集計してaggregationsにデータを突っ込むコード}
end
end
require File.expand_path(File.dirname(__FILE__) + "/environment")
set :environment, @environment
set :output, Rails.root.join('log', 'cron.log')
every 1.day, at: '12pm' do
rake 'aggregation:basic'
end
これで毎日12:00に集計データを保存する事が可能。bundle exec whenever -s 'environment=production' --update-crontab
でcronに設定して終わり。
同じようにして集計結果をメールで送信する事も可能。
補足:デプロイ先に環境構築する時参考にしたページ
ruby。
http://www.task-notes.com/entry/20150624/1435114800
依存関係で足りないものがあって怒られたら指示通り入れる。mysql-develとか。
nginx。yumで。
http://qiita.com/nenokido2000/items/3cbb76dac2b9940f339e