Ruby on Rails Tutorial (24)

先週末に終われるか、とか言ってたのですがとんでもなかったですね。でもようやくラストの節になります。頑張ろう。

11.3 The status feed

もうすぐてっぺん。つうか自分とフォローしてる人の post を表示ってのは色々な意味で確かにアレですね。

11.3.1 Motivation and strategy

確かにこんなのがあれば話は早いですね。

Micropost.from_users_followed_by(user)

これを実装するのかな。でも今のところは分かんないよね、とのこと。とりあえず試験を書きましょう、ということで spec/models/user_spec.rb に以下を追加する模様。
まず、micropost associations な先頭部分を以下にして

  describe "micropost associations" do
    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

末端を以下に、とのこと。

    describe "status" do
      let(:unfollowed_post) do
        FactoryGirl.create(:micropost, user: FactoryGirl.create(:user))
      end
      let(:followed_user) { FactoryGirl.create(:user) }

      before do
        @user.follow!(followed_user)
        3.times { followed_user.microposts.create!(content: "Lorem ipsum") }
      end

      its(:feed) { should include(newer_micropost) }
      its(:feed) { should include(older_micropost) }
      its(:feed) { should_not include(unfollowed_post) }
      its(:feed) do
        followed_user.microposts.each do |micropost|
          should include(micropost)
        end
      end
    end

どーゆーことかというと、@user.save しておいて let! で older_micropost と newer_micropost なオブジェクトを作っていますね。その上で status な試験として

  • 準備として以下
    • unfollowed_post として micropost なオブジェクトを作る
      • post したユーザは @user から follow されていない
    • followed_user としてユーザを作る
    • followed_user は @user に follow されている状態に
    • followed_user が 3 件ポスト
  • @user.feed が newer_micropost を含んでいること
  • @user.feed が older_micropost を含んでいること
  • @user.feed が unfollowed_post を含んでいないこと
  • @user.feed が followed_user.microposts を全て含んでいること

ということを確認しているのか。
で、最後に app/models/user.rb の feed な属性 (メソド) を追加。

  def feed
    Micropost.from_users_followed_by(selr)
  end

試験は全部追加って訳ではなかったんですね。あと feed なメソドも既存でした。

  def feed
    # This is preliminary. See "Following users" for the full implementation.
    Micropost.where("user_id = ?", id)
  end
11.3.2 A first feed implementation

Micropost.from_users_followed_by 実装とのこと。とりあえずどんな query が必要とされているのか、ということを考えましょう、と。

SELECT * FROM microposts
WHERE user_id IN (<list of ids>) OR user_id = <user id>

確かに。そして上に引用した通り最初は確かにこうなってます。

    Micropost.where("user_id = ?", id)

これをこう、とい記述がありますね。

where("user_id in (?) OR user_id = ?", following_ids, user)

SQL そのまんまです。次に何をするのか、と思ったら following_ids を作る作戦に出たのかどうか。以下は

>> User.first.followed_users.map(&:id) 

User の最初のレコード (?) の followed_users の id を配列にしたもの、が戻っていますね。ちなみにこれは AR で以下のように書けるようなナニがある模様。

>> User.first.followed_user_ids

上は配列が戻るので文字列にしたけりゃこうしろ、と。

>> User.first.followed_user_ids.join(',')

つーことで Micropost.from_users_followed_by(user) は以下で書けるよね、とのこと。

  def self.from_users_followed_by(user)
    followed_user_ids = users_followed_user_ids
    where("user_id IN (?) OR user_id = ?", followed_user_ids, user)
  end

where という記述で midroposts なソレを選って戻せるのか。知らなんだ。そしてこれを盛り込むことで試験 green になる模様。ヤッてみます。
あら、試験 red ですね。何が悪いのやら。盛り込んだファイルは三つくらいしかないんですが 12 も failure が云々て。

この時点で

横着して git add . していないおかげで Untracked files がアレなことが分かりました。これはもうどうにもなりませんな。修復不可能orz
つーか fail なソレの意味が分からない箇所が多数あります。これは困りました。
結果をよくよく見るに Micropost.from_users_followed_by の記述誤りでした。

    followed_user_ids = users_followed_user_ids

正しくは以下。

    followed_user_ids = users.followed_user_ids

しかしなんでこんなナチュラルをやってしまったかorz
ちゃんと git status 見れよ、って話ですねorz
しかも最後の直前に自分で見つけるとかアレ杉orz

11.3.3 Subselects

構わず続行。sub query ではなくて subselects って言うのかな。確かに件数多い場合非常にやっかいなのは簡単に分かります。subselect を使って云々、とのこと。
とりあえず Micropost.from_users_followed_by のリファクタリングから開始。

  # Returns microposts from the users being followed by the given user.
  def self.from_users_followed_by(user)
    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)
  end

ちなみに

    where("user_id IN (?) OR user_id = ?", followed_user_ids, user)

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

は同じ意味とのこと。
つうか結局のところ、AR てきにメモリを消費しそうなソレについては subselect という名目で直接 SQL 書け、というのが普通なの?

  def self.from_users_followed_by(user)
    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)
  end

一応盛り込んでおきます。試験 green も確認。

11.3.4 The new status feed

最後のこれは何なのか。paginate は全部読むんじゃなくて一定の件数 (30?) 分を云々なので、ということなのか。このあたりって中身を確認したいなと思っています。

11.4 Conclusion

とりあえず以下で。

$ git add .
$ git commit -m 'Add user following'
$ git checkout master
$ git merge following-users --no-ff

Heroku は別途。新規作成します。しかしこれは酷い。バージョン管理した意味が全然無いです。とは言えすでにやりなおすリキは無いかも。
とりあえず、明日以降で掘削に着手します。