dev/mom0tomo

技術メモ

Railsのモデルと一対多のリレーションのまとめ

読んだもの

Rails4で1対多のリレーションをモデルに実装する - Rails Webook Active Record の関連付け (アソシエーション) | Rails ガイド

学んだこと

下記のようなUser has many Itemsという関係のモデルを考える。

user.rb

class User < ApplicationRecord
  has_many :items
end

item.rb

class Item < ApplicationRecord
  belongs_to :user

  validates :user_id, presence: true
end

指定できるオプション

has_many / belongs_toメソッドには下記のオプションが指定できる。

  • class_name
    • 関連するモデルのクラス名を指定する。
    • リレーションの名前(has_manyの直後に指定する。上記の例ではitems)https://railsguides.jp/association_basics.htmlと参照先のクラス名を異なるものにできる。
  • foreign_key
    • 参照する外部キーの名前を指定する。
    • 指定しない場合、参照先のモデル名_idになる(上記の例ではuser_id)。

使えるようになるメソッド

has_many と belongs_to で一対多の関係を指定することで、下記のようなメソッドが使えるようになる。

userとitemの作成と保存に関するメソッド

# userを作成し、DBに保存する
user = User.create(name: "momo")

# item1を作成する
item1 = user.items.build(content: 'アイテム1') 

# item1をDBに保存する
item1.save

# item2をuserとの関係を示して作成し、保存する
item2 = user.items.create(content: 'アイテム2はuserのつくるitem')

userとitemのリレーションに関するメソッド

# itemオブジェクトの配列
user.items

# true # itemオブジェクトが存在するか判定する
user.items.present?

# userオブジェクト(itemを持っているuser)
item1.user

userの持つitemから検索するメソッド

# userの持つitemから条件を指定して検索する
user.items.find(...)

user.items.find_by(...)

user.items.where(...)

インスタンスメソッドの理解

ずっともやっとしてた部分を整理したらアハ体験があったのでまとめる。

こんなモデルがある場合。

models/user.rb

class User < ApplicationRecord
  has_many :items
end

models/item.rb

class Item < ApplicationRecord
  belongs_to :user

  validates :user_id, presence: true
end

中間テーブルはLikeとする。

models/like.rb

class Like < ApplicationRecord
  belongs_to :user
  belongs_to :item

  validates :user_id, presence: true
  validates :item_id, presence: true
end

上の関係は英文にするとこんな風に書ける。

User like item.

冠詞がないのと複数形じゃないのはご愛嬌。

モデル、メソッドのレシーバ・引数の関係を上記の英文を例に考えてみる。

  • モデルは主語を担う。(User)
  • レシーバは動詞を担う。(like)
  • 引数は目的語を担う。(item)

上の関係をメソッドで表す。

models/user.rb

class User < ApplicationRecord
  has_many :items

  has_many :likes
  has_many :like_items, through: :likes, source: :item

  def like(item)
    self.likes.find_or_create_by(item_id: item.id)
  end
end

主語はUserなので、Userモデルにメソッドを書く。
メソッドのレシーバが動詞なので、likeというインスタンスメソッドをつくる。
メソッドの引数は目的語なので、itemを引数にする。

Rails 5.1以降 でjqueryを使う

Rails5.1から、デフォルトではjqueryに依存しない仕様になった。

Ruby on Rails 5.1リリースノート | Rails ガイド

jqueryを使うためには下記の手順を踏むこと。

Gemの追加とbundle update

Gemfileに gem "jquery-rails"を追加し、bundle updateを実行する。

Gemfile.lockはこんな感じに変更される。

+    jquery-rails (4.3.3)
+      rails-dom-testing (>= 1, < 3)
+      railties (>= 4.2.0)
+      thor (>= 0.14, < 2.0)

-    sass (3.5.7)

+    sass (3.6.0)
       sass-listen (~> 4.0.0)
     sass-listen (4.0.0)
       rb-fsevent (~> 0.9, >= 0.9.4)

+  jquery-rails

application.jsに追加

application.jsに下記2行を追記する。

application.js

//= require activestorage
//= require turbolinks
//= require_tree .
# 以降追記
//= require jquery
//= require jquery_ujs

Railsアプリを作るときの共通パーツの設定

初期設定はこちら。 mom0tomo.hateblo.jp

共通レイアウトはapp/views/layouts/ に配置する。

headタグのパーツ

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>アプリケーション名</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <!-- Bootstrap CSS-->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <!-- app/assets/stylesheets -->
    <%= stylesheet_link_tag  'application', media: 'all', 'data-turbolinks-track': 'reload' %>

    <!-- app/assets/javascripts-->
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
    <!-- Bootstrap JavaScript-->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
  </head>

  <body>
  <%= render 'layouts/navbar' %>

  <%= yield :cover %>

  <div class="container">
    <%= render 'layouts/flash_messages' %>

    <%= yield %>
  </div>

  <%= render 'layouts/footer' %>
  </body>
