月別アーカイブ: 2021年12月

Ruby mine2.5(他のプラグインの導入)

前回の復習(プラグインの導入準備)

RubyMine-EAPのウィンドウをクリックしてアクティブ状態にして、画面上部にマウスを移動し、メニューバーを表示してRubyMine-EAPタブのPreferences…をクリックで環境設定をする。

1:またはショートカットキーの「⌘,」で環境設定をする。

2:左のサイドバーのプラグインをクリック

3:上部中央にあるマーケットプレイスをクリックして準備完了 続きを読む

Rubymine(Docker環境でのデバッグ)

参考にした記事

システム環境設定→セキュリティーとプライバシー→デベロッパツールから[+]を押し、プログラムの登録ダイアログを表示する。
スクリーンショット 2019-12-24 2.18.16.png

画面右上の検索窓からを選択(複数ある場合は一番新しくてファイルサイズの大きいもの)を指定します。JetBrains Toolboxを起動中の場合、JetBrains Toolboxを終了させる通知が表示されるので従う。

バージョン管理から取得を選択

任意のものをgithubからクローン

ダウンロードが完了すると、RubyMineが自動的にローカルのRakeを呼ぼうとしてエラーになるが無視する。

Gemfileに’debase’と’ruby-debug-ide’を追加

database.ymlの設定(MySQLの場合)database:の部分は任意で

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  socket: /tmp/mysql.sock
  host: db

development:
  <<: *default
  database: lifehack_development

続きを読む

Ruby Mine2(プラグインの導入)

プラグインの導入

1:FinderでアプリケーションにあるRubyMine 2020.2 EAP.appをダブルクリックして「開く」をクリックして起動する。

2:新規プロジェクトの作成をクリック

3:適当なディレクトリを指定して右下の「作成」をクリック

4:RubyMine-EAPのウィンドウをクリックしてアクティブ状態にして、画面上部にマウスを移動しメニューバーを表示してRubyMine-EAPタブのPreferences…をクリックで環境設定をする。

またはショートカットキーの「⌘,」で環境設定をする。

5:左のサイドバーのプラグインをクリック

6:上部中央にあるマーケットプレイスをクリック

プラグインの導入(例としてHighlightBracketPairをインストール)

HighlightBracketPairとは?

カーソルがある箇所を囲んでいるカッコ()をハイライト表示して

ソースコードを読みやすくできるプラグイン

 

1:マーケットプレイスの下部にある検索窓にHighlightBracketPairで検索して

インストールをクリックしRestart IDEをクリックして再起動

プラグイン導入前↓(user_params)に注目

 

 

 

 

 

 

 

プラグイン導入後↓(user_params)に注目

使い方:プラグインを有効にしている限り有効

その他のプラグインに関しては先程のように検索窓で検索して追加していく。

オススメのプラグインはこちら↓で紹介されているのでチェック!

RubyMineをさらに便利にするプラグインたち:前編

 

Ruby Mine1.5(Ruby MineEAPのインストールと日本語化)

RubyMine-EAP(早期アクセスプログラム)とは?

Jetbrains公式より引用

早期アクセスプログラム(EAP)では、プレスリリースビルドの製品を無料でご利用いただけます。 新しい機能をお試しいただき、貴重なご意見をお寄せください。

1ヶ月の無料期間が終わってまだ購入を検討している方などにオススメ

ただし日本語化をする場合、注意しないと起動しなくなるので注意!

起動しなくなった場合QiitaのRubyMine-EAP を日本語化を参照

Ruby MineEAPをダウンロード

1:Ruby Mine公式から左下の「Download」をクリック

2:「保存」をクリックしてダウンロード。

続きを読む

Rails-tutorialのまとめ(14.3 ステータスフィード)

その14.25から続く

14.3 ステータスフィード

最後の難関、ステータスフィードの実装に取りかかりましょう。
本書の中でも最も高度なものです。完全なステータスフィードは、
13章で扱ったプロトフィードをベースにします。

現在のユーザーにフォローされているユーザーのマイクロポストの配列を作成し、現在のユーザー自身のマイクロポストと合わせて表示します。
このセクションを通して、複雑さを増したフィードの実装に進んでいきます。
これを実現するためには、RailsとRubyの高度な機能の他に、
SQLプログラミングの技術も必要です。

14.3.1 動機と計画

フィードに必要な3つの条件を満たすことです。具体的には、

1) フォローしているユーザーのマイクロポストがフィードに含まれていること
2) 自分自身のマイクロポストもフィードに含まれていること。
3) フォローしていないユーザーのマイクロポストがフィードに含まれていないこと

の3つです。

まずはMichaelがLanaをフォローしていて、
Archerをフォローしていないという状況を作ってみましょう。
この状況のMichaelのフィードでは、Lanaと自分自身の投稿が見えていて、Archerの投稿は見えないことになります

先ほどの3つの条件をアサーションに変換して、
Userモデルにfeedメソッドがあることに注意しながら、
更新したUserモデルに対するテストを書いてみましょう。
結果を↓に示します。

ステータスフィードのテスト
test/models/user_test.rb

演習

マイクロポストのidが正しく並んでいると仮定して
(すなわち若いidの投稿ほど古くなる前提で)、データセットで
user.feed.map(&:id)を実行すると、
どのような結果が表示されるでしょうか? 考えてみてください。
たdefault_scopeを思い出してください。

user.feed.map($:id)
=>[1,2,7,8,10]

