LoginSignup
0
0

More than 3 years have passed since last update.

STIパターンでtweet機能を作った学習記録

Last updated at Posted at 2020-07-22

この記事の内容は現在レビュー待ちなので変更あるかもです!

STI(単一テーブル継承)とは

文字通り一つのテーブルを親クラスから子クラスに継承させる事!
今回の場合、親Tweet→ 子Mediatweet Texttweetになります。それぞれメディアツイートとテキストツイート用のクラスです!

といっても、テーブルが同じなので、親も子も全てのクラスが同じカラムを持ってます。
なのでMediatweetにもcontentカラムがありますし、Texttweetにもimgカラムがあります。

db/schema.rb
  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/に直接作ります

models/tweet.rb
class Tweet < ApplicationRecord
end
models/mediatweet.rb
class Mediatweet < Tweet
end
models/texttweet.rb
class Texttweet < Tweet
end

とりあえずこれだけでSTIの実装は終わりです。あとは、自動でやってくれます。
例えばこれでMediatweet.create等でデータベース登録すればtypeカラムにMediatweetとデータが入ったレコードが追加されます。

form_withで取得したparamsの内容でモデル振り分け

メディアデータがあるかどうかで自動的にモデルを振り分けたいので下記のように実装しました。

index.html.erb
<%= 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アクションへ自動でマッチします

tweets_controller.rb
  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

こんな感じで、画像がアップロードされたかどうかで自動でモデルを振り分けてみました。

こうすることで、それぞれのモデルで役割を書きわけやすくなりました!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0