railstutorial.jp 1章メモ
$ bundle install --without production
$ heroku login
$ heroku create
Creating xxxxxx-xxx-0000... done, stack is cedar
http://xxxxxx-xxx-0000.herokuapp.com/ | git@heroku.com:xxxxxx-xxx-0000.git
Git remote heroku added
$ git remote -v github git@github.com:violetyk/first_app.git (fetch) github git@github.com:violetyk/first_app.git (push) heroku git@heroku.com:xxxxxx-xxx-0000.git (fetch) heroku git@heroku.com:xxxxxx-xxx-0000.git (push)
$ git push heroku master
$ heroku open Missing `secret_key_base` for 'production' environment, set this value in `config/secrets.yml`
$ rake secret
これを貼り付けるのではなく、herokuの環境に設定する。 https://devcenter.heroku.com/changelog-items/426
$ heroku config:set SECRET_KEY_BASE=`rake secret`
確認
$ heroku config
$ heroku restart
$ heroku run rake db:migrate
config/secret.ymlが.gitignoreに入ってたからっぽい
railstutorial.jp 2章メモ
- テーマ
- scaffolding
- MVC
- rake
- REST
$ mkdir demo_app $ cd demo_app $ rails new . --skip-bundle
Gemfileと.envrcは前章のをコピー
$ bundle install --path=vendor/bundle --binstubs --without production $ bundle config --delete bin $ rake rails:update:bin
Userリソースの作成
$ rails generate scaffold User name:string email:string
rakeの確認
$ rake -T $ rake -T db
REpresentational State Transfer (REST)
Micropostsリソースの作成
$ rails generate scaffold Micropost content:string user_id:integer
$ rake db migrate
スキーマ確認
$ rails db
sqlite> .schema
バリデーションの設定
User has_many Micropost Micropost belongs_to User
$ rails console > first_user = User.first > first_user.microposts > exit
railstutorial.jp 3章メモ
- テーマ
- rails generate
- erb
- 振舞駆動開発 (Behavior-Driven Development, BDD)
- Guard
--ski-test-unit
は、Test::Unitを使うのではなくRspecを使う為にtestディレクトリを作成しないオプション。
$ rails new sample_app --skip-bundle --skip-test-unit $ bundle install --path=vendor/bundle --binstubs --without production
--without production
はremembered option。一度実行するとコマンドに保存される。
$ bundle config --delete bin
$ rake rails:update:bin
.secretを.gitignoreに追加
$ rails generate rspec:install
$ heroku create $ git push heroku master $ heroku config:set SECRET_KEY_BASE=`rake secret` $ heroku run rake db:migrate $ heroku restart $ heroku open
ログが確認できる
$ heroku logs
静的ページ用のコントローラをつくる
テストは後から手動で作る為ここではつくらない
$ rails generate controller StaticPages home help --no-test-framework
underscoreメソッドを使ってキャメルケースからスネークケースに変換している
http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-underscore
rails generateはrails destroyで元に戻せる
$ rails generate controller FooBars baz quux $ rails destroy controller FooBars baz quux
マイグレーションを戻す方法
$ rake db:migrate $ rake db:rollback #一つ戻す $ rake db:migrate VERSION=0 # 最初に戻す
- アプリケーションの振る舞いをテストしながら実装する直感的手法
- 振る舞い駆動開発(Behavior Driven Development, BDD)
- TDDから派生
- 結合テスト integration test (RSpecではリクエストspecと呼ぶ)
- 単体テスト unit test
静的ページの振る舞いテストを生成
$ rails generate integration_test static_pages
spec/requests/static_pages_spec.rbが作成される
RSpec はダブルクォート (") で囲まれた文字列を無視する
page
変数はCapybaraが提供している
spec/spec_helper.rbにCapybara DSLを追加
RSpec.configure do |config| config.include Capybara::DSL end
テスト実行
$ rspec spec/requests/static_pages_spec.rb sample_app/spec/spec_helper.rb:93:in `block in <top (required)>': uninitialized constant Capybara (NameError)
エラー。
spec/rails_helper.rbのまねして、spec_helper.rbにも以下を書いたら動いた
ENV["RAILS_ENV"] ||= 'test' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails'
have_titleメソッドを使って<title>
要素をチェック
Don’t Repeat Yourself, DRY原則
<% provide(:title, 'Home') %> <%= yield(:title) %>
レイアウトファイル application.html.erb
RSpecのlet関数でテスト内変数を定義
Guardでファイル変更を感知してテストの実行を自動化する
gem 'guard-rspec’
$ bundle install $ bundle exec guard init rspec
生成されたGuardfileにrequire追加
テストが失敗したらそこで終了する
# guard :rspec, cmd: 'bundle exec rspec' do guard :rspec, all_after_pass: false, cmd: 'bundle exec rspec’ do end
$ bundle exec guard
Sportを使ったテストの高速化
プリローダー。今はspringなのか?
railstutorial.jp 4章メモ
rails console
コメント
文字列
式展開 #{first_name}はダブルクォートのなかのみ
putsはput string
nil.to_s.empty? “".nil? nil.nil?
Rubyにおいてifがfalseになるのはfalseとnilのみ
if nil then true else false end #=> false if 0 then true else false end #=> true
Rubyは最後の評価値が自動的に返される
moduleはクラスにincludeすることでmixed inできる
ヘルパーモジュールの場合はRailsが自動的にインクルードするので全ビューで利用可能
配列
a = [ 42, 8, 17] a.first a.second a.last == a[-1] #=> true a.length a.push(6) a << 7 a << "foo" << “bar" a.join a.join(‘,’)
Range
0..9 (0..9).to_a
%w
a = %w[foo bar baz quux] a[0..2] # インデックスの範囲指定
区切り文字は何でもいいぽい
a = %w|foo bar baz quux| a = %w@foo bar baz quux@
ブロック
(1..5).each { |i| puts 2 * i } (1..5).each do |i| puts 2 * i end 3.times { puts "Betelgeuse!" } (1..5).map { |i| i**2 } # **はべき乗
('a'..'z').to_a.shuffle[0..7].join # アルファベットの配列をシャッフルして最初の8要素を取り出して連結 ('a'..'z’) ('a'..'z’).to_a ('a'..'z’).to_a.shuffle ('a'..'z’).to_a.shuffle[0..7] ('a'..'z’).to_a.shuffle[0..7].join
ハッシュとシンボル
user = { "first_name" => "Michael", "last_name" => "Hartl” }
ハッシュの要素の順番は1.9以降保証されるようになった
ハッシュロケット =>
ハッシュのキーはシンボルを使うのが一般的
user = { :name => "Michael Hartl", :email => "michael@example.com” } # Ruby1.9 以降は、新しい記法でJavaScriptぽく書ける { name: "Michael Hartl", email: "michael@example.com” }
オブジェクトを文字列にするinspectメソッド便利メソッド
puts :name, :name.inspect, "name”.inspect
pは putsとinspectを合わせた効果。phpでいうvar_dumpやprint_r
省略された記法のルールと読み方
関数呼び出しの丸括弧は省略可能
stylesheet_link_tag("application", media: "all", "data-turbolinks-track" => true) stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true
最後の引数がハッシュの場合、波括弧は省略可能
# data-turbolinks-track: trueになっていないのは、新しいハッシュ記法ではハイフンが使えないため。 stylesheet_link_tag "application", { media: "all", "data-turbolinks-track" => true } stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true
コンストラクタ
文字列
s = “foobar”
s.class
s = String.new(“foobar”)
配列
a = [1,3,2] a = Array.new([1, 3, 2])
ハッシュだけコンストラクタの引数がデフォルト値になる
h = {} h = Hash.new h[:hoge] # => nil 存在しないキーはnil h = hash.new(0) h[:hoge] # => 0 存在しないキーのデフォルト値になる
クラス
superclassメソッド
Ruby1.9以降、ObjectクラスのスーパークラスとしてBasicObjectがある
Rubyは定義済みのクラスの拡張ができる(オープンクラス)
たとえばRailsではblank?メソッドが追加されていて、Railsコンソールではその拡張も読み込まれる
Railsコンソールでコントローラのアクションも呼べる
controller = StaticPagesController.new
controller.home
require ‘./example_user'
attr_accessor
インスタンス変数 @name
initializeはnewを実行するときに呼ばれるメソッド
ハッシュ引数を使用してオブジェクトを初期化する手法、マスアサインメント(mass assignment)
railstutorial.jp 5章メモ
Gem追加
gem 'bootstrap-sass' gem ‘sprockets'
config/application.rbに追加
config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif)
Bootstrap CSSを追加
app/assets/stylesheets/custom.css.scss @import "bootstrap”;
partialに切り出す
パーシャルファイルは先頭アンダースコアから始まる
<%= render 'layouts/shim' %> app/views/layouts/_shim.html.erb
Asset Pipeline
アセットディレクトリ、マニフェストファイル、プリプロセッサエンジン
アセットディレクトリ
Rails3.0 以前の静的ファイルの置き場所(以降でも使える)
- public/stylesheets
- public/javascripts
- public/images
Rails3.1以降は静的ファイルを目的別に分類する
- app/assets: 現在のアプリケーション固有のアセット
- lib/assets: あなたの開発チームによって作成されたライブラリ用のアセット
- vendor/assets: サードパーティのアセット
マニフェストファイル
どのように1つにまとめるかの設定
Sprockets gemが実際にまとめる(css, jsのみ、画像は除く)
app/assets/stylesheets/application.css
app/assets/stylesheetsディレクトリ配下全CSSをアプリケーションCSSに含む
*= require_tree .
application.css自身も含む
*= require_self
プリプロセッサエンジン
ファイルの拡張子で判断される
foobar.js.erb.coffeeだと、Coffeeスクリプト→ERbと実行されてjsが作成される
Sass
- $で変数が使える
- 継承をネストでまとめられる
- 同一のネームスペースを持つプロパティ(font-とか)はネストできる
- @importでCSSを1個にまとめられる
- まとまったスタイルを変数のようにパッケージしておけるミックスイン機能
- @mixin(=)で定義して、@include(+)で呼び出す
- ミックスインは関数のように変数を引数に使うことができる
- 値の加減乗除ができる
- pxとinなど、別の単位同士でも演算できる
- 条件分岐とループ処理ができる@if @for @each @while
- 可読率と圧縮率がコンパイルオプションで帰られる :nested, :expanded, :compact, :compressed
親属性の参照は&
#logo { &:hover { } }
bootstrap-sassはSassでもLESS変数が使える
ルーティング
match '/about', to: 'static_pages#about', via: ‘get'
rake routesで確認できるPrefix+path, url
about_path -> '/about'
about_url -> 'http://localhost:3000/about'
Rspecの洗練
beforeブロック( before(:each) )
subject { page }
Rspecのユーティリティspec/support/utilities.rb
spec/supportディレクトリはRSpecによって自動的に読み込まれる
full_titieがないってエラーになった
Failure/Error: it { should have_title(full_title('')) } NoMethodError: undefined method `full_title' for #<RSpec::ExampleGroups::StaticPages::HomePage:0x007fa3aa6ddc60>
static_pages_spec.rbの最初に以下入れたら動いた
include ApplicationHelper
Usersコントローラの追加
コントローラは複数系
$ rails g controller Users new --no-test-framework
ふるまいのテスト追加
$ rails generate integration_test user_pages
$ rspec spec/requests/user_pages_spec.rb
ディレクトリを指定してテスト可能
$ rspec spec/requests/ $ rspec spec/
Rakeタスクでテスト全実行もできる
$ rake spec
さらにrakeのデフォルトはテスト実行なので、rakeだけでOK
$ rake
railstutorial.jp 6章メモ
テーマ
- ユーザ登録
- モデル
- バリデーション
認証と権限のモジュールはたくさんあるけどここでは自前で作る
- Clearance
- Authlogic
- Device
- Cancan
モデルは単数形で指定
$ rails generate model User name:string email:string
t.timestampsはマジックカラム
マイグレーションアップ/ダウン
$ rake db:migrate $ rake db:rollback $ rails db .schema
サンドボックスモードでのDB変更は終了時に全てロールバックされる
$ rails console —sandbox
検索
User.find(3) User.find_by_email("mhartl@example.com") User.first User.all
リロード
user.email user.email = "foo@bar.com” user.reload.email
更新
user.save user.update_attribute(:name, "The Dude")
テスト用データベースの作成
$ rake db:migrate $ rake test:prepare rake aborted! Don't know how to build task 'test:prepare'
エラー。Rails4.1で無くなったらしい。代替コマンドを実行
$ rake db:migrate RAILS_ENV=test
テストでつかわれているrespond_toはRubyのrespond_to?
シンボルが表すメソッドまたは属性に対して応答する場合はtrue、応答しない場合はfalse
要はあるかどうか、のテスト
バリデーション
validates :name, presence: true
user.save #=> false user.valid? #=> false
エラー取得
user.errors user.errors.full_messages
be_fooはfoo?を実行しているのと同じ
it { should be_valid }
バリデーション結果が正しくないこと
it { should_not be_valid }
文字×数字は文字数分連結と同じ
@user.name = "a" * 51
文字数チェックのバリデーション
validates :name, presence: true, length: { maximum: 50 }
emailのバリデーション
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i.freeze validates :email, presence: true, format: {with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive:false }
uniqueness: { case_sensitive:false }
で大小文字を無視してユニークチェックをする
\Aは文字列の先頭
\zは文字列の末日
正規表現エディタ http://www.rubular.com/
dupメソッドで、id, updated_at, created_atを空にした状態のコピーを作る
@user.dup
String.upcaseメソッド
インデックスを追加するマイグレーション
$ rails generate migration add_index_to_users_email
def change add_index :users, :email, unique: true end
$ rake db:migrate
失敗するときはrails console —sandboxによってDBをロックしていないかを確認
モデルのコールバック
before_save
パスワードを暗号化するgem
gem bcrypt-ruby
モデルにhas_secure_passwordを書いてデータベースにpassword_digestカラムを追加しただけで以下全部やってくれる
- password、password_confirmationの追加
- パスワードの存在の要求
- パスワードの一致の要求
- 認証のためのauthenticateメソッドの提供
_to_usersとすると、usersテーブルにカラム追加するマイグレーションになる
$ rails generate migration add_password_digest_to_users password_digest:string
add_column :users, :password_digest, :string
仮想カラムpassword、password_confirmationを追加
リスト6.25ではパスワードの空欄チェックの時に与えているpasswordとpassword_confirmationには半角スペースが入っているぽいけどそれだとテストが通らなかった。
letメソッド 変数の作成と変数への値をセット
let(:found_user) { User.find_by(email: @user.email) }
eqメソッド オブジェクト同士が同値であるか
it { should eq found_user.authenticate(@user.password) }
specifyはitと同義 英語として不自然な場合の代替
it { should_not eq user_for_invalid_password } specify { expect(user_for_invalid_password).to be_false }
be_falseのテストが通らない。Rspec3から利用できなくなったらしい。
http://qiita.com/yujinakayama/items/a1d31b2caa35642e8e69
be_trueは、その名前にもかかわらず、テスト対象が真(false, nil以外)であればパスしており、trueとの同一性はテストされていなかった。 be_falseも同様に、テスト対象が偽(false, nil)であればパスしており、falseとの同一性はテストされていなかった。 といったように、名前と挙動が一致していませんでした。
なので、be_falseのように曖昧なままであればbe_falsey、厳格に比較するなら be falseをつかう。
be はequalマッチャへのalias。
パスワードの長さバリデーション
validates :password, length: { minimum: 6 }
authenticateメソッド
パスワードが合っていないときにはfalseを返す
パスワードが合っているときにはUser自身を返す
railstutorial.jp 7章メモ
- テーマ
- フォーム
paramsでデバッグ
<%= debug(params) if Rails.env.development? %>
- 環境
- test
- development
- production (Herokuは常にコレ)
パラメータを指定して起動
$ rails console test $ rails server --environment production $ rake db:migrate RAILS_ENV=production
Herokuのリモートコンソール
$ heroku run console
RESTfulなリソースの設定 config/routes.rb
resources :users
params[:id]に/users/1の1が入ってくる
params[:id]は文字列だが、findに渡すと数値に変換される
def show @user = User.find(params[:id]) end
factorygirlのインストール。test環境のみ。
group :test do gem 'factory_girl_rails' end
$ bundle install
spec/factories.rb
FactoryGirl.define do factory :user do name "Michael Hartl" email "michael@example.com" password "foobar" password_confirmation "foobar" end end
テストの中で、上で定義した:userが使用できるようになる
let(:user) { FactoryGirl.create(:user) }
genarateもできた
specやtest配下は自動的にロードされるのかな?
$ rails generate factory_girl:model User
create test/factories/users.rb
specやtest配下は自動的にロードされるのかな?
config/environments/test.rb
にtestの環境変数が設定できる
ここではtestの実行速度を速くしたいのでbcryptのコスト関数を下げる
ActiveModel::SecurePassword.min_cost = true
Gravatarの利用
GravatarのURL(https://secure.gravatar.com/avatar/ID)はMD5ハッシュを用いてユーザーのメールアドレスをハッシュ化している
Digest::MD5::hexdigest(email.downcase)
モデルのupdate_attributesメソッドで一部データの更新
rails c user = User.first user.update_attributes(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar")
dbの変更を全てリセットしてやり直す
rake db:reset
Capybaraの文法
visit signup_path fill_in "Name", with: "Example User" fill_in "Email", with: "user@example.com" fill_in "Password", with: "foobar" fill_in "Confirmation", with: "foobar" click_button "Create my account"
件数の取得
User.count
Rspecのメソッド - expectへのブロックを渡し - to/not_to - change
expect { click_button "Create my account" }.not_to change(User, :count)
これは次と同じ
initial = User.count click_button "Create my account" final = User.count expect(initial).to eq final
フォームの作成 form_forヘルパーメソッド
# @userはUserオブジェクト <%= form_for(@user) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.text_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account", class: "btn btn-large btn-primary" %> <% end %>
rspecでdescriptionに対応するテストを実行する
ネストしたdescribeの引数文字列は連結するっぽい
$ rspec spec/requests/user_pages_spec.rb -e "signup page"
signup_pathがないというエラーが出る。。。
Failures: 1) User pages signup page Failure/Error: before { visit signup_path } NameError: undefined local variable or method `signup_path' for #<RSpec::ExampleGroups::UserPages::SignupPage:0x007ffbd0fdac18> # ./spec/requests/user_pages_spec.rb:32:in `block (3 levels) in <top (required)>' 2) User pages signup page Failure/Error: before { visit signup_path } NameError: undefined local variable or method `signup_path' for #<RSpec::ExampleGroups::UserPages::SignupPage:0x007ffbd0f94a60> # ./spec/requests/user_pages_spec.rb:32:in `block (3 levels) in <top (required)>'
spec/requests/user_pages_spec.rbに以下書いたら動いた。
require 'rails_helper' include ApplicationHelper
フォームの値はparamsとしてControllerに渡る
user[name] や user[email] -> params[:user]
strong parameters機能
このままPOSTするとActiveModel::ForbiddenAttributesError in UsersController#create となる
マスアサインメントの脆弱性
@user = User.new(params[:user])
は以前のRailsで動いた。user[admin]='1'とかを強制的に渡されると危険。
Rails4からは、paramsをそのまま渡すとエラー
Controllerでstrong parametersの利用を推奨
params.require(:user).permit(:name, :email, :password, :password_confirmation)
コントローラ間で共通のパーシャル
app/views/shared/に置く
user.errors.count user.errors.empty? user.errors.any?
include ActionView::Helpers::TextHelper pluralize(5, "error")
redirect_to でリダイレクト
# user_urlへリダイレクトすることと同様 redirect_to @user
エラーでrspecが通らない。謎。
Failure/Error: let(:user) { FactoryGirl.create(:user) } ActiveRecord::RecordInvalid: Validation failed: Email has already been taken
test:prepareしなさいと言われたのでやってみたがないらいしい。
$ rake test:prepare rake aborted! Don't know how to build task 'test:prepare' (See full trace by running task with --trace)
db:test:prepareもRails4.1からdeprecatedらしい。
$ rake db:test:prepare WARNING: db:test:prepare is deprecated. The Rails test helper now maintains your test schema automatically, see the release notes for details.
以下でやっとテストが通った
$ rake db:create db:migrate RAILS_ENV=test $ rake spec
特殊な変数flash
flash[:success] = "Welcome to the Sample App!"
Railsでは自動的にシンボルを文字列に変換
してテンプレートに渡すようになっている
本番でSSLをつかう config/environments/production.rb
config.force_ssl = true
railstutorial.jp 8章メモ
セッションコントローラの作成
$ rails generate controller Sessions --no-test-framework
$ rails generate integration_test authentication_pages
限定したリソースのルーティング
resources :sessions, only: [:new, :create, :destroy]
match '/signin', to: 'sessions#new', via: 'get' match '/signout', to: 'sessions#destroy', via: 'delete'
Capybara 特定のHTMLセレクタ要素(とテキスト)があるかのテスト
it { should have_selector('div.alert.alert-error', text: 'Invalid') }
Capybara 特定のHTMLリンクの有無テスト
it { should have_link('Profile', href: user_path(user)) } it { should have_link('Sign out', href: signout_path) } it { should_not have_link('Sign in', href: signin_path) }
モデル指定有無によるform_forヘルパーの書き方の違い
form_for(@user) form_for(:session, url: sessions_path)
flash
メッセージの残留
リダイレクトのときとは異なり、render
で表示したテンプレートを再描画してもリクエストと見なされない
ログイン失敗後に他のページを表示してもflashメッセージが残る
flash.now
を使う
flash.now[:error] = 'Invalid email/password combination'
すべてのヘルパーは、なにもせずともすべてのビューで使用可能だがControllerで使う場合には明示的にincludeする必要がある
include SessionsHelper
Railsセッションについて
セッションごとに生成される特別のセッションidによって不一致を検出するのでユーザIDのなりすましはできない
session[:remember_token] = user.id User.find(session[:remember_token])
ブラウザにこのbase64トークンを保存しておき、データベースにはトークンを暗号化したものを保存する方法でセッションを実装する
usersテーブルにremember_tokenを追加
$ rails generate migration add_remember_token_to_users
add_column :users, :remember_token, :string add_index :users, :remember_token
$ rake db:migrate $ rake db:migrate RAILS_ENV=test
itsメソッドはitのようにsubjectを指すのではなく、引数を指すときに使用する
# 両方同じ内容 its(:remember_token) { should_not be_blank } it { expect(@user.remember_token).not_to be_blank }
itsメソッドはもうないらしい。。。
undefined method `its' for RSpec::ExampleGroups::User::RememberToken:Class (NoMethodError)
rspec-its (https://github.com/rspec/rspec-its)に分けられたらしいので追加して解決
gem 'rspec-rails' gem 'rspec-its'
コールバックはメソッド参照で。
before_create :create_remember_token
クラスメソッド
privateキーワード以降はprivateメソッド。一段インデントを下げたほうがいい。
class User < ActiveRecord::Base before_save { self.email = email.downcase } before_create :create_remember_token def User.new_remember_token SecureRandom.urlsafe_base64 end def User.encrypt(token) Digest::SHA1.hexdigest(token.to_s) end private def create_remember_token self.remember_token = User.encrypt(User.new_remember_token) end end
Railsが提供するCookieユーティリティについて
valueとexpires(オプション)のハッシュとして扱うことができる
cookies[:remember_token] = { value: remember_token, expires: 20.years.from_now.utc } # expiresは20年後、という指定がよく使われるので、permanentというメソッドが存在する # 上のコードと同じ。 cookies.permanent[:remember_token] = remember_token
なんでpermanentはメソッドなのにハッシュ風に扱うんだろうか、、、?
20.years.from_now.utc
について
timeヘルパーはRailsによって数字のFixnumクラスに追加されている
> 10.year.from_now => Sun, 13 Oct 2024 06:08:17 UTC +00:00 > 10.weeks.ago => Mon, 04 Aug 2014 06:08:20 UTC +00:00 > 1.kilobyte => 1024 > 5.megabyte => 5242880
def sign_in(user) ... self.current_user = user end # 要素代入を定義しているcurrent_user=という関数。引数はuser。 def current_user=(user) @current_user = user end def current_user # 暗号化して、usersテーブルのremember_tokenと比べる remember_token = User.encrypt(cookies[:remember_token]) # ||= ("or equals") 代入演算子。nilガード。 # 「x = x ● y」と「x ●= 」は同じ。●が+や-と同様に、||になっているだけ。 # かつ||は左辺が真だとその場で評価終了なので、それを利用して # @current_userが未定義の場合のみ、右辺がコールされて代入される。 # つまりfind_byの実行は1度のみ。 @current_user ||= User.find_by(remember_token: remember_token) end # ログインしている = @current_userがnilでない かどうか def signed_in? !current_user.nil? end def sign_out self.current_user = nil cookies.delete(:remember_token) end
ハッシュを引数として、HTTPのDELETEリクエストを送信する
<%= link_to "Sign out", signout_path, method: "delete" %>
Railsではユーザへの直接リンクが許可されるので、以下2つは同じ
<%= link_to "Profile", current_user %> <%= link_to "Profile", user_path(current_user) %>
bootstrap-sass gemが入っているので、bootstrapのjsが使える
app/assets/javascripts/application.js
//= require jquery //= require jquery_ujs //= require bootstrap //= require turbolinks //= require_tree .
cookieの削除
cookies.delete(:remember_token)
Cucumber
高度な振る舞いのテストに使う
内部的にRspecとCapybaraを使っている
Gherkinと呼ばれる言語でテストを書く
gem
gem 'cucumber-rails', '1.4.0', :require => false gem 'database_cleaner', github: 'bmabey/database_cleaner'
ディレクトリとファイルの作成
$ rails generate cucumber:install
features/signing_in.featureにテストのシナリオを書き、 features/step_definitions/authentication_steps.rb にそれをパスするステップ定義を書く
$ bundle exec cucumber features/
もしくは
$ rake cucumber
ヘルパーメソッドとカスタムマッチャの追加
spec/support/utilities.rb
include ApplicationHelper def valid_signin(user) fill_in "Email", with: user.email fill_in "Password", with: user.password click_button "Sign in" end RSpec::Matchers.define :have_error_message do |message| match do |page| expect(page).to have_selector('div.alert.alert-error', text: message) end end
railstutorial.jp 9章メモ
- テーマ
- ユーザーの更新・表示・削除
- 認証と認可
- フレンドリーフォワーディング
- ページング機能
webブラウザからPATHリクエストができないので、POSTのhiddenパラメータ_method
にpatch
が入っている
<form accept-charset="UTF-8" action="/users/2" class="edit_user" id="edit_user_2" method="post"> <input name="_method" type="hidden" value="patch" />
Active Recordのnew_record?メソッド
Railsは、form_for(@user)を使用してフォームを構成すると、
@user.new_record?
がtrueのときにはPOSTを、falseのときにはPATCHを使用します。
spec/support/utilities.rb
sign_inメソッド
no_capyabara: trueオプション
変更を保存した後に@user.reloadメソッドを使ってDBから再読込した値とチェック
specify { expect(user.reload.name).to eq new_name } specify { expect(user.reload.email).to eq new_email }
utilities.rbにsign_inメソッド足してからテストが通らなくなった。謎。
Failure/Error: sign_in user NoMethodError:
user_pages_spec.rbとauthentication_pages_spec.rbに以下を追記したらテストが通った。謎。
include 'spec/support/utilities.rb'
Capybaraではeditのアクションはテストできても、updateのアクションはテストできない。
なので、Capybaraでpatchリクエスト(更新)を直接実行しテストする。(get, post, deleteなども同様にメソッドがある。)
response
オブジェクトが返ってくる。
describe "submitting to the update action" do before { patch user_path(user) } specify { expect(response).to redirect_to(signin_path) } end
before_action
before_action :signed_in_user, only: [:edit, :update]
redirect_toの引数にハッシュを渡すとflashの設定になる。
flashは:notice、:success、:errorの3種類が設定できる。
redirect_to signin_url, notice: "Please sign in." unless signed_in? # 上と同じ unless signed_in? flash[:notice] = "Please sign in." redirect_to signin_url end
FactoryGirlで生成したユーザ情報を一部更新するには第2引数にハッシュ
let(:user) { FactoryGirl.create(:user) } let(:wrong_user) { FactoryGirl.create(:user, email: "wrong@example.com") }
フレンドリーフォワーディング
ログイン前に見ようとしていたページへログイン後に遷移させる機能
request
オブジェクト
def redirect_back_or(default) redirect_to(session[:return_to] || default) # セッションから削除 session.delete(:return_to) end def store_location # リクエストのURLをrequestオブジェクトから取得してセッションへ保存 session[:return_to] = request.url end
Railsで予約されているオブジェクト名がいままでいろいろでてきた
- params
- request
- response
- flash
- cookies
- session
gravatar_forでエラーが出る。。。
1) User pages index Failure/Error: visit users_path ActionView::Template::Error: wrong number of arguments (2 for 1) # ./app/helpers/users_helper.rb:4:in `gravatar_for'
UsersHelper::gravatar_forの引数にデフォルトの値を与えるように修正して動いた。
module UsersHelper # 与えられたユーザーのGravatar (http://gravatar.com/) を返す。 # def gravatar_for(user) # gravatar_id = Digest::MD5::hexdigest(user.email.downcase) # gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}" # image_tag(gravatar_url, alt: user.name, class: "gravatar") # end def gravatar_for(user, options={size:50}) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) size = options[:size] gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end end
Faker gemでダミーのユーザデータをrakeタスクで生成する
gem 'faker'
lib/tasks/sample_data.rake
以下コマンドでもgenerateできた
$ rails g task sample_data
db:populateタスク(:dbはただの名前空間)
task populate: :environment do
はRakeタスクがUserモデルなどのローカルのRails環境にアクセスできるようにする
User.create!は失敗したときにfalseではなく例外を発生させる
99.times do |n| end
Faker::Name.nameでダミーデータを作成する
$ rake db:reset
$ rake db:populate
$ rake test:prepare
タスク実行に成功したが、データは古いままだった。
いったんrails sをおとしてからもう一度ログインし直したら、データが新しくなった。謎。
ページネーションの実装
gem 'will_paginate' gem 'bootstrap-will_paginate'
FactoryGirlのsequenceメソッドで連続データを作る
factory :user do sequence(:name) { |n| "Person #{n}" } sequence(:email) { |n| "person_#{n}@example.com"}
timesのブロック引数にFactoryGirl.createが与えられると、sequesceでインクリメントされる
before(:all) { 30.times { FactoryGirl.create(:user) } } after(:all) { User.delete_all }
before(:each)
ブロックのテストに対して1回づつ?
before(:all)
ブロックにあるすべてのテストの前に一括実行
after(:all)
ブロックにある全てのテストの後に一括実行
will_paginate
usersビューのコードの中から@usersオブジェクトを自動的に見つけ出し、 それから他のページにアクセスするためのページネーションリンクを作成
<%= will_paginate %>
rails consoleでpaginateメソッドをやってみると、デフォルト30件ずつ取得することがわかる。
nilの場合は最初の1ページ目
> User.paginate(page: 1) User Load (2.1ms) SELECT "users".* FROM "users" LIMIT 30 OFFSET 0 > User.paginate(page: 2) User Load (0.5ms) SELECT "users".* FROM "users" LIMIT 30 OFFSET 30 > User.paginate(page: nil) User Load (0.4ms) SELECT "users".* FROM "users" LIMIT 30 OFFSET 0
def index @users = User.paginate(page: params[:page]) end
パーシャルにUserクラスのuser変数を渡すと、_user.html.erb
を探しに行く
見ているのは変数名ではなく、変数のクラス名。
<ul class="users"> <% @users.each do |user| %> <%= render user %> <% end %> </ul>
パーシャルにオブジェクトのコレクションを渡すと、自動でコレクションの要素分、クラス名に対応するパーシャル_user.html.erb
をレンダリングする
each文を省略できる
<ul class="users"> <%= render @users %> </ul>
admin権限のテスト
toggle!
引数のadmin属性を反転させてDBに保存
@user.toggle!(:admin)
be_admin
admin?メソッドを実行し、trueが返ること
it { should be_admin }
$ rails generate migration add_admin_to_users admin:boolean
default: false
でデフォルト値を与えている
def change add_column :users, :admin, :boolean, default: false end
user番号17を管理者へ変更するPATCHリクエスト
Strong Parametersによってパラメータ名adminを許可していないので防ぐことができる
patch /users/17?admin=1
:userのadminだけ上書きした:adminを定義
# FactoryGirl.create(:admin)が使える factory :admin do admin true end
match: :first
expect do # Capybaraが最初に見つけたdeleteのリンクをクリック click_link('delete', match: :first) end.to change(User, :count).by(-1)
User.find(params[:id]).destroy
herokuの本番データのリセット
$ git push heroku $ heroku pg:reset DATABASE $ heroku run rake db:migrate $ heroku run rake db:populate
railstutorial.jp 10章メモ
- テーマ
- アソシエーション
- パーシャル
micropostsテーブルの追加
$ rails generate model Micropost content:string user_id:integer
$ rake db:migrate
$ rake test:prepare
やはりテストがこける
Failure/Error: let(:user) { FactoryGirl.create(:user) } ActiveRecord::RecordInvalid: Validation failed: Email has already been taken
いったんtest.sqlite3を削除するとうまくいった
$ rm db/test.sqlite3 $ rake db:create db:migrate RAILS_ENV=test $ rspec spec/models/micropost_spec.rb
User has_many Micropost
class User < ActiveRecord::Base # 複数形 # 自分が削除されたら関連先も削除する has_many :microposts, dependent: :destroy end
Micropost belongs_to User
class Micropost < ActiveRecord::Base # 単数形 belongs_to :user end
メソッド | 用途 |
---|---|
micropost.user | マイクロポストに関連付けられたユーザーオブジェクトを返す。 |
user.microposts | ユーザーのマイクロポストの配列を返す。 |
user.microposts.create(arg) | マイクロポストを作成する (user_id = user.id)。 |
user.microposts.create!(arg) | マイクロポストを作成する (失敗した場合は例外を発生する)。 |
user.microposts.build(arg) | 新しいMicropostオブジェクトを返す (user_id = user.id)。 |
FactoryGirlでの関連づけ
:micropostの定義内にuser
を含めるだけ。userはこのコードの上の方でfactory :user do
と定義しているので使えると思われる
factory :micropost do content "Lorem ipsum" # userと関連づけ user end
letはその変数が参照されたときに初めてつくられる(遅延)
let!は即座に変数を作るので順番が重要なときに便利
before { @user.save } let!(:older_micropost) do FactoryGirl.create(:micropost, user: @user, created_at: 1.day.ago) end let!(:newer_micropost) do FactoryGirl.create(:micropost, user: @user, created_at: 1.hour.ago) end # has_many関連づけの正しさをmicropostの順序でチェック it "should have the right microposts in the right order" do expect(@user.microposts.to_a).to eq [newer_micropost, older_micropost] end
デフォルトスコープ
ラムダ-> { }
を受け取り、遅延評価される
default_scope -> { order('created_at DESC') }
Procの生成を確かめる
$ rails c > -> { puts "foo" } => #<Proc:0x007fe63cf604d0@(irb):2 (lambda)> > -> { puts "foo" }.call foo => nil
to_aメソッドはオブジェクトのコピーを返している
microposts = @user.microposts.to_a @user.destroy # ユーザにひも尽くmicropostsは削除されていないこと expect(microposts).not_to be_empty
where
メソッドはない場合には空オブジェクトを返す
expect(Micropost.where(id: micropost.id)).to be_empty
find
メソッドは内場合には例外をスローする
expect do Micropost.find(micropost) end.to raise_error(ActiveRecord::RecordNotFound)
def show @user = User.find(params[:id]) @microposts = @user.microposts.paginate(page: params[:page]) end
usersコントローラにおいて@userがあると推測される場合は引数無しでもOKだが、そうでない場合には手動でpaginateされたコレクションを渡す
<%= will_paginate %> <%= will_paginate @microposts %>
カウント数
<h3>Microposts (<%= @user.microposts.count %>)</h3>
app/views/users/show.html.erb
で使っていたとしてもapp/views/microposts/_micropost.html.erb
(単数形!)がパーシャルとなる
<%= render @microposts %>
Fakerを使ってダミーテキストの生成
content = Faker::Lorem.sentence(5)
エラーになった。。。
$ rake db:reset $ rake db:populate rake aborted! ArgumentError: wrong number of arguments (1 for 0)
allの引数、limitの指定が間違ってた。
# users = User.all(limit: 6) users = User.all.limit(6)
$ rake db:reset $ rake db:populate $ rake test:prepare $ rm db/test.sqlite3 $ rake db:create db:migrate RAILS_ENV=test
rails sし直して、micropostsが表示された。
resources :microposts, only: [:create, :destroy]
onlyを明示的に指定しなければ全アクションがbefore_actionの対象になる
before_action :signed_in_user # before_action :signed_in_user, only: [:create, :destroy]
micropostsページの結合テストをgenerate
$ rails generate integration_test micropost_pages
error_messagesパーシャルに明示的にオブジェクトを渡す方法
object: f.object
で@micropost
を渡している
また、 object: f.object
はerror_messagesパーシャルの中でobjectという変数を使うように指定している
<%= form_for(@micropost) do |f| %> <%= render 'shared/error_messages', object: f.object %> ... <% end %>
def home @micropost = current_user.microposts.build if signed_in? end
テストが落ちる -> micropost_pages_spec.rbにrequire 'support/utilities.rb'
を追加すると直る
$ rspec spec/requests/micropost_pages_spec.rb FFF Failures: 1) Micropost pages micropost creation with invalid information should not create a micropost Failure/Error: before { sign_in user } NoMethodError: undefined method `sign_in' for #<RSpec::ExampleGroups::MicropostPages::MicropostCreation::WithInvalidInformation:0x007fc98bab2ff0> # ./spec/requests/micropost_pages_spec.rb:19:in `block (2 levels) in <top (required)>'
Rspecのinclude?メソッド
include?は本来、配列が指定の要素を含んでいるかどうか返すメソッド。
its(:feed) { should include(newer_micropost) } its(:feed) { should include(older_micropost) } its(:feed) { should_not include(unfollowed_post) }
プレイスホルダ
Micropost.where("user_id = ?", id)
li##{item.id}の最初の# は CSS idを示す Capybara独自の文法で、2番目の#は Rubyの式展開#{}の先頭部分
expect(page).to have_selector("li##{item.id}", text: item.content)
renderはコレクションに与えられたパーシャルfeed_item変数を使う
<%= render partial: 'shared/feed_item', collection: @feed_items %>
関連づけて検索
find_byはみつからないと、nilを返す
def correct_user @micropost = current_user.microposts.find_by(id: params[:id]) redirect_to root_url if @micropost.nil? end
findはみつからないと、例外をスローする
def correct_user @micropost = current_user.microposts.find(params[:id]) rescue redirect_to root_url end
railstutorial.jp 11章メモ
ユーザ has_many リレーションシップ
リレーションシップ has_many ユーザ
リレーションシップをリンクテーブルとしてユーザとユーザが多対多の関係にある
$ rails generate model Relationship follower_id:integer followed_id:integer
ユニークインデックスを張る
add_index :relationships, [:follower_id, :followed_id], unique: true
リレーションの関連づけ
Railsはデフォルト、クラス名_id
という名前のカラムは外部キーとして認識する
そうでない場合には外部キーforeign_key
を明示的に指定する
class User < ActiveRecord::Base # relationshipsにあるusersへの参照は、user_idではなくfollower_id。 has_many :relationships, foreign_key: "follower_id", dependent: :destroy
関連 | モデル | Relationshipで持つ参照 |
---|---|---|
belongs_to :follower | Follower | follower_id |
belongs_to :followed | Followed | followed_id |
を推測するが、FollowerもFollowedモデルも存在しない。ここではUserモデルへの参照を持つ必要がある。
明示的にクラス名class_name
を明示的に指定する。
class Relationship < ActiveRecord::Base belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" end
has_many through関連付け
User と Userで多対多
user.followed_usersで取得できる用に設定
sourceパラメータで、followed_users
はfollowed
のidの集合であることを明示的に指定
class User < ActiveRecord::Base has_many :followed_users, through: :relationships, source: :followed
つまり、こんなじょうたいの表現
User.id
<- has_many -> Relationship.follower_id
Relationship.followed_id
<- has_many -> User.id
# 逆リレーションシップを使ってuser.followersを実装する # クラスを明示的に指定してReverseRelationshipクラスを探しに行かないようにする has_many :reverse_relationships, foreign_key: "followed_id", class_name: "Relationship", dependent: :destroy has_many :followers, through: :reverse_relationships, source: :follower
?をつけたメソッドは、慣習的に論理値を返す
!をつけたメソッドは、慣習的に失敗した場合には例外を発生する(create!とかsave!)
def following?(other_user) relationships.find_by(followed_id: other_user.id) end def follow!(other_user) relationships.create!(followed_id: other_user.id) end
最初のユーザに3から51までをフォローさせ、
4から14のユーザは最初のユーザをフォローさせる
def make_relationships users = User.all user = users.first followed_users = users[2..50] followers = users[3..40] followed_users.each { |followed| user.follow!(followed) } followers.each { |follower| follower.follow!(user) } end
ルーティング
memberメソッドのルーティングはidを含むURLに対応するURLを生成する
collectionメソッドのルーティングはidを指定せずにURLに対応するURLを生成する
resources :users do member do get :following, :followers end collection do get :tigers end end
$ rake routes > Prefix Verb URI Pattern Controller#Action > following_user GET /users/:id/following(.:format) users#following > followers_user GET /users/:id/followers(.:format) users#followers > tigers_users GET /users/tigers(.:format) users#tigers > . > . > .
カレントユーザと特定ユーザIDの両立
<% @user ||= current_user %>
have_xpath
メソッドを使ってFollowしたときにUnfollowボタンに切り替わったことをテスト
describe "toggling the button" do before { click_button "Follow" } it { should have_xpath("//input[@value='Unfollow']") } end
フォームでAjaxを使用する方法
form_for
をform_for ..., remote:true
にする。
内部的にはformタグにdata-remote="true"
が加えられる
<%= form_for(current_user.relationships.build(followed_id: @user.id), remote: true) do |f| %>
xhr
メソッドでAjaxのテスト
xhr, HTTPリクエスト, アクション, paramsの内容を指定する。
it "should increment the Relationship count" do expect do xhr :post, :create, relationship: { followed_id: other_user.id } end.to change(Relationship, :count).by(1) end it "should respond with success" do xhr :post, :create, relationship: { followed_id: other_user.id } expect(response).to be_success end
controller側でAjaxレスポンスをする
実際下記の例だと、リクエストの種類に応じて、続く行の中から1つだけが実行される
# respond_toはRspecのメソッドとは別物なので注意 respond_to do |format| format.html { redirect_to @user } # htmlが要求されていたらこっち format.js # jsが要求されていたらこっち end
format.jsの場合、js.erbファイルが使われる
app/views/relationships/create.js.erb
app/views/relationships/destroy.js.erb
js.erbファイルのなかではjQueryJavaScriptヘルパーが使える
escape_javascript
関数でHTMLをエスケープできる
列挙可能(enumerable)オブジェクト(配列やハッシュなど)
アンパサンド(&)とメソッド名に対応するシンボルで短縮表記
$ rails console > [1,2,3,4].map { |i| i.to_s } => ["1", "2", "3", "4"] > [1,2,3,4].map(&:to_s) => ["1", "2", "3", "4"] > [1,2,3,4].map(&:to_s).join(',') => "1,2,3,4"
これを利用して、フォローしているユーザのID配列を取得する
> User.first.followed_users.map(&:id) > User.first.followed_user_ids => 上と同じ結果!
ActiveRecordでは、:followed_users
アソシエーションから、followed_user_ids
メソッドが使えるようになっている
さらにSQL文字列に挿入するときには?プレースホルダに配列を突っ込むと.join(',')
を自動で行って文字列にしてくれる
relationships_controller_spec.rbがこける
Failures: 1) RelationshipsController creating a relationship with Ajax should increment the Relationship count Failure/Error: before { sign_in user, no_capybara: true } NameError: undefined local variable or method `cookies' for #<RSpec::ExampleGroups::RelationshipsController::CreatingARelationshipWithAjax:0x007f9901433e28> # ./spec/support/utilities.rb:30:in `sign_in' # ./spec/controllers/relationships_controller_spec.rb:8:in `block (2 levels) in <top (required)>'
requireを追記したらうごいた
require 'rails_helper' require 'support/utilities.rb' require 'spec_helper' describe RelationshipsController do
名前付きプレースホルダ
where("user_id IN (:followed_user_ids) OR user_id = :user_id", followed_user_ids: followed_user_ids, user_id: user)
railstutorial全11章を終えた
railstutorialはRuby on RailsとデファクトスタンダードなGemを使ってTwitter風アプリケーションを作るということを題材に、実際の仕事の開発でも行うGitHub Flowや振る舞い駆動開発をしながら、Webアプリケーション開発を学ぶチュートリアル。
Ruby on Rails チュートリアル:実例を使って Rails を学ぼう
長かったー。一週間以上かかった。本文中にも書いてあったが、飛ばし読みできない。とても濃厚なチュートリアルなので、1日1章ペース。休みの日に8,9,10章を一気にやったら大変過ぎて10章はあまり頭に入ってこなかった。中ボスと呼ばれている演習は全て飛ばした。
勉強になった点や疑問点、ハマった点をメモしながらやってみた。
メモ
- http://yuheikagaya.hatenablog.jp/entry/2014/10/15/005554
- http://yuheikagaya.hatenablog.jp/entry/2014/10/15/010001
- http://yuheikagaya.hatenablog.jp/entry/2014/10/15/010406
- http://yuheikagaya.hatenablog.jp/entry/2014/10/15/010622
- http://yuheikagaya.hatenablog.jp/entry/2014/10/15/010935
- http://yuheikagaya.hatenablog.jp/entry/2014/10/15/011213
- http://yuheikagaya.hatenablog.jp/entry/2014/10/15/011457
- http://yuheikagaya.hatenablog.jp/entry/2014/10/15/012008
- http://yuheikagaya.hatenablog.jp/entry/2014/10/15/012447
- http://yuheikagaya.hatenablog.jp/entry/2014/10/15/012728
- http://yuheikagaya.hatenablog.jp/entry/2014/10/15/013242
できあがったソース
- 作者: 高橋征義,後藤裕蔵,まつもとゆきひろ
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2013/06/04
- メディア: 単行本
- この商品を含むブログ (27件) を見る
Rubyの勉強は事前にやった方が良いか?チュートリアルの第1章でも触れられていたが、やった方が良いと思う。ざっとRubyはどんな文法なのかや、ブロック、クラス、モジュールあたりの知識は入れておいた方が良いと思った。RailsではRubyに味付けされていたり、省略記法や様々なルールがあって、Rubyの話なのかRailsの話なのか頭の整理がつかなくなってうわーってなっちゃったんだけど。
自分のブレイクスルーは第4章だった。 括弧()を省いて半角スペース区切りでつなげたり、最後の引数のハッシュの{}を省いたり、ブロックとProcについて理解が深まると、RailsやRSpecの不思議な文法が楽しくなってきた。
大変だったぶん、とてもためになるチュートリアルだった。特に、RspecとCapybaraをつかったテストの書き方とテストを書く感覚がつかめたのが良かった。
会社のRails神ら曰く「railstutorialを3周はやったほうがよい」とのことだったが、すみません。次はパーフェクトRuby on Railsを勉強したいと思います。
- 作者: すがわらまさのり,前島真一,近藤宇智朗,橋立友宏
- 出版社/メーカー: 技術評論社
- 発売日: 2014/06/06
- メディア: 大型本
- この商品を含むブログ (6件) を見る