1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

基礎Ruby on Rails Chapter10 モデル間の関連付け/ブログ機能の追加(モデル編)

Last updated at Posted at 2018-10-07

基礎Ruby on Rails Chapter9 ActiveRecord/スコープ/ページネーション
基礎Ruby on Rails Chapter10 モデル間の関連付け/ブログ機能の追加(コントローラ/ビュー編)

関連付けの概要

モデル間の関連付けと外部キー

  • テーブルの中で、別のテーブルの主キーを参照するカラムのことを外部キーという。

  • 変数@carにモデルクラスCarのインスタンスがセットされているとき、次の式はその車輪(Wheel)の集合を表すリレーションオブジェクトを返す。

Wheel.where(car_id: @car.id)
  • しかし、Rubyではもっと直感的な書き方がある。
@car.wheels
  • wheelsはCarクラスのインスタンスメソッド。
  • where、order、firstなどのメソッドを連結することができる。
@car.wheels.where(coloer: "red").order(:created_at).first
  • wheelメソッドを使うには、以下のようにhas_manyで子を定義する必要がある。
has_many :wheels

関連付けを作るメソッド

1対多の関連付け

  • 親は、子をhas_manyで定義する。
class Car < ApplicationRecord
  has_many :wheels
end
  • 子は、親をbelongs_toで定義する。
class Wheel < ApplicationRecord
  belongs_to :car
end
  • これにより、@car.wheelsで参照元のモデルオブジェクトの集合を取り出したり、@wheel.carで参照先のモデルオブジェクトを取り出したりできる。

  • 車輪を作成し、自動車に関連付けて保存する方法

@wheel = Wheel.new
@wheel.car = @car
@wheel.save
  • 逆に、自動車のほうから車輪を関連付けるには、<<で追加する。
  • これにより、関連付けと車輪のレコードの保存が同時に行われる。
@car.wheels << @wheel
  • 車輪を自動車に結び付け、保存は行わないようにするには、wheels.buildのようにbuildメソッドを使う。引数にはハッシュでモデルの属性を指定できる。
@car.wheels.build(name: "車輪1")
# ハッシュを複数指定することができる
@car.wheels.build({ name: "車輪1" },{ name: "車輪2" })
  • wheelsメソッドが返すのはリレーションオブジェクト。なので、集計用のメソッドやクエリーメソッドを呼び出せる。
# 車輪の数を返す
@car.wheels.count
# クエリーメソッドを返す
@car.wheels.order("created_at DESC")

命名規約とオプション

has_manyとbelongs_toでモデル間の関連付けを表すときには、以下のようなルールがある。

  • 外部キーのカラム名は、参照先のテーブル名の単数形_id。例、car_id

  • belongs_toに指定する名前は、テーブル名の単数形

  • has_manyに指定する名前は、テーブル名の複数形

  • 外部キーのカラム名がルールと異なるときは、foreign_keyオプションでカラム名を指定する。

# car.idとengine.vihicle_idでjoinする(cat_idではなく)
class Car < ApplicationRecord
  has_many :engines, foreign_key: "vihicle_id"
end

class Wheel < ApplicationRecord
  belongs_to :car, foreign_key: "vihicle_id"
end
  • 関連付けで使われるメソッド名を変えたい場合は、class_nameオプションを使う。
  • メソッド名をmotorsでなく、enginesにしたい場合は、class_nameオプションに本当のクラス名を指定する。
# car.enginesが使えるようになる。実際はMotorモデルが存在する。
class Car < ApplicationRecord
  has_many :engines, class_name: "Motor"
end
  • dependentオプションを:destroyにすると、参照先のレコードを削除した時に、参照元のレコードも自動的に削除される。
  • dependentオプションを:nullifyにすると、参照先のレコードを削除した時に、参照元の外部キーがNULLになる。
class Car < ApplicationRecord
  has_many :wheel, dependent: :destroy
end

会員ブログ関連モデルの準備

ブログ記事の関連付け

Entryモデルの作成

  • rails gコマンドで、Entryモデルを作成する。
$ bin/rails g model entry
      invoke  active_record
      create    db/migrate/20181006230957_create_entries.rb
      create    app/models/entry.rb
  • 上記で生成されたマイグレーションスクリプトを修正する。
  • t.references :memberは外部キーの定義で、entriesテーブルに整数型のmember_idカラムが追加される。
    • referencesを使うと、外部キーにインデックスが設定される。設定したくない場合は、, index: falseを追加する。
