Model(rails)

Model

Modelクラス

「モデル」でデータベースから情報を取り出す。
あるデータベースから取り出せるモデルは限定されており、命名にも強い関連がある。

コントローラでデータのやり取りをするために必要で、通常はコントローラー作成後にモデルも作成する。

種類命名規則
モデルクラス名先頭は大文字、単数形Product
モデルクラスのファイル名先頭は小文字、単数形product.rb
テーブル名先頭は小文字、複数形products

以下のようにしてモデルを作成する。命名規則からhogehogeは全て小文字

rails g model hogehoge

migrationファイル

データベースを作成するための設計図のようなもの。
前述のmodelをgenerateした際に同時に作成されている。

  class CreateTweets < ActiveRecord::Migration[5.2]
    def change
      create_table :tweets do |t|
        t.string      :name
        t.text        :text
        t.text        :image
        t.timestamps null: true
      end
    end
  end

t.string ~ :imageまでは自分で作成。
テーブルに必要な型とカラム名を指定する。
t.型 :カラム名と記述。

このままだと設計図止まりなのでテーブルを作成する。

rake db:migrate

rails generate migrationコマンド

テーブルにカラムを追加したり、削除するようなテーブルの構造を変えたい時もマイグレーションファイルを作成する。
rails generate migrationコマンドで新たなマイグレーションファイルを作成した後、
rake db:migrateでカラムを追加/削除する。

bundle exec rails g migration AddRateToProducts
#migrationファイルを編集
rake db:migrate

テーブルへカラムを追加  add_column

マイグレーションファイルのchangeメソッド内に書いてカラムを追加する。

add_column :テーブル名, :カラム名, :カラムの型

なおmigrationファイルを作成する際に指定することもできる。

rails g migration AddIntroductionToUsers introduction:text

Addカラム名Toテーブル名 追加するカラム:型
Addの後のカラム名に関してはなんでもいい。
AddColumnToUser とか。見てわかりやすい名称が望ましいだけなので好みで。

テーブルからカラムを削除

migrationファイルを作成

rails g migration Removeカラム名From削除元テーブル名 削除するカラム名:型

rakeする

rake db:migrate

データの型の変更

rails g migration change_datatype_変更したいカラム名_テーブル名

この表記をよく見るが、結局できるのは空のmigrationファイル。

class ChangeDatatypeUserIdPosts < ActiveRecord::Migration[5.2]
  def change
    change_column :posts, :user_id, :integer
  end
end

change_colomnでテーブル名、カラム名、型を指定することで変更する。
migrationファイルができたらrake db:migrateして終了

属性の貼り方

index

add_index / remove_index

migrationファイルに書き込む場合は以下

class CreateGroups < ActiveRecord::Migration[5.0]
  def change
    create_table :groups do |t|
      t.string :name, null: false
      t.index :name, unique: true
    end
  end
end

unique: trueは必要ないですが、一意制約を掛けたい場合に使ってください。
t.string :name, index: trueとも書けるようですが、あまり書かれてる形跡がないので使わない方がよさそう。

後からindexを張る場合にはこちら。

  def change
    add_index :products,  :title
  end

コマンドで一気に作成するには以下

rails g migration AddTitleToProducts title:sting:index

外部キー制約

多くの場合IDを外部キーとして用いるが、integerではなくreferences型と宣言する。
その上でforeign_key: trueにする。

Add_Columnのときはreferenceと単数形

class AddIntroductionToMembers < ActiveRecord::Migration[5.0]
  def change
    add_reference :members, :user, foreign_key: true
    add_reference :members, :group, foreign_key: true
  end
end

新規にmigrationファイルに書き込んで定義する時は複数形
(こんなん分かるわけないだろ)
ちなみにわざわざuser_idと書く必要はなく、テーブル名の記載にすれば、自動的にuser_idのカラムが作成される。

t.references :user, foreign_key: true

外部キーに設定すると、mySQL上ではMUL(multiple key)という名称になる。

Railsの外部キー制約とreference型について - Qiita
Railsで外部キーのカラムを追加する際に、reference型を使うことがあると思います。 reference型の使い方に関しては以下のように様々な記事があります。 外部キーをreferences型カラムで保存する Rails4 ...

カラムの足し引きと外部キー

足す場合

rails g migration AddColumnToTable

migrationファイルを作成して、以下を書き込む

class AddIntroductionToMembers < ActiveRecord::Migration[5.0]
  def change
    add_reference :members, :user, foreign_key: true
    add_reference :members, :group, foreign_key: true
  end
end

rails db:migrateして終了

引く場合

 rails g migration RemoveUser_idFromMembers

Remove_Fromでmigrationファイルを作成。

def change
  remove_column :users, :name
end

アソシエーション

モデルクラスにhas_manyやbelongs_toなどの定義する。

class Hogehoge < ApplicationRecord
  belongs_to :user(単数形)
end
class User < ApplicationRecord
  has_many :hogehoges(複数形)
end

このように定義するとuserに所属するhogehogeが簡単に呼び出せる。

アソシエーションなし。
user = User.find(1)
Hogehoge.where(user_id: user.id)

アソシエーションあり。
user = User.find(1)
user.hogehoges

Userのインスタンスから所属するhogehogeを引くことができる。
逆も然りで、HogehogeのインスタンスからUserを引くこともできる。
hogehoge.userでユーザーのインスタンスを取得できる。

