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に追加

Rspecを使うようにRailsの設定をする

$ 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 存在しないキーのデフォルト値になる

new メソッドはクラスメソッド

クラス

superclassメソッド
Ruby1.9以降、ObjectクラスのスーパークラスとしてBasicObjectがある
Rubyは定義済みのクラスの拡張ができる(オープンクラス
たとえばRailsではblank?メソッドが追加されていて、Railsコンソールではその拡張も読み込まれる

Railsコンソールでコントローラのアクションも呼べる

controller = StaticPagesController.new

controller.home

Railsコンソールで相対パスからロードもできる

require ‘./example_user'

attr_accessor

インスタンス変数 @name
initializeはnewを実行するときに呼ばれるメソッド

ハッシュ引数を使用してオブジェクトを初期化する手法、マスアサインメント(mass assignment)

railstutorial.jp 5章メモ

  • テーマ
    • Twitter Bootstrap
    • レイアウト
    • パーシャル
    • ルーティング
    • Asset Pipeline
    • CSS
    • Sass

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章メモ

  • テーマ

    • ユーザ登録
    • モデル
    • バリデーション
  • 認証と権限のモジュールはたくさんあるけどここでは自前で作る

モデルは単数形で指定

$ 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?

pluralizeメソッドでいい感じに単数形/複数形にする

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パラメータ_methodpatchが入っている

<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.objecterror_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 throughアソシエーション
    • member, collection ルーティング
    • コレクションとアンパサンドシンボルメソッド
    • Ajaxリクエストとレスポンス

ユーザ 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_usersfollowedの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_forform_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章を終えた

railstutorialRuby on RailsデファクトスタンダードなGemを使ってTwitter風アプリケーションを作るということを題材に、実際の仕事の開発でも行うGitHub Flowや振る舞い駆動開発をしながら、Webアプリケーション開発を学ぶチュートリアル

Ruby on Rails チュートリアル:実例を使って Rails を学ぼう

長かったー。一週間以上かかった。本文中にも書いてあったが、飛ばし読みできない。とても濃厚なチュートリアルなので、1日1章ペース。休みの日に8,9,10章を一気にやったら大変過ぎて10章はあまり頭に入ってこなかった。中ボスと呼ばれている演習は全て飛ばした。

勉強になった点や疑問点、ハマった点をメモしながらやってみた。

メモ

できあがったソース

チュートリアルをやる前に、たのしいRubyを読んだ。

Rubyの勉強は事前にやった方が良いか?チュートリアルの第1章でも触れられていたが、やった方が良いと思う。ざっとRubyはどんな文法なのかや、ブロック、クラス、モジュールあたりの知識は入れておいた方が良いと思った。RailsではRubyに味付けされていたり、省略記法や様々なルールがあって、Rubyの話なのかRailsの話なのか頭の整理がつかなくなってうわーってなっちゃったんだけど。

自分のブレイクスルーは第4章だった。 括弧()を省いて半角スペース区切りでつなげたり、最後の引数のハッシュの{}を省いたり、ブロックとProcについて理解が深まると、RailsRSpecの不思議な文法が楽しくなってきた。

大変だったぶん、とてもためになるチュートリアルだった。特に、RspecとCapybaraをつかったテストの書き方とテストを書く感覚がつかめたのが良かった。

会社のRails神ら曰く「railstutorialを3周はやったほうがよい」とのことだったが、すみません。次はパーフェクトRuby on Railsを勉強したいと思います。

パーフェクト Ruby on Rails

パーフェクト Ruby on Rails