Rails4 + devise + cancancan + rails_adminが最強すぎ

Rails4 + devise + cancancan + rails_adminが最強過ぎたので自分用にメモ。

deviseで認証をして
cancancanでロール毎のパーミッションを割り当てて
rails_adminをdeviseとcancancanでadminユーザのみに使わせる

また、自分はrvmを使っているのでその辺りの設定も入っているが、使っていない場合はその辺りを無視で。

環境

  • Ruby 2.1.2
  • Rails 4.1.4
  • gem
    • devise 3.2.4
    • cancancan 1.8.4
    • rails_admin 0.6.2

今回のプロジェクト(sample)用にrvmの設定

rvmのgemsetを作成してrailsをインストール

rvm gemset create sample
rvm 2.1@sample
gem install rails

プロジェクト作成

railsプロジェクトの作成

rails new sample
cd sample/
rvm --rvmrc --create 2.1@sample

Gemfile

Gemfileを編集

gem 'devise'
gem 'cancancan'
gem 'rails_admin'

を追加

bundle installする

bundle install

deviseの設定

devise関連のファイルをgenerate

rails g devise:install

config/environments/development.rbを編集

config.action_mailer.default_url_options = { :host => 'localhost:3000' }

を追加

app/views/layouts/application.html.erbを編集

<% if notice %>
  <p class="alert alert-notice"><%= notice %></p>
<% end %>
<% if alert %>
  <p class="alert alert-error"><%= alert %></p>
<% end %>

を<%= yield %>の前あたりに追加。

deviceのユーザ管理モデルとロール、それらを結ぶ連結モデルの追加

rails g devise user
rails g model role name
rails g migration CreateJoinTableUserRole user role
rake db:migrate

app/models/user.rbを編集

  has_and_belongs_to_many :roles

  def has_role?(name)
    self.roles.where(name: name).length > 0
  end

app/models/role.rbを編集

  has_and_belongs_to_many :users

を追加。

cancancanの設定

cancancan関連のファイルをgenerate

rails g cancan:ability

app/models/ability.rbを編集

    if user.has_role?('admin')
      can :read, :all
      can :access, :rails_admin
      can :dashboard
      if user.has_role?('superadmin')
        can :manage, :all
      else
        can :manage, [] # A
      end
    else
      can :read, [] # B
      can :create, [] # C
    end

を追加。

  • superadminは全てのモデルの管理が可能
  • adminユーザは全てのモデルのreadと、Aで指定されたも出るの管理が可能
  • それ以外のユーザはBで指定されたモデルのreadと、Cで指定されたモデルのcreateが可能

アプリケーションの用途等に合わせて上記を変更する

rails_adminの設定

rails_admin関連のファイルをgenerate

rails g rails_admin:install
rake db:migrate

初期データ(管理者ユーザ)の作成

railsサーバの起動

rails s

http://localhost:3000/adminにアクセスして
Roleで

  • admin
  • superadmin

を作成して

Userでadminとsuperadminを関連付けた管理者ユーザを作成する。

終わったらCtrl+cでサーバを終了しておく。
スクリーンショットではログインしている状態だけどこの時点ではまだなので気にしない。

rails_adminの認証設定

config/initializers/rails_admin.rbを編集

  # config.authenticate_with do
  #   warden.authenticate! scope: :user
  # end
  # config.current_user_method(&:current_user)

  # config.authorize_with :cancan

となっているコメントを外す。

動作確認

railsサーバの起動

rails s

して

  • 先ほど作った管理者ユーザでログイン出来る
  • 全てのオブジェクトの管理が出来る
  • 管理者以外のユーザを作成して狙い通りに動く事

を確認する

おまけ

メール送信関連の設定
config/environments/development.rbを編集

  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    :address => 'smtp.gmail.com',
    :port => 587,
    :authentication => :plain,
    :user_name => 'メールアドレス',
    :password => 'パスワード'
  }

ログアウトをGETに変更
config/initializers/rails_admin.rbを編集
デフォルトではdeleteになっているので

  config.sign_out_via = :delete

getに変更しておくといいかも?

  config.sign_out_via = :get

