Railsチュートリアル 13章を終えました。
13章ではユーザーが短いメッセージを投稿できるようにするためのリソース「マイクロポスト」を追加していきます
いわゆるtwitterの簡易版コピーみたいなものですね
概要
12章までで、ユーザ登録および操作、管理まで行うことができました。
ユーザ関連の操作ができるようになって基盤ができたので、ここではサービスとして簡易的なMicropostを作成します。
ユーザがツイートして、表示できるようにします。
そのため、ユーザとツイートを関連付け、そのツイートを操作できるようにします
Micropostモデル
ということで、まずはツイートを格納するためのモデルを作成していきます
以下のようなテーブルを作成します
このモデルを作成するため$ rails g
を行います
$ rails generate model Micropost content:text user:references
ここでcontentカラムのText型はString型と違い255文字よりも多く格納することができます
英数字だけでなく、さらに色々な言語にも対応することができます
モデルの関連づけ
またUserモデル関連づけを行います。
user:references
とreferenceを指定すると、ユーザモデルと関連付けするための準備を行うため、自動的にインデックスと外部キー参照付きのuser_idカラムが追加されます
$ rails db:migrate
にてマイグレを行えばモデルを作成します
次にUserモデルとMicropostモデルとの関連づけを、model上で行います
belongs_to
で関連付けできます。イメージは以下です。
Micropostモデルにbelongs_to
を付与します
class Micropost < ApplicationRecord belongs_to :user ・・・ end
また1対nの場合はhas_many
になります。以下のようなイメージになります
Userモデルに付与します
class User < ApplicationRecord has_many :microposts end
これを行うと、紐付いているユーザーを通してマイクロポストを作成することができます
例えばMicropost.new(content: "Lorem ipsum", user_id: @user.id)
は@user.microposts.build(content: "Lorem ipsum")
と書き換えられます
default_scopeメソッド
現状、user.microposts
とした時の並び順がランダムです。
これを順序を決定するためにdefault_scope
メソッドを使用します
ラムダ式(->)を使用し引数を設定し、データベースから要素を取得したときの、デフォルトの順序を指定します。
default_scope -> { order(created_at: :desc) }
Dependent: destroy
次にユーザを削除した時に、それ関連付けたMicropostのレコードがゴミとして残ってしまいますので、それを削除するようにします。
具体的にはhas_many
にDependent: destroy
オプションを付与します
class User < ApplicationRecord has_many :microposts, dependent: :destroy end
Micropost表示
次にツイートを表示します。ここではユーザの詳細ページに作成することにします。
このため、app/view/users/show.html.erb
に追記します。
<div class="col-md-8"> <% if @user.microposts.any? %> <h3>Microposts ( <%= @user.microposts.count %>)</h3> <ol class="microposts"> <%= render @microposts %> </ol> <%= will_paginate @microposts %> <% end %> </div>
ここで_micropost.html.erbパーシャルを指定するに<%= render @microposts %>
を指定します
<%= will_paginate @microposts %>
とwill_paginateに@micropostsを指定していますが、変数を省略できるのは、Usesコントローラのコンテキストにおいて、@usersインスタンス変数があるなど、同じコントローラ内の同インスタンス変数が存在している場合です
ここで、ユーザにひもづくmicropostsのレコード数を表示するためにはuser.microposts.count
を使用することでできます
Micropost操作
Micropostは新しい描写はいらなく、作成と削除しか行わないためcreate
とdestroy
があればいいです。route.rb
は以下のようになります
resources :microposts, only: [:create, :destroy]
次にログインしている場合にルートページにmicropostを表示するようにします
app/view/static_pages
<% 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>
ここでapp/views/shared/_user_info.html.erb
内で全ポスト数を表示します。
pluralize
この時pluralizeを使用し<%= pluralize(current_user.microposts.count, "micropost") %>
とすることで、count数によりmicropostを単数形、複数形で表示ができます
これは簡易にqiitaにまとめてみました(宣伝)
whereメソッド
また<%= render 'shared/feed' %>
について、実際にMicropostを表示します。
ここで、Micropostは複数レコードを表示することもありますので、
モデルではfind_by
ではなくwhere
を使用します
Micropost.where("user_id = ?", id)
ここで検索条件に?を指定することで、idがエスケープされるため、SQLインジェクション (SQL Injection) と呼ばれる深刻なセキュリティホールを避けることができます。
投稿削除
最後に投稿を削除します。この時どのユーザも好き勝手に削除するわけにはいきません。
・自分の投稿した投稿のみ削除可能
とする必要があります
これはcontrollerで実装します
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController before_action :correct_user, only: :destroy def destroy @micropost.destroy flash[:success] = "Micropost deleted" redirect_back(fallback_location: root_url) end private def correct_user @micropost = current_user.microposts.find_by(id: params[:id]) redirect_to root_url if @micropost.nil? end
destroyを行う前にcorrect_userメソッドで、削除しようとするmicropostがログインしているユーザに紐付くmicropostかどうかを判定します。
destroyメソッド内にredirect_back(fallback_location: root_url)
がありますが、これはRails5から追加されたメソッドで一つ前のURLを返します。
DELETEリクエストが発行されたページに戻すことができます。
元に戻すURLが見つからなかった場合はroot_url
に遷移します
Micropost画像表示
ここまでくると、ツイートを投稿と削除することができます。
次に画像を投稿する実装をします
ここでは画像関係のgemを使用します。
Gem追加
今回は以下のgemを使用します
・CarrierWave・・・画像uploader
・mini_magick・・・画像をリサイズ
・fog・・・本番環境にuploadする
gemfileについてはfog
のみ
group :production do gem 'fog', '1.42' end
とgroup :production の中に入れます
それ以外は全ての環境に追加します
あとは$bundle install
です
次に$ rails generate uploader Picture
でPicture uploaderの作成ができます
次に画像名をUserモデルに保存するために、pictureカラムにstring型を追加します
$ rails generate migration add_picture_to_microposts picture:string $ rails db:migrate
次にCarrierWaveに画像と関連付けたモデルを設定します。mount_uploader
を使用します
app/models/micropost.rb
class Micropost < ApplicationRecord mount_uploader :picture, PictureUploader end
画像の拡張子指定とサイズ指定(rails側での制御)
画像をuploadする際にjpgなど、画像以外の拡張子のファイルをアップデートしないようにします。
また、特定のサイズ以上の画像を上げれないようにします
app/uploaders/picture_uploader.rb
# アップロード可能な拡張子のリスト def extension_whitelist %w(jpg jpeg gif png) end
app/models/micropost.rb
についてはvalidateを使用します(独自のvalidateのため、validatesではなくvalidateを使用します)
class Micropost < ApplicationRecord validate :picture_size private # アップロードされた画像のサイズをバリデーションする def picture_size if picture.size > 5.megabytes errors.add(:picture, "should be less than 5MB") end end
また、フロント側でも制御します。こうすることでアップロードを行う前に警告を出すことができます
app/views/shared/_micropost_form.html.erb
<span class="picture"> <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %> </span> <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>
画像リサイズ
最後に、画像のリサイズを行います。極端にサイズが横長だったり、小さすぎる場合、レイアウトがいけていないのでそれを修正する実装を行います
ImageMagick
画像をリサイズするにはImageMagicを使用します。これはgemではないですので
Linuxの場合は$ sudo yum install -y ImageMagick
で
Macの場合は$ brew install imagemagick
でinstallします
MiniMagick
次にImageMagickとRubyをつなぐMiniMagickを使用して、画像をリサイズします。
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] end
最後に
ここまでくると記事投稿および画像投稿ができます。
画像などはAWSのS3サーバやその他ストレージサーバに上げる方法がよくとられます
ただ個人で開発する場合費用などが発生するため
そのあたりを含めストレージサーバを選択する必要がありそうです
あと1章で終わりですね
参考
今回も記事作りに参考になりました。ありがとうございます