このように、引数として受け取った自分のidと、
フォローしているidが組み合わさって表示される。

14.3.2 フィードを初めて実装する

早速フィードの実装に着手してみましょう。最終的なフィードの実装はやや込み入っているため、細かい部品を1つずつ確かめながら導入していきます。

このフィードで必要なクエリについて考えましょう。
ここで必要なのは、micropostsテーブルから、
あるユーザー (つまり自分自身) がフォローしているユーザーに対応するidを持つマイクロポストをすべて選択 (select) することです。
このクエリを模式的に書くと次のようになります。

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

SQLがINというキーワードをサポートしていることを前提にしています

このキーワードを使うことで、
idの集合の内包 (set inclusion) に対してテストを行えます。

Active Recordのwhereメソッドを使っていることを思い出してください。このときに選択すべき対象はシンプルで、
現在のユーザーに対応するユーザーidを持つマイクロポストを選択すればよかったのでした。

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

今回必要になる選択は、上よりも少し複雑で、例えば次のような形になります。

Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)

フォローされているユーザーに対応するidの配列が必要であることが
わかってきました。これを行う方法の1つは、Rubyのmapメソッドを使うことです。
このメソッドはすべての「列挙可能 (enumerable)」なオブジェクト
(配列やハッシュなど、要素の集合で構成されるあらゆるオブジェクト)で使えます。なお、このメソッドは前にもも出てきました。
mapメソッドを使って配列を文字列に変換すると、次のようになります。

rails console
>> [1, 2, 3, 4].map { |i| i.to_s }
=> ["1", "2", "3", "4"]

& と、メソッドに対応するシンボルを使った短縮表記が使えます。
この短縮表記であれば、変数iを使わずに済みます。

>> [1, 2, 3, 4].map(&:to_s)
=> ["1", "2", "3", "4"]

>> [1, 2, 3, 4].map(&:to_s).join(', ')
=> "1, 2, 3, 4"

上のコードを使えば、user.followingにある各要素のidを呼び出し、
フォローしているユーザーのidを配列として扱うことができます。

>> User.first.following.map(&:id)
=> [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]

実際、この手法は実に便利なので、Active Recordでは次のようなメソッドも用意されています。

>> User.first.following_ids
=> [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51]

following_idsメソッドは、has_many :followingの関連付けをしたときにActive Recordが自動生成したものです。
これにより、user.followingコレクションに対応するidを得るためには、関連付けの名前の末尾に_idsを付け足すだけで済みます。
結果として、フォローしているユーザーidの文字列は、
次のようにして取得することができます。

>> User.first.following_ids.join(', ')
=> "3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51"

実際にSQL文字列に挿入するときは、このように記述する必要はありません。
実は、?を内挿すると自動的にこの辺りの面倒を見てくれます。
さらに、データベースに依存する一部の非互換性まで解消してくれます。
つまり、ここではfollowing_idsメソッドをそのまま使えばよいだけなのです。結果、最初に想像していたとおり、

Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
とりあえず動くフィードの実装 green
app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  # パスワード再設定の期限が切れている場合はtrueを返す
  def password_reset_expired?
    reset_sent_at < 2.hours.ago
  end

  # ユーザーのステータスフィードを返す
  def feed
    Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
  end

  # ユーザーをフォローする
  def follow(other_user)
    following << other_user
  end
  .
  .
  .
end
 green
rails test

いくつかのアプリケーションにおいては、この初期実装だけで目的が達成され、十分に思えるかもしれません。しかしにはまだ足りないものがあります。
それが何なのか、次に進む前に考えてみてください。
(フォローしているユーザーが5,000人もいたらどうなるでしょうか?)。

演習

1:現在のユーザー自身の投稿を含めないようにするにはどうすれば良いでしょうか? また、そのような変更を加えると、どのテストが失敗するでしょうか?

OR user_id = 1で、自分自身のユーザーidを渡している点に注目。
(user_id IN (3,..,51) OR user_id = 1)

2:フォローしているユーザーの投稿を含めないようにするにはどうすれば良いでしょうか? また、そのような変更を加えると、どのテストが失敗するでしょうか?

def feed
  Micropost.where("user_id = ?", following_ids, id)
end
ActiveRecord::PreparedStatementInvalid: wrong number of bind variables (2 for 1) in: user_id = ?
test/models/user_test.rb:15:in `block (2 levels) in <class:UserTest>'
test/models/user_test.rb:14:in `block in <class:UserTest>'

3:フォローしていないユーザーの投稿を含めるためには
どうすれば良いでしょうか?
また、そのような変更を加えると、どのテストが失敗するでしょうか?
自分自身とフォローしているユーザー、そしてそれ以外という集合は、
いったいどういった集合を表すのか考えてみてください。

# ユーザーのステータスフィードを返す
def feed
  Micropost.all
end

Expected true to be nil or false
test/models/user_test.rb:23:in `block (2 levels) in <class:UserTest>'
test/models/user_test.rb:22:in `block in <class:UserTest>'
# フォローしていないユーザーの投稿を確認
archer.microposts.each do |post_unfollowed|
  assert_not michael.feed.include?(post_unfollowed)
end

フォローしていないものが含まれているのはダメです

14.3.3 サブセレクト

つまり、フォローしているユーザーが5,000人程度になるとWebサービス全体が遅くなる可能性があります。ステータスフィードを改善していきましょう。