</html>

flash[:success] / flash[:danger]などよく使う。

エラーメッセージのパーツ

app/views/layouts/_error_messages.html.erb

<% if モデル名.errors.any? %>
  <div class="alert alert-warning">
    <ul>
      <% モデル名.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

モデル名のところはあとで入れる。


以下は必須とは限らないがだいたい必要になるパーツ。

ナビバーのパーツ

app/views/layouts/_navbar.html.erb

<header>
  <nav class="navbar navbar-inverse navbar-static-top">
    <div class="container">
      <div class="navbar-header">
        <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="/">アプリケーション名</a>
      </div>
      <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
        <ul class="nav navbar-nav navbar-right">
          <li><a href="#">Signup</a></li>
          <li><a href="#">Login</a></li>
        </ul>
      </div>
    </div>
  </nav>
</header>

Railsの便利なコマンドと設定

新規アプリケーション作成するとき

DBの種類を設定する

$ rails new {app_name} --database=mysql

テストを作成しない

$ rails new {app_name} --skip-test

bundle installするとき

gemのインストールパスを指定する

$ bundle install --path vendor/bundle

Production環境のgemを除いてインストールする

$ bundle install --without production

CSS/JS/Helper/Routingを自動生成しない

config/initializers/generators.rbを作成し設定を書く。

Rails.application.config.generators do |g|
  g.stylesheets false
  g.javascripts false
  g.helper false
  g.skip_routes true
end

おまけ
gitignoreはこの辺りを設定しておく。
https://www.gitignore.io/api/vim,ruby,macos,rails,rubymine,intellij+all

モデル名と異なるアソシエーション名を使う場合の決まり

Rails tutorialでも出てくるuserがfollowings/followers(どちらもuserテーブルを使う)を持つ関係を例にまとめる。

models/user.rb

class User < ApplicationRecord

  # followingsのアソシエーション
  has_many :relationships
  has_many :followings, through: :relationships, source: :follow

  # followersのアソシエーション
  has_many :reverses_of_relationship, class_name: 'Relationship', foreign_key: 'follow_id'
  has_many :followers, through: :reverses_of_relationship, source: :user
end

中間テーブルはrelationshipsとする。

models/relationship.rb

class Relationship < ApplicationRecord
  belongs_to :user
  belongs_to :follow, class_name: 'User'
end

class_nameについて

モデル名_id という名前になっている場合、Railsでは自動的にモデルを参照する。 しかし follow_id のように命名規則に従っていないものを使う場合は、 class_name: 'User'と補足設定する必要がある。

through, sourceについて

既存のリレーションから中間テーブルを経由して向う側にあるモデルを参照するようにするためには、 through: ... , source: ...を使用する。

  has_many :followings, through: :relationships, source: :follow

これにより、 user.followingsと書けば、該当のuser がフォローしている User群を取得できるようになる。

仕組み

  • has_many :followings
    • usersとfollowingsの関係を新しく命名し、フォローしているUser群を表現している。
    • Followingというモデルは無いため、この後ろに取得する情報の補足を付け足している。
  • through: :relationships
    • has_many: relationshipsの結果を中間テーブルとして指定している。
  • source: :follow
    • 中間テーブルのカラムの中でどれを参照先の id とすべきかを選択している。
    • user_id というカラム名にすると重複してしまう為、フォローされる側は follow_id とする

上の仕組みによって、 user.followingsでuser が中間テーブル relationships を取得し、
その1つ1つの relationship の follow_id から 自分がフォローしている User 達 を取得する流れができる。


models/user.rbに見やすく色をつけるとこんな感じ。

has_many :relationships
has_many :followings, through: :relationships, source: :follow

has_many :reverses_of_relationships, class_name: 'Relationship', foreign_key: 'follow_id'
has_many :followers, through: :reverses_of_relationship, source: :user

Railsのアソシエーションの仕組み

理解するのにものすごく時間がかかったので、まとめておく。

アソシエーションとは

簡単にいうとモデルを参照するためのメソッド。
例は下記に示す。

メソッドはふだん使うときに書くが(当たり前)、アソシエーションは使い道も書き方も決まっていてるので、初めにまとめて書いておく決まり。

アソシエーションを利用するケース

usersとitemsという多対多のテーブルがあり、relationshipsという中間テーブルを使う場合を考える。

models/user.rb

class User < ApplicationRecord
  has_many :relationships
  has_many :items, through: :relationships
end

models/item.rb

class Item < ApplicationRecord
  has_many :relationships
  has_many :users, through: :relationships
end

models/like.rb

class Relationship < ApplicationRecord
  belongs_to :user
  belongs_to :item
end

下記のように使うことができるが、

@user = item.user

この理由は、 models/item.rb

has_many :users, through: :relationships

上記のアソシエーションはもともと以下のようなメソッドを省略したものだから。

def user
  User.find_by(id: Relationship.find(self.id).item_id)
end