35歳だけどRailsチュートリアルやってみた。[第4版 13章 13.1 Micropostモデル まとめ&解答例]

#はじめに
最近、プロジェクト管理業務が業務の大半を占めており、
プログラムを書く機会がなかなかありません。

このままだとプログラムがまったく書けない人になってしまう危機感（迫り来る35歳定年説）と、
新しいことに挑戦したいという思いから、
[Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版](https://railstutorial.jp/)を学習中です。
業務で使うのはもっぱらJavaなのですが、Rails楽しいですね。


これまでEvernoteに記録していましたが、ソースコードの貼付けに限界を感じたため、
Qiitaで自分が学習した結果をアウトプットしていきます。

個人の解答例なので、誤りがあればご指摘ください。

##動作環境
- cloud9
- ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
- Rails 5.0.0.1

##13.1.1 基本的なモデル
###本章での学び
以下の属性を持つmicropostsテーブルを作成する。

![image](https://qiita-image-store.s3.amazonaws.com/0/98130/761063d0-9748-7437-712e-044e3f74be67.png)

####【Model】Micropostモデルの生成
`user:references`を付与することで、ユーザーと１対１の関係であることを示す、`belongs_to:user`がモデルの中に付与される。

```irb
yokoyan:~/workspace/sample_app (user-microposts) $ rails generate model Micropost content:text user:references
Running via Spring preloader in process 1727
Expected string default value for '--jbuilder'; got true (boolean)
      invoke  active_record
      create    db/migrate/20170719214241_create_microposts.rb
      create    app/models/micropost.rb
      invoke    test_unit
      create      test/models/micropost_test.rb
      create      test/fixtures/microposts.yml
```

####【DB】マイグレーションファイルの編集
自動生成されたマイグレーションファイルに、インデックスを追加する。
user_idとcreated_atによる複合キーを生成する。

```/sample_app/db/migrate/20170719214241_create_microposts.rb
class CreateMicroposts < ActiveRecord::Migration[5.0]
  def change
    create_table :microposts do |t|
      t.text :content
      t.references :user, foreign_key: true

      t.timestamps
    end
    add_index :microposts, [:user_id, :created_at]
  end
end
```

###【DB】マイグレーションファイルの実行
```irb
yokoyan:~/workspace/sample_app (user-microposts) $ rails db:migrate
== 20170719214241 CreateMicroposts: migrating =================================
-- create_table(:microposts)
   -> 0.0074s
-- add_index(:microposts, [:user_id, :created_at])
   -> 0.0010s
== 20170719214241 CreateMicroposts: migrated (0.0087s) ========================
```

###演習１
*RailsコンソールでMicropost.newを実行し、インスタンスを変数micropostに代入してください。その後、user_idに最初のユーザーのidを、contentに "Lorem ipsum" をそれぞれ代入してみてください。この時点では、 micropostオブジェクトのマジックカラム (created_atとupdated_at) には何が入っているでしょうか?*

```irb
?> user = User.first
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-06-20 04:07:12", updated_at: "2017-07-19 04:04:54", password_digest: "$2a$10$noNSSwFRwOmZj.z7lvNt0u8Y2miZecow0uxK.cLpJZ0...", remember_digest: nil, admin: true, activation_digest: "$2a$10$ufdL0hGRXuEm9biMP3H0deF99cC.BpPrrobGbyL4FQ/...", activated: true, activated_at: "2017-06-20 04:07:12", reset_digest: nil, reset_sent_at: "2017-07-19 04:04:20">
>> micropost = Micropost.new
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
>> 
?> micropost.user_id = user.id
=> 1
>> 
?> micropost.content = "Lorem ipsum"
=> "Lorem ipsum"
>> 
?> micropost.created_at
=> nil
>> 
?> micropost.updated_at
=> nil
```

###演習２
*先ほど作ったオブジェクトを使って、micropost.userを実行してみましょう。どのような結果が返ってくるでしょうか? また、micropost.user.nameを実行した場合の結果はどうなるでしょうか?*

```irb
?> micropost.user
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-06-20 04:07:12", updated_at: "2017-07-19 04:04:54", password_digest: "$2a$10$noNSSwFRwOmZj.z7lvNt0u8Y2miZecow0uxK.cLpJZ0...", remember_digest: nil, admin: true, activation_digest: "$2a$10$ufdL0hGRXuEm9biMP3H0deF99cC.BpPrrobGbyL4FQ/...", activated: true, activated_at: "2017-06-20 04:07:12", reset_digest: nil, reset_sent_at: "2017-07-19 04:04:20">
>> micropost.user.name
=> "Example User"
```

###演習３
*先ほど作ったmicropostオブジェクトをデータベースに保存してみましょう。この時点でもう一度マジックカラムの内容を調べてみましょう。今度はどのような値が入っているでしょうか?*

```irb
?> micropost.save
   (0.1ms)  begin transaction
  SQL (2.4ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", 2017-07-23 21:30:50 UTC], ["updated_at", 2017-07-23 21:30:50 UTC]]
   (10.2ms)  commit transaction
=> true
>> 
?> micropost.created_at
=> Sun, 23 Jul 2017 21:30:50 UTC +00:00
>> micropost.updated_at
=> Sun, 23 Jul 2017 21:30:50 UTC +00:00
```

##13.1.2 Micropostのバリデーション
###本章での学び
####【test】新しいmicropostの有効性に対するテスト

```/sample_app/test/models/micropost_test.rb
  def setup
    @user = users(:michael)
    # このコードは慣習的に正しくない
    @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)
  end

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

  test "user id should be present" do
    @micropost.user_id = nil
    assert_not @micropost.valid?
  end
```

####【Model】micropostモデルにuser_idのバリデーションを追加
user_idに対して、「存在性 (Presence)」のバリデーションを追加する。

```
class Micropost < ApplicationRecord
  belongs_to :user
  validates :user_id, presence: true
end
```

####動作確認
現時点でテストがgreenになることを確認。

```irb
yokoyan:~/workspace/sample_app (user-microposts) $ rails test:models
Started with run options --seed 38876

  13/13: [===================================================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.55010s
13 tests, 22 assertions, 0 failures, 0 errors, 0 skips
```

####【test】contentに対するバリデーションのテスト
content属性が存在すること、最大140文字であることの検証を追加する。

```/sample_app/test/models/micropost_test.rb
  test "content should be present" do
    @micropost.content = "   "
    assert_not @micropost.valid?
  end
  
  test "content should be at most 140 characters" do
    @micropost.content = "a" * 141
    assert_not @micropost.valid?
  end
```

####【Model】micropostモデルにcontent属性のバリデーションを追加
content属性に対して、「存在性 (Presence)」のバリデーションを追加する。

```/sample_app/app/models/micropost.rbh
class Micropost < ApplicationRecord
  belongs_to :user
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end
```

###演習１
*Railsコンソールを開き、user_idとcontentが空になっているmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?*

user_id属性とcontent属性にブランクが許可されていないことを確認。

```irb
?> micropost = Micropost.new
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
>> 
?> micropost.valid?
=> false
?> micropost.errors.messages
=> {:user=>["must exist"], :user_id=>["can't be blank"], :content=>["can't be blank"]}
```

###演習２
*コンソールを開き、今度はuser_idが空でcontentが141文字以上のmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?*

content属性は最大140文字であることを確認。

```irb
?> micropost.errors.messages
=> {:user=>["must exist"], :user_id=>["can't be blank"], :content=>["can't be blank"]}
>> 
?> 
?> micropost.content = "a" * 141
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
>> micropost.valid?
=> false
>> micropost.errors.messages
=> {:user=>["must exist"], :user_id=>["can't be blank"], :content=>["is too long (maximum is 140 characters)"]}
```


##13.1.3 User/Micropostの関連付け
###本章での学び
####【Model】マイクロポストは1人のユーザーに属する
Micropost側から見ると、常に1人のユーザーに所属する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/98130/f730a70e-296b-7f51-5a85-f67748adc721.png)

`belongs_to :user`で関連付けを表すことができる。

```
class Micropost < ApplicationRecord
  belongs_to :user
```

####【Model】ユーザーがマイクロポストを複数所有する（has_many）
User側から見ると、1人のユーザーが複数のMicropostを持つ。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/98130/38e140cb-87f5-f5ae-b659-4a9977e1eb1f.png)