following_idsでフォローしているすべてのユーザーをデータベースに
問い合わせし、さらに、フォローしているユーザーの完全な配列を作るために再度データベースに問い合わせしている点です。
SQLのサブセレクト (subselect) を使うと解決できます。

フィードをリファクタリングすることから始めましょう。
whereメソッド内の変数に、キーと値のペアを使う green
app/models/user.rb

class User < ApplicationRecord
  .
  .
  .
  # ユーザーのステータスフィードを返す
  def feed
    Micropost.where("user_id IN (:following_ids) OR user_id = :user_id",
     following_ids: following_ids, user_id: id)
  end
  .
  .
  .
end

これまでのコード

Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)

を次のように置き換えました。

Micropost.where("user_id IN (:following_ids) OR user_id = :user_id",
    following_ids: following_ids, user_id: id)

前者の疑問符を使った文法も便利ですが、同じ変数を複数の場所に挿入したい場合は、後者の置き換え後の文法を使う方がより便利です。

これからSQLクエリにもう1つのuser_idを追加します。特に、次のRubyコードは、

following_ids

このようなSQLに置き換えることができます。

following_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"

このコードをSQLのサブセレクトとして使います。つまり、「ユーザー1がフォローしているユーザーすべてを選択する」というSQLを既存のSQLに内包させる形になり、結果としてSQLは次のようになります。

SELECT * FROM microposts
WHERE user_id IN (SELECT followed_id FROM relationships
                  WHERE follower_id = 1)
OR user_id = 1

集合のロジックを (Railsではなく) データベース内に保存するので、
より効率的にデータを取得することができます。

もっと効率的なフィードを実装する準備ができました。
(ここに記述されているコードは生のSQLを表す文字列であり、
following_idsという文字列はエスケープされているのではなく、
見やすさのために式展開しているだけだという点に注意してください。)

