いいね機能のDB設計
よくある「いいね機能」のDB設計を考える。
ツイッターで言えばいわゆる「ファボ」機能。
前提条件
・UserモデルとPostモデルが存在する
・User has_many posts という関連付けが行われている
ER図
Like Modelを作る
Postにいいねしたユーザー一覧を入れるという力技も出来そうですが
SQLアンチパターンで言う所の「ジェイウォーク」になりそうです。
カラムに複数の値を入れると将来運用上のデメリットしかありません。
なので定石ではありますが新しくLikeモデルを作成し、
1レコード=1つのLike
を表して、1カラムに1つの値だけが入るようにします。
外部キーを設定してLIkeモデルは必ずUser, Postと整合性が保たれるようにします。
$ rails g model Like user:references post:references $ rais db:migrate
レコードの挿入と削除
あとはいつも通りActiveRecordを使ってSQLを発行するだけ。
ただLike専用のビューは作らないと思うのでuserやpostのビューから
効率的にアクセスする為に関連付けが必要
#models/user.rb has_many :likes, dependent: :destroy
#models/post.rb has_many :posts, dependent: :destroy
これで
User.find(1).posts.find(1).likes.count
などをしてlikeモデルにアクセスできる。
また、User, Postどちらが削除された時に紐付くLikeも削除されます。
一意性のバリデーション
Likeはuser_id属性とpost_id属性を持ちますが、
この2つの組み合わせは常に一意(unique)です。
なのでこの組み合わせに対して一意性のバリデーションを設定しておきます。
#models/like.rb validates :user_id, uniqueness: { scope: :post_id }
なお、このバリデーションのエラーメッセージは通常のuniquenessのままなので
状況に合わせてmessageオプションで変更しておきましょう。
throughオプションを使う。
こちらの図のような関連付けが行われている時に
「あるuserがlikeしたpostを抽出したい」という場面があると思います。
そんな時は
1.Userに紐付くLikeを抽出し、selectメソッドを利用してpost_idだけを抜き出す
2.Post.where(post_id: ? )に1の結果を入れる
といったようなややこしいことをしないといけません。
そこでthroughオプションを使って簡潔にアクセスできるように変更します。
#/app/models/user.rb has_many :liked_posts, through: :likes, source: :post
上記の記述でlikesを通してpostへアクセスする
liked_postsというメソッドが出来上がります。
これで新しいメソッドliked_postsが使えます。
@user.liked_posts
とするだけでuserがlikeしたpostオブジェクトが返ってきます。
Controllerへの実装
controllerへの実装は他のモデルと同じようにすれば大丈夫ですね。
ルーティングはlikes#createとlikes#destoryだけで良いのではないでしょうか。
Userモデルに紐付くModelの作り方
かなり基本の部類に入るとは思うがUserモデルに紐づくModel
(例えばPostだったりTaskだったりTweetだったりする)
の作り方を確認してみる
Modelの生成
まずはgenerate(カラムはお好み)
$ rails g Model Post title:string content:text user:references
user:referencesをつけることでmigrationファイルに
t.references :user, foreign_key: true
が追記されます。
さらにmodels/post.rbに
belongs_to :user
が追記されています。
userテーブルに対して外部キーが設定されますのでPostとUserの整合性が保たれます。
また、下記のようにインデックスを貼っておくのが定石のようです。
#user_idとcreated_atに対してインデックスを追加 add_index :posts, [:user_id, :created_at]
Migrationとmodels/user.rbの編集
そしてmigration
$ rails db:migrate
またuser.rb側でも関連付けを行います。
(dependent: :destoryは親を削除した時に関連する子も削除する設定)
has_many :posts, dependent: :destroy
これによりUserのインスタンスからPostモデルにアクセスできるようになりましたので
user_controllerでは関連付けを利用してモデルへアクセスしましょう。
特定のvalidationをスキップする
WEBサービスを作っている際に、
「passwordのバリデーションが邪魔だな、、、、」と思う場面がありました。
具体的にはユーザーのプロフィールを変更する時にパスワードを入力せずに更新したい、 つまりパスワードのバリデーションのみ適用無しにしたい、というケースでした。 いくつか方法があるようです。
onオプションを使用する
onオプションを使用すれば特定のアクションにのみバリデーションを適用できます。
#例(createアクションのみにpresenceが適用される) validates :name, presence: true, on: :create
before_actionやrouteのonlyオプションのイメージですね
update_columnsを使う(非推奨)
バリデーションをそもそも行わないupdate_columnsを使う。
しかし全てのカラムからvalidationが外れてしまうので非推奨。
カラム1つだけの更新であればupdate_columnを使う手もありますが
今回はpassword以外の複数カラムを更新したいので使えません。
Railsのメッセージを日本語化する
Railsで表示されるメッセージ(エラーや曜日等)はもちろんデフォルトで英語です。
これらのRailsで用意されているメッセージを日本語化する。
I18nはRails自体に同梱されているのでGemfileに追記は不要。
流れとしては
1. locale: ja 用の翻訳ファイルをダウンロードする(または作成)
2. デフォルトのlocaleをjaに設定する。
日本語翻訳ファイルを手に入れる
翻訳の元となるファイルをconfig/localesに配置する必要があります。
自分で作成しても良いですが既に作成されたものがあるので
ダウンロードしてconfi/localesに配置しましょう。
DL先:https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/ja.yml
デフォルトのlocaleを :ja に設定する
config/initializer/locale.rbを作成し以下に変更
I18n.config.available_locales = [:en, :ja] I18n.default_locale = :ja
localesのホワイトリストを:enと:ja、デフォルトを:jaに設定
Active Recordに翻訳ファイルを対応させる
以下Rails Guideに掲載されているサンプルコード
en: activerecord: models: user: Dude attributes: user: login: "Handle" # "login"属性は"Handle"という語に翻訳される
ActiveRecordに対応させるには上記のように
modelsにテーブル名、attributes以下にカラム名の翻訳情報を書く。
model_name.humanメソッド、human_attribute_nameメソッドを使って
それぞれの翻訳情報にアクセスできる
User.model_name.human #=>"Dude" User.human_attribute_name(:login) #=>"Handle"
参考リンク
コミットメッセージの良い習慣を身につける
コミットメッセージの良い習慣を身につける
チーム開発を前提として他の人が見たとき、または未来の自分が見た時に
分かりやすいコミットメッセージをつけるべきである。
基本としては下記の3点を抑える
・3行構成でコミットメッセージ書く
・内容は「更新内容」+「空行」+「更新理由」
・タイトルにはPrefixをつける
3行構成でコミットメッセージを書く
コミットメッセージは複数行で書くのが基本。
git commit -m 'message'
と mオプションを指定している場合は1行のコミットメッセージになりますが
-mを指定しない場合はvimが起動して複数行の記入が可能です。
また
git commit -m '1行目' -m '2行目' -m '3行目'
と指定することで複数行のメッセージを書くことも可能。
「更新内容」+「空行」+「更新理由」の構成
3行のコミットメッセージは更新内容、空行、更新理由という構成にします。
$ git commit
[prefix] title body
titleには「何をしたか」が具体的に分かるように。
bodyには「なぜそうしたか」という理由が分かるように書く。
titleだけで具体的に伝われば一番良いと思う。
ちなみに2行目の空行が無くてもGitHubの表示に問題は無かった。
しかしlogを取ったりする時に上手く行かない場合があるらしいのでしっかり空行は入れたほうが吉。
Prefixをつけよう
コミットメッセージにはPrefixをつけよう。
例えば、
「post_controllerを追加」
なら
[Add]post_controllerを追加
という形。
メールで件名の頭に【報告】だったり【依頼】とつけるようなイメージ。
これは開発チームで規約化するので世界標準でカチッと決まっているわけではない。
それぞれのプロジェクトに沿って運用する。
また、Prefixをビジュアルとして表現するEmoji_Prefixを使っている人も見かける。
参考リンク
GitHubのコミットメッセージに絵文字を入れて開発効率をあげる - Qiita
Emojiで楽しく綺麗なコミットを手に入れる | Goodpatch Blog
GitのコミットメッセージにEmoji Prefixを使ってテンションをあげたい話🕺💃🕺💃 - LiBz Tech Blog
Gitのコミットメッセージの書き方 - Qiita
Railsで検索フォームを実装する(ransack)
Ruby on Railsで検索フォームを実装する方法。
ransackというgemを使って実装できる。
ちなみにransackは「くまなく探す」という意味らしい。凄い検索してくれそう。
githubレポジトリはこちら(https://github.com/activerecord-hackery/ransack)
gem 'ran sack' $ bundle
Controllerの記述は
def index @q = User.ransack(params[:q]) @users = @q.result(distinct: true) end
のような形になります。
ちなみにdistinctをつけることでSQLが
SELECT hoghoge FROM ~
から
SLECT DISTINCT hogehoge FROM ~
に変わります。(重複が無くなる)
Viewの方ではransackを入れたことにより
search_form_forヘルパーが使用できます。
これを使ってフォームエリアを作ります。
<%= search_form_for @q do |f| %> <%= f.label :name_cont %> <%= f.search_field :name_cont %> <%= f.submit %> <%= end %>
フィールドの :name_cont は 「:nameカラムに対してcontで検索する」という意味になります。
contはSQLのLIKE句と同じ意味になります。
ここら辺のmatcherについては公式レポジトリのMatcher項に記載されています。
このようにransackはviewのmatcherとcontrollerのresultをカスタムして使う。
Railsルーティングのあれこれ(routes.rb)
Railsのルーティングの指定方法に色々種類があったので 自分用に整理する
基本形
get '/login', to: 'sessions#new'
httpメソッド 'パス', to: 'コントローラー#アクション'
という書き方。
CRUD機能を一括で指定
resources :users
resources :コントローラー名
でCRUDの各機能を満たすルーティングが一括で指定される。
上記の例でいうと
get '/users', to: 'users#index' get '/users/:id', to: 'users#show' get '/users/new', to: 'users#new' post '/users', to: 'users#create' get '/users/:id/edit', to: 'users#edit' put '/users/:id', to: 'users#update' delete '/users/:id, to: 'users#destroy'
の7つのルーティングが一括で指定される。 基本的な機能は一発でルーティングされるがURLパターンを変更したい場合(login等)は個別に指定するべし
resourceから特定の機能のみルーティング
resourcesメソッドの第二引数に'only: []'をつけることで特定のアクションのみ実装する
#indexとshowアクションのみにルーティング resources :users, only: [:index, :show]
namespace(名前空間)を指定する
Controllerに名前空間が指定されている場合、routes.rbにもnamespaceを指定する。
#admin/posts_controllerを指定する場合 namespace :admin do get '/users', to: 'users#index end
上記のroutesは
'admin/users', to: 'admin/users#index
と同じになる。
scopeを使って柔軟に指定する
URLパターンだけ一括で変更したい、逆にコントローラーだけ一括で指定したい。
という場合にはscopeを使う。
scope :admin do get '/users', to: 'users#index end
これは
get 'admin/users', to: 'users#index'
を同じになる。(URLパターンだけ名前空間が付与される)
scope module: 'admin' do get '/users', to: 'users#index end
は
get 'users', to: 'admin/users#index'
と同じ。(コントローラーにだけ名前空間が付与される。)