`has_many :micropost`で関連付けを表すことができる。
*micropost*ではなく、複数形の*microposts*であることに注意。

```/sample_app/app/models/user.rb
class User < ApplicationRecord
  has_many :microposts
```

####【Test】慣習的に正しいコードを作成する
micropostのインスタンス変数の生成の仕方を修正する。
userに紐づいた、新しいmicropostオブジェクトを生成する。

```/sample_app/test/models/micropost_test.rb
  def setup
    @user = users(:michael)
    # このコードは慣習的に正しくない
    # @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)
    # このコードは慣習的に正しい
    @micropost = @user.microposts.build(content: "Lorem ipsum")
  end
```

テストを実行し、greenになることを確認。

```irb
yokoyan:~/workspace/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 2466
Started with run options --seed 4897

  51/51: [========================================================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.85601s
51 tests, 229 assertions, 0 failures, 0 errors, 0 skips
```

###演習１
*データベースにいる最初のユーザーを変数userに代入してください。そのuserオブジェクトを使ってmicropost = user.microposts.create(content: "Lorem ipsum")を実行すると、どのような結果が得られるでしょうか?*

データベースに保存される。

```irb
>> user = User.first
  User Load (0.4ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-06-20 04:07:12", updated_at: "2017-07-19 04:04:54", password_digest: "$2a$10$noNSSwFRwOmZj.z7lvNt0u8Y2miZecow0uxK.cLpJZ0...", remember_digest: nil, admin: true, activation_digest: "$2a$10$ufdL0hGRXuEm9biMP3H0deF99cC.BpPrrobGbyL4FQ/...", activated: true, activated_at: "2017-06-20 04:07:12", reset_digest: nil, reset_sent_at: "2017-07-19 04:04:20">
>> micropost = user.microposts.create(content: "Lorem ipsum")
   (0.2ms)  begin transaction
  SQL (3.0ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", 2017-07-24 04:17:44 UTC], ["updated_at", 2017-07-24 04:17:44 UTC]]
   (12.0ms)  commit transaction
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2017-07-24 04:17:44", updated_at: "2017-07-24 04:17:44">
```

