おーしまブログ

プログラミングやってます

<rails>データとタグと中間テーブルを同時に保存する方法

こんにちは、おーしまです。

今回は、自動車レビューアプリのレビュー投稿機能まで実装できたので、一緒に見ていこうと思います。データとタグと中間テーブルを同時に保存する方法は下の方にあるので、飛ばしてください。

今の出来はこんな感じです。
f:id:tomo_bb_aki0118115:20201027214643p:plain

投稿機能までできて、「そろそろhtml,cssを作っていかないとまずいかな」と思ったので、少しずつそちらの編集も始めました。

「新規レビュー」ボタンから、新規レビュー作成画面に遷移できるようになっています。新規レビューは「タイトル」と「本文」の2つがあれば投稿可能になっていて、今後タイトルと写真だけでも投稿できるようにします。また、新しく投稿したものから、上から順に表示されるようになっています。



実際に、投稿してみましょう。

「新規レビュー投稿」をクリックすると、新規レビュー作成画面に遷移できます。
f:id:tomo_bb_aki0118115:20201027215923p:plain

おっと、ログインしていなかったので、ログイン画面に遷移しました。
Sign up」を押して新規登録画面に遷移しましょう。
f:id:tomo_bb_aki0118115:20201027220317p:plain

好きな車と自己紹介の欄は記入しなくても、ユーザー登録ができるようになっています。
これで、レビューを投稿することができるようになりました。
新規レビュー投稿画面に移動します。

f:id:tomo_bb_aki0118115:20201027220912p:plain

フォームに対して何の説明も書いてありませんが、左から順に、
タイトル、画像、本文、メーカー、車名、ボディタイプ、タグ
という風になっています。
これで「投稿する」を押すと、、、
f:id:tomo_bb_aki0118115:20201027221245p:plain

このように一番上に表示されました。

見た目がカスなので、あまり良い出来には見えませんが、何とかできたのでよかったです。この程度に二日もかけてしまったのが本当に情けない。。。
それではコードの方も説明します。

まず今回は、レビューとタグが多対多の関係なので、中間テーブルが必要なのと、レビューとタグに同時に保存しなければいけないのでさらにもう一つモデルが必要で、計4つのモデルが必要になります。そして、メーカーとボディタイプはActiveHashGemを用いて、選択できる形にします。

class Maker < ActiveHash::Base
  self.data = [
    { id: 1, name: nil },
    { id: 2, name: 'トヨタ' },
    { id: 3, name: '日産' },
    { id: 4, name: 'ホンダ' },
    { id: 5, name: 'マツダ' },
    { id: 6, name: 'スバル' },
    { id: 7, name: 'ダイハツ' },
    { id: 8, name: '三菱' },
    { id: 9, name: 'スズキ' },
    { id: 10, name: 'レクサス' },
    { id: 11, name: 'フォルクスワーゲン'},
    { id: 12, name: 'BMW' },
    { id: 13, name: 'メルセデス・ベンツ' },
    { id: 14, name: 'アウディ' },
    { id: 15, name: 'ボルボ' },
    { id: 16, name: 'プジョー' },
    { id: 17, name: 'ランドローバー' },
    { id: 18, name: 'ポルシェ' },
#以下省略
class BodyType < ActiveHash::Base
  self.data = [
    { id: 1, name: nil },
    { id: 2, name: '軽自動車' },
    { id: 3, name: 'コンパクト' },
    { id: 4, name: 'セダン' },
    { id: 5, name: 'ワゴン' },
    { id: 6, name: 'SUV' },
    { id: 7, name: 'ミニバン' },
    { id: 8, name: 'クーペ' },
    { id: 9, name: 'オープンカー' }
  ]
end

「オススメの車はありませんか?」みたいな投稿もできるようにしたかったので、メーカーなどは空欄で投稿できます。そのため id:1 は nil という形にしました。これで表示するときに、メーカーなどの欄は空白になります。

次はコントローラーです。

class CarsController < ApplicationController
  before_action :authenticate_user!, only:[:new]

  def index
    @cars = Car.all.includes(:user).order('created_at DESC')
  end

  def new
    @car = SaveCarsTag.new
  end

  def create
    @car = SaveCarsTag.new(car_params)
    if @car.valid?
      @car.save
      return redirect_to root_path
    else
      render :new
    end
  end

  private

  def car_params
    params.require(:save_cars_tag).permit(:title, :image, :text, :maker_id, :car_name, :body_type_id, :name).merge(user_id: current_user.id)
  end
  
end

特に工夫した点はありません。、、、、そのままですね。以前、ブログに書いたようなコードはもう当たり前に使えるようになりました。成長。
2つのテーブルに同時に保存するため、新しくSaveCarsTagというモデルを作っています。そのモデルのインスタンスを生成して、newアクションからフォームに持って行っていますね。それで帰ってきたparamsをvalid?で振り分けています。保存できるようであればSaveCarsTagモデルのsaveに持っていきます。

class SaveCarsTag

  include ActiveModel::Model
  attr_accessor :title, :image, :text, :maker_id, :car_name, :body_type_id, :name, :user_id


  with_options presence: true do
    validates :title
    validates :text
  end

  def save
    car = Car.create(title: title, image: image, text: text, maker_id: maker_id, car_name: car_name, body_type_id: body_type_id, user_id: user_id)
    tag = Tag.where(name: name).first_or_initialize
    tag.save
    
    CarTag.create(car_id: car.id, tag_id: tag.id)
  end

end

include ActiveModel::Model
で、普通のクラスをモデルのように扱うことができるようになります。

attr_accessor はゲッターとセッターを定義してくれるメソッドで、簡単にいうと、これを書くことで普通のクラスなのに他のモデルのカラムを保存したり、取り出したりすることができるようになります。

その下に、このクラスで取り扱うデータのバリデーションを記述しています。

あとは、それぞれのデータをcarsテーブル、tagsテーブル、car_tagsテーブルに保存して、終わりです。

tag = Tag.where(name: name).first_or_initialize
tag.save

これで、すでに保存してあるタグがないか探して、あれば保存しない。無ければ保存する。という流れにしています。
これは正直、ちゃんと把握していません。すいません。
とにかく、これ書いとけば良いです。



今回はここまでにします。
明日は、HTML、CSSに捧げます。
おやすみなさい。

ここはどこ おれはだれ それに近いものがあんだよ 始めようとした奴らも迷い始めてる 怖がらせないでよ そりゃ甘くはないけど まだまだ 夢見ていい世界なんでしょ {UVERwould「ハイ!問題作」}