ユーザSign up時にメール確認
db/migrate/*_devise_create_users.rbを編集

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

コメントアウトを外す。
app/models/user.rbを編集

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :confirmable

:confirmableを追加

与えられたぷよぷよフィールドが19連鎖であることを証明する

とあるHP*1にて発見した問題。
久しぶりにCで書いたらSEGVで落ちまくり結局1時間弱かかってしまった。。。

問題

ゲーム「ぷよぷよ」で、フィールドの状態がテキストで与えられたとき、消える「ぷよ」を消して次のフィールドの状態を出力するプログラムを書け。
 たとえば、色をG/Y/Rで表すとき(Green/Yellow/Red)、

GGR
YGG

であればGが消えて

Y R

になります。

また、このプログラムを使って次のフィールドを与えると19連鎖ののちすべてのぷよが消えることを確認し、消える途中の様子をあわせて提出すること。

  GYRR 
RYYGYG 
GYGYRR 
RYGYRG 
YGYRYG 
GYRYRG 
YGYRYR 
YGYRYR 
YRRGRG 
RYGYGG 
GRYGYR 
GRYGYR 
GRYGYR 

※1行目の"GYRR"の前には半角スペースが2つ入っている

バージョン1(思いつくがままに書いたバージョン)
塗りつぶしアルゴリズム(mark関数)が糞

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char **input(int *w, int *h);
void free_board(char **board, int h);
void show(char **board, int w, int h);
int mark(char **board, int w, int h);
void del(char **board, int w, int h);

char **input(int *w, int *h) {
	int ww =0, hh = 0, alloc_h = 10;
	char *p, **board = (char **)malloc(sizeof (char *) * alloc_h);
	char buf[256];
	while (fgets(buf, 256, stdin) != NULL) {
		while (buf[strlen(buf) - 1] == '\n' || buf[strlen(buf) - 1] == '\r') buf[strlen(buf) - 1] = '\0';
		if (ww == 0) {
			ww = strlen(buf);
		} else if (ww != strlen(buf)) {
			free_board(board, hh);
			return NULL;
		}
		board[hh] = strcpy((char *)malloc(ww + 1), buf);
		hh++;
		if (hh >= alloc_h) {
			alloc_h += 10;
			board = (char **)realloc(board, sizeof (char *) * alloc_h);
		}
	}
	*w = ww;
	*h = hh;
	return board;
}

void free_board(char **board, int h) {
	int i;
	for (i = 0; i < h; i++) {
		free(board[i]);
	}
	free(board);
}

void show(char **board, int w, int h) {
	int i;
	for (i = 0; i < w + 2; i++) putchar('-');
	printf("\n");
	for (i = 0; i < h; i++) {
		printf("|%s|\n", board[i]);
	}
	for (i = 0; i < w + 2; i++) putchar('-');
	printf("\n");
}

int mark(char **board, int w, int h) {
	int i, j, k, l, dels, count, total = 0;
	char c;

	for (i = 0; i < h; i++) {
		for (j = 0; j < w; j++) {
			c = board[i][j];
			if (c == ' ' || c == '*') continue;
			board[i][j] = '%';
			dels = 1;
			do {
				count = 0;
				for (k = 0; k < h; k++) {
					for (l = 0; l < w; l++) {
						if (board[k][l] != c) continue;
						if (
							(k - 1 >= 0 && board[k - 1][l] == '%') ||
							(k + 1 < h && board[k + 1][l] == '%') ||
							(l - 1 >= 0 && board[k][l - 1] == '%') ||
							(l + 1 < w && board[k][l + 1] == '%')
						) {
							board[k][l] = '%';
							count++;
						}
					}
				}
				dels += count;
			} while (count > 0);
			if (dels >= 4) {
				c = '*';
				total += dels;
			}
			for (k = 0; k < h; k++) {
				for (l = 0; l < w; l++) {
					if (board[k][l] == '%') board[k][l] = c;
				}
			}
		}
	}
	return total;
}

void del(char **board, int w, int h) {
	int i, j, k, l;
	for (i = h - 1; i >= 0; i--) {
		for (j = 0; j < w; j++) {
			if (board[i][j] == '*') board[i][j] = ' ';
			if (board[i][j] == ' ') {
				for (k = i - 1; k >= 0; k--) {
					if (board[k][j] != ' ' && board[k][j] != '*') {
						board[i][j] = board[k][j];
						if (board[i][j] == '*') board[i][j] = ' ';
						board[k][j] = ' ';
						break;
					}
				}
			}
		}
	}
}

int main(int argc, char **argv) {
	int i = 0, w, h;
	char **board = input(&w, &h);

	if (board == NULL) {
		exit(1);
	}
	show(board, w, h);
	while (mark(board, w, h) > 0) {
		printf("%d:\n", ++i);
		show(board, w, h);
		del(board, w, h);
		show(board, w, h);
	}
	free_board(board, h);
	return 0;
}

塗りつぶしアルゴリズムをスタックを使ったバージョンにしたもの。5重ループが3重ループになりすっきり。108連鎖*2を与えた場合には10倍程度早くなった。

int mark(char **board, int w, int h) {
	int i, j, k, dels, total = 0, x, y;
	int *stack = (int *)malloc(sizeof (int) * w * h), sp;
	char c;

	for (i = 0; i < h; i++) {
		for (j = 0; j < w; j++) {
			c = board[i][j];
			if (c == ' ' || c == '*') continue;
			dels = sp = 0;
			stack[dels++] = i * w + j;
			while (sp < dels) {
				x = stack[sp] % w;
				y = stack[sp] / w;
				board[y][x] = '%';
				if (x - 1 >= 0 && board[y][x - 1] == c) stack[dels++] = y * w + x - 1;
				if (x + 1 < w && board[y][x + 1] == c) stack[dels++] = y * w + x + 1;
				if (y - 1 >= 0 && board[y - 1][x] == c) stack[dels++] = (y - 1) * w + x;
				if (y + 1 < h && board[y + 1][x] == c) stack[dels++] = (y + 1) * w + x;
				sp++;
			}
			if (dels >= 4) {
				c = '*';
				total += dels;
			}
			for (k = 0; k < dels; k++) {
				board[stack[k] / w][stack[k] % w] = c;
			}
		}
	}
	free(stack);
	return total;
}

最初からこれを書けなくなっているのは衰えている証拠かな。。

RubyでOAuth1.0のrequest tokenを取得

テスト用に使ったコード。

REQUEST_TOKEN_URI = 'https://api.twitter.com/oauth/request_token'
CALLBACK_URI      = 'http://localhost/callback'
CONSUMER_KEY      = '**********************'
CONSUMER_SECRET   = '**********************'

や場合によってはいろいろを書き換えるとTwitter以外にも使えると思います。

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require 'uri'
require 'cgi'
require 'openssl'
require 'net/https'

class OAuthTest
  NONCE_CHARS       = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
  NONCE_LENGTH      = 32
  REQUEST_TOKEN_URI = 'https://api.twitter.com/oauth/request_token'
  CALLBACK_URI      = 'https://c.na1.visual.force.com/apex/OAuthComplete'
  CONSUMER_KEY      = 'xxxxxxxxxxxxxxxxxxxxxx'
  CONSUMER_SECRET   = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

  def create_nonce
    NONCE_LENGTH.times.inject('') {|str| str + NONCE_CHARS[rand(NONCE_CHARS.size)]}
  end

  def create_signature secret, params
    signature_base_string = [
      'POST',
      REQUEST_TOKEN_URI,
      params.map{|k, v| "#{k}=#{CGI.escape(v)}"}.join('&'),
    ].map{|s| CGI.escape(s)}.join('&')
    signature_digest = OpenSSL::HMAC::digest(OpenSSL::Digest::SHA1.new, secret, signature_base_string)
    [signature_digest].pack('m').gsub!(/\n/u, '')
  end

  def ger_request_token params
    request_token_uri = URI.split(REQUEST_TOKEN_URI)
    if request_token_uri[0] == 'https'
        http = Net::HTTP.new(request_token_uri[2], 443)
        http.use_ssl = true
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE
        http.verify_depth = 5
    else
        http = Net::HTTP.new(request_token_uri[2])
    end
    header = {
      'Content-type' => 'application/x-www-form-urlencoded',
      'Authorization' => 'OAuth ' + params.map{|k, v| "#{k}=#{CGI.escape(v)}"}.select{|s| s =~ /^oauth_/}.join(',')
    }
    http.post(request_token_uri[5], nil, header).body
  end

  def request_token
    params = {
      'oauth_callback' => CALLBACK_URI,
      'oauth_consumer_key' => CONSUMER_KEY,
      'oauth_nonce' => create_nonce,
      'oauth_signature_method' => 'HMAC-SHA1',
      'oauth_timestamp' => (Time.now - Time.utc(1970, 1, 1)).to_i.to_s,
      'oauth_version' => '1.0',
    }
    params['oauth_signature'] = create_signature("#{CONSUMER_SECRET}&", params)
    ger_request_token(params)
  end
end

puts OAuthTest.new.request_token

C言語でKaratsuba開平を利用して整数の平方根と余りを求める

math.hのsqrt(f)を使わずに整数の平方根を求めてみた。(intが4byteであるマシンでのみ動作)
sqrtmod0はアルゴリズム上0x40000000以上の値の平方根を計算でようなので、0x40000000以上の値はKaratsuba開平を利用して求めている。

↓は乱数で平方根と余りを求めてsqrtで求めたものとassertをし続けるプログラムです。

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>

#define BASE (sizeof (unsigned int) * 8 / 4)

int sqrtmod(unsigned int n, unsigned int *pmod);
int sqrtmod_karatsuba(unsigned int n, unsigned int *pmod);
int sqrtmod0(unsigned int n, unsigned int *pmod);

int sqrtmod(unsigned int n, unsigned int *pmod) {
	return sqrtmod_karatsuba(n, pmod);
}

int sqrtmod_karatsuba(unsigned int n, unsigned int *pmod) {
	int s, s1, s0, r, r1, r0;

	if ((0xc0000000 & n) == 0) {
		return sqrtmod0(n, pmod);
	}

	s1 = sqrtmod0(n >> (BASE * 2), &r1);

	r0 = (r1 << BASE) | ((n >> BASE) & 0x000000ff);
	s0 = r0 / (s1 << 1);
	r0 -= s0 * (s1 << 1);

	s = (s1 << BASE) + s0;
	r = ((r0 << BASE) | (n & 0x000000ff)) - s0 * s0;
	if (r < 0) {
		r += (s << 1) - 1;
		s--;
	}
	if (pmod) *pmod = r;
	assert(s * s + r == n);

	return s;
}

int sqrtmod0(unsigned int n, unsigned int *pmod) {
	int p = 0, q = 1, r = n, h = 0;

	while (q <= n) {
		q = q << 2;
	}

	while (1 != q) {
		q = q >> 2;
		h = p + q;
		p = p >> 1;
		if (r >= h) {
			p = p + q;
			r = r - h;
		}
	}
	if (pmod) *pmod = r;
	assert(p * p + r == n);

	return p;
}

int main(int argc, char **argv) {
	unsigned int n, s1, r1, s2, r2;

	srand((unsigned)time(NULL));
	while (1) {
		n = rand();
		s1 = sqrtmod(n, &r1);
		s2 = sqrt(n);
		r2 = n - s2 * s2;
		assert(s1 == s2 && r1 == r2);
	}

	return 0;
}

とりあえずassertエラーが出ないので、実装は出来ているらしい。。


参考:
2 Karatsuba$B7ONs$N%"%k%4%j%:%`(B
GNU MP 6.1.2: Square Root Algorithm

Rubyでn桁の円周率を求める

検証用に使っていた物です。

len = ARGV[0].to_i
B = 10 ** len
B2 = B << 1
pi = (len * 8 + 1).step(3, -2).inject(B) {|a, i| (i >> 1) * (a + B2) / i} - B
puts "3.#{pi}"
# time ruby pi.rb 100
3.141592653589793238462643383279502884197169399375105820974944592307816406286208
9986280348253421170679

real    0m0.045s
user    0m0.027s
sys     0m0.007s

10万桁位なら2分前後で行けるはずです。
※ウチのPCはショボイのでもっと早いかも

Linuxでユーザ管理関係のコマンドまとめ

ユーザ確認

各ユーザには大体/home下に自分専用のディレクトリがあるので、

ls -l /home

で確認できる。

ユーザ追加

useradd ユーザ名

グループ、パスワードも一気に

useradd -g グループ名 -p パスワード ユーザ名

ユーザに利用有効期限を付ける

useradd -e yyyy-mm-dd ユーザ名

ログインシェルをnologinにして、TelnetSSHでログインさせないようなユーザを作る
メールとHPは許可

useradd -s /sbin/nologin ユーザ名

同様に、メールしか利用できないようなユーザを作る

useradd -s /sbin/false ユーザ名

発効から指定日数までにログインしないと、無効になるユーザを作る

useradd -f 日数 ユーザ名

ユーザ削除

userdel ユーザ名

ユーザのホームディレクトリも消す場合は、

userdel -r ユーザ名

パスワード変更

自分のパスワードなら

passwd
の後、パスワード入力。

管理者が他のユーザのパスワードを変更する場合は、

passwd ユーザ名

グループを追加

groupadd グループ名
グループは、

cat /etc/group

で確認できる。

グループ名変更

groupmod -l 旧グループ名 新グループ名

グループを削除

groupdel グループ名