ぞえの技術めも

Ruby on Rails勉強中

【169日目】【1日20分のRailsチュートリアル】【第12章】Relationshipモデルのバリデーションを追加する

Ruby on Railsチュートリアル(第3版)

今日は「12.1.3 Relationshipのバリデーション」から。

12.1.3 Relationshipのバリデーション

先に進む前に、Relationshipモデルの検証を追加して完全なものにしておきましょう。

Relationshipモデルのバリデーションのテストとバリデーション自体を追加する。
followerfollowedに対してidがnilの場合はRelationshipが有効じゃないことを確認する、って感じなのかな…??

test/models/relationship_test.rb

require 'test_helper'

class RelationshipTest < ActiveSupport::TestCase

  def setup
    @relationship = Relationship.new(follower_id: 1, followed_id: 2)
  end

  test "should be valid" do
    assert @relationship.valid?
  end

  test "should require a follower_id" do
    @relationship.follower_id = nil
    assert_not @relationship.valid?
  end

  test "should require a followed_id" do
    @relationship.followed_id = nil
    assert_not @relationship.valid?
  end
end

app/models/relationship.rb

  validates :follower_id, presence: true
  validates :followed_id, presence: true

ユーザーのときと同じで (リスト6.30でfixtureの内容を削除したように)、今の時点では生成されたリレーションシップ用のfixtureファイルも空にしておきましょう (リスト12.6)。

ユーザーのときのこと覚えてないけど今は空にしておきましょう。

test/fixtures/relationships.yml

# empty

これでテストが通るようになりました。

$ bundle exec rake test
62 tests, 309 assertions, 0 failures, 0 errors, 0 skips

今日の学習時間は【12分】

次は「12.1.4 フォローしているユーザー」から。

【168日目】【1日20分のRailsチュートリアル】【第12章】UserモデルとRelationshipモデルの関連付け

Ruby on Railsチュートリアル(第3版)

今日は「12.1.2 User/Relationshipの関連付け」から。

12.1.2 User/Relationshipの関連付け

フォローしているユーザーとフォロワーを実装する前に、UserとRelationshipの関連付けを行います。
1人のユーザーにはhas_many (1対多) のリレーションシップがあり、このリレーションシップは2人のユーザーの間の関係なので、フォローしているユーザーとフォロワーの両方に属します (belongs_to)。

うーん、よく分からない…。active_relationshipsactiveってどこから来たんや…。relationshipsじゃ駄目なの…??

Micropostと似たような実装にはなるけど、そのままそっくりでは駄目で、クラス名を明示してあげる必要があるのは分かった。

先ほどの説明をコードにまとめると、UserとRelationshipの関連付けはリスト12.2リスト12.3のようになります。

ふむふむ、Relationshipモデル内にはユーザーIDと関連付ける属性が2つあるので、それぞれ別の名前付けないとね、ってことか。
で、設計時点でfollowerfollowedを用意しているのでそれぞれどっちもUserモデルと関連してるよ、って明示しないといけないのかー。

app/models/user.rb

  :
  has_many :active_relationships, class_name:  "Relationship",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy
  :

app/models/relationship.rb

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

(ユーザーを削除したら、ユーザーのリレーションシップも同時に削除される必要があります。そのため、関連付けにdependent: :destroyも追加しています。)

なるほど。便利だなぁ。

今日の学習時間は【23分】

次は「12.1.3 Relationshipのバリデーション」から。

【167日目】【1日20分のRailsチュートリアル】【第12章】relationshipsテーブルを作成する

Ruby on Railsチュートリアル(第3版)

今日は「12.1.1 データモデルの問題 (および解決策)」から。

12.1.1 データモデルの問題 (および解決策)

ユーザーをフォローするデータモデル構成のための第一歩として、典型的な場合を検討してみましょう。

まず名前付けとして、
* あるユーザーがフォローしているすべてのユーザーの集合→following * あるユーザーがフォローされているすべてのユーザーの集合→followers とするらしい。
うーん、ややこしい。英語って難しい。とりあえずTwitterと一緒にしたのね…。

能動的関係も受動的関係も、最終的にはデータベースの同じテーブルを使うことになります。したがって、テーブル名にはこの「関係」を表す「relationship」を使いましょう。モデル名も同様にして、Relationshipモデルとします。作成したRelationshipデータモデルを図12.8に示します。

なんやかんやあって図12.8の形に落ち着いた、と。

このデータモデルを実装するために、まずは次のように図12.8に対応したマイグレーションを生成します。

マイグレーション生成しましょう。

$ rails generate model Relationship follower_id:integer followed_id:integer
  :
      invoke  active_record
      create    db/migrate/20170726030438_create_relationships.rb
      create    app/models/relationship.rb
      invoke    test_unit
      create      test/models/relationship_test.rb
      create      test/fixtures/relationships.yml

