dev.twitterから発行されるconsumer keyやconsumer secret keyがgithubに上がっていってしまわないように環境変数に設定する。プログラムの中から環境変数を呼び出して使用。
設定
export 変数名=変数
確認
echo $変数名
例)
export TWITTER_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
echo $TWITTER_SECRET
プログラムからの呼び出し(omniauthの設定ファイル)
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
#provider :facebook,"App ID","App Secret"
end
アプリが1つ以上になるとちょっと不便になってくる気がするけどとりあえずこれで。
Saturday, May 19, 2012
Tuesday, May 15, 2012
devise認証やってみる(4/12)モデルの関連付け、バリデーション、ユニットテスト、セキュリティ [rails3]
devise認証やってみる(4/12)モデルの関連付け、バリデーション、ユニットテスト、セキュリティ [rails3]
CommunityGuides 4/12 - Relations, Validations, Unit Tests, Security
(回を重ねるごとにキレイになるどころか、より自分メモ的になってしまって読みづらくなってきてます。。。)
モデルの関連付け
migratationファイルの修正
DBに空白が入らないように、いくつかの項目に :null => false で制約をかける。
db/migrate/…create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration
def self.up
create_table(:users) do |t|
t.database_authenticatable :null => false
t.recoverable
t.rememberable
t.trackable
t.confirmable
t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :time
# t.token_authenticatable
+ # author information
+ t.string :fullname
+ t.text :shortbio
+ t.string :weburl
+ t.integer :country_id, :null => false, :default => 1 # foreign key to country table
t.timestamps
end
+ add_index :users, :fullname
+ add_index :users, :country_id
add_index :users, :email, :unique => true
add_index :users, :reset_password_token, :unique => true
add_index :users, :confirmation_token, :unique => true
# add_index :users, :unlock_token, :unique => true
end
def self.down
drop_table :users
end
end
( +の行を追加。バージョン差異で吐き出されているコードが多少違うので不安を感じつつ。。。 )
class CreateArticles < ActiveRecord::Migration
def change
create_table :articles do |t|
t.integer :user_id, :null => false # + foreign key
t.string :title, :null => false # +
t.text :teaser, :null => false # +
t.text :body, :null => false # +
t.string :version
t.text :changelog
t.string :message # 却下時のユーザへのメッセージ
t.text :freezebody # 記事を受け取ると同時にtextbodyをこのフィールドにコピー
t.integer :state, :null => false, :default => 0 # + 0...下書き, 1...送信済み, 2...却下, 3...全記事, 4...オススメ記事
t.date :submitted
t.date :accepted
t.timestamps
end
add_index :articles, :user_id # +
end
end
def self.down
drop_table :articles
end
はとりあえず入れないでおく。。。
% rake db:drop
% rake db:migrate
モデルの関連づけ ユーザと記事
ユーザは複数の記事を持っている。
ひとつの記事はひとりのユーザに属する。
user.rb
has_many :articles, :dependent => :destroy
と
attr_accessibleの行に :fullname, :shortbio, :weburl
を追加
article.rb
空だったのでまるまる追加。
class Article < ActiveRecord::Base
belongs_to :user
validates :user_id, :presence => true
validates :title, :presence => true, :length => { :maximum => 80 }
validates :teaser, :presence => true, :length => { :maximum => 500 }
validates :body, :presence => true
validates :version, :length => { :maximum => 120 }
validates :changelog, :length => { :maximum => 2000 }
validates :message, :length => { :maximum => 5000 }
validates :state, :presence => true, :numericality => true, :inclusion => { :in => 0..4 }
end
:dependent => :destroy
関連するユーザが削除されると記事も削除される
その他ヴァリデーションはこちらを見てね、と。
ここまでのテストではarticle controllerに対してfunctional testをやってきたが、ヴァリデーションのテストはunit testを使用。
test/unit/article_test.rb
require 'test_helper'
class ArticleTest < ActiveSupport::TestCase
test "should not have empty title teaser body" do
article = Article.new
article.user_id = 1
assert article.invalid?
assert article.errors[:title].any?
assert article.errors[:teaser].any?
assert article.errors[:body].any?
assert !article.save
end
test "must belong to a user" do
article = Article.new :title => "Title", :teaser => "Teaser", :body => "Body"
assert article.invalid?
assert !article.save
end
test "should not have a state outside boundaries" do
article = Article.new :title => "Title", :teaser => "Teaser", :body => "Body"
article.user_id = 1
article.state = -1
assert !article.save
article.state = 'a'
assert !article.save
article.state = 5
assert !article.save
article.state = 0
assert article.save
article.state = 2
assert article.save
article.state = 4
assert article.save
end
end
Rescue
などのように存在しない記事を呼び出された場合。
通常はエラー画面や public/404.html などだがflash messageをページ内に表示させて対応したほうが親切。ということで。
app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
...
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
...
protected
def record_not_found
flash[:error] = 'The article you requested could not be found.'
redirect_to root_url
end
end # end of controller file
ActiveRecordからnotfoundのエラーをもらってrecord_not_foundメソッドに渡して、rootのflash message部分に表示させる、のような感じ?
rescue_fromの詳細は、
もっとテストする
ちなににユニットテストも rake ?
% rake
9 tests, 13 assertions, 0 failures, 0 errors, 0 skips
テストが通るということは「適切なテストがテストスートにない」ということを表している。
他ユーザによる記事編集、ログインしていない状況、など。。。
未登録ユーザのリダイレクト
test/functional/articles_controller_test.rb
require 'test_helper'
class ArticlesControllerTest < ActionController::TestCase
setup do
@article_user1 = articles(:one)
@article_user2 = articles(:three)
end
# index and all
test "should get index and all anonymous" do
get :index
assert_response :success
assert_not_nil assigns(:articles)
get :all
assert_response :success
assert_not_nil assigns(:articles)
end
test "should get index and all signed in" do
sign_in users(:user2)
get :index
assert_response :success
assert_not_nil assigns(:articles)
get :all
assert_response :success
assert_not_nil assigns(:articles)
end
# about
test "should get about anonymous" do
get :about
assert_response :success
end
test "should get about signed in" do
sign_in users(:user2)
get :about
assert_response :success
end
# show
test "should show article anonymous" do
get :show, :id => @article_user1.to_param
assert_response :success
end
test "should show article signed in" do
sign_in users(:user2)
get :show, :id => @article_user1.to_param
assert_response :success
end
# new and edit
test "should not get new and edit anonymous" do
get :new
assert_redirected_to new_user_session_path
get :edit, :id => @article_user1.to_param
assert_redirected_to new_user_session_path
end
test "should get new signed in" do
sign_in users(:user2)
get :new
assert_response :success
end
test "new article has to belong to current user" do
sign_in users(:user2)
get :new
assert assigns(:article).user_id == users(:user2).id, "Article does not belong to current user"
end
test "should get edit to own article signed in" do
sign_in users(:user2)
get :edit, :id => @article_user2.to_param
assert_response :success
end
test "edited article has to belong to current user" do
sign_in users(:user2)
get :edit, :id => @article_user1.to_param
assert_redirected_to root_url, "Should be redirected to root url if article of other user is requested"
assert_equal 'The article you requested could not be found.', flash[:error]
end
# create
test "should not create article anonymous" do
assert_no_difference('Article.count') do
post :create, :article => @article_user1.attributes
end
end
test "should not create article linked to other user" do
sign_in users(:user2)
post :create, :article => { :user_id => users(:user1).id, :title => 'Title', :teaser => 'Teaser', :body => 'Body' }
assert assigns(:article).user_id == users(:user2).id, "Article does not belong to current user"
end
test "should create article signed in" do
sign_in users(:user2)
assert_difference('Article.count', 1, "Article count has not changed") do
post :create, :article => { :user_id => users(:user2).id, :title => 'Title', :teaser => 'Teaser', :body => 'Body' }
end
assert_redirected_to article_path(assigns(:article))
assert_equal 'Article was successfully created.', flash[:notice]
end
# update
test "should not update article anonymous" do
put :update, :id => @article_user1.to_param, :article => @article_user1.attributes
assert_redirected_to new_user_session_path
end
test "should update article signed in" do
sign_in users(:user2)
put :update, :id => @article_user2.to_param, :article => @article_user2.attributes
assert_redirected_to article_path(assigns(:article))
end
test "should not update article linked to other user" do
sign_in users(:user2)
put :update, :id => @article_user1.to_param, :article => @article_user1.attributes
assert_redirected_to root_url, "Should be redirected to root url if article of other user is requested"
assert_equal 'The article you requested could not be found.', flash[:error]
end
# destroy
test "should not destroy article anonymous" do
assert_no_difference('Article.count') do
delete :destroy, :id => @article_user1.to_param
end
assert_redirected_to new_user_session_path
end
test "should destroy article signed in" do
sign_in users(:user2)
assert_difference('Article.count', -1) do
delete :destroy, :id => @article_user2.to_param
end
assert_redirected_to articles_path
end
test "should not destroy article linked to other user" do
sign_in users(:user2)
assert_no_difference('Article.count', "Article count has changed") do
delete :destroy, :id => @article_user1.to_param
end
assert_redirected_to root_url, "Should be redirected to root url if article of other user is requested"
assert_equal 'The article you requested could not be found.', flash[:error]
end
end
と
test/fixtures/articles.yml
one:
id: 1
user_id: 1
title: MyString
teaser: MyText
body: MyText
version: MyString
changelog: MyText
message: MyString
freezebody: MyText
state: 0
submitted: 2011-02-11
accepted: 2011-02-11
two:
id: 2
user_id: 1
title: MyString
teaser: MyText
body: MyText
version: MyString
changelog: MyText
message: MyString
freezebody: MyText
state: 3
submitted: 2011-02-11
accepted: 2011-02-11
three:
id: 3
user_id: 2
title: MyString
teaser: MyText
body: MyText
version: MyString
changelog: MyText
message: MyString
freezebody: MyText
state: 0
submitted: 2011-02-11
accepted: 2011-02-11
four:
id: 4
user_id: 2
title: MyString
teaser: MyText
body: MyText
version: MyString
changelog: MyText
message: MyString
freezebody: MyText
state: 1
submitted: 2011-02-11
accepted: 2011-02-11
five:
id: 5
user_id: 2
title: MyString
teaser: MyText
body: MyText
version: MyString
changelog: MyText
message: MyString
freezebody: MyText
state: 2
submitted: 2011-02-11
accepted: 2011-02-11
six:
id: 6
user_id: 2
title: MyString
teaser: MyText
body: MyText
version: MyString
changelog: MyText
message: MyString
freezebody: MyText
state: 3
submitted: 2011-02-11
accepted: 2011-02-11
seven:
id: 7
user_id: 2
title: MyString
teaser: MyText
body: MyText
version: MyString
changelog: MyText
message: MyString
freezebody: MyText
state: 4
submitted: 2011-02-11
accepted: 2011-02-11
% rake
1) Failure:
test_edited_article_has_to_belong_to_current_user(ArticlesControllerTest) [... ... .../test/functional/articles_controller_test.rb:76]:
Expected response to be a <:redirect>, but was <200>
2) Failure:
test_new_article_has_to_belong_to_current_user(ArticlesControllerTest) [... ... .../test/functional/articles_controller_test.rb:66]:
Article does not belong to current user
3) Failure:
test_should_not_create_article_linked_to_other_user(ArticlesControllerTest) [... ... .../test/functional/articles_controller_test.rb:89]:
Article does not belong to current user
4) Failure:
test_should_not_destroy_article_linked_to_other_user(ArticlesControllerTest) [... ... .../test/functional/articles_controller_test.rb:133]:
Article count has changed.
"Article.count" didn't change by 0.
<7> expected but was
<6>.
5) Failure:
test_should_not_update_article_linked_to_other_user(ArticlesControllerTest) [... ... .../test/functional/articles_controller_test.rb:113]:
Expected response to be a redirect to <http://test.host/> but was a redirect to <http://test.host/articles/1>
20 tests, 32 assertions, 5 failures, 0 errors, 0 skips
失敗5つ。
articles controller の修正
@article = Article.find(params[:id])
をすべて以下に変更
@article = current_user.articles.find(params[:id])
現在ログイン中のユーザにひもづく記事だけを呼び出すようになる。
% rake
errorが出た。
6) Error:
test_should_show_article_anonymous(ArticlesControllerTest):
NoMethodError: undefined method `articles' for nil:NilClass
結局showだけ
@article = Article.find(params[:id])
のままにしておいて、 edit, update, destroy だけを変更したら通った。showは普通に考えたらcurrent_userに限定する必要はない気がするけど違うのかな?
20 tests, 35 assertions, 2 failures, 0 errors, 0 skips
new
@article = current_user.articles.new
and create
@article = current_user.articles.new(params[:article])
% rake
あとひとつ
1) Failure:
test_should_not_create_article_linked_to_other_user(ArticlesControllerTest) [... ... .../test/functional/articles_controller_test.rb:89]:
Article does not belong to current user
20 tests, 35 assertions, 1 failures, 0 errors, 0 skips
なぜか?
test "should not create article linked to other user" do
sign_in users(:user2)
post :create, :article => { :user_id => users(:user1).id, :title => 'Title', :teaser => 'Teaser', :body => 'Body' }
+ puts assigns(:article).user_id
assert assigns(:article).user_id == users(:user2).id, "Article does not belong to current user"
end
puts assigns(:article).user_idから出力されるのは「1」。つまりこの記事のuser_idはtestで指示した通りに1を持ってしまっている。もしuserがuser_idをparamに入力したらRailsはそのidをアサインする。ようになっている。mass asignment。
Railsはこのマスアサインメントの管理のために attr_accessible を使う。
article model:
app/models/article.rb
class Article < ActiveRecord::Base
belongs_to :user
+ attr_accessible :title, :teaser, :body, :version, :changelog
validates :user_id, :presence => true
validates :title, :presence => true, :length => { :maximum => 80 }
validates :teaser, :presence => true, :length => { :maximum => 500 }
validates :body, :presence => true
validates :version, :length => { :maximum => 120 }
validates :changelog, :length => { :maximum => 2000 }
validates :message, :length => { :maximum => 5000 }
validates :state, :presence => true, :numericality => true, :inclusion => { :in => 0..4 }
end
attr_accessibleではmass assignmentで有効なすべての属性?を定義する。もしパラメータがその他の属性を含む場合は、assignされない。
attr_protected という有効ではない属性を明示的に宣言するメソッドもある。がブラックリストよりもホワイトリストの方がより安全なのでお勧めしない。ホワイトは漏れがあった場合、それを追加すればいいが、ブラックの場合はすべてを追加する必要があり、なおかつ漏れがあるとセキュリティリスクが発生する。
エラー出るけどfailure 0 なら意図通りオッケー。(エラー3つ出るけどほんとにいいのか?)
1) Error:
test_should_create_article_signed_in(ArticlesControllerTest):
ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes: user_id
20 tests, 30 assertions, 0 failures, 3 errors, 0 skips
疑問点:
逆にmass assignさせてもいいケースはどんなケースなのか?
mass assignmentはどう便利なのか?
user_idとかはできるようにしとくと危ないのはわかるけど、全部できないようにしといたらどんな不便があるのか?
回答:
mass assignmentできるということはユーザが値をコントロールできるということ。
ユーザ入力を許すフィールドはattr_accessibleしてオッケーだけど、システムが振る番号や関連付けのためのuser_idなど、ユーザに任意で入力させたくない項目はmass assignの対象から外しておく、ということ。
Tuesday, April 17, 2012
devise認証やってみる(3/12)サイト共通レイアウト、メソッド、ビュー、ルートの追加 [rails3]
CommunityGuides 3/12 - Basic Layout, Adding Methods, Views and Routes
http://www.communityguides.eu/articles/3
サイト共通レイアウト、メソッド、ビュー、ルートの追加
ちょっとhamlを使ってみる。
gem 'haml'
% bundle install
application.html.erb を http://html2haml.heroku.com/ で変換してそのままペーストした。
application.html.erb を application.html.haml にリネーム。
app/asset/stylesheets/
http://dl.dropbox.com/u/56229/communityguides/download/communityguides.css
http://dl.dropbox.com/u/56229/communityguides/download/awesome-buttons.css
を保存。
application.html.hamlにcssリンク追加
= stylesheet_link_tag :comguide
= stylesheet_link_tag :button
トップページのview
app/views/articles/index.html.haml
app/views/articles/show.html.haml
css入れたら見た目が一気に本家と同じになる。。。
新しいルートの追加
aboutページを作ってみる
まずtest.
tests/functional/articles_controller_test.rb
test "should get about" do
get :about
assert_response :success
end
失敗するので、
1. route追加
2. メソッド追加
3. view作成
する。
route追加
config/routes.rb
resources :articles do
collection do
get 'about'
end
end
エラー:The action 'about' could not be found
メソッド追加
(訂正:app/controllers/application_controller.rbではなく)
app/controllers/articles_controller.rb
...
before_filter :authenticate_user!, :except => [:index, :show, :about]
...
def about
end
view作成
app/views/articles/about.html.erb
%h1 About CommunityGuides
成功。
2つ目のインデックスページ作成。
トップはおすすめ記事のみにして、2つ目にはすべての記事を表示させる。
controllerにはindex.html.hamlを共有する2つのメソッドを。
いつもどおり、テストを書く>失敗する>そしてテストをパスするコードを書く、というやり方。でやれ、と。
tests/functional/articles_controller_test.rb
test "should get all articles index" do
get :all
assert_response :success
assert_not_nil assigns(:articles)
end
failure!
Expected response to be a <:success>, but was <302>
が出るので、assert_response :redirect に変えたけどいいのか?(ダメです。追記参照。)
もうひとつ 1) Failure:
test_should_get_all_articles_index(ArticlesControllerTest) [/users/keepon/Desktop/Dropbox/lr3/comguide/test/functional/articles_controller_test.rb:63]:
9 tests, 13 assertions, 1 failures, 0 errors, 0 skips
assert_not_nil assigns(:articles) の部分でnilになってるぽいけど、これはどこのことかよくわからない。article.yml とか?
---------------------
追記
原因はarticles_controller.rbのbefore_filterでした。exceptにindexとshowだけ入っている状態だったので以下のようにまとめて記述。
before_filter :authenticate_user!, :except => [:index, :all, :show, :about]
非ログイン時に許可されていないメソッドを呼び出していたためにredirectになっていた。assert_not_nil assigns(:articles)が評価される以前に、ログインしているかしていないかの評価のところでredirectされていた。
ログインが必要なサイトでは、ログイン状況パターンも考慮したテストでなければ、ということですね。
---------------------
ナビゲーションバーにリンク追加
= link_to "All Articles", all_articles_path
= link_to "About", about_articles_path
おわり
Saturday, April 14, 2012
devise認証やってみる(2/12)MVCの流れとテスト [rails3]
CommunityGuides 2/12 - Model-View-Controller Principle, Routes and Tests
http://www.communityguides.eu/articles/2
MVCの流れ
(省略、、、。本家に図があります)
例えば、controllerのshowメソッドではこんなふうに@articleを定義
例えば、route.rbではメソッドをルーティング(?) config/routes.rb
% rake routes でroute一覧を表示できる。
など。。。
テスト(初めての)
自動生成テストの修正
deviseの認証が必要なメソッドがある
devise helpersをtest_helpers.rbに追加
test_helper.rb
config/environments/test.rb に
config.action_mailer.default_url_options = { :host => 'localhost:3000' }
test/fixtures/users.yml
testに使うダミーusers?
test/functional/articles_controller_test.rb
書いてある通りに。。。
% rake db:test:prepare
% rake
=> 7 tests, 0 assertions, 0 failures, 7 errors, 0 skips
ActiveRecord::StatementInvalid: SQLite3::SQLException: table users has no column named password_salt:
と出るのでusers.ymlからpassword_saltの部分をコメントアウト。
schema.rbでuserテーブル確認してもなかったので、deviseのバージョンによる違いかなと。。。
7 tests, 10 assertions, 0 failures, 0 errors, 0 skips
テストか。。。
MVCの流れ
(省略、、、。本家に図があります)
例えば、controllerのshowメソッドではこんなふうに@articleを定義
def show @article = Article.find(params[:id]) respond_to do |format| format.html # show.html.erb format.xml { render :xml =>@article } end end
例えば、route.rbではメソッドをルーティング(?) config/routes.rb
Communityguides::Application.routes.draw do devise_for :users resources :articles root :to =>"articles#index" end
% rake routes でroute一覧を表示できる。
articles GET /articles(.:format) articles#index #(/articlesへのGETリクエストはindexメソッドで処理される。(articles_path) ) POST /articles(.:format) articles#create #(同じパスへのPOSTリクエストはcreateメソッドにより処理。) new_article GET /articles/new(.:format) articles#new #(/articles/new へのGETリクエストはnewメソッドで処理される(new_article_path) )
など。。。
テスト(初めての)
自動生成テストの修正
deviseの認証が必要なメソッドがある
devise helpersをtest_helpers.rbに追加
test_helper.rb
class ActionController::TestCase include Devise::TestHelpers end
config/environments/test.rb に
config.action_mailer.default_url_options = { :host => 'localhost:3000' }
test/fixtures/users.yml
user1: id: 1 email: user1@communityguides.eu encrypted_password: abcdef1 #password_salt: efvfvffdv confirmed_at: <%= Time.now %> user2: id: 2 email: user2@communityguides.eu encrypted_password: abcdef2 #password_salt: hjujzjjzt confirmed_at: <%= Time.now %> user3: id: 3 email: user3@communityguides.eu encrypted_password: abcdef3 #password_salt: gheureuf confirmed_at: <%= Time.now %>
testに使うダミーusers?
test/functional/articles_controller_test.rb
書いてある通りに。。。
require 'test_helper' class ArticlesControllerTest < ActionController::TestCase setup do @article = articles(:one) end test "should get index" do get :index assert_response :success assert_not_nil assigns(:articles) end test "should get new" do sign_in users(:user2) get :new assert_response :success end test "should create article" do sign_in users(:user2) assert_diffrence('Article.count') do post :create, :article => @article.attributes end assert_redirected_to article_path(assings(:article)) end test "should show article" do get :show, :id => @article.to_param assert_response :success end test "should get edit" do sign_in users(:user2) get :edit, :id => @article.to_param assert_response :success end test "should update article" do sign_in users(:user2) put :update, :id => @article.to_param, :article => @article.attributes assert_redirected_to article_path(assigns(:article)) end test "should destroy article" do sign_in users(:user2) assert_difference('Article.count', -1) do delete :destroy, :id => @article.to_param end assert_redirected_to articles_path end end
% rake db:test:prepare
% rake
=> 7 tests, 0 assertions, 0 failures, 7 errors, 0 skips
ActiveRecord::StatementInvalid: SQLite3::SQLException: table users has no column named password_salt:
と出るのでusers.ymlからpassword_saltの部分をコメントアウト。
schema.rbでuserテーブル確認してもなかったので、deviseのバージョンによる違いかなと。。。
7 tests, 10 assertions, 0 failures, 0 errors, 0 skips
devise認証やってみる(1/12)イントロ、認証 [rails3]
CommunityGuides 1/12 - Introduction, Authentication with Devise
http://www.communityguides.eu/articles/1
に、書いてある通りにやってみる。
my 環境
ruby 1.9.2p290
Rails 3.2.0
devise (2.0.4)
tutorialサイトの方は、Rails 3.0.5, Devise 1.1.7
プロジェクトのプランニング
例の通り「コミュニティガイド」というサイトを作る
ユーザ登録したユーザは記事を書いたり、編集。
記事はコメントや評価される。
Model構造
xxxx_idの部分は他モデルの要素と連携。xxxxの部分に他モデルで使っている名前が入る。
ひとりのユーザはひとつの国に属す、ので Model Userにはcountry_idが必要というわけ。
作成開始
% rails new comguide
(本物はcommuniyguideでやってますが、自分は2回目なのでcomguideでやってます)
% cd comguide
% rails generate scaffold Article user_id:integer title:string teaser:text body:text version:string changelog:text message:string freezebody:text state:integer submitted:date accepted:date
% rake db:migrate
% rails server
Gemfileにgem 'devise'して
% bundle install
% rails generate devise:install
3つのことをやれというメッセージ
config/environments/development.rbに
“config.action_mailer.default_url_options = { :host => ‘localhost:3000’ }”
config/routes.rbに
root :to => “articles#index”
public/index.htmlを消してなければ消す。
views/layouts/application.html.erbに警告メッセージの枠を追加する、のは後回しにする
flash messages
deviseが使うメールアカウントの設定
テスト用にgmailアカウント取得。
config/environments/development.rb
以下追記
deviseのバージョンのせいか、tutorialとファイル内容が少し違う。
route追加
route.rbに
resources :articles
*resourcesはCRUD系の7つの基本ルーティングをまとめて作成する。
Userモデル作成
% rails g devise User
メアド確認と失敗ログイン制限を追加。
:confirmable, :lockable
user.rb
deviseは2.0からschema styleが変わっているらしい。
https://github.com/plataformatec/devise/wiki/How-To:-Upgrade-to-Devise-2.0-migration-schema-style
migrationファイルにも追加と書いてあるが、tutorialと違うのでcomfirmableとlockableのところのコメントアウトを全部外した。
db/migrate/….create_users.rb
% rake db:migrate
viewを編集
application.html.erb
ログイン/ログアウト/サインアップ部
フラッシュメッセージ部分
実は、ここで最初のscaffoldしてないことに気づいてこのタイミングでやりました。。。
ログインしていない時のアクションを限定
ariticles_controllerにbefore_filterを追記して、ログインしていない時にできるアクションをindexとshowに限定。
before_filter :authenticate_user!, :except => [:index, :show]
動作確認
登録、メアド確認などokだがsign_outするとエラー。
Routing Error
No route matches [GET] "/users/sign_out"
なぜならsign_outのroutesはDELETEメソッドだから、らしい。
http://stackoverflow.com/questions/6557311/no-route-matches-users-sign-out-devise-rails-3
application.html.erbのdestroy_user_session_pathのところに追加
:method => :delete
Signed out successfully.
1/12おわり。
http://www.communityguides.eu/articles/1
に、書いてある通りにやってみる。
my 環境
ruby 1.9.2p290
Rails 3.2.0
devise (2.0.4)
tutorialサイトの方は、Rails 3.0.5, Devise 1.1.7
プロジェクトのプランニング
例の通り「コミュニティガイド」というサイトを作る
ユーザ登録したユーザは記事を書いたり、編集。
記事はコメントや評価される。
・ひとつの記事は、一人のユーザに属す。一人のユーザは複数の記事を持てる ・ひとつのコメントは、一人のユーザとひとつの記事に属す。 ・ひとつの評価は、一人のユーザとひとつの記事に属す。 ・一人のユーザはひとつの国に属し、ひとつの国はたくさんのユーザを持つ。(日本語って冗長。。。)
Model構造
Model User: country_id string: name, email, weburl text: shortbio Model Article: user_id string: title, version, message text: teaser, body, changelog, freezebody integer: state date: submitted, accepted Model Comment: user_id article_id text: body Model Rating: user_id article_id integer: stars Model Country: name
xxxx_idの部分は他モデルの要素と連携。xxxxの部分に他モデルで使っている名前が入る。
ひとりのユーザはひとつの国に属す、ので Model Userにはcountry_idが必要というわけ。
作成開始
% rails new comguide
(本物はcommuniyguideでやってますが、自分は2回目なのでcomguideでやってます)
% cd comguide
% rails generate scaffold Article user_id:integer title:string teaser:text body:text version:string changelog:text message:string freezebody:text state:integer submitted:date accepted:date
% rake db:migrate
% rails server
Gemfileにgem 'devise'して
% bundle install
% rails generate devise:install
3つのことをやれというメッセージ
config/environments/development.rbに
“config.action_mailer.default_url_options = { :host => ‘localhost:3000’ }”
config/routes.rbに
root :to => “articles#index”
public/index.htmlを消してなければ消す。
views/layouts/application.html.erbに警告メッセージの枠を追加する、のは後回しにする
flash messages
deviseが使うメールアカウントの設定
テスト用にgmailアカウント取得。
config/environments/development.rb
以下追記
# Send emails via Gmail config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { :address => "smtp.gmail.com", :port => 587, :domain => 'gmail.com', :user_name => 'テスト用アカウント@gmail.com', :password => 'パスワード', :authentication => 'plain', :enable_starttls_auto => true }
deviseのバージョンのせいか、tutorialとファイル内容が少し違う。
route追加
route.rbに
resources :articles
*resourcesはCRUD系の7つの基本ルーティングをまとめて作成する。
Userモデル作成
% rails g devise User
メアド確認と失敗ログイン制限を追加。
:confirmable, :lockable
user.rb
class User < ActiveRecord::Base # Include default devise modules. Others available are: # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable, :lockable # Setup accessible (or protected) attributes for your model attr_accessible :email, :password, :password_confirmation, :remember_me end
deviseは2.0からschema styleが変わっているらしい。
https://github.com/plataformatec/devise/wiki/How-To:-Upgrade-to-Devise-2.0-migration-schema-style
migrationファイルにも追加と書いてあるが、tutorialと違うのでcomfirmableとlockableのところのコメントアウトを全部外した。
db/migrate/….create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration def change create_table(: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 t.datetime :current_sign_in_at t.datetime :last_sign_in_at t.string :current_sign_in_ip t.string :last_sign_in_ip ## Encryptable # t.string :password_salt ## 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 # Only if lock strategy is :failed_attempts t.string :unlock_token # Only if unlock strategy is :email or :both t.datetime :locked_at ## Token authenticatable # t.string :authentication_token t.timestamps end add_index :users, :email, :unique => true add_index :users, :reset_password_token, :unique => true add_index :users, :confirmation_token, :unique => true add_index :users, :unlock_token, :unique => true # add_index :users, :authentication_token, :unique => true end end
% rake db:migrate
viewを編集
application.html.erb
ログイン/ログアウト/サインアップ部
<% if user_signed_in? %>
<%= current_user.email %>
<%= link_to "My Profile", edit_user_registration_path %>
<%= link_to "Sign out", destroy_user_session_path %>
<% else %>
<%= link_to "Sign up", new_user_registration_path %>
<%= link_to "Sign in", new_user_session_path %>
<% end %>nk_to "Sign in", new_user_session_path %>
フラッシュメッセージ部分
<% flash.each do |key, value| %> <%= value %> <% end %>*hashなんですね。
実は、ここで最初のscaffoldしてないことに気づいてこのタイミングでやりました。。。
ログインしていない時のアクションを限定
ariticles_controllerにbefore_filterを追記して、ログインしていない時にできるアクションをindexとshowに限定。
before_filter :authenticate_user!, :except => [:index, :show]
動作確認
登録、メアド確認などokだがsign_outするとエラー。
Routing Error
No route matches [GET] "/users/sign_out"
なぜならsign_outのroutesはDELETEメソッドだから、らしい。
http://stackoverflow.com/questions/6557311/no-route-matches-users-sign-out-devise-rails-3
application.html.erbのdestroy_user_session_pathのところに追加
:method => :delete
Signed out successfully.
1/12おわり。
Sunday, March 25, 2012
omuniauth使ったrailsサンプルサイト作成中のメモ
omuniauthを使ったサンプルサイトを作った過程の自分用メモです。まだ途中です。
demo
http://muitter.herokuapp.com/
code
https://github.com/cieux1/muitter
omniauth
https://github.com/intridea/omniauth/wiki
ほぼこちらのサイト通りにやった。感謝。
http://blog.twiwt.org/e/c3afce
# rails app作成
% rails new muitter
#Gemfile
gem 'omniauth'
gem 'omniauth-twitter'
% bundle
# config/initializer/omniauth.rb 作成
# add application info you registered at dev.twitter.com.
# *call back URL at dev.twitter can NOT be empty.
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter,"xxxxxxxxxxxxxxxxx","xxxxxxxxxxxxxxxxxxxxxxxxxxx"
#provider :facebook,"App ID","App Secret"
end
% rails g controller sessions
# app/sessions_controller.rb
class SessionsController < ApplicationController
def callback
auth = request.env["omniauth.auth"]
user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) || User.create_with_omniauth(auth)
session[:user_id] = user.id
redirect_to root_url, :notice => "Signed in!"
end
def destroy
session[:user_id] = nil
redirect_to root_url, :notice => "Signed out!"
end
end
# routes.rb
match '/auth/:provider/callback' => 'sessions#callback'
match "/signout" => "sessions#destroy", :as => :signout
% rails g model user
# app/user.rb
class User < ActiveRecord::Base
def self.create_with_omniauth(auth)
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["info"]["name"]
user.screen_name = auth["info"]["nickname"]
end
end
end
# チュートリアルでは[user_info]になっているがtwitterAPIの仕様変更で[user]に変更になった?
https://github.com/intridea/omniauth/issues/249#issuecomment-3229038
# user_infoだとauth/failure のcallback routeが見つからないとでるので変更したら動いた。
# db/migrate/XXXXX_create_users.rb
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :provider, :null => false
t.string :uid, :null => false
t.string :screen_name, :null => false, :uniq => true
t.string :name, :null => false
t.timestamps
end
add_index :users, [:provider, :uid]
add_index :users, [:screen_name]
end
def self.down
drop_table :users
end
end
% rake db:migrate
# app/controllers/application_controller.rb
# add helper method
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
private
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
% rails g scaffold Mutter user_id:integer mutterbody:text
# app/views/layouts/application.html.erb
# add login/show twiiter ID part to basic template erb.
<% if current_user %>
Welcome <%= current_user.name %>!
<%= link_to "Sign Out", signout_path %>
<% else %>
<%= link_to "Sign in with Twitter", "/auth/twitter" %>
<% end %>
# route.rb
# set mutter/index as root.
root :to => "mutters#index"
# model/mutter.rb
belongs_to :user
# model/user.rb
has_many :mutters
# _form.html.erb
# ログイン中のuserのidをmutterのuser_idにひもづける処理。(なかなかわからなかった)
<%= f.number_field :user_id, :type => 'hidden', :value => current_user.id %>
このへん省略というか忘れた。。。というかなんで英語で書いてたのか。。。
okinawa rubyの毎週meetupでomniauthが入った時点?でUser modelに必要なtableが作成されていてあとは使うだけ、という状態になっていることを教えてもらい道が開けた感。
#rails consoleでデータ呼び出しフォーマットの確認
% rails c
Mutter.all
User.find_by_id(1)
これで例えば、userとmutterをひもづけているmutterのuser_idがnilのままじゃん、とかが確認できた。
view / erb
#ログイン時とログインしていない時の見た目の切り替え
if current_user
# 自分の書き込みかどうかの判定
<% if current_user && mutter.user.screen_name == current_user.screen_name %>
...
<% end %>
#mutters_controller.rb
# ログインしているかどうかの判定
http://guides.rubyonrails.org/action_controller_overview.html
# before_filter ログインしていないと以下の操作ができないように。
before_filter :require_login, :only => [:new, :create, :edit, :create, :update, :destroy]
...
...
private
def require_login
unless logged_in?
#flash[:error] = "You must be logged in to create/update a mutter."
redirect_to root_url, :notice => 'You must sign in to create/update a mutter.'
end
end
def logged_in?
!!current_user
end
need to be fixed!!!!!!!!!!!!!!!!!!
ログイン時、urlのid指定したら他の人の書き込みが触れてしまう。。。
どこでやるのかな。。これ。
heroku に deploy
#ssh keyの設定
github のssh key と herokuの ssh keyは同一である必要。
2つの端末で同じアカウントからpushしていることはどーなんだ?
ssh keyは端末のID的なもの?privateとpublicのペアができるので、public keyをサーバ側に保存する。
public keyは ~/.ssh/xxx_rsa.pub
レポジトリを登録してそこにpushする。
レポジトリは複数登録できる。
git add remote ....
一覧を見るには
git remote -v
# herokuはsqlite3ではなく、PostgreSQL
# Gemfileを修正
http://railsapps.github.com/rails-heroku-tutorial.html
group :development, :test do
gem 'sqlite3'
end
group :production do
gem 'pg'
end
# rails serverをthinに変更。webrickは頼りない?
webrickはlocalのままに残すなら
group :production do
gem 'thin'
end
# herokuのstackによってrailsやrubyのバージョンが違う。
最新環境は cedar (beta)で、rails3.2の場合はこれでないと動かない?
heroku createする時に指定しなければならない。
http://devcenter.heroku.com/articles/cedar
http://devcenter.heroku.com/articles/cedar-migration
$ heroku create --stack cedar --remote heroku-cedar app名
$ git push heroku-cedar HEAD:master
なぜかHEADにしないとエラー。stackを[変更]するほうのtutorialを参考にしてたからか?
error: src refspec cedar does not match any.
error: failed to push some refs to 'git@heroku.com:app名.git'
http://stackoverflow.com/questions/4181861/src-refspec-master-does-not-match-any-when-pushing-commits-in-git
#gitignoreの設定
gitignoreは設定されるとそのファイルが gitから無視される。
設定される前に上がったものは基本的には消せない。
global_gitignore
globalは端末全体に設定される。
http://d.hatena.ne.jp/passingloop/20110903/p1
~/.global_gitignoreというファイルを作って記述
git config --global core.excludesfile ~/.global_gitignore で有効化。
例多数) https://github.com/github/gitignore/tree/master/Global
# 一度pushしてしまったファイルを消すのは大変&基本やってはいけない。
http://help.github.com/remove-sensitive-data/
git filter-branch
$ git filter-branch --tree-filter 'rm -f ファイル名' HEAD
$ git filter-branch --tree-filter 'rm -f omniauth.rb' HEAD
historyが書き変わるのでcloneされていたらおかしくなる?
# db:migrate
% heroku run rake db:migrate
成功するとlocalの時と同じようなログが出る。
% heroku restart
再起動で反映
% heroku run rakd db:reset
で元に戻す。
#pageを開く
% heroku open
# 修正の流れ
修正
git add app/asset/stylesheets/application.css
git commit -m 'adjusted font size.'
git push heroku-cedar HEAD:master
の繰り返し。
# 気になること
stack変えるために新規でheroku createしたので、githubにpushする方法がよくわからなくなった模様。。。
Subscribe to:
Posts (Atom)