フィードの最終的な実装 green
app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  # ユーザーのステータスフィードを返す
  def feed
    following_ids = "SELECT followed_id FROM relationships
                     WHERE follower_id = :user_id"
    Micropost.where("user_id IN (#{following_ids})
                     OR user_id = :user_id", user_id: id)
  end
  .
  .
  .
end

RailsとRubyとSQLのコードが複雑に絡み合っていて厄介ですが、
ちゃんと動作します。

green
rails test

大規模なWebサービスでは、バックグラウンド処理を使ってフィードを非同期で生成するなどのさらなる改善が必要でしょう。

ステータスフィードの実装は完了です。

rails test
git add -A
git commit -m "Add user following"
git checkout master
git merge following-users

コードをリポジトリにpushして、本番環境にデプロイしてみましょう。

git push
git push heroku
heroku pg:reset DATABASE
heroku run rails db:migrate
heroku run rails db:seed

演習

1:Homeページで表示される1ページ目のフィードに対して、
統合テストを書いてみましょう。

 test "feed on Home page" do
   get root_path
   @user.feed.paginate(page: 1).each do |micropost|
    assert_match CGI.escapeHTML(micropost.content), response.body
  end 
 end

2:期待されるHTMLをCGI.escapeHTMLメソッドでエスケープしています(このメソッドはCGI.escapeと同じ用途です)。
このコードでは、なぜHTMLをエスケープさせる必要があったのでしょうか?考えてみてください。
ヒント: 試しにエスケープ処理を外して、
得られるHTMLの内容を注意深く調べてください。
マイクロポストの内容が何かおかしいはずです。また、
ターミナルの検索機能 (Cmd-FもしくはCtrl-F) を使って
「sorry」を探すと原因の究明に役立つはずです。

contentをエスケープしている為、CGI.esapeHTMLを加える必要がある。

ステータスフィードが追加され、Ruby on Railsチュートリアルの
サンプルアプリケーションがとうとう完成しました。
このサンプルアプリケーションには、
Railsの主要な機能 (モデル、ビュー、コントローラ、テンプレート、パーシャル、beforeフィルター、バリデーション、コールバック、has_many/belongs_to/has_many through関連付け、セキュリティ、テスティング、デプロイ)が多数含まれています。

14.4.1 サンプルアプリケーションの機能を拡張する

Railsアプリケーションに何らかの機能を実装していて困ったときは、RailsガイドやRails APIをチェックしてみてください。
いずれも1,000ページを超える大型の公式ドキュメントなので、
今自分がやろうとしていることに関連するトピックがあるかもしれません。

できるだけ念入りにGoogleで検索し、自分が調べようとしているトピックに言及しているブログやチュートリアルがないかどうか、よく探すことです。
Webアプリケーションの開発には常に困難がつきまといます。
他人の経験と失敗から学ぶことも重要です。

14.4.1 サンプルアプリケーションの機能を拡張する

Twitterには、マイクロポスト入力中に@記号に続けてユーザーのログイン名を入力するとそのユーザーに返信できる機能があります。
このポストは、宛先のユーザーのフィードと、
自分をフォローしているユーザーにのみ表示されます。
この返信機能の簡単なバージョンを実装してみましょう。具体的には、@replyは受信者のフィードと送信者のフィードにのみ表示されるようにします。
これを実装するには、micropostsテーブルのin_reply_toカラムと、
追加のincluding_repliesスコープをMicropostモデルに追加する
必要があると思います。スコープの詳細については、
RailsガイドのActive Record クエリインターフェイスを参照してください。

このサンプルアプリケーションではユーザー名が重なり得るので、
ユーザー名を一意に表す方法も考えなければならないでしょう。
1つの方法は、idと名前を組み合わせて@1-michael-hartlのようにすることです

もう1つの方法は、ユーザー登録の項目に一意のユーザー名を追加し、@replyで使えるようにすることです。

メッセージ機能

Twitterでは、ダイレクトメッセージを行える機能がサポートされています。この機能をサンプルアプリケーションに実装してみましょう
(ヒント: Messageモデルと、新規マイクロポストにマッチする正規表現が必要になるでしょう)。

フォロワーの通知

ユーザーに新しくフォロワーが増えたときにメールで通知する機能を
実装してみましょう。続いて、メールでの通知機能をオプションとして選択可能にし、不要な場合は通知をオフにできるようにしてみましょう。
メール周りで分からないことがあったら、
RailsガイドのAction Mailerの基礎にヒントがないか調べてみましょう。

RSSフィード

ユーザーごとのマイクロポストをRSSフィードする機能を実装してください。次にステータスフィードをRSSフィードする機能も実装し、
余裕があればフィードに認証スキームも追加してアクセスを制限してみてください。

REST API

多くのWebサイトはAPI (Application Programmer Interface)を
公開しており、第三者のアプリケーションからリソースのget/post/put/deleteが行えるようになっています。
サンプルアプリケーションにもこのようなREST APIを実装してください。解決のヒントは、respond_toブロックをコントローラーの多くのアクションに追加することです。
このブロックはXMLをリクエストされたときに応答します。
セキュリティには十分注意してください。
認可されたユーザーにのみAPIアクセスを許可する必要があります。

検索機能

現在のサンプルアプリケーションには、ユーザーの一覧ページを端から探す、もしくは他のユーザーのフィードを表示する以外に他のユーザーを検索する手段がありません。
この点を強化するために、検索機能を実装してください。
続いて、マイクロポストを検索する機能も追加してください
(ヒント: まずは自分自身で検索機能に関する情報を探してみましょう。難しければ、@budougumi0617 さんの簡単な検索フォームの実装例を参考にしてください)。

他の拡張機能

上記の他にも、「いいね機能」「シェア機能」「minitestの代わりにRSpecで書き直す」「erbの代わりにHamlで書き直す」
「エラーメッセージをI18nで日本語化する」「オートコンプリート機能」といったアイデアがありそうです。

14.4.3 本章のまとめ

1:has_many :throughを使うと、複雑なデータ関係をモデリングできる

2:has_manyメソッドには、クラス名や外部キーなど、いくつものオプションを渡すことができる

3:適切なクラス名と外部キーと一緒に
has_many/has_many :throughを使うことで、
能動的関係 (フォローする) や受動的関係 (フォローされる)がモデリングできた

4:ルーティングは、ネストさせて使うことができる

5:whereメソッドを使うと、
柔軟で強力なデータベースへの問い合わせが作成できる

6:Railsは (必要に応じて) 低級なSQLクエリを呼び出すことができる

7:本書で学んだすべてを駆使することで、フォローしているユーザーのマイクロポスト一覧をステータスフィードに表示させることができた

has_many through
多対多の関係性を定義する関連付けメソッド。

source
has_manyに対してパラメータを与えるオプション。
sourceオブションで与えた値は配列の元を表しているので、実際の配列のインデックスは変わらない。

collection
コレクションルーティングを追加するメソッド。
idを指定せずに全てのメンバーを表示したりできる

Rails-tutorialのまとめ14.25(FollowingとFollowersページ)

その14.2から続く

14.2.3 [Following] と [Followers] ページ

フォローしているユーザーを表示するページと、フォロワーを表示するページは、
いずれもプロフィールページとユーザー一覧ページを合わせたような作りになるという点で似ています。
どちらにもフォローの統計情報などのユーザー情報を
表示するサイドバーと、ユーザーのリストがあります。
さらに、サイドバーには小さめのユーザープロフィール画像のリンクを格子状に並べて表示する予定です。

フォローしているユーザーのリンクとフォロワーのリンクを
動くようにすることでTwitterに倣って、
どちらのページでもユーザーのログインを要求するようにします。

フォローしているユーザーのリンクとフォロワーのリンクを動くようにすることです。 Twitterに倣って、どちらのページでもユーザーのログインを要求するようにします。そこで前回のアクセス制御と同様に、まずはテストから書いていきましょう。

フォロー/フォロワーページの認可をテストする red
test/controllers/users_controller_test.rb

require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest

 def setup
  @user = users(:michael)
  @other_user = users(:archer)
 end
.
.
.
 test "should redirect following when not logged in" do
  get following_user_path(@user)
  assert_redirected_to login_url
 end

 test "should redirect followers when not logged in" do
  get followers_user_path(@user)
  assert_redirected_to login_url
 end
end

この実装には1つだけトリッキーな部分があります。
それはUsersコントローラに2つの新しいアクションを追加する必要があるということです。

定義した2つのルーティングにもとづいており、これらはそれぞれfollowingおよびfollowersと呼ぶ必要があります。
それぞれのアクションでは、タイトルを設定し、ユーザーを検索し、@user.followingまたは@user.followersからデータを取り出し、
ページネーションを行なって、ページを出力する必要があります。

followingアクションとfollowersアクション red
app/controllers/users_controller.rb

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
                                        :following, :followers]
  .
  .
  .
  def following
    @title = "Following"
    @user  = User.find(params[:id])
    @users = @user.following.paginate(page: params[:page])
    render 'show_follow'
  end

  def followers
    @title = "Followers"
    @user  = User.find(params[:id])
    @users = @user.followers.paginate(page: params[:page])
    render 'show_follow'
  end

  private
  .
  .
  .