###演習２
*先ほどの演習課題で、データベース上に新しいマイクロポストが追加されたはずです。user.microposts.find(micropost.id)を実行して、本当に追加されたのかを確かめてみましょう。また、先ほど実行したmicropost.idの部分をmicropostに変更すると、結果はどうなるでしょうか?*

得られる結果は変わらないが、警告が出る。

```irb
?> user.microposts.find(micropost.id)
  Micropost Load (0.4ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? AND "microposts"."id" = ? LIMIT ?  [["user_id", 1], ["id", 2], ["LIMIT", 1]]
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2017-07-24 04:17:44", updated_at: "2017-07-24 04:17:44">
>> 
?> user.microposts.find(micropost)
DEPRECATION WARNING: You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id`. (called from irb_binding at (irb):6)
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? AND "microposts"."id" = ? LIMIT ?  [["user_id", 1], ["id", 2], ["LIMIT", 1]]
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2017-07-24 04:17:44", updated_at: "2017-07-24 04:17:44">
```
###演習３
*user == micropost.userを実行した結果はどうなるでしょうか? また、user.microposts.first == micropost を実行した結果はどうなるでしょうか? それぞれ確認してみてください。
*

どちらもtrueになる。

```irb
?> user == micropost.user
=> true
?> 
?> user.microposts.first == micropost
=> false
```

##13.1.4 マイクロポストを改良する
###本章での学び
####default scopeによるデータの並び替え
default scopeを使って、作成時間の降順の結果を得られるようにする。

#####【Test】マイクロポストを降順で得るテスト

```/sample_app/test/models/micropost_test.rb
  test "order should be most recent first" do
    assert_equal microposts(:most_recent), Micropost.first
  end
```

#####【Fixture】マイクロポストのサンプルデータを定義する

```/sample_app/test/fixtures/microposts.yml
orange:
  content: "I just ate an orange!"
  created_at: <%= 10.minutes.ago %>

tau_manifesto:
  content: "Check out the @tauday site by @mhartl: http://tauday.com"
  created_at: <%= 3.years.ago %>

cat_video:
  content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk"
  created_at: <%= 2.hours.ago %>

most_recent:
  content: "Writing a short test"
  created_at: <%= Time.zone.now %>
```

#####【Model】default_scopeで作成日の降順に並び替える
ラムダ式を使って、ブロック内で作成日時`created_at`の降順`:desc`を指定する。

```/sample_app/app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  default_scope -> { order(created_at: :desc) }
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end
```

####ラムダ式
callメソッドが呼ばれた際に、ブロックの中身を評価する。

```irb
?> -> { puts "foo" }
=> #<Proc:0x00000003005868@(irb):5 (lambda)>
>> -> { puts "foo" }.call
foo
=> nil
```

###Dependent: destroy
ユーザーが破棄された場合、ユーザーのマイクロポストも同時に破棄する。

####【Model】マイクロポストはユーザーと一緒に破棄されることを保証する
`has_many`メソッドに、`dependent: :destroy`オプションを追加する。

```/sample_app/app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
```

####【Test】Dependent: destroyのテスト
以下のテストを実装する。

- ユーザーをDBに保存する
- ユーザーに紐付いたマイクロポストを生成する
- マイクロポストの数が−１されていることを確認する
    - ユーザーを削除する

```/sample_app/test/models/user_test.rb
  test "associated microposts should be destroyed" do
    @user.save
    @user.microposts.create!(content: "Lorem ipsum")
    assert_difference 'Micropost.count', -1 do
      @user.destroy
    end
  end
