decadence

個人のメモ帳

railstutorial やったメモ

これなに

railstutorial 読んだ個人のメモで、中身は railstutorial のままだし人の為にならないやつ

経緯とか

Web系だし Ruby とか RoR 教養として学びたかった
そもそも Ruby ほとんど知らないしこの辺読んで、 s-99 を60までだが Ruby でやったりした
で、railstutorial 読みつつ考えながら写経した感じ

s-99 やってて思ったのは Scala みたいにコレクション関数多かったりとかで、ありがたいとかそんな感じ

railstutorial 分量多くて3日ぐらいかかったしそこそこ長い、けどまぁ普通にもっとやる必要あるし、gem知らなさ過ぎるからつらい感じ
全部通して書いたらテスト143個とか出来てたし、3章から11章が繋がってて git で適当にコミットしてたら3桁ぐらいコミット数あった感じ
f:id:krrrr:20140519012535p:plain

rails/rails-dev-box · GitHub あったからこれいじって使ってた

あれだな

大体困ったら spork 再起動させるか `rake test:prepare` とか
後一応 bootstrap が古かったのと、bcryptあたりで少し詰まったぐらい

最初

環境作る
$ gem install rails # 雛形作りたい
$ rails new MyApp --skip-bundle
bundle

開発環境は

$ bundle install --without production

以降rvm使ってるから `bundle exec` 省く

rails generate

scaffoldとか、個々にmodelとかcontollerとか
注意として、コントローラは複数形、モデルは単数形、scaffoldは単数形

$ rails g scaffold CamelCaseModelName name:string
$ rails generate controller Users new
$ rails generate model User name:string email:string

rspec使う時はgenerate に --no-test-framework つけて、rspecのテストを以下で生成

$ rails g rspec:controller ...
$ rails g rspec:integration ...
$ rails g rspec:model ...
$ rails g rspec:scaffold ...

つか大体ヘルプ見ろって話

$ rails g --help
$ rails g scaffold --help

3章

rspec入れる
$ rails generate rspec:install

rspec使う際にgeneraterでテストページを作成しないようにする

$ rails generate controller StaticPages home help --no-test-framework

キャメルケースでジェネレータに渡すとスネークケースのものが出来る

rake db:rollback について

migrationファイルが無い場合は削除がされないため、rails destroy modelする前にrollbackする必要がある
誤って先にrails destroy modelなどでmigration fileを消してしまった場合、再度作成した上でrollbackしても良いが、手で消す際にはdb/schema.rbも変更されているためrake db:schema:dumpでdb/schema.rbも更新すべき

rspec integration test
$ rails g rspec:integration static_pages

spec/spec_helper.rb で Capybara を使う事を指定

RSpec.configure do |config|
  .
  .
  config.include Capybara::DSL
end

テストコード例

require 'spec_helper'

describe "StaticPages" do
  describe "index page" do
    it do
      visit '/static_pages/home'
      expect(page).to have_content('Home')
    end
  end
end
application.html.erb を使った構造化

application.html.erb

<title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
<%= yield %>

各種ファイル

<% provide(:title, 'Contact') %> <!-- yield(:title) -->
<h1>Contact</h1> <!-- ここが yield -->
guard を用いたテストの自動化

Gemfile

gem 'guard-rspec'
gem 'growl' # for OS X

guardの初期化

$ guard init rspec

Guardfileに追記する例
`all_after_pass: false`で落ちた際に他のテストが実行されないようにする

require 'active_support/inflector'

guard 'rspec', all_after_pass: false do
  .
  # Custom Rails Tutorial specs
  watch(%r{^app/controllers/(.+)_(controller)\.rb$})  do |m|
    ["spec/routing/#{m[1]}_routing_spec.rb",
     "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb",
     "spec/acceptance/#{m[1]}_spec.rb",
     (m[1][/_pages/] ? "spec/requests/#{m[1]}_spec.rb" :
                       "spec/requests/#{m[1].singularize}_pages_spec.rb")]
  end
  watch(%r{^app/views/(.+)/}) do |m|
    (m[1][/_pages/] ? "spec/requests/#{m[1]}_spec.rb" :
                      "spec/requests/#{m[1].singularize}_pages_spec.rb")
  end
  watch(%r{^app/controllers/sessions_controller\.rb$}) do |m|
    "spec/requests/authentication_pages_spec.rb"
  end
  .