end

Railsは慣習に従って、アクションに対応するビューを暗黙的に呼び出します。
例えば、showアクションの最後でshow.html.erbを呼び出す、といった具合です。

renderを明示的に呼び出し、show_followという同じビューを出力しています。つまり、作成が必要なビューはこれ1つです。
renderで呼び出しているビューが同じである理由は、
ERbはどちらの場合でもほぼ同じであり、で両方の場合をカバーできるためです。

↓フォローしているユーザーとフォロワーの両方を表示するshow_followビュー green
app/views/users/show_follow.html.erb

<% provide(:title, @title) %>
<div class="row">
 <aside class="col-md-4">
  <section class="user_info">
   <%= gravatar_for @user %>
   <h1><%= @user.name %></h1>
   <span><%= link_to "view my profile", @user %></span>
   <span><b>Microposts:</b> <%= @user.microposts.count %></span>
  </section>
 <section class="stats">
  <%= render 'shared/stats' %>
   <% if @users.any? %>
    <div class="user_avatars">
     <% @users.each do |user| %>
      <%= link_to gravatar_for(user, size: 30), user %>
   <% end %>
    </div>
   <% end %>
  </section>
 </aside>
<div class="col-md-8">
 <h3><%= @title %></h3>
  <% if @users.any? %>
   <ul class="users follow">
    <%= render @users %>
   </ul>
  <%= will_paginate %>
 <% end %>
 </div>
</div>

 

続きを読む

Rails-tutorialのまとめ14.2(FollowのWebインターフェイス)

その14から続く

14.2 FollowのWebインターフェイス

フォロー/フォロー解除の基本的なインターフェイスを実装します。また、
フォローしているユーザーと、フォロワーにそれぞれ表示用のページを作成します。
ユーザーのステータスフィードを追加して、サンプルアプリケーションを完成させます。

14.2.1 フォローのサンプルデータ

サンプルデータを自動作成するrails db:seedを使って、
データベースにサンプルデータを登録できるとやはり便利です。
先にサンプルデータを自動作成できるようにしておけば、
Webページの見た目のデザインから先にとりかかることができ、
バックエンド機能の実装を後に回すことができます。

最初のユーザーにユーザー3からユーザー51までをフォローさせ、
それから逆にユーザー4からユーザー41に最初のユーザーをフォローさせます。

ソースを見るとわかるように、このような設定を自由に行うことができます。こうしてリレーションシップを作成しておけば、
アプリケーションのインターフェイスを開発するには十分です。

サンプルデータにfollowing/followerの関係性を追加する
db/seeds.rb

# ユーザー
User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar",
             admin:     true,
             activated: true,
             activated_at: Time.zone.now)

99.times do |n|
  name  = Faker::Name.name
  email = "example-#{n+1}@railstutorial.org"
  password = "password"
  User.create!(name:  name,
               email: email,
               password:              password,
               password_confirmation: password,
               activated: true,
               activated_at: Time.zone.now)
end

# マイクロポスト
users = User.order(:created_at).take(6)
50.times do
  content = Faker::Lorem.sentence(5)
  users.each { |user| user.microposts.create!(content: content) }
end

# リレーションシップ
users = User.all
user  = users.first
following = users[2..50]
followers = users[3..40]
following.each { |followed| user.follow(followed) }
followers.each { |follower| follower.follow(user) }
rails db:migrate:reset
rails db:seed

演習

1:コンソールを開き、User.first.followers.countの結果が
期待している結果と合致していることを確認してみましょう。

User.first.followers.count
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
(0.4ms) SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ? [["followed_id", 1]]
=> 38

2:先ほどの演習と同様に、User.first.following.countの結果も合致していることを確認してみましょう。

User.first.following.count
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
(0.2ms) SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? [["follower_id", 1]]
=> 49

14.2.2 統計とFollowフォーム

これでサンプルユーザーに、フォローしているユーザーとフォロワーができました。プロフィールページとHomeページを更新して、
これを反映しましょう。
最初に、プロフィールページとHomeページに、
フォローしているユーザーとフォロワーの統計情報を表示するための
パーシャルを作成します。次に、フォロー用とフォロー解除用のフォームを作成します。
それから、フォローしているユーザーの一覧 (“following”)と
フォロワーの一覧 (“followers”) を表示する専用のページを作成します。

resourcesブロックの内側で:memberメソッドを使っています。
これは初登場のメソッドですが、まずはどんな動作をするのか推測してみてください

Usersコントローラにfollowingアクションとfollowersアクションを追加する
config/routes.rb

Rails.application.routes.draw do
  root   'static_pages#home'
  get    '/help',    to: 'static_pages#help'
  get    '/about',   to: 'static_pages#about'
  get    '/contact', to: 'static_pages#contact'
  get    '/signup',  to: 'users#new'
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
  resources :users do
    member do
      get :following, :followers
    end
  end
  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
  resources :microposts,          only: [:create, :destroy]
end

どちらもデータを表示するページなので、
適切なHTTPメソッドはGETリクエストになります。
したがって、getメソッドを使って適切なレスポンスを返すようにします。