```

テストがgreenになることを確認。

```irb
yokoyan:~/workspace/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 1892
Started with run options --seed 54679

  53/53: [===================================================================================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.04239s
53 tests, 231 assertions, 0 failures, 0 errors, 0 skips
```

###演習１
*Micropost.first.created_atの実行結果と、Micropost.last.created_atの実行結果を比べてみましょう。*

降順と昇順の結果を比較するため、一致しない。

```irb
>> Micropost.first.created_at == Micropost.last.created_at
  Micropost Load (2.7ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 1]]
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" ASC LIMIT ?  [["LIMIT", 1]]
=> false
>> 
?> Micropost.first.created_at
  Micropost Load (0.1ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 1]]
=> Mon, 24 Jul 2017 04:17:44 UTC +00:00
>> 
?> Micropost.last.created_at
  Micropost Load (0.1ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" ASC LIMIT ?  [["LIMIT", 1]]
=> Sun, 23 Jul 2017 21:30:50 UTC +00:00
```

###演習２
*Micropost.firstを実行したときに発行されるSQL文はどうなっているでしょうか? 同様にして、Micropost.lastの場合はどうなっているでしょうか? ヒント: それぞれをコンソール上で実行したときに表示される文字列が、SQL文になります。*

Micropost.firstは、ORDER BYの並び順にDESC(降順)が指定されている。
つまり、一番新しいマイクロポストから表示される。

Micropost.lastは、ORDER BYの並び順にASC（昇順）が指定されている。
つまり、一番古いマイクロポストから表示される。

```irb
?> Micropost.first
  Micropost Load (0.1ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 1]]
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2017-07-24 04:17:44", updated_at: "2017-07-24 04:17:44">
>> 
?> Micropost.last
  Micropost Load (0.1ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Micropost id: 1, content: "Lorem ipsum", user_id: 1, created_at: "2017-07-23 21:30:50", updated_at: "2017-07-23 21:30:50">
```

###演習３
*データベース上の最初のユーザーを変数userに代入してください。そのuserオブジェクトが最初に投稿したマイクロポストのidはいくつでしょうか? 次に、destroyメソッドを使ってそのuserオブジェクトを削除してみてください。削除すると、そのuserに紐付いていたマイクロポストも削除されていることをMicropost.findで確認してみましょう。*

DB上の最初のユーザーには、マイクロポストが２つ紐付いており、
ユーザーを削除すると、マイクロポストも削除されることを確認。

ユーザーを削除して、Micropost.findを実行するとraise_record_not_found_exceptionが発生する。

```irb
yokoyan:~/workspace/sample_app (user-microposts) $ rails console --sandbox
Running via Spring preloader in process 2512
Loading development environment in sandbox (Rails 5.0.0.1)
Any modifications you make will be rolled back on exit
?> user = User.first
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-06-20 04:07:12", updated_at: "2017-07-19 04:04:54", password_digest: "$2a$10$noNSSwFRwOmZj.z7lvNt0u8Y2miZecow0uxK.cLpJZ0...", remember_digest: nil, admin: true, activation_digest: "$2a$10$ufdL0hGRXuEm9biMP3H0deF99cC.BpPrrobGbyL4FQ/...", activated: true, activated_at: "2017-06-20 04:07:12", reset_digest: nil, reset_sent_at: "2017-07-19 04:04:20">
>> 
?> user.microposts
=> #<ActiveRecord::Associations::CollectionProxy [#<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2017-07-24 04:17:44", updated_at: "2017-07-24 04:17:44">, #<Micropost id: 1, content: "Lorem ipsum", user_id: 1, created_at: "2017-07-23 21:30:50", updated_at: "2017-07-23 21:30:50">]>
>> 
?> user.destroy
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.4ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 2]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 1]]
  SQL (0.1ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 1]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-06-20 04:07:12", updated_at: "2017-07-19 04:04:54", password_digest: "$2a$10$noNSSwFRwOmZj.z7lvNt0u8Y2miZecow0uxK.cLpJZ0...", remember_digest: nil, admin: true, activation_digest: "$2a$10$ufdL0hGRXuEm9biMP3H0deF99cC.BpPrrobGbyL4FQ/...", activated: true, activated_at: "2017-06-20 04:07:12", reset_digest: nil, reset_sent_at: "2017-07-19 04:04:20">
>> 
?> Micropost.find(1)
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ?  [["id", 1], ["LIMIT", 1]]
ActiveRecord::RecordNotFound: Couldn't find Micropost with 'id'=1
        from /usr/local/rvm/gems/ruby-2.3.0/gems/activerecord-5.0.0.1/lib/active_record/relation/finder_methods.rb:357:in `raise_record_not_found_exception!'
```

#おわりに
Userモデルに加えて、Micropostモデルが登場し、
belongs_toとhas_manyを使うことで複数のモデルを簡単に関連付けることができました。
Railsは本当に便利ですね。