Rails4でActiveAdminとdeviseとcancancanをつかって管理者ページを作る
環境
Gemfile
gem 'rails', '4.1.2' gem 'cancancan' gem 'devise' gem 'activeadmin', github: 'activeadmin'
bundleする
$ bundle install
まずはdeviseでモデルを作る.
role
でadminかどうかを判定するのでUser
コントローラに追加
$ rails generate devise:install $ rails generate devise User $ rails generate migration add_role_to_users role:integer $ rake db:migrate
Deviseのモデルを使うのでActiveAdminでUserモデルをつくらないようにするために--skip-users
$ rails generate active_admin:install --skip-users create config/initializers/active_admin.rb create app/admin create app/admin/dashboard.rb route ActiveAdmin.routes(self) generate active_admin:assets create app/assets/javascripts/active_admin.js.coffee create app/assets/stylesheets/active_admin.css.scss create db/migrate/20140829091410_create_active_admin_comments.rb $ rake db:migrate
これするとconfig/intializers/active_admin.rb
のなかを幾つか変えないといけない
config.current_user_method = :current_admin_user ↓ config.current_user_method = :current_user config.logout_link_path = :destroy_admin_user_session_path ↓ config.logout_link_path = :destroy_user_session_path
またapp/application_controller.rb
に以下を追加
def authenticate_admin_user! authenticate_user! unless current_user.admin? flash[:alert] = "This area is restricted to administrators only." redirect_to root_path end end
user
モデルはadmin?
は持ってないので以下をmodel/user.rb
に追加(Rails 4からenum
が使える)
enum role: %(admin normal)
cancancanいらなかった!!!
role
が3つ以上あるといる気がするけどめんどくさそう
参考
Railsで新規にWebサービスを立ち上げる際にやったことまとめ [Rails] Devise and Active Admin Single User Model
集合知プログラミング 2章をrubyで書いた
書きました。
APIを使うところは省略しています
critics = { 'Lisa Rose' => { 'Lady in the Water' => 2.5, 'Snakes on a Plane' => 3.5, 'Just My Luck' => 3.0, 'Superman Returns' => 3.5, 'You, Me and Dupree' => 2.5, 'The Night Listener' => 3.0 }, 'Gene Seymour' => { 'Lady in the Water' => 3.0, 'Snakes on a Plane' => 3.5, 'Just My Luck' => 1.5, 'Superman Returns' => 5.0, 'The Night Listener' => 3.0, 'You, Me and Dupree' => 3.5 }, 'Michael Phillips' => { 'Lady in the Water' => 2.5, 'Snakes on a Plane' => 3.0, 'Superman Returns' => 3.5, 'The Night Listener' => 4.0 }, 'Claudia Puig' => { 'Snakes on a Plane' => 3.5, 'Just My Luck' => 3.0, 'The Night Listener' => 4.5, 'Superman Returns' => 4.0, 'You, Me and Dupree' => 2.5 }, 'Mick LaSalle' => { 'Lady in the Water' => 3.0, 'Snakes on a Plane' => 4.0, 'Just My Luck' => 2.0, 'Superman Returns' => 3.0, 'The Night Listener' => 3.0, 'You, Me and Dupree' => 2.0 }, 'Jack Matthews' => { 'Lady in the Water' => 3.0, 'Snakes on a Plane' => 4.0, 'The Night Listener' => 3.0, 'Superman Returns' => 5.0, 'You, Me and Dupree' => 3.5 }, 'Toby' => { 'Snakes on a Plane' => 4.5, 'You, Me and Dupree' => 1.0, 'Superman Returns' => 4.0 } } def sim_distance(prefs, person1, person2) si = prefs[person1].select { |k, _v| prefs[person2].key?(k) }.keys return 0 if si.size.zero? sum_of_square = si.inject(0) do |a, e| a + (prefs[person1][e] - prefs[person2][e])**2 end 1 / (1 + sum_of_square) end def sim_piason(prefs, person1, person2) si = prefs[person1].keys.select { |k| prefs[person2].key?(k) } n = si.size return 0 if n.zero? sum1 = si.inject(0) { |a, e| a + prefs[person1][e] } sum2 = si.inject(0) { |a, e| a + prefs[person2][e] } p_sum = si.inject(0) { |a, e| a + (prefs[person2][e] * prefs[person1][e]) } sum1_sq = si.inject(0) { |a, e| a + prefs[person1][e]**2 } sum2_sq = si.inject(0) { |a, e| a + prefs[person2][e]**2 } s_xy = p_sum - (sum1 * sum2 / n) s_xx = sum1_sq - (sum1**2 / n) s_yy = sum2_sq - (sum2**2 / n) return 0 if Math.sqrt(s_xx * s_yy).zero? s_xy / Math.sqrt(s_xx * s_yy) end def top_match(pref, person, n = 5, similarity = method(:sim_piason)) personp = ->(x) { x == person } cal_sim = ->(p) { [p, similarity.call(pref, p, person)] } descend = ->((_, p1), (_, p2)) { p2 <=> p1 } pref.keys.reject(&personp).map(&cal_sim).sort(&descend)[0...n] end def get_recommendation(pref, person, similarity = method(:sim_piason)) personp = ->(x) { x == person } cal_sim = ->(p) { [p, similarity.call(pref, p, person)] } gt_zero = ->(x) { x[1] > 0 } person_has_item = ->(x) { pref[person].key?(x) } totals = {} sim_sum = {} pref.keys.reject(&personp).map(&cal_sim).select(>_zero).each do |other, sim| pref[other].keys.reject(&person_has_item).each do |item| totals[item] = (totals[item] || 0) + pref[other][item] * sim sim_sum[item] = (sim_sum[item] || 0) + sim end end totals.map do |(k, v)| [(v / sim_sum[k]), k] end.sort.reverse end def transform_prefs(prefs) prefs.each_with_object({}) do |(person, items), a| items.each do |movie, v| a[movie] ||= {} a[movie][person] = v end end end def calculate_similar_item(prefs, n = 10) item_pref = transform_prefs(prefs) item_pref.keys.each_with_object({}) do |item, a| a[item] = top_match(item_pref, item, n, method(:sim_distance)) end end def get_recommended_items(prefs, item_match, user) user_rating = prefs[user] scores = {} total_sim = {} user_rating.each do |item, rating| item_match[item].reject { |item2, _| user_rating.keys.include?(item2) }.each do |item2, sim| scores[item2] = (scores[item2] || 0) + sim * rating total_sim[item2] = (total_sim[item2] || 0) + sim end end scores.map do |item, score| [(score / total_sim[item]), item] end.sort.reverse end if __FILE__ == $PROGRAM_NAME # p transform_prefs(critics) # p sim_piason(critics, 'Lisa Rose', 'Gene Seymour') # p sim_distance(critics, 'Lisa Rose', 'Gene Seymour') # p top_match(critics, 'Toby', 3) # p get_recommendation(critics, 'Toby') item_sim = calculate_similar_item(critics) p get_recommended_items(critics, item_sim, 'Toby') end
今日解いた問題3
今日というか今日と昨日
単一始点最短経路問題(ベルマンフォード)
ある頂点sからのすべての頂点の最短経路
d[j] > d[i] + cost
この条件を1度使っただけでは明らかに最短にならないので
update
変更がなくなるまでループを繰り返す
INF = 100_000 G = [[INF, 2, 5, INF, INF, INF, INF], [2, INF, 4, 6, 10, INF, INF], [5, 4, INF, 2, INF, INF, INF], [INF, 6, 2, INF, INF, 1, INF], [INF, 10, INF, INF, INF, 3, 5], [INF, INF, INF, 1, 3, INF, 9], [INF, INF, INF, INF, 5, 9, INF]] def shotest_path(s) d = Array.new(G.size, INF) d[s] = 0 loop do update = false G.each_with_index do |row, i| row.each_with_index do |cost, j| next if cost == INF || d[i] == INF if d[j] > d[i] + cost d[j] = d[i] + cost update = true end end end break unless update end d end p shotest_path(0) # => [0, 2, 5, 7, 11, 8, 16]
単一始点最短経路問題(ダイクストラ)
ベルマンフォードと目的は一緒
まだ訪れていない頂点の中から最短の頂点を見つけてそこから他の頂点への経路のコストを得る
INF = 100_000 G = [[INF, 2, 5, INF, INF, INF, INF], [2, INF, 4, 6, 10, INF, INF], [5, 4, INF, 2, INF, INF, INF], [INF, 6, 2, INF, INF, 1, INF], [INF, 10, INF, INF, INF, 3, 5], [INF, INF, INF, 1, 3, INF, 9], [INF, INF, INF, INF, 5, 9, INF]] def dijkstra(s) size = G.size d = Array.new(size, INF) used = Array.new(size, false) d[s] = 0 loop do min_node = -1 size.times do |i| min_node = i if !used[i] && (min_node == -1 || d[min_node] > d[i]) end break if min_node == -1 used[min_node] = true size.times do |i| d[i] = [d[min_node] + G[min_node][i], d[i]].min end end d end p dijkstra(0) # => [0, 2, 5, 7, 11, 8, 16]
全点対最短経路( ワーシャルフロイド)
すべての頂点からそれ以外のすべての頂点の最短経路
i
からj
までのパスの中で頂点k
通るか通らないかのDP
通るときはg[i][k] + g[k][j]
で通らない時はg[i][j]
ホントはd[k][i][j]
とかなるんだけどループ内で省略してる
INF = 100_000 def warshall_floyd g = [[0, 2, 5, INF, INF, INF, INF], [2, 0, 4, 6, 10, INF, INF], [5, 4, 0, 2, INF, INF, INF], [INF, 6, 2, 0, INF, 1, INF], [INF, 10, INF, 0, INF, 3, 5], [INF, INF, INF, 1, 3, 0, 9], [INF, INF, INF, INF, 5, 9, 0]] size = g.size size.times do |k| size.times do |i| size.times do |j| g[i][j] = [g[i][j], g[i][k] + g[k][j]].min end end end g end p warshall_floyd # [[0, 2, 5, 7, 11, 8, 16], [2, 0, 4, 6, 10, 7, 15], [5, 4, 0, 2, 6, 3, 11], [7, 6, 2, 0, 4, 1, 9], [7, 6, 2, 0, 4, 1, 5], [8, 7, 3, 1, 3, 0, 8], [12, 11, 7, 5, 5, 6, 0]]
アリ本のP102
2番めの最短経路をだす問題
priority_queueがなかったのでかなり汚い感じになった
基本方針はすべての頂点に対して2番めの最短経路も持っとくこと
N = 4 M = 4 INF = 100_000 G = [[INF, 100, INF, INF], [100, INF, 250, 200], [INF, 250, INF, 100], [INF, 200, 100, INF]] def solve d1 = Array.new(N, INF) used1 = Array.new(N, false) d2 = Array.new(N, INF) used2 = Array.new(N, false) d1[0] = 0 d2[0] = 0 loop do v = [-1, 1] N.times do |i| v = [i, 1] if !used1[i] && (v[0] == -1 || d1[v[0]] > d1[i]) v = [i, 2] if !used2[i] && (v[0] == -1 || d2[v[0]] > d2[i]) end break if v[0] == -1 eval("used#{v[1]}[#{v[0]}]=true") N.times do |i| d = eval("d#{v[1]}[#{v[0]}]") + G[v[0]][i] if d < d1[i] d, d1[i] = d[i], d end if d > d1[i] && d < d2[i] d2[i] = d end end end d2[N-1] end p solve # => 450
今日解いた問題2
彩色問題
アリ本の93ページ
隣接したりノードが同じ色にならないように色をぬる。
今回は2色で塗ることができるかという問題
特に2色でぬれるグラフを2部グラフという
コードで言うと0が塗ってないノードなので下の2つを繰り返す感じ
- 隣接したノード
j
が0ならノードj
に対して色(-c
)を塗る - 隣接したノード
j
と現在のノードがi
が同じ色c(1 or 0)
ならfalse
を返す
G1 = [[0, 1, 1], [1, 0, 1], [1, 1, 0]].freeze G2 = [[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]].freeze s = G2.size $color = Array.new(s, 0) # 0は塗ってない 1と-1が塗った def dfs(i, c) $color[i] = c G2[i].each_with_index do |e, j| next unless e == 1 return false if $color[j] == c return false if $color[j].zero? && !dfs(j, -c) end true end s.times do |i| next unless $color[i].zero? unless dfs(i, 1) puts 'NO' break end puts 'YES' end
今日解いた問題
分割数はいまいちわかっていない
最長増加部分列問題
自分(i)より小さい部分問題を解いていけばできる
N = 5 A = [4, 2, 3, 1, 5].freeze $dp = Array.new(N, 1) def solve N.times do |i| i.times do |j| $dp[i] = [$dp[j] + 1, $dp[i]].max if A[i] > A[j] end end $dp.max end p solve # => 3
分割数
NをM個以下に分割する総数
N=4,M=3の場合
dp(3,4)=dp(3,1)+dp(2,4)
になる.
dp(2,4)
は総数なので2個以下で分割するときの総数($dp[i-1][j]
)
dp(3,1)
は3分割したもののそれぞれから1引いてそれを分割する総数($dp[i][j-1]
この場合 j < iなので0)
3分割したものから1引くとは4=1+1+2
となるのでそれぞれから1引いて0+0+1=1
N = 4 M = 3 $dp = Array.new(M + 1).map { Array.new(N + 1, 0) } def solve $dp[0][0] = 1 (1..M).each do |i| (0..N).each do |j| a = j - i >= 0 ? $dp[i][j-i] : 0 $dp[i][j] = $dp[i-1][j] + a end end $dp[M][N] end p solve # => 4
ナップザック問題
久しぶりにナップザック問題書いてみたら思いの外時間がかかって辛かった
N = 4 W = 5 ITEMS = [[2, 3], [1, 2], [3, 4], [2, 2]].freeze # [重さ, 価値] INF = 10000000 # 全探索 def dfs(i, w) return 0 if i == N b = dfs(i + 1, w) a = w - ITEMS[i][0] >= 0 ? dfs(i + 1, w - ITEMS[i][0]) + ITEMS[i][1] : 0 [a, b].max end p dfs(0, W) # => 7 # memo化 $memo = Array.new(N).map { Array.new(N, INF) } def dfs_memo(i, w) return 0 if i == N b = dfs_memo(i + 1, w) a = w - ITEMS[i][0] >= 0 ? dfs_memo(i + 1, w - ITEMS[i][0]) + ITEMS[i][1] : 0 $memo[i][w] = [a, b].max end p dfs_memo(0, W) # => 7 # dp $dp = Array.new(N + 1).map { Array.new(W + 1, 0) } def solver (1..N).each do |i| (1..W).each do |j| a = $dp[i-1][j] b = j >= ITEMS[i-1][0] ? $dp[i-1][j-ITEMS[i-1][0]] + ITEMS[i - 1][1] : 0 $dp[i][j] = [a, b].max end end end solver p $dp[N][W] # => 7
rspec3で標準入力と標準出力のテスト
rspecで標準入力からユーザの入力を受け取って標準出力に出力する方法の覚書 rspec2とrspec3がまざっててややこしかった
プロダクションコード
solver.rb
get
でユーザの入力を受け取ってprint
で標準出力に答え(文字列)を出力するのクラス
class Solver attr_reader :n def get @n = gets end def print puts 'answer is foo' end end
テストコード
solver_spec.rb
標準入力ではallow(ARGF).to receive(:gets) { 1000 }
とするといいらしい.
1000
の部分を入力したい値に変更すればいい.
標準出力はoutput("answer is foo\n").to_stdout
とするといいらしい.
"answer is foo\n"
の部分を出力したい値に変更すればいい.
require 'solver' describe Solver do let(:solver) { described_class.new } describe 'solver#getは標準入力を受け取って@nにセットする' do before do allow(ARGF).to receive(:gets) { 1000 } solver.get end it { expect(solver.n).to eq 1000 } end describe 'solver#printは標準出力に答えを出力する' do it { expect { solver.print }.to output("answer is foo\n").to_stdout } end end