このリレーションシップは今後follower_idとfollowed_idで頻繁に検索することになるので、リスト12.1に示したように、それぞれのカラムにインデックスを追加します。

db/migrate/[timestamp]_create_relationships.rb

    add_index :relationships, :follower_id
    add_index :relationships, :followed_id
    add_index :relationships, [:follower_id, :followed_id], unique: true

リスト12.1では複合キーインデックスという行もあることに注目してください。これは、follower_idとfollowed_idの組み合わせが必ずユニークであることを保証する仕組みです。これにより、あるユーザーが同じユーザーを2回以上フォローすることを防ぎます。

なるほど、確かにあるユーザーが同じユーザーを2回以上フォローすることはないもんね。

ふと気になったけど、フォロー解除したときはレコード消すのかな…?今後出てくるの待とう。

relationshipsテーブルを作成するために、いつものようにデータベースのマイグレーションを行います。

relationshipsテーブル作成できましたー。

$ bundle exec rake db:migrate
== 20170726030438 CreateRelationships: migrating ==============================
-- create_table(:relationships)
   -> 0.0016s
-- add_index(:relationships, :follower_id)
   -> 0.0009s
-- add_index(:relationships, :followed_id)
   -> 0.0010s
-- add_index(:relationships, [:follower_id, :followed_id], {:unique=>true})
   -> 0.0135s
== 20170726030438 CreateRelationships: migrated (0.0173s) =====================

今日の学習時間は【26分】

次は「12.1.2 User/Relationshipの関連付け」から。

【167日目】【1日20分のRailsチュートリアル】【第12章】第12章向けのブランチを作成する

Ruby on Railsチュートリアル(第3版)

今日は「第12章 ユーザーをフォローする」から。

第12章 ユーザーをフォローする

この章では、他のユーザーをフォロー (およびフォロー解除) できるソーシャルレイヤーを追加し、各ユーザーのHomeページに、現在フォロー中のユーザーのステータスフィードを表示できるようにして、サンプルアプリケーションのコアを完成させます。

うーん、難しそう。ゆっくり順を追って勉強していくか。。。

12.1 Relationshipモデル

ユーザーをフォローする機能を実装する第一歩は、データモデルを構成することです。ただし、これは見た目ほど単純ではありません。
(中略)
これを解決するためのhas_many through (多対多の関係を表すのに使用) についてもこの後で説明します。

ユーザーをフォローする機能は多対多の関係を使うのか。

Gitユーザーはこれまで同様新しいトピックブランチを作成してください。

新しいブランチ作る前に第11章の演習内容をコミットしておこう。
これしておかないと演習の内容が次の章のブランチでコミットするときに入っちゃうことに最後になって気づいた…今まで忘れてたから微妙に入ってる気がする。。。

$ git add -A
$ git commit -m "Add user microposts exercises"

よし、第12章用のブランチ作ろう。

$ git checkout master
$ git checkout -b following-users

$ git statusで演習の内容が残ってないことも確認。これでばっちり!

ブランチ作ったところで今日はここまで。

今日の学習時間は【20分】

次は「12.1.1 データモデルの問題 (および解決策)」から。

【166日目】【1日20分のRailsチュートリアル】【第11章】演習の3.

Ruby on Railsチュートリアル(第3版)

今日は「11.6 演習」の3.から。

11.6 演習

3.リスト11.69に示すテンプレートを参考に、11.4で実装した画像アップローダーをテストしてください。テストの準備として、まずはサンプル画像をfixtureディレクトリに追加してください (コマンド例: “cp app/assets/images/rails.png test/fixtures/”)。
(省略)

問題文面が長い…!

まずサンプル画像をfixtureディレクトリに追加する。

$ cp app/assets/images/rails.png test/fixtures/

紛らわしいエラーを回避するためには、CarrierWaveの設定を変更し、テスト環境では画像リサイズをしないようにする必要があるので、リスト11.70に示す設定ファイルを使ってください。

紛らわしいエラーって何のことなんだろう…。
とりあえずテスト環境では画像リサイズしないようにする。

$ touch config/initializers/skip_image_resizing.rbでファイルを生成して下記のように更新。

config/initializers/skip_image_resizing.rb

if Rails.env.test?
  CarrierWave.configure do |config|
    config.enable_processing = false
  end
end

いよいよテスト作成かな。テンプレートを参考に穴埋めしてみた。

test/integration/microposts_interface_test.rb

  test "micropost interface" do
    :
    assert_select 'input[type=file]'
    :
    # 有効な送信
    content = "This micropost really ties the room together"
    picture = fixture_file_upload('test/fixtures/rails.png', 'image/png')
    assert_difference 'Micropost.count', 1 do
      # post microposts_path, micropost: { content: content }
      post microposts_path, micropost: { content: content, picture: picture }
    end
    assert assigns(:micropost).picture?
    :