has_many throughオプション

through: :photos_tags 両方ともシンボル
多対多を解消する際に用いる。
顧客ー注文ー商品のようなテーブル構造にした際、連携するテーブルと中間テーブルをhas_manyで連携し、なおかつ中継テーブルはthroughオプションを使う。
中間テーブルは慣例として、名称を連携テーブル名1_連携テーブル名2にする。

# app/models/photo.rb
class Photo < ActiveRecord::Base
  has_many :photos_tags
  has_many :tags, through: :photos_tags
end

# app/models/tag.rb
class Tag < ActiveRecord::Base
  has_many :photos_tags
  has_many :photos, through: :photos_tags
end

# app/models/photos_tag.rb
class PhotosTag < ActiveRecord::Base
  belongs_to :photo
  belongs_to :tag
end
オプション名用途
class_name関連するモデルのクラス名を指定でき、関連名(photos_tags等、has_manyの直後に書くもの)と参照先のクラス名(PhotosTagのようなモデル名)を異なるものにできる
foreign_key参照先を参照する外部キーの名前を指定できる(デフォルトは、参照先のモデル名_id)
dependent親モデルのデータを消したら関連するモデルのデータも連動して消したいときに使用します。destroyとdelete_allでひとつひとつ消していくか、一気に消すかを指定できる
source関連テーブルから先のモデルにアクセスするための(関連モデルから見た)関連名を指定できる

中間テーブル

userとgroupで中間テーブルを組む場合、単数形で命名する。

rails g model user_group

この時、テーブル名はuser_groupsとなったり、
has_many :users, through: :user_groups
となったり、単数複数が入り混じるが仕方がない。
気になる場合は中間テーブルを別名にしたりで対処する。

dependent: :destroy

親が削除された際の挙動を決める。
destroyの場合は削除。

has_many :shippings, dependent: :destroy

validation

モデルの作成後はvalidationを追加する。

class Group < ApplicationRecord
  has_many :members
  validates :name, presence: true
end

rakeコマンド

rake db:reset

全てのテーブルをドロップして、schemaを元にmigrateする。

rake db:migrate:reset

全てのテーブルをドロップして、migrationファイルを元にmigrateする。

rake db:rollback

1つ前のmigration状態へ戻る。

rake db:version

現在のmigrationの位置を見れる。

validation (検証)

入力フォームを通じてビューからサーバー側へパラメーターが送られてきた際、正常な値か検証する。未入力を撥ねるために使う。

 validates :カラム名, presence: true

フォームの中身の有無を検出してくれる。ない場合には保存処理をしない。

例えば、userのemailを入力必須にしたい場合、以下のように書く。
user.rb

class User < ApplicationRecord
 validates :email, presence: true 

ユーザーの新規登録時にemailを入力しなかった場合、userを登録することができなくなる。

Validator

モデルの役割の一つにバリデーションがある。
バリデーションとはデータの整合性を保つために、データを検証する機能のこと。

あるモデルのバリデーションに複雑な処理があったり、複数のモデルに共通のバリデーションが存在する場合にはそれらをモデルから切り離すことでリファクタリングが可能。

app/models/articles.rb

class Article < ActiveRecord::Base
  validates :name, url_format: true
end

app/validators/url_format_validator.rb

class UrlFormatValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    if value.present? && value !~ /\A#{URI::regexp(%w(http https))}\z/
      record.errors[attribute] << "のフォーマットが不正です"
    end
  end
end

validatorsフォルダ内のファイル名から_validatorを取り除いたものを各クラスのvalidatesメソッドに引数として渡すと、そのカラムを検証する際にファイル内のメソッド(validate_eachメソッド)が実行される。

カスタムバリデーション

app/validatorsフォルダを作成し、配下にvalidationファイルを設置する。

user_validator.rb

class UserValidator < ActiveModel::Validator
  def validate(record)
    items = [record.nickname, record.email, record.birthday]
    return if items.any?(:present?)
    record.errors[:base] << '入力漏れがあります'
  end
end

定義したvalidatorを使うにはvalidate_withで呼び出す。

model   user.rb

class User < ApplicationRecord
  validates_with UserValidator
end

valid?

条件分岐する場合にはsaveと違い、if createだとcreateに失敗してもtrueとなるため、valid?でバリデーションをかけて評価する。

def create
  if User.create(user_information_params).valid?
    redirect_to root_path
  else
    render "phone_confirmation.html.haml"
  end
end

エラー

foreign_key bigint

usersテーブルを設定したあと、t.referencesで外部キーを持つテーブルを作成した際にuser_idがbigint(20)になって外部キーが貼れない。(integer型と型が違うから)

def change
  create_table :shippings do |t|
    t.references     :user, foreign_key: true
  end
end

外部キーを含まない状態でcreate_tableしたのち、add referenceすれば型違いは吸収される。

class CreateShippings < ActiveRecord::Migration[5.2]
  def change
    create_table :shippings do |t|
      t.integer     :zipcode, null: false
    end
    add_reference :shippings, :user, foreign_key: true, null: false
  end
end

migrationの順番で外部キーがないと怒られるのでmodel作成順に注意する。
適当な順でmodelを作る場合は、外部キー以外のテーブルを作成して、別途外部キーを最後にadd_tableする。

コメント