続きを読む

Rails-tutorialのまとめ(第14章 ユーザーをフォロー 主に演習)

13章から続く

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

他のユーザーをフォロー(およびフォロー解除)できるソーシャルな仕組みの追加と、
フォローしているユーザーの投稿をステータスフィードに表示する機能を追加します。

ここで学んだデータモデルは、今後自分用のWebアプリケーションを開発するときに必ず役に立ちます。

14.1 Relationshipモデル

ユーザーをフォローする機能を実装する第一歩は、データモデルを構成することです。ただし、これは見た目ほど単純ではありません。

素朴に考えれば、has_many (1対多) の関連付けを用いて「1人のユーザーが複数のユーザーをhas_manyとしてフォローし、1人のユーザーに複数のフォロワーがいることをhas_manyで表す」といった方法でも実装できそうです。しかし後ほど説明しますが、この方法ではたちまち壁に突き当たってしまいます。これを解決するためのhas_many throughについてもこの後で説明します。

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

あるユーザーをフォローしているすべてのユーザーの集合はfollowersとなりuser.followersはそれらのユーザーの配列を表すことになります。

Twitterの慣習にならい、本チュートリアルではfollowingという
呼称を採用します (例: “50 following, 75 followers”)。

したがって、あるユーザーがフォローしているすべてのユーザーの集合はcalvin.followingとなります。

followingテーブルと has_many関連付けを使って、
フォローしているユーザーのモデリングができます。
user.followingはユーザーの集合でなければならないため、

followingテーブルのそれぞれの行は、followed_idで識別可能なユーザーでなければなりません (これはfollower_idの関連付けについても同様です)。

さらに、それぞれの行はユーザーなので、
これらのユーザーに名前やパスワードなどの属性も追加する必要があるでしょう。

2つの疑問が生じます。

1. あるユーザーが別のユーザーをフォローするとき、何が作成される?

2. あるユーザーが別のユーザーをフォロー解除するとき、何が削除される?。

1人のユーザーは1対多の関係を持つことができ、
さらにユーザーはリレーションシップを経由して多くのfollowing
(またはfollowers) と関係を持つことができるということです。

Facebookのような友好関係 (Friendships) では本質的に
左右対称のデータモデルが成り立ちますが、
Twitterのようなフォロー関係では左右非対称の性質があります。
すなわち、CalvinはHobbesをフォローしていても、
HobbesはCalvinをフォローしていないといった関係性が成り立つのです。

左右非対称な関係性を見分けるために、
それぞれを能動的関係 (Active Relationship)と
受動的関係 (Passive Relationship)と呼ぶことにします

CalvinがHobbesをフォローしているが、
HobbesはCalvinをフォローしていない場合では、
CalvinはHobbesに対して「能動的関係」を持っていることになります。

つまりアイドルとファンの関係のようなものでCalvinはアイドルなわけよ

逆に、HobbesはCalvinに対して「受動的関係」を持っていることになります。

つまりアイドルとファンの関係のようなものでHobbesはファンなわけよ
フォローしているユーザーを生成するために、能動的関係に焦点を当てていきます
フォローしているユーザーはfollowed_idがあれば識別することができるので、

先ほどのfollowingテーブルをactive_relationshipsテーブル
と見立ててみましょう。

ただしユーザー情報は無駄なので、ユーザーid以外の情報は削除します。そして、followed_idを通して、
usersテーブルのフォローされているユーザーを見つけるようにします。

テーブル名にはこの「関係」を表す「relationships」を使いましょう。モデル名はRailsの慣習にならって、Relationshipとします。

続きを読む

Rails-tutorial(13.5画像の検証)

その13.4から続く

13.4.2 画像の検証

アップローダーも悪くはありませんが、いくつかの目立つ欠点があります。例えば、アップロードされた画像に対する制限がないため、
もしユーザーが巨大なファイルを上げたり、無効なファイルを上げると問題が発生してしまいます。

この欠点を直すために、画像サイズやフォーマットに対するバリデーションを実装し、サーバー用とクライアント (ブラウザ)用の両方に追加しましょう。

画像のファイル名から有効な拡張子 (PNG/GIF/JPEGなど) を検証する
画像フォーマットのバリデーション
app/uploaders/picture_uploader.rb

class PictureUploader < CarrierWave::Uploader::Base
  storage :file

  # アップロードファイルの保存先ディレクトリは上書き可能
  # 下記はデフォルトの保存先
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # アップロード可能な拡張子のリスト
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end

2つ目のバリデーションでは、画像のサイズを制御します。
これはMicropostモデルに書き足していきます。

今回は手動でpicture_sizeという独自のバリデーションを定義します。

今まで使っていたvalidatesメソッドではなく、
validateメソッドを使っている点に注目してください。

 画像に対するバリデーションを追加する
app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  default_scope -> { order(created_at: :desc) }
  mount_uploader :picture, PictureUploader
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
  validate  :picture_size

  private

    # アップロードされた画像のサイズをバリデーションする
    def picture_size
      if picture.size > 5.megabytes
        errors.add(:picture, "should be less than 5MB")
      end
    end

validateメソッドでは、引数にシンボル (:picture_size) を取り、
そのシンボル名に対応したメソッドを呼び出します。
また、呼び出されたpicture_sizeメソッドでは、5MBを上限とし、
それを超えた場合はカスタマイズした
エラーメッセージをerrorsコレクションに追加しています。