db/migrate/20181006230957_create_entries.rb
class CreateEntries < ActiveRecord::Migration[5.2]
  def change
    create_table :entries do |t|
      
      t.references :member, null: false   # 外部キー
      t.string :title, null: false    # タイトル
      t.text :body    # 本文
      t.datetime :posted_at, null: false    # 投稿日
      t.string :status, null: false, default: "draft"   # 状態(draft/member_only/public)

      t.timestamps
    end
  end
end
  • マイグレーションを実行する。
  • マイグレーションスクリプトに誤りがあってエラーが出た場合は、修正後bin/rails db:rebuildを実行する。
$ bin/rails db:migrate

モデル間の関連付け

  • Memberモデルにhas_manyメソッドでEntriesと関連付ける。member.id=entry.member_id
  • 会員はブログ記事を複数持つ。メンバーは削除されると、記事も削除される。
app/models/member.rb(一部)
  has_many :entries, dependent: :destroy
  • 子モデルは、親モデルを定義する。authorメソッドを追加し、本当のモデルであるMemberを指定する。
  • 外部キーは、member_idとする。
app/models/entry.rb
class Entry < ApplicationRecord
  belongs_to :author, class_name: "Member", foreign_key: "member_id"
end

Entryモデルでの準備

モデルの属性名の日本語化

  • 以下のようにモデルの属性名をロケールテキストに追加する。
config/locales/ja.yml(一部)
ja:
  activerecord:
    models:
#(省略)
      entry: ブログ記事
#(省略)
    attributes:
      entry:
        title: タイトル
        body: 本文
        posted_at: 日時
        status: 状態
        status_draft: 下書き
        status_member_only: 会員限定
        status_public: 公開

バリデーション

  • titleは空禁止、200文字以内。
  • 本文と投稿日は空禁止。
  • 状態は、下書き/会員限定/公開のいずれか。
app/models/entry.rb(一部)
  STATUS_VALUES = %w(draft member_only public)

  validates :title, presence: true, length: {maximum: 200}
  validates :body, :posted_at, presence: true
  validates :status, inclusion: { in: STATUS_VALUES }

ブログ記事を絞り込むスコープ

  • 以下のようにスコープを追加する
app/models/entry.rb(一部)
  # 公開記事のみ
  scope :common, -> {where(status: "public")}
  # 下書き以外(公開・会員限定)
  scope :published, -> {where("status <> ?", "draft")}
  # 下書き以外と、自分が書いた記事全部
  scope :full, -> (member) {where("status <> ? OR member_id = ?", "draft", member.id)}
  # ログインしていればfull、していなければcommon
  scope :readable_for, -> (member) {member ? full(member) : common}

ビュー用のメソッド

app/models/entry.rb(一部)
  class << self
    # statusを引数に、日本語文字列を取得する
    def status_text(status)
      I18n.t("activerecord.attributes.entry.status_#{status}")
    end

    # [["下書き", "draft"], ...]のような配列を作成する。画面上のリストに使用する。
    def status_options
      STATUS_VALUES.map {|status| [status_text(status), status]}
    end
  end

シードデータ

  • シードデータの読み込み設定にテーブルを追加する。entriesテーブルを読み込みする。
db/seeds.rb(一部)
table_names = %w(members articles entries)
#(省略)
  • 以下のようなシードデータを追加する。
db/seeds/development/entries.rb
body =
  "今晩は久しぶりに神宮で野球観戦。内野B席の上段に着席。\n" +
  "先発はヤクルトがブキャナン、広島はジョンソン。" +
  "2回裏に中村選手のセーフティスクイズなどでヤクルトが3点を先取。" +
  "そして、8回裏には代打・荒木選手がレフトスタンドへ2号満塁ホームラン。\n" +
  "ブキャナン投手の今季初完封を見届けて、気分良く家路に着きました。"

%w(Taro Jiro Hana).each do |name|
  member = Member.find_by(name: name)
  0.upto(9) do |idx|
    entry = Entry.create(
      author: member,
      title: "野球観戦#{idx}",
      body: body,
      posted_at: 10.days.ago.advance(days: idx),
      status: %w(draft member_only public)[idx % 3])
  end
end
  • データベースの再構築を行い、シードデータをセットする。
bin/rails db:rebuild
  • データが無事入りました。

image.png

参考
改訂4版 基礎 Ruby on Rails (IMPRESS KISO SERIES)

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?