合ってるか分からないけどテストは通った。のでよしとしよう。。。。

$ bundle exec rake test
60 tests, 314 assertions, 0 failures, 0 errors, 0 skips

今日の学習時間は【23分】

次は「第12章 ユーザーをフォローする」から。いよいよ最後の章だー!

【165日目】【1日20分のRailsチュートリアル】【第11章】演習の2.

Ruby on Railsチュートリアル(第3版)

今日は「11.6 演習」の2.から。

11.6 演習

2.サイドバーにあるマイクロポストの合計投稿数をテストしてください。このとき、単数形 (micropost) と複数形 (microposts) が正しく表示されているかどうかもテストしてください。(リスト11.68を参考にしてみてください)

マイクロポスト投稿数が1のときは単数形 (micropost) になるんだっけ…。

@user.microposts.count'Micropost.count'の使い分けがよく分かってないことに気づいた。 'Micropost.count'ってなんだ……
使えるケースが限られてるのかな。

test/integration/microposts_interface_test.rb

  test "micropost sidebar count" do
    log_in_as(@user)
    get root_path
    assert_match "#{@user.microposts.count} microposts", response.body
    # まだマイクロポストを投稿していないユーザー
    other_user = users(:mallory)
    log_in_as(other_user)
    get root_path
    assert_match "0 microposts", response.body
    other_user.microposts.create!(content: "A micropost")
    get root_path
    assert_match "1 micropost", response.body
  end

とりあえずテストは通りました。

$ bundle exec rake test
60 tests, 312 assertions, 0 failures, 0 errors, 0 skips

今日の学習時間は【22分】

次は「11.6 演習」の3.から。

【164日目】【1日20分のRailsチュートリアル】【第11章】演習の1.

Ruby on Railsチュートリアル(第3版)

今日は「11.6 演習」から。

11.6 演習

なお、演習とチュートリアル本編の食い違いを避ける方法については、演習用のトピックブランチに追加したメモ (3.6) を参考にしてください。

演習用にブランチ切っておきましょう。

$ git checkout user-microposts
$ git checkout -b user-microposts-exercises

1.if-else文の2つの分岐に対して、それぞれ異なるパーシャルを使用するようにHomeページをリファクタリングしてください。

ノーヒント…!!

11.3.2 マイクロポストを作成する」にちらっと書かれてる下記のことか。

if-else分岐を使用してコードを書き分けている点が少し汚いですが、このコードのクリーンアップは演習に回すことにします (11.6)。

Homeページのif-else文ていうのはユーザーがログインしているかどうかのif-else文。 ユーザーがログインしていればユーザー情報やマイクロポストの表示をするけど、ログインしていなければサインアップを促す内容を表示する。

それぞれの内容をパーシャル化するとして、ユーザーログインに関するif文はどこに持ってけばいいのかな…。パーシャルの方でいいのかな。

まずパーシャル用のファイルを作成して、(ファイル名が微妙なのは気にしてはいけない)

$ touch app/views/shared/_home_logged_in.html.erb
$ touch app/views/shared/_home_welcome.html.erb

if-else分岐の内容をとりあえずコピペ。if文も持ってきてみたよ。

app/views/shared/_home_logged_in.html.erb

<% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
    <div class="col-md-8">
      <h3>Micropost Feed</h3>
      <%= render 'shared/feed' %>
    </div>
  </div>
<% end %>

app/views/shared/_home_welcome.html.erb

<% if !logged_in? %>
  <div class="center jumbotron">
    <h1>Welcome to the Sample App</h1>
  
    <h2>
      This is the home page for the
      <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
      sample application.
    </h2>
  
    <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
  </div>
  
  <%= link_to image_tag("rails.png", alt: "Rails logo"),
              'http://rubyonrails.org/' %>
<% end %>

そして作成したパーシャルを使ってHomeページのビューを更新。わー、シンプル。

app/views/static_pages/home.html.erb

<%= render 'shared/home_logged_in' %>
<%= render 'shared/home_welcome' %>

これでいいのかな…。テストはとりあえず通った。

$ bundle exec rake test
59 tests, 306 assertions, 0 failures, 0 errors, 0 skips

心配なので実動作も見てみたけどとりあえず問題なさそう…??

↓ログイン済みの場合

f:id:kt_zoe:20170718123547p:plain

↓ログインしてない場合

f:id:kt_zoe:20170718123602p:plain

合ってるかは分からないけどシンプルにはなったので良しとしよう。

今日の学習時間は【29分】

次は「11.6 演習」の2.から。