end

実行

$ guard
sporkを用いたテストの高速化

テスト用のサーバを立ち上げ環境を読み込んでおくもの
どっちかってっとguardよりこっちの方が嬉しい?
Gemfile

gem 'spork-rails', '4.0.0'
gem 'guard-spork', '1.5.0'
gem 'childprocess', '0.3.6'

初期化

$ spork --bootstrap

設定は`spec/spec_helper.rb`に prefork 云々書くのと、`.rspec`に`--drb`を追記

require 'spork'

Spork.prefork do
  ENV["RAILS_ENV"] ||= 'test'
  RSpec.configure do |config|
...

実行

$ spork

注意として、routes増やした時とか spork 再起動しないとテストの際に No route matches になる
guard使う際は他[http://railstutorial.jp/chapters/static-pages?version=4.0#sec-spork_and_guard:title=Guardfileの変更も必要

4章

application全体の views で使えるhelper関数をapplication_helperで定義
ここで書かれたものは全ての views から呼び出せて、 controllers からは呼べない

5章

views の便利関数
<%= link_to "sample app", '#', id: "logo" %>
<%= link_to image_tag("rails.png", alt: "Rails"), 'http://rubyonrails.org/' %>
bootstrap-sass と Asset Pipeline の互換性を保つ

config/application.rb

module SampleApp
  class Application < Rails::Application
    ...
    config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif)
  end
end
Assets Pipeline

app/assets/stylesheets 以下に置かれたスタイルシートは application.css の一部として勝手にインクルードされる
app/assets/stylesheets/custom.css.scss のようなファイルに対し、拡張子を見て勝手に sass で処理する
custom.css.scss に対し、 bootstrap を使う事を記述する

@import "bootstrap";

このまま custom.css.scss に色々書くと、 application.css に入るため、全ページ共通のレイアウトが適用される

view の構造化

再利用するものはrender使う
app/views/layouts/_header.html.erb とか書いて、application.html.erbから以下のようにして使う

<%= render 'layouts/header' %>
Asset
  • app/assets: 現在のアプリケーション固有のアセット
  • lib/assets: あなたの開発チームによって作成されたライブラリ用のアセット
  • vendor/assets: サードパーティのアセット

app/assets/stylesheets/application.css

 *= require_tree .  <- app/assets/stylesheetsの全てのCSSファイルをapplication.cssに突っ込む
 *= require_self    <- このファイルに書かれた内容も含める

プリプロセッサ拡張子を見て勝手に実行される
foobar.js.erb.coffee の場合、 coffeeのコンパイルの後に erbのコンパイルが実行される

routes の書き換え

以下の場合、 `/static_pages/about` にアクセスしても404

-  get 'static_pages/home'
-  get 'static_pages/about'
+  root  'static_pages#home'
+  match '/about',   to: 'static_pages#about',   via: 'get'
routes関連の便利メソッド

上記routesに関して

root_path => '/'
root_url => 'http://localhost:3000/'
about_path => '/about'
about_url => 'http://localhost:3000/about'

以下のような感じで使える

<%= link_to "About", about_path %>

`rake routes` した時のPREFIXに_pathや_urlを付けれる

specをもっと綺麗に書く

