この記事の内容は現在レビュー待ちなので変更あるかもです!
##STI(単一テーブル継承)とは
文字通り一つのテーブルを親クラスから子クラスに継承させる事!
今回の場合、親Tweet
→ 子Mediatweet
Texttweet
になります。それぞれメディアツイートとテキストツイート用のクラスです!
といっても、テーブルが同じなので、親も子も全てのクラスが同じカラムを持ってます。
なのでMediatweet
にもcontent
カラムがありますし、Texttweet
にもimg
カラムがあります。
create_table "tweets", options: hogehoge
t.text "content"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "img"
t.integer "user_id"
t.string "type"
end
##何故そうするのか
モデル事に差分を書き分けやすくなります。
例えば空欄無効のバリデーションをtexttweetにだけ書くと、画像はテキスト無しのツイートが可能になります。
でもほぼ同じ内容なので、テーブルは共有できちゃうSTIが良いのでは、というわけです。
##実装方法
1、しれ〜っとdb/schema.rb
に既にありましたが、親クラス(テーブル)にtype
カラム(string型)を持たせます。
2、各モデルを作ります。親モデルはrails g model
などで作って子モデルなどはapp/model/
に直接作ります
class Tweet < ApplicationRecord
end
class Mediatweet < Tweet
end
class Texttweet < Tweet
end
とりあえずこれだけでSTIの実装は終わりです。あとは、自動でやってくれます。
例えばこれでMediatweet.create
等でデータベース登録すればtype
カラムにMediatweet
とデータが入ったレコードが追加されます。
##form_withで取得したparamsの内容でモデル振り分け
メディアデータがあるかどうかで自動的にモデルを振り分けたいので下記のように実装しました。
<%= form_with model: @tweet, url: tweets_path do |form| %>
<%= form.text_area :content %><br>
<%= form.label "画像をアップロード" %><br>
<%= form.file_field :img %>
<%= form.submit '投稿' %><br>
<% end %>
今回は、indexにそのまま投稿フォームがある投稿兼一覧ページです。
画像のアップローダーについてはcarriewave
で実装しています。そこについては割愛します。
上記フォームからsubmitして送られるパラメータは
{
"authenticity_token"=>"hoge",
"tweet"=>{"content"=>"hoge", "img"=>"hoge"},
"commit"=>"投稿"
}
こんな感じの二重構造({}の中に{}がある構造)のパラメータが取得できます。
ちなみにですが
form_withで model: を指定するとこのような構造になり、url:のみ指定すると二重構造になりません。
僕はこれがわかってなくてかなりハマってました、、、
そしてコントローラーのcreateアクションへ自動でマッチします
def create
@tweet = tweettype_class.new(tweet_params)
if @tweet.save
redirect_to("/tweets")
else
@tweets = Tweet.all.order(created_at: :desc)
render("index")
#レンダリングで元の投稿件一覧ページに戻りますこの際、form_withのurlを指定していないと、
#form_withのルートが子モデルに引っ張られてエラーになります(例)元teets_path レンダリング後→texttweets_pathでno muchになる。
end
end
private
def tweet_params
#requireでキーがtweetの部分だけ抽出してます({"content"=>"hoge","img"=>"hoge"})
params.require(:tweet).permit(:content, :img).merge(user_id: @current_user.id)
end
#[:img]のデータの有無でモデルを振り分けています
#二重構造なのでキーも二重に指定しないと正しく値がとれません
def tweettype
case
when params[:tweet][:img].present?
'mediatweet'
when params[:tweet][:img].blank?
'texttweet'
end
end
#もらった文字列をキャメルケースに変換して,さらに定数名に変換しています
#(例)mediatweet.cmelize=>Mediatweet.constantize=>Mediatweet(id: integer, content: text, created_at: datetime, hogehoge.... )
def tweettype_class
tweettype.camelize.constantize
end
こんな感じで、画像がアップロードされたかどうかで自動でモデルを振り分けてみました。
こうすることで、それぞれのモデルで役割を書きわけやすくなりました!