定義した画像のバリデーションをビューに組み込むために、
クライアント側に2つの処理を追加しましょう。

まずはフォーマットのバリデーションを反映するためには、
file_fieldタグにacceptパラメータを付与して使います。

<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>

このときacceptパラメータでは、リスト 13.64で許可したファイル形式を、MIMEタイプで指定するようにします。

次に、大きすぎるファイルサイズに対して警告を出すために、
ちょっとしたJavaScript (正確にはjQuery) を書き加えます。
こうすることで、長すぎるアップロード時間を防いだり、
サーバーへの負荷を抑えたりすることに繋がります。

$('#micropost_picture').bind('change', function() {
var size_in_megabytes = this.files[0].size/1024/1024;
if (size_in_megabytes > 5) {
alert('Maximum file size is 5MB. Please choose a smaller file.');
}
});

上のコードでは(ハッシュマーク#から分かるように)
CSS idのmicropost_pictureを含んだ要素を見つけ出し、
この要素を監視しています。

そしてこのidを持った要素とは、マイクロポストのフォームを指します(なお、ブラウザ上で画面を右クリックし、インスペクターで要素を調べることで確認できます)。

つまり、このCSS idを持つ要素が変化したとき、このjQueryの関数が動き出します。そして、もしファイルサイズが大きすぎた場合、
alertメソッドで警告を出すといった仕組みです。

これらの追加的なチェック機能をまとめると、↓のようになります。

ファイルサイズをjQueryでチェックする
app/views/shared/_micropost_form.html.erb

<%= form_for(@micropost) do |f| %>
 <%= render 'shared/error_messages', object: f.object %>
 <div class="field">
  <%= f.text_area :content, placeholder: "Compose new micropost..." %>
 </div>
<%= f.submit "Post", class: "btn btn-primary" %>
 <span class="picture">
  <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
 </span>
<% end %>

<script type="text/javascript">
 $('#micropost_picture').bind('change', function() {
  var size_in_megabytes = this.files[0].size/1024/1024;
   if (size_in_megabytes > 5) {
    alert('Maximum file size is 5MB. Please choose a smaller file.');
   }
 });
</script>

実装はまだ不完全です。

仮に送信フォームを使った投稿をうまく制限できても、ブラウザのインスペクタ機能でJavaScriptをいじったり、curlなどを使って直接POSTリクエストを送信する場合には対応しきれません

演習

1:5MB以上の画像ファイルを送信しようとした場合、どうなりますか?
また無効な拡張子のファイルを送信しようとした場合、どうなりますか?

エラーメッセージが以下のようになる。
画像は5MB未満である必要があります
画像「xmind」ファイルのアップロードは許可されていません。
許可されているタイプ:jpg、jpeg、gif、png

13.4.3 画像のリサイズ

画像を表示させる前にサイズを変更する

画像をリサイズするためには、画像を操作するプログラムが必要になります。今回はImageMagickという
プログラムを使うので、これを開発環境にインストールします。

本番環境がHerokuであれば、既に本番環境でImageMagickが使えるようになっています。クラウドIDEでは、
次のコマンドでこのプログラムをインストールできます。

sudo yum install -y ImageMagick

もしローカル環境で開発している場合、それぞれの環境に応じてImagiMagickをインストールする手順が異なります。

例えばMacの場合であれば、Homebrewを導入し、
brew install imagemagick
コマンドを使ってインストールします。

次に、MiniMagickというImageMagickとRubyを繋ぐgemを使って、
画像をリサイズしてみましょう。

MiniMagickのドキュメントを見ると
様々な方法でリサイズできることがわかりますが、
今回はresize_to_limit: [400, 400]という方法を使います。これは、
縦横どちらかが400pxを超えていた場合、適切なサイズに縮小するオプションです(ただし小さい画像であっても拡大はしません)。

画像をリサイズするために画像アップローダーを修正する
app/uploaders/picture_uploader.rb

class PictureUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  process resize_to_limit: [400, 400]

  storage :file

  # アップロードファイルの保存先ディレクトリは上書き可能
  # 下記はデフォルトの保存先
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # アップロード可能な拡張子のリスト
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end

演習

1:解像度の高い画像をアップロードし、リサイズされているかどうか確認してみましょう。画像が長方形だった場合、リサイズはうまく行われているでしょうか?

うまく出来る。バリデーション実装前のはもちろんリサイズされない

2:テストを追加していた場合、この時点でテストスイートを走らせると
紛らわしいエラーメッセージが表示されることがあります。
このエラーを取り除いてみましょう。
設定ファイルを修正し、テスト時はCarrierWaveに画像のリサイズを
させないようにしてみましょう。

テスト時は画像のリサイズをさせない設定config/initializers/skip_image_resizing.rb
if Rails.env.test?
  CarrierWave.configure do |config|
    config.enable_processing = false
  end
end

13.4.4 本番環境での画像アップロード

storage :fileという行によって、
ローカルのファイルシステムに画像を保存するようになっているからです。
本番環境では、ファイルシステムではなくクラウドストレージサービスに画像を保存するようにしてみましょう。

本番環境でクラウドストレージに保存するためにはfog gemを使うと簡単です。

本番環境での画像アップロードを調整する
app/uploaders/picture_uploader.rb

class PictureUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  process resize_to_limit: [400, 400]

  if Rails.env.production?
    storage :fog
  else
    storage :file
  end

  # アップロードファイルの保存先ディレクトリは上書き可能
  # 下記はデフォルトの保存先
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # アップロード可能な拡張子のリスト
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end

世の中には多くのクラウドストレージサービスがありますが、今回は有名で信頼性も高いアマゾンの「Simple Storage Service (S3) 」を使います。

Amazon Web Servicesアカウントにサインアップする

AWS Identity and Access Management (IAM)でユーザーを
作成し、AccessキーとSecretキーをメモする

AWS ConsoleからS3 bucketを作成し(bucketの名前はなんでも大丈夫です)作成したユーザーに対してRead権限とWrite権限を付与する

fogでリージョンを指定する場合は
:region => ENV[‘S3_REGION’] といったパラメータを渡し、
heroku config:set S3_REGION=”リージョン名”
といったコマンドを実行することで設定できます。
なお、東京のリージョン名は “ap-northeast-1” です。

CarrierWaveを通してS3を使うように修正するconfig/initializers/carrier_wave.rb
if Rails.env.production?
  CarrierWave.configure do |config|
    config.fog_credentials = {
      # Amazon S3用の設定
      :provider              => 'AWS',
      :region                => ENV['S3_REGION'],     # 例: 'ap-northeast-1'
      :aws_access_key_id     => ENV['S3_ACCESS_KEY'],
      :aws_secret_access_key => ENV['S3_SECRET_KEY']
    }
    config.fog_directory     =  ENV['S3_BUCKET']
  end
end

今回は手動で設定する必要があります。heroku config:setコマンドを使って、次のようにHeroku上の環境変数を設定してください。

heroku config:set S3_ACCESS_KEY="ココに先ほどメモしたAccessキーを入力" 
heroku config:set S3_SECRET_KEY="同様に、Secretキーを入力" 
heroku config:set S3_BUCKET="Bucketの名前を入力" 
heroku config:set S3_REGION="Regionの名前を入力"

.gitignoreファイルにアップロード用ディレクトリを追加する

# アップロードされたテスト画像を無視する
/public/uploads

それでは、これまでの変更をトピックブランチにコミットし、masterブランチにマージしていきましょう。

rails test
git add -A
git commit -m "Add user microposts"
git checkout master
git merge user-microposts
git push

次に、Herokuへのデプロイ、データベースのリセット、サンプルデータの生成を順に実行していきます。

git push heroku
heroku pg:reset DATABASE
heroku run rails db:migrate
heroku run rails db:seed

最終状態のGemfile

source 'https://rubygems.org'

gem 'rails',                   '5.1.6'
gem 'bcrypt',                  '3.1.12'
gem 'faker',                   '1.7.3'
gem 'carrierwave',             '1.2.2'
gem 'mini_magick',             '4.7.0'
gem 'will_paginate',           '3.1.6'
gem 'bootstrap-will_paginate', '1.0.0'
gem 'bootstrap-sass',          '3.3.7'
gem 'puma',                    '3.9.1'
gem 'sass-rails',              '5.0.6'
gem 'uglifier',                '3.2.0'
gem 'coffee-rails',            '4.2.2'
gem 'jquery-rails',            '4.3.1'
gem 'turbolinks',              '5.0.1'
gem 'jbuilder',                '2.7.0'

group :development, :test do
  gem 'sqlite3', '1.3.13'
  gem 'byebug',  '9.0.6', platform: :mri
end

group :development do
  gem 'web-console',           '3.5.1'
  gem 'listen',                '3.1.5'
  gem 'spring',                '2.0.2'
  gem 'spring-watcher-listen', '2.0.1'
end

group :test do
  gem 'rails-controller-testing', '1.0.2'
  gem 'minitest',                 '5.10.3'
  gem 'minitest-reporters',       '1.1.14'
  gem 'guard',                    '2.14.1'
  gem 'guard-minitest',           '2.4.6'
end

group :production do
  gem 'pg',   '0.20.0'
  gem 'fog',  '1.42'
end

# Windows環境ではtzinfo-dataというgemを含める必要があります
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

13.5.1 本章のまとめ

1:Active Recordモデルの力によって、マイクロポストも
(ユーザーと同じで) リソースとして扱える

2:Railsは複数のキーインデックスをサポートしている

3:Userは複数のMicropostsを持っていて (has_many)、Micropostは
1人のUserに依存している (belongs_to) といった関係性をモデル化した

4:has_manyやbelongs_toを利用することで、
関連付けを通して多くのメソッドが使えるようになった

5:user.microposts.build(…)というコードは、
引数で与えたユーザーに関連付けされたマイクロポストを返す

6:default_scopeを使うとデフォルトの順序を変更できる

7:default_scopeは引数に無名関数 (->) を取る

8:dependent: :destroyオプションを使うと、
関連付けされたオブジェクトと自分自身を同時に削除する

9:paginateメソッドやcountメソッドは、
どちらも関連付けを通して実行され、効率的にデータベースに問い合わせしている

10:fixtureは、関連付けを使ったオブジェクトの作成もサポートしている

11:パーシャルを呼び出すときに、一緒に変数を渡すことができる

12:whereメソッドを使うと、Active Recordを通して選択 (部分集合を取り出すこと) ができる

13:依存しているオブジェクトを作成/削除するときは、
常に関連付けを通すようにすることで、よりセキュアな操作が実現できる

14:CarrierWaveを使うと画像アップロードや画像リサイズができる

14章に続く