テストヘルパーは spec/support/*.rb, spec/support/*/*.rb に書く
spec/spec_helper.rb を見ればその辺のファイルが勝手に読み込まれる事が書いてある
もし spec/helpers/application_helper_spec.rb とかでhelperのテストをしてるのなら spec/support/*.rb の中身は `include ApplicationHelper` と1行書くだけでもいい
注意として、sporkを使ってる場合はsupportを書いた後にsporkサーバを再起動する

it のブロック内に対して subject で指定されたものがテストの対象となる
describe で作ったブロック内で before { visit *_path } とかして subject { page } とかすると describe 毎に it 内の page が変化して楽 (Capybara 使ってる場合)
同じような内容に関するテストについては、shared example使うともっとまとめたりも出来る

before について、ネストされたブロックにおける before は 全てのブロック毎に、親ブロックの before が毎回実行された後に子ブロックの before が実行される

6章

モデルの作成
$ rails generate model User name:string email:string --no-test-framework
$ rails generate rspec:model User

dbにmigrate

$ rake db:migrate

戻すのは `rake db:rollback`

rails コンソールでデータベースをいじる
    • sandbox つけると全ての変更がロールバックされるので適当にデータ作っていじれる
rails console --sandbox
ActiveRecord
  • Userモデルとインスタンスuserについて
    • User.create
    • user.destroy
    • user.save
    • User.find(id) : 無い場合は例外投げるので注意
    • User.find_by_*(カラム値)
    • User.find_by(email: "hoge@*.com") 無い場合は nil
    • User.where(id: 2) : findと違い、レコードが無い場合にからのオブジェクトを返すやつ
    • User.first
    • User.all
  • 更新
    • user.email = "hoge" からの user.save
    • user.update_attributes(name: "", email: "") : クエリ投げるとこまでして真偽値が返る
  • 変更の取り消し
    • user.reload.email とかすると、DBからもっかい値持ってくる
データベースのテスト

db/development.sqlite3 を db/test.sqlite3 に反映するやつ
migrationした後に毎度実行する

$ rake db:test:prepare

*1

データベースを空にするのは

$ rake db:reset
モデルの検証

先に色々

  • エラーは user.erros.full_messages とかで中見れる
  • user.valid? で妥当かどうか調べれる

検証追加は validates 関数の第二引数にハッシュを加えまくる

validates :name, presence: true, length: { maximum: 50 }
  • 一覧
    • presence: true 存在
    • length: {maximum 50} 長さ
    • format: { with: REGEX } 正規表現によるマッチング
    • uniqueness: true ユニークか
    • uniqueness: { case_sensitive: false } 大文字小文字いずれにも重複が無い

注意: uniqueness: true でも連続クリックとかで通ってしまうので一意性はDBのスキーマに定義させるべし

あるて~用のmigrationファイルのじぇねれーと
$ rails generate migration add_index_to_users_email

add_{index/カラム名}_to_{テーブル名} って感じ

以下見たいのも出来る

$ rails generate migration add_age_to_users age:integer
modelの保存前に実行するやつとか
class User < ActiveRecord::Base
  before_save { self.email = email.downcase }
end
普通のパスワードログイン

モデルでパスワードの制限与えるのが慣習らしく、以下の2つの事を行う

  • Gemfile に bcrypt 追加して、users テーブルに password_digest:string カラム持たせる
  • モデルに `has_secure_password` という1行を加える
    • モデルに自動的に password と password_confirmation という属性が加わる

ユーザ認証について、例としてemailをIDに、パスワード認証する方法

user = User.find_by(email: email)
current_user = user.authenticate(password)

authenticate の返り値はユーザもしくはfalse

undefined method `cost' for BCrypt::Engine:Class ってので少し詰まったけど、これでいけた
http://stackoverflow.com/questions/21853579/rails-nomethoderror-undefined-method-cost-for-bcryptengineclass

テストが遅い

BCryptのコストファクターをテスト環境向けに再定義する。
config/environments/test.rb

SampleApp::Application.configure do
  ...
  # bcrypt'のコスト関数を下げることでテストの速度を向上させる。
  ActiveModel::SecurePassword.min_cost = true
end
rspec の更なる構造化

一応、モデルをDBと連携してテストする際、saveしなければ保存されないので注意

  • let について
    • let(:found_user) { user }
    • スコープの狭いローカル変数を作れる
  • specify について
    • it と等しく、英語としての正しさのために用いる
  • its について、英語通り
    • its(:name) { should_not be_blank }

7章

Rails.env

RAILS_ENV の値を取って色んな情報をviewに表示する
application.html.erb

<%= debug(params) if Rails.env.development? %>

RAILS_ENV は test, development, production の3つがデフォルトで用意されてる

rails console は RAILS_ENV の値を引数に渡せる

$ rails console test

rails sever の場合は --environment で付け加える
production 環境に migrate する時は以下な感じ

$ RAILS_ENV=production rake db:migrate
routes++

`resource :users` という記述はRESTスタイルのURLを追加する

controller

パラメータは `params[:id]` とすることで文字列で取得出来る
`User.find(params[:id])` とかすると勝手に整数型に変換される

FactoryGirl
  • gem に追加
  • spec/factories.rb
FactoryGirl.define do
  factory :user do
    name     "Michael Hartl"
    email    "michael@example.com"
    password "foobar"
    password_confirmation "foobar"
  end
end
  • テスト

オプションで少し値変えるとかも出来る

let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong@example.com") }
gravatarの画像を取得する

gravatar の仕様に沿って、userページ用のヘルパー関数を作る
app/helpers/users_helper.rb

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
end
Railsのform_forメソッド
<%= form_for(@user) do |f| %>
  <%= f.label :name %>
  <%= f.text_field :name %>
  <%= f.label :password %>
  <%= f.password_field :password %>
  <%= f.submit "Create my account", class: "btn" %>
<% end %>
Capybara を使ってフォームの送信テスト

change はブロックの実行前と後でクエリを吐いて比較を行うもの
by は値の増減値

上で作成したformに対して、以下のように扱う事が出来る

before do
  visit signup_path
  fill_in "Name", with: "Example User"
end

it "should create a user" do
  let(:submit) { "Create my account" }
  expect { click_button submit }.to change(User, :count).by(1)
end
create に関して

以前のRailsではモデルに attr_accessible を利用することで、パラメータに制限をかけていた
現在のRails4.0ではコントローラ層で create に入れる値を検証する(Strong Parameters)
create に params をそのまま渡すとエラーが発生するようになっている
方法は以下の通り

params.require(:user).permit(:name, :email, :password, :password_confirmation)

private 関数にすると便利

  def create
    @user = User.new(user_params)
    ...
  end

  private
  def user_params
    params.require(:user).permit(:name, :email, :password,
                                 :password_confirmation)
  end
formのエラー表示

app/views/shared/_error_messages.html.erb とか作って form_for の次の行へ `<%= render 'shared/error_messages' %>` 突っ込む

<% if @user.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-error">
      The for contains <%= pluralize(@user.errors.count, "errors") %>.
    </div>
    <ul>
      <% @user.errors.full_messages.each do |msg| %>
        <li>* <%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>
flush

flash は ハッシュで表される

<% flash.each do |key, value| %>
  <%= content_tag(:div, value, class: "alert alert-#{key}") %>
<% end %>

controller

if @user.save
  flash[:success] = "Welcome to the Sample App!"
  redirect_to @user
...

上記のflushは次のリンク先へ遷移した時も永続的に残るが、1つの画面のみで表示させたい時は以下のものを用いる

flash.now[:success] = "Welcome to the Sample App!"

8章

ユーザのログインの状態を cookie を使って保持する
ページ遷移先においてもサインイン状態を保持するために、 session 関数を用いるのだが、Railsの session は cookie を用いているため、このような取り扱いになる
永続的なセッションを作るために、サインインしたユーザに恒久的な識別子を与える設計をここでは採用する
識別子は User モデルの属性として table に保持する
なお、トークンは暗号化したものを保存しておき、新しいセッションを作成するたびにトークンは更新されるべきである

RESTfulの一部を実装する

routest

resources :sessions, only: [:new,:create, :destroy]
Capybara++
it { should have_selector('div.alert.alert-error', text: 'Invalid') }
it { should have_link('Sign out', href: signout_path) }
モデルの無いform

form_forはモデルオブジェクトを渡さなくても使える
ressources という resources に準拠させる場合、以下のような引数を渡せば良い

form_for(:session, url: sessions_path)

注意: ユーザ登録フォームは `form_for` を使うのが一般的であるが、その他のフォームについては `form_tag` を使う

コントローラで利用出来るヘルパー

通常 Helper は各種 view で自動的に include されるものであって、コントローラでは利用出来ない
そこで、ヘルパーを全てのコントローラで利用出来る用にするには、以下のように ApplicationController へ include させれば良い
app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  ...
  include SessionsHelper
end
セキュアなトークンをユーザに付与させる

before_create には関数名シンボルを渡せる
ランダムトークン生成は `SecureRandom.urlsafe_base64`

class User < ActiveRecord::Base
  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
cookie

20年有効な cookie

cookies.permanent[:remember_token] = remember_token
ログインユーザを取得するSessionsHelperについて

ゲッターでは @current_user がいなければ取得する、的なコードになる

module SessionsHelper
  def sign_in(user)
    remember_token = User.new_remember_token
    cookies.permanent[:remember_token] = remember_token
    user.update_attribute(:remember_token, User.encrypt(remember_token))
    self.current_user = user
  end

  def sign_out
    self.current_user = nil
    cookies.delete(:remember_token)
  end

  def current_user=(user)
    @current_user = user
  end

  def current_user
    remember_token = User.encrypt(cookies[:remember_token])
    @current_user ||= User.find_by(remember_token: remember_token)
  end
end

この SessionsHelper が ApplicationController へ include されている場合、どこからでも current_user 関数を呼び出せ、ログイン状態が取得出来る

Cucumber を使う

Cucumber はBDDツール
あんま使いたくない

9章

Capybara-

get とか patch とか指定して直接リクエストを投げて page に突っ込む事とか出来る

describe "submitting to the update action" do
  before { patch user_path(user) }
  specify { expect(response).to redirect_to(signin_path) }
end

ユーザが他のユーザの設定変更するとかの、複数の状態使うテストとかする際に用いる感じ
あれだ、こっちの方が慣れてて良いなぁって思ったけど、テスト落ちる時のログが辛い

before_action

コントローラのアクションを呼ぶ前に認可とか出来るやつ
only で特定のメソッドのみに適用
ついでに redirect_to にはオプションで flash の値とか渡せる

class UsersController < ApplicationController
  before_action :signed_in_user, only: [:edit, :update]
  ...
  private
  # Before actions
  def signed_in_user
    redirect_to signin_url, notice: "Please sign in." unless signed_in?
  end
フレンドリーフォワーディング

ログインの要るページにアクセスしてログインした場合に、行きたかったページへ redirect するやつ
はてなグループにないやつ
適当な場所で session に request.url して、ログイン後にそれ見て中身消したりそんな感じ

Rakeのタスクを追加する

適当なユーザ作りまくるやつとかこんな感じ (faker 使ってるけど別にいらん)
lib/tasks/sample_data.rake

namespace :db do
  desc "Fill database with sample data"
  task populate: :environment do
    User.create!(name: "Example User",
                 email: "example@railstutorial.jp",
                 password: "foobar",
                 password_confirmation: "foobar")
    99.times do |n|
      name  = Faker::Name.name
      email = "example-#{n+1}@railstutorial.jp"
      password  = "password"
      User.create!(name: name,
                   email: email,
                   password: password,
                   password_confirmation: password)
    end
  end
end
FactoryGirl++

sequences ってメソッドがあって、複数のモデル一度に作れるっぽい
spec/factories.rb にはこんな感じで書く

FactoryGirl.define do
  factory :user do
    sequence(:name)  { |n| "Person #{n}" }
    sequence(:email) { |n| "person_#{n}@example.com"}
    password "foobar"
    password_confirmation "foobar"
  end
end

こんな感じで使うらしい

before(:all) { 30.times { FactoryGirl.create(:user) } }
ページネーション

Gemfile: will_pagenate 使う感じ
kaminari? とか有名なんじゃなかったっけ知らん

gem 'will_paginate'
gem 'bootstrap-will_paginate'

これ追加してると、勝手にモデルクラスに `paginate` ってメソッドが生える
使う際は、controllerにこんな感じ

def index
  @users = User.paginate(page: params[:page])
end

view は `<%= will_pagenate %>` 入れるだけで、入れたとこがページャになる感じ
ただし、これは users のコンテキスト以下だからであり、もし user が microposts 持っててそれをページングする際は `<%= will_pagenate @microposts %>` とかって書く

Rails の views++

_user.html.erb とか作ったら <%= render user %> とかするんだけど、

<ul class="users">
  <% @users.each do |user| %>
    <%= render user %>
  <% end %>
</ul>

<ul class="users">
  <%= render @users %>
</ul>

って書ける

Capybara++

`match: :first` : 初めに見つけたリンクをクリックするようになる

click_link('delete', match: :first)

10章

migration++

migration時にindex張るのこんな感じらしい

class CreateMicroposts < ActiveRecord::Migration
  def change
    create_table :microposts do |t|
      t.string :content
      t.integer :user_id

      t.timestamps
    end
    add_index :microposts, [:user_id, :created_at]
  end
end
1:多関連

user が複数の microposts を持つ関連がある時、以下のようなメソッドを生やす

メソッド 用途
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)。

新しい micropost を増やす際には、 build を使えば user_id を指定せずに済む

micropost に user_id:integer を持たせつつ、以下のような記述をモデルに加える事で1:多が出来る

class Micropost < ActiveRecord::Base
  belongs_to :user
end

class User < ActiveRecord::Base
  has_many :microposts
end

`has_many :microposts, dependent: :destroy` とかすると、 user が削除された時に関連する microposts も全て消される

1:多に対するFactoryGirl設定

以下の様な記述にすることで、micropost 生成時に user を渡せば良い

factory :micropost do
  content "Lorem ipsum"
  user
end

使い方

FactoryGirl.create(:micropost, user: @user, created_at: 1.day.ago)
Rspec++

let は遅延評価で let! は即時評価される

model の default_scope

例えば以下のようにすると、デフォルトで `created_at` 順のオーダーで引かれる

class Micropost < ActiveRecord::Base
  default_scope -> { order('created_at DESC') }
end
-> について

Proc とか lambda と呼ばれるらしい
ブロックを遅延評価する際に `-> { puts "foo }` って記述する
明示的に直接呼びたいとかなら、 `-> { puts "foo" }.call` とかすればいい

render に対して引数を与える

と、直接 partial の中で以下の場合だと object が使える
f.object は `form_for(@user) do |f|` における @user

<%= render 'shared/error_messages', object: f.object %>
ActiveRecord の where でバインディング
Micropost.where("user_id = ?", id)
render partial++
collection などのパラメータ渡す時は、 partial
を明示しなくてはならない
partial は
collection などの引数が無い場合には省略が可能となる

ちなみに :collection パラメータは、この partial を繰り返し表示するもの

<%= render partial: 'shared/feed_item', collection: @feed_items %>

11章

多対多の関連を表す

migration++

複数インデックスでユニークにする

add_index :relationships, [:follower_id, :folloed_id], unique: true
関連に *_id 以外のものを使う

:foreign_key ってパラメータ使う

has_many :relationships, foreign_key: "follower_id", dependent: :destroy

モデル内でこんな風に使えて、一応 self は省略可

self.relationships.create!(followed_id: other_user.id)
多対多の関連を繋ぐ

これは User <-> User を多対多で繋いでいる例

class Relationship < ActiveRecord::Base
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
end

この段階で多対多の関連は作れてるけど、 user.followed_users と user.followers とかが出来ないから作りたい

has_many through

follower_id と関連付けてるから followerd_users を取る方が簡単
:sourceパラメーターを使用し、followed_users 配列の元は followed_id の集合であることを明示的に伝える
`has_many :symbol_name, through: :relation_table_name, source: :relation_table_column_name` ってとこか

class User < ActiveRecord::Base
  has_many :followed_users, through: :relationships, source: :followed
end
follower の取得

チュートリアルで魔法がどうとか言ってるウケる
方針として、 relationships テーブルについて、カラムが反対になったものが存在してれば follower 一覧が取得出来るので、それをエミュレートする感じ
で、それを使って followers を取得するって、なんだこれ... `Relationship.select(User).join(User, follower_id: :user_id).where(followed_id = ?, self.user_id)` とかで良いんじゃないの...(文法知らんから適当)

has_many :reverse_relationships, foreign_key: "followed_id",
                                 class_name:  "Relationship",
                                 dependent:   :destroy
has_many :followers, through: :reverse_relationships, source: :follower

本当は user_id は relationship.follower_id に関連してるけど、 ruby 上で relationship.followerd_id に関連付けさせるのが上記の reverse_relationships って所か
引数の :class_name は内場合、 :reverse_relationships から逆引きして ReverseRelationShip クラスを探しに行く

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
resources :users do
  member do
    get :following, :followers
  end
end

もし `/users/tigers` を作りたいなら 上のところの `member` を `collection` に変えたりしたら出来る

controlelr++

明示的に view テンプレートを変更/指定する

render 'show_follow'
Capybara++

have_xpath(xpath) ってのが使える

it { should have_xpath("//input[@value='Unfollow']") }
form の Ajax 対応

`form_for ..., remote: true` するだけで、

<form action="/relationships/117" class="edit_relationship" data-remote="true"
      id="edit_relationship_117" method="post">
...
</form>

のように、 data-remote="true" がついた form が出来て、これは Rails 用のうんたらかんたら
Ajax のテストはコントローラのテストを行うけど、他は一般的に結合テスト重視

テスト、こんな感じ

xhr :delete, :destroy, id: relationship.id
expect(response).to be_success

expect do
  xhr :post, :create, relationship: { followed_id: other_user.id }
end.to change(Relationship, :count).by(1)
controller の Ajax 対応

respond_to を使い、リクエストの種類に応じたレスポンスを生成する

def controller_method
  @user = User.find(params[:id])
  respond_to do |format|
    format.html { redirect_to @user }
    format.js # controller_method.js.erb とか呼ぶ
  end
end

フォロー追加ボタン押した際の create.js.erb はこんな感じ
erb のコンパイル入って、 unfollow ボタンが入った form が展開されるので、Rails が自動的に提供する jQuery を用いて挿入を行う
app/views/relationships/create.js.erb

$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>")
$("#followers").html('<%= @user.followers.count %>')
ActiveRecord++

has_many に対して、`User.first.followed_user_ids` のようにモデルの配列返すものに *_ids とかつけるとidの配列が返ってくる

バインディングはハッシュのようにも行える

followed_user_ids = user.followed_user_ids
where("user_id IN (:followed_user_ids) OR user_id = :user_id",
  followed_user_ids: followed_user_ids, user_id: user)

タイムラインを取得しようとする場合、これは2回クエリを引いているため今回の場合においては効率が悪い
そこで、サブクエリを用いる方法に変更すると、上のコードは以下のようになる

followed_user_ids = "SELECT followed_id FROM relationships
                         WHERE follower_id = :user_id"
where("user_id IN (#{followed_user_ids}) OR user_id = :user_id",
      user_id: user.id)

*1:本当は `rake db:prepare` が良いんだけど、やってもschemaがtestDBに突っ込まれないなんでだろう

広告を非表示にする