古いバージョンのドキュメントです。最新のバージョンはRails7.0.0

Railsドキュメント(v6.0.2.1)

モデルについて

layout: page

説明

モデルとはアプリケーションが扱うデータや処理を表現する仕組みのこと

モデル名とテーブル名の規約

  • 英大文字から始まる
  • 英数字のみ
  • 単語の区切りでは、先頭文字を大文字
  • 単数形の名詞
    • Entry
    • UserComment
  • ファイルはapp/modelsディレクトリに格納
  • ファイル名は、モデル名の単語区切りを「_」にし、すべて小文字にしたもの
    • app/models/entry.rb
    • app/models/user_comment.rb

命名規則

種類 説明
モデル名 先頭は大文字で単数形 User
モデルのファイル名 先頭は小文字で単数形 user.rb
テーブル名 先頭は小文字で複数形 users
テストスクリプト名 xxx_test.rb user_test.rb

モデルを生成

layout: page

説明

モデルオブジェクトを生成
保存はまだされていないため、saveメソッドなどを使って保存
生成と同時に保存したい場合は、createメソッドを使用

使い方

モデル.new([属性])

モデルオブジェクトを生成

User.new
# <User id: nil, created_at: nil, updated_at: nil>

属性を指定

User.new(name: "DHH")
# <User id: nil, name: "DHH", created_at: nil, updated_at: nil>

ブロックで指定

users.new do |user|
  user.name = 'Oscar'
end

ソースコード

モデルを生成(newの別名)

layout: page

説明

モデルオブジェクトを生成
newの別名

使い方

モデル.build([属性])

モデルを生成

person.pets.build
# <Pet id: nil, name: nil, person_id: 1>

属性を指定

person.pets.build(name: 'Fancy-Fancy')
# <Pet id: nil, name: "Fancy-Fancy", person_id: 1>

ブロックを指定

person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
# [
#   <Pet id: nil, name: "Spook", person_id: 1>,
#   <Pet id: nil, name: "Choo-Choo", person_id: 1>,
#   <Pet id: nil, name: "Brain", person_id: 1>
# ]

ソースコード

モデルを生成して保存

layout: page

説明

モデルオブジェクトを生成して保存
生成のみしたい場合は、newメソッドを使用

使い方

モデル.create([属性])

失敗したら例外

モデル.create!([属性])

モデルを生成して保存

User.create(name: "TestUser", profile: "profile")

属性ハッシュの配列を保存

User.create([{name: "A"},{name: "B"}])

ソースコード

条件を指定して最初の1件を取得

layout: page

説明

条件を指定して最初の1件を取得
存在しない場合はnil

使い方

モデル.find_by(条件)

取得するレコードが存在しない場合に例外が発生

モデル.find_by!(条件)

category_idが1の最初の値を取得

Page.find_by category_id: 1
# <Page id: 1, category_id: 1>
# SQL: SELECT "pages".* FROM "pages" WHERE "pages"."category_id" = 1 LIMIT 1

category_idの1が存在しない場合

Page.find_by category_id: 1
# nil

複数条件を指定

Page.find_by category_id: 1, user_if: 1

whereで書き換える

# Page.find_by category_id: 1
Page.where(category_id: 1).take

ソースコード

IDを指定してレコードを取得

layout: page

説明

IDを指定してレコードを取得
指定したIDの中に存在しないIDが1つでもあると例外が発生

使い方

モデル.find(件数)

IDを指定してレコードを取得

Person.find(1)
# <Person id: 1, ...>

複数IDを指定

Person.find(1, 2, 6)
# [
#   <Person id: 1, ...>,
#   <Person id: 2, ...>,
#   <Person id: 6, ...>
# ]

配列で指定

Person.find([7, 17])
# [
#   <Person id: 7, ...>,
#   <Person id: 17, ...>
# ]

ソースコード

分割してレコードを取得して1件ずつ処理

layout: page

説明

分割してレコードを取得して1件ずつ処理
デフォルトでは1000件ずつ処理 大きなデータをもつモデルなどを処理する時に使う

使い方

モデル.find_each([オプション]) do |i|
  処理内容
end

オプション

オプション 説明 デフォルト値
:batch_size 同時処理数 1000
:start 処理開始位置  
:finish 終了するときのキー  
:error_on_ignore 例外を発生させる  

category_idが1の値をeachする

Person.find_each do |person|
  person.do_awesome_stuff
end

同時処理数を10

Post.find_each(:batch_size => 10) do |post|
end

開始位置を指定

Person.find_each(start: 2000, batch_size: 2000) do |person|
end

補足

  • find_in_batchesとの違いは1件ずつ処理すること

ソースコード

分割してレコードを取得して処理

layout: page

説明

分割してレコードを取得して処理
デフォルトで1000件ずつ処理

使い方

モデル.find_in_batches([オプション]) do |i|
  処理内容
end

オプション

オプション 説明 デフォルト値
:batch_size 同時処理数 1000
:start 処理開始位置  
:finish 終了するときのキー  
:error_on_ignore 例外を発生させる  

分割してレコードを取得して処理

Person.where("age > 21").find_in_batches do |group|
  sleep(50) # Make sure it doesn't get too crowded in there!
  group.each { |person| person.party_all_night! }
end

同時処理数を2000

Person.all.find_in_batches(start: 2000, batch_size: 2000) do |group|
  group.each { |person| person.party_all_night! }
end

補足

  • 処理する順序は指定できない
  • find_eachとの違いは配列で値を受けとること
  • 順番に処理したい場合は、find_eachを使用

ソースコード

条件を指定して初めの1件を取得し1件もなければ作成(create)

layout: page

説明

条件を指定して初めの1件を取得し、1件もなければ作成

使い方

モデル.find_or_create_by(条件)

新しいレコードが無効な場合に例外

モデル.find_or_create_by!(条件)

存在しないので新しく作成

User.find_or_create_by(first_name: 'Penélope')
# <User id: 1, first_name: 'Penélope', last_name: nil>

既に存在する場合は既存のレコードを取得

User.find_or_create_by(first_name: 'Penélope')
# <User id: 1, first_name: 'Penélope', last_name: nil>

ブロック指定

User.find_or_create_by(first_name: 'Scarlett') do |user|
  user.last_name = "O'Hara"
end
# <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>

補足

  • find_or_create_byとの違いは、作成する時に呼ぶメソッドがnewではなくcreate

ソースコード

データベースのユニーク制約を使って作成、できなければ初めの1件を取得

layout: page

説明

データベースのユニーク制約を使って作成、できなければ初めの1件を取得
find_or_create_byでは作成されるまでに別プロセスによって作成されている可能性があったので、その問題を解決した処理
create_or_find_by!はエラーの時に例外が発生

使い方

create_or_find_by(属性)

ブロック指定

create_or_find_by(属性) do
end

データベースのユニーク制約を使って作成、できなければ初めの1件を取得

User.create_or_find_by(first_name: 'Penélope')

ソースコード

条件を指定して初めの1件を取得し1件もなければ作成(new)

layout: page

説明

条件を指定して初めの1件を取得し、1件もなければ作成

使い方

モデル.find_or_initialize_by(条件)

新しいレコードが無効な場合に例外

モデル.find_or_initialize_by!(条件)

存在しないので新しく作成

User.find_or_initialize_by(first_name: 'Penélope')

既に存在する場合は既存のレコードを取得

User.find_or_initialize_by(first_name: 'Penélope')

ブロック指定

User.find_or_initialize_by(first_name: 'Scarlett') do |user|
  user.last_name = "O'Hara"
end

補足

  • find_or_create_byとの違いは、作成する時に呼ぶメソッドがcreateではなくnew

ソースコード

SQLを直接指定して取得

layout: page

説明

SQL文を直接書いて取得

使い方

モデル.find_by_sql(SQL文)

SQLを直接使って検索

Page.find_by_sql("SELECT * FROM pages LIMIT 10")
# SELECT * FROM pages LIMIT 10

ソースコード

SQLを直接指定して取得(非モデルオブジェクト)

layout: page

説明

SQL文を直接書いて取得

使い方

select_all(SQL文, [名前, binds, preparable])

Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'").to_hash
# [
#   {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"},
#   {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"}
# ]

補足

  • find_by_sqlとの違いは取得したオブジェクトのインスタンス化は行わないこと

ソースコード

すべてのレコードを取得

layout: page

説明

すべてのレコードを取得

使い方

モデル.all

Pagesテーブルからすべてのレコードを取得

Page.all
# SELECT "pages".* FROM "pages"

ソースコード

先頭のレコードを取得

layout: page

説明

モデルの先頭のレコードを取得

使い方

モデル.first([件数])

取得するレコードが存在しない場合に例外が発生

モデル.first!([件数])

モデルの先頭のレコードを取得

Page.first
# <Page id: 1, ...>
# SQL: SELECT "pages".* FROM "pages" LIMIT 1

複数件数を取得

Page.first(3)
# [
#   <Page id: 1, ...>,
#   <Page id: 2, ...>,
#   <Page id: 3, ...>
# ]
# SQL: SELECT "pages".* FROM "pages" LIMIT 3

orderで並び替え後のレコードを取得

Page.order(:title).first
# <Page title: a, ...>
# SQL: SELECT * FROM pages ORDER BY pages.title ASC LIMIT 1

モデルが空の場合

Page.first
# nil

ソースコード

モデルが指定したメソッドを呼び出せるかチェック

layout: page

説明

モデルが指定したメソッドを呼び出せるかチェック

使い方

モデル.respond_to?(メソッド名 [, include_private=false])

Personモデルがname属性を呼び出せるかチェック

person.respond_to?(:name)
# true

プライベートメソッドを呼び出せるかチェック

person.respond_to?(:private_name, true)
# true

ソースコード

最後のレコードを取得

layout: page

説明

モデルの最後のレコードを取得

使い方

モデル.last([件数])

取得するレコードが存在しない場合に例外が発生

モデル.last!([件数])

pagesテーブルの最後のレコードを取得

Page.last
# #<Page id: 10, ...>
# SQL: SELECT "pages".* FROM "pages" ORDER BY "pages"."id" DESC LIMIT 1

複数を取得

Page.last(2)
# [
#   <Page id: 10, ...>,
#   <Page id: 9, ...>,
#   <Page id: 8, ...>
# ]
# SELECT "pages".* FROM "pages" ORDER BY "pages"."id" DESC LIMIT 3

orderで並び替え後のレコードを取得

Page.order(:title).last
# <Page title: z, ...>
# SQL: SELECT * FROM pages ORDER BY pages.title DESC LIMIT 1

モデルが空の場合

Page.last
# nil

ソースコード

指定した数のレコードを取得

layout: page

説明

引数で指定した件数のレコードを取得
取得するレコードをIDなどで指定することはできない

使い方

モデル.take([件数])

取得するレコードが存在しない場合に例外が発生

モデル.take!([件数])

1件のレコードを取得

Person.take
# <Person id: 1, ...>
# SQL: SELECT * FROM people LIMIT 1

2件のレコードを取得

Person.take(2)
# [
#   <Client id: 1, ...>,
#   <Client id: 220, ...>
# ]
# SQL: SELECT * FROM people LIMIT 5

whereの後に使用

Person.where(["name LIKE '%?'", name]).take

ソースコード

条件に当てはまる値を全て取得

layout: page

説明

条件に当てはまるレコードを全て取得

使い方

モデル.where(条件)

文字列で指定

Page.where("category_id = '1'")
# SELECT "pages".* FROM "pages" WHERE "pages"."category_id" = 1

ハッシュで指定

Page.where(category_id: 1)
# SELECT "pages".* FROM "pages" WHERE "pages"."category_id" = 1

配列で指定

Page.where(["category_id = ? and url_id = ?", 1, 1])
# SELECT "pages".* FROM "pages" WHERE (category_id = 1 and url_id = 1)

プレースホルダを使用

Page.where("category_id = :category_id", {category_id: params[:category_id]})

複数キーを指定

Page.where(category_id: [1, 2])
# SELECT * FROM pages WHERE (pages.category_id IN (1,2))

NULLのすべてのデータを取得

Page.where(title: nil)

NOT条件

Page.not.where category_id: 1
# SELECT * FROM pages WHERE (pages.category_id != 1)

AND条件

Page.where(category_id: 1).where(user_id: 1)

OR条件

Page.where(category_id: 1).or(Page.where(user_id: 1))

範囲指定

User.where(:id => 1..10)

正規表現

User.where("name like '%hoge%'")

1年

User.where(created_at: '2020-1-5'.in_time_zone.all_year)

1月

User.where(created_at: '2020-1-5'.in_time_zone.all_month)

1日

User.where(created_at: '2020-1-5'.in_time_zone.all_day)

終端なしの範囲指定

User.where(age: 20..)
# SELECT * FROM users WHERE (users.age >= 20)

補足

  • 文字列で指定する場合は、SQLインジェクションの脆弱性が発生する可能性があるので、外部入力されるキーに関しては配列やプレースホルダを使用してください

ソースコード

既存のwhere条件を上書き

layout: page

説明

既存のwhere条件を上書き
上書きされるのは指定したキーのみで、指定されていないwhereの条件はそのまま

使い方

モデル.rewhere(条件)

既存のwhere条件を上書き

Post.where(trashed: true).rewhere(trashed: false)
# WHERE `trashed` = 0

指定したキーのみ上書き

Post.where(active: true).where(trashed: true).rewhere(trashed: false)
# WHERE `active` = 1 AND `trashed` = 0

ソースコード

取得した値をグループ化

layout: page

説明

取得した値をグループ化

使い方

モデル.group(グループ化キー)

usersテーブルをnameでグルーピング

User.group("name")
# SELECT * FROM users GROUP BY name

複数指定

User.group('name, age')
# SELECT * FROM users GROUP BY name, age

年齢ごとの件数

User.group(:age).count

結合してグルーピング

User.joins(:categories).group("categories.name")

3つのテーブル

User.joins(categories: :tags).group("user.name")

ソースコード

取得した値を並び替え

layout: page

説明

取得したレコードを特定のキーで並び替える

使い方

モデル.order(:キー名 [ :並び順])
# または
モデル.order("キー名 [並び順]")

並び順

並び順 説明
ASC 小さい方から大きい方に並ぶ(昇順)
DESC 大きい方から小さい方に並ぶ(降順)

pagesテーブルをcategory_idで並び替える

Page.order(:category_id)
# SELECT "pages".* FROM "pages" ORDER BY category_id

昇順で並び替える

Page.order(:category_id :asc)
# SELECT "pages".* FROM "pages" ORDER BY category_id ASC

文字列で指定

Page.order("category_id ASC")
# SELECT "pages".* FROM "pages" ORDER BY category_id ASC

複数指定

User.order(:name, email: :desc)
# SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC

ソースコード

既存の並び順を指定した条件に置き換える

layout: page

説明

既存の並び順を指定した条件に置き換える

使い方

モデル.reorder(ソート式)

並び順

並び順 説明
ASC 小さい方から大きい方に並ぶ(昇順)
DESC 大きい方から小さい方に並ぶ(降順)

既存の並び順を指定した条件に置き換える

User.order('email DESC').reorder('id ASC')
# generated SQL has 'ORDER BY id ASC'

reorderより後の並び順は追加

User.order('email DESC').reorder('id ASC').order('name ASC')

ソースコード

取得した値を逆順に並び替え

layout: page

説明

取得した値を特定のキーで逆順に並び替える

使い方

モデル.reverse_order()

pagesテーブルをcategory_idを逆順で並び替える

Page.order("category_id").reverse_order

ソースコード

取得するレコード数の上限を指定

layout: page

説明

取得するレコード数の上限を指定

使い方

モデル.limit(最大取得行数)

usersテーブルから最大5件を取得

User.limit(5)
# SELECT * FROM users LIMIT 5

重複する場合あとが優先(最大20件取得)

User.limit(5).limit(20)
# SELECT * FROM users LIMIT 20

途中から指定した件数を取得

User.limit(5).offset(30)
# SELECT * FROM users LIMIT 5 OFFSET 30

ソースコード

空のモデルを取得

layout: page

説明

空のモデルオブジェクトを取得

使い方

モデル.none()

空のモデルオブジェクトを取得

Post.none

ソースコード

特定のレコード位置から取得

layout: page

説明

特定のレコード位置から取得

使い方

モデル.offset(取得開始位置)

usersテーブルの5件目以降を取得

Page.offset(5)
# SELECT "pages".* FROM "pages" LIMIT -1 OFFSET 5

途中から指定した件数を取得

User.limit(5).offset(30)
# SELECT * FROM users LIMIT 5 OFFSET 30

ソースコード

読み込み専用で取得

layout: page

説明

読み込み専用としてモデルを取得

使い方

モデル.readonly(値)

読み込み専用としてモデルを取得

users = User.readonly
users.first.save
# ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord

ソースコード

取得した値を絞り込む

layout: page

説明

モデルから取得した値を元に絞り込む

使い方

モデル.having(条件式)

平均ageが30以上を取得

User.having(['AVG(age) >= ?', 30])
# SELECT * FROM users HAVING AVG(age) >= 30

ソースコード

関連するテーブルをまとめて取得

layout: page

説明

関連するテーブルをまとめて取得

使い方

モデル.includes(:モデル [, ...])

基本的な使い方

Page.includes(:category)
# SELECT "pages".* FROM "pages"
# SELECT "categories".* FROM "categories" WHERE "categories"."id" IN (1)

複数指定

User.includes(:address, :friends)

孫モデルをまとめる

User.includes(friends: :address)

子モデルが存在するレコードを取得

User.joins(:friends).where("friends.id IS NOT NULL")

孫モデルが存在するレコードを取得

User.friends.joins(:followers).where("followers.id IS NOT NULL")

OR演算子を指定

User.where(address: 'hoge').or(User.where(friends: 'fuga'))

ソースコード

複数のテーブルを結合して取得

layout: page

説明

複数のテーブルを結合して取得

使い方

モデル.joins(条件)

categoryテーブルにpostテーブルを結合して取得

Category.joins(:posts)
# SELECT categories.* FROM categories INNER JOIN posts ON posts.category_id = categories.id

2個のテーブルをjoin

Post.joins(:category, :comments)
# SELECT posts.* FROM posts
# INNER JOIN categories ON posts.category_id = categories.id
# INNER JOIN comments ON comments.post_id = posts.id

ネストして結合

Page.joins(categories: :element)
#  SELECT "pages".* FROM "pages" INNER JOIN "pages_categories" ON "pages_categories"."page_id" = "pages"."id" INNER JOIN "categories" ON "categories"."id" = "pages_categories"."category_id" INNER JOIN "elements" ON "elements"."id" = "categories"."element_id"

SQLで記述

User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
# SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id

ソースコード

適応した条件式を除外

layout: page

説明

モデルに適応した条件式を除外

使い方

モデル.except(条件式)

orderによる並び替えを除外

Page.order("category DESC").except(:order)

ソースコード

レコードが参照されるテーブルを指定

layout: page

説明

レコードが参照されるテーブルを指定

使い方

モデル.from(値 [, サブクエリ])

レコードが参照されるテーブルを指定

Topic.select('title').from('posts')
# SELECT title FROM posts

関連付けモデル

Topic.select('title').from(Topic.approved)
# SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery

サブクエリ

Topic.select('a.title').from(Topic.approved, :a)
# SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a

ソースコード

デフォルトのスコープを定義

layout: page

説明

デフォルトのスコープを定義

使い方

default_scope(条件式 [, ブロック])

whereを付与

class Article < ActiveRecord::Base
  default_scope { where(published: true) }
end
Article.all
# SELECT * FROM articles WHERE published = true

ソースコード

モデルのレコード数を取得

layout: page

説明

モデルのレコード数を取得

使い方

モデル.count(カラム名)

テーブルの行数を計算

User.count

カラム指定

Person.count(:age)

groupで集計してから件数取得

Person.group(:city).count
# {Rome: 5, Paris: 3 }

ソースコード

カラムの平均値を計算

layout: page

説明

カラムの平均値を計算

使い方

モデル.average(カラム名)

平均を計算

Page.average(:count)
# SELECT AVG("pages"."count") AS avg_id FROM "pages"

ソースコード

カラムの最大値を計算

layout: page

説明

カラムの最大値を計算

使い方

モデル.maximum(カラム名 [, オプション])

オプション

オプション 説明
:conditions 関数を条件に一致する行に制限
:include 関連テーブルのデータを取得
:joins テーブル結合
:order 結果を並び替える
:select 集計に使う行を選択
:distinct カラム内の重複した値を除いて、別個の値のみカウント
:group グルーピング命令

usersテーブルのageカラムの最大値を計算

User.maximum('age')
# SELECT MAX("users"."age") AS max_id FROM "users"

条件を指定

Account.maximum(:limit, :conditions => "limit > 50")

ソースコード

カラムの最小値を計算

layout: page

説明

カラムの最小値を計算

使い方

モデル.minimum(カラム名 [, オプション])

オプション

オプション 説明
:conditions 関数を条件に一致する行に制限
:include 関連テーブルのデータを取得
:joins テーブル結合
:order 結果を並び替える
:select 集計に使う行を選択
:distinct カラム内の重複した値を除いて、別個の値のみカウント
:group グルーピング命令

usersテーブルのageカラムの最小値を計算

User.minimum('age')
# SELECT MIN("users"."age") AS min_id FROM "users"

条件を指定

Account.minimum(:limit, :conditions => "limit > 50")

ソースコード

主キーを全て取得

layout: page

説明

主キーを全て取得

使い方

モデル.ids()

主キーを全て取得

Person.ids
# SELECT people.id FROM people

JOIN

Person.joins(:companies).ids
# SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id

ソースコード

カラムの合計値を計算

layout: page

説明

カラムの合計値を計算

使い方

モデル.sum(カラム名)

Ratingsテーブルのscoreカラムの合計を計算

Rating.sum(:score)
#  SELECT SUM("Ratings"."score") AS sum_id FROM "Ratings"

ソースコード

指定したカラムのレコードの配列を取得

layout: page

説明

指定したカラムのレコードの配列を取得

使い方

モデル.pluck(カラム名 [, ...])

指定したカラムのレコードの配列を取得

Person.pluck(:id)
# SELECT people.id FROM people
# [1, 2, 3]

複数指定

Person.pluck(:id, :name)
# SELECT people.id, people.name FROM people
# [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]

whereなどと一緒に使用

Person.where(age: 21).limit(5).pluck(:id)
# SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
# [2, 3]

ソースコード

取得済みのモデルから先頭のレコードの値を1件取得

layout: page

説明

whereやorderなどで取得済みのモデルから先頭のレコードの値を1件取得
relation.limit(1).pluck(*column_names).firstの省略形

使い方

pick(カラム名 [, ...])

先頭のレコードの値を1件取得

User.where(id: 1).pick(:name)

ソースコード

モデルを再取得

layout: page

説明

レコードを再取得

使い方

モデル.reload()

レコードを再取得

@page = Page.find(1)
# SELECT "pages".* FROM "pages" WHERE "pages"."id" = ? LIMIT 1  [["id", 1]]
@page.reload
# SELECT "pages".* FROM "pages" WHERE "pages"."id" = ? LIMIT 1  [["id", 1]]

ソースコード

データベースに保存

layout: page

説明

生成したモデルオブジェクトをデータベースに保存

使い方

モデル.save()

基本的な使い方

user = User.new
user.name = "A"
user.save

新しいローカル変数を作らずに保存

User.new do |i|
  i.name = "A"
  i.save
end

属性ハッシュの保存

user = User.new(name: "A")
user.save

データベースへの保存の有無で処理を分ける

if @user.save
  redirect_to :list
else
  render action: "new"
end

複数の項目を保存

begin
  Entry.transaction do
    @many_entries.each { |entry| entry.save! }
  end
 vredirect_to :list
  rescue ActionRecord::RecordInvalid, ActionRecord::RecordNotSaved: ex
  render action: "input_multi_entries"
end

属性を変更して保存

@entry = Entry.find(params[:id])
@entry.message = "new Message"
@entry.save

複数の項目を1度に更新

@user = User.find(parms[:id])
@user.attributes = { username: 'Phusion', is_admin: true }
@user.save

ソースコード

データベースを更新

layout: page

説明

条件に一致するレコードを更新

使い方

モデル.update(ID, 属性)

条件に一致する属性を更新

Person.update(15, user_name: "Samuel", group: "expert")

ハッシュを使って更新

people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
Person.update(people.keys, people.values)

更新メソッドの種類

メソッド バリデーションの有無
update
update_all ×
update_attributes
update_attribute ×

ソースコード

条件に一致するレコードをすべて更新

layout: page

説明

条件に一致するレコードをSQLを直接実行して全て更新
updated_atとupdated_onは更新されない

使い方

モデル.update_all(カラム名: 値 [, オプション])

オプション

オプション 説明
:limit 取得する件数
:order 並び順

複数のデータを更新

User.update_all("name='A'", "category='B')

更新メソッドの種類

メソッド バリデーションによるバリデーションの有無
update
update_all ×
update_attributes
update_attribute ×

ソースコード

属性ハッシュを指定して更新

layout: page

説明

条件に一致するモデルオブジェクトを更新

使い方

モデル.update_attributes(属性名, 値)

複数の項目を一度に変更・保存するメソッド

@user.update_attributes(:name, "hoge")

更新メソッドの種類

メソッド バリデーションの有無
update
update_all ×
update_attributes
update_attribute ×

ソースコード

属性ハッシュを指定して更新

layout: page

説明

条件に一致するモデルオブジェクトを更新
バリデーションはスキップ

使い方

モデル.update_attribute(属性名, 値)

条件に一致するレコードを更新

@user.update_attribute(:name, "hoge")

更新メソッドの種類

メソッド バリデーションによるバリデーションの有無
update
update_all ×
update_attributes
update_attribute ×

ソースコード

複数レコードを一括登録

layout: page

説明

複数レコードを一括登録
直接SQLを実行するのでバリデーションやコールバックはスキップ
insert_all!はエラーの時に例外が発生

使い方

insert_all(属性 [, オプション])

オプション

オプション 説明
:returning 戻り値の属性を指定(PostgreSQLのみ)
:unique_by 重複でスキップするカラムを指定(PostgreSQLとSQLiteのみ)

複数レコードを一括登録

Book.insert_all([
  { id: 1, title: "Rework", author: "David" },
  { id: 1, title: "Eloquent Ruby", author: "Russ" }
])

ソースコード

ActiveRecordを使って指定した条件のレコードを削除

layout: page

説明

ActiveRecordを使って指定した条件のレコードを削除
dependentが設定されている場合は関連付けられたモデルも削除

使い方

モデル.destroy([条件])

指定した条件のレコードを削除

person.pets.destroy(Pet.find(1))

複数指定

person.pets.destroy(Pet.find(2), Pet.find(3))

ソースコード

指定した条件に一致するレコードをSQLを直接実行して削除

layout: page

説明

指定した条件に一致するレコードをSQLを直接実行して削除
関連付けられたモデルは削除しない

使い方

モデル.delete(条件)

レコードを削除

person.pets.delete(Pet.find(1))
# [<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]

ソースコード

指定した条件に一致するレコードをSQLを直接実行して全て削除

layout: page

説明

指定した条件に一致するレコードをSQLを直接実行して全て削除
関連付けられたモデルは削除しない

使い方

モデル.delete_all([検索条件])

全てのレコードを削除

person.pets.delete_all

条件を指定

person.pets.delete_all("id > 0")

ソースコード

ActiveRecordを使って指定した条件の全てのレコードを削除

layout: page

説明

ActiveRecordを使って指定した条件の全てのレコードを削除
dependentが設定されている場合は関連付けられたモデルも削除

使い方

モデル.destroy_all([検索条件])

条件を指定して削除

Person.destroy_all("last_login < '2004-04-04'")

条件を指定して削除

Person.destroy_all(status: "inactive")

全て削除

Person.where(age: 0..18).destroy_all

ソースコード

条件式に一致しないものを取得

layout: page

説明

WHEREと一緒に使用し、条件式に一致しないものを取得

使い方

モデル.where.not(条件)

条件式に一致しないものを取得

User.where.not(name: "Jon")
# SELECT * FROM users WHERE name != 'Jon'

nilを指定

User.where.not(name: nil)
# SELECT * FROM users WHERE name IS NOT NULL

配列を指定

User.where.not(name: %w(Ko1 Nobu))
# SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')

複数条件

User.where.not(name: "Jon", role: "admin")
# SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'

ソースコード

汎用的なカラム処理

layout: page

説明

汎用的なカラム処理

使い方

モデル.calculate(処理方法, カラム名)

処理方法

処理方法 説明
:count カウント
:sum 合計値
:average 平均値
:minimum 最小値
:maximum 最大値

pagesテーブルのカラム数

Page.calculate(:count, :all)
# SELECT COUNT(*) FROM "pages"

ソースコード

コピーを生成

layout: page

説明

モデルのコピーを生成

使い方

モデル.clone()

モデルのコピーを生成

user = User.first
new_user = user.clone
user.name               # "Bob"
new_user.name = "Joe"
user.name               # "Joe"

ソースコード

特定のレコードをインクリメント

layout: page

説明

特定のレコードをインクリメント

使い方

モデル.increment(レコード, インクリメントする値)

Pageテーブルのnumレコードを3ずつインクリメント

Page.increment(:num, 3)

ソースコード

属性変更を取得

layout: page

説明

変更点を取得

使い方

モデル.changed()

person.changed # []
person.name = 'bob'
person.changed # ["name"]

ソースコード

保存していないレコードがあるかチェック

layout: page

説明

保存していないレコードがあるかチェック

使い方

モデル.changed?()

保存していないレコードがあるかチェック

@user.changed?

ソースコード

カラム情報の取得

layout: page

説明

指定したカラム名のオブジェクトを取得

使い方

モデル.column_for_attribute([カラム名])

pageのtitleを取得

@page = Page.find(1)
@page.column_for_attribute(:title)

ソースコード

すべての属性名を配列で取得

layout: page

説明

すべての属性名を配列で取得

使い方

モデル.attribute_names()

すべての属性名を配列で取得

Person.attribute_names
# ["id", "created_at", "updated_at", "name", "age"]

ソースコード

属性の値に似た文字列を取得

layout: page

説明

属性の値に似た文字列を取得
50文字以降は省略
日時などは:db形式で返却

使い方

モデル.attribute_for_inspect(属性名)

属性の値に似た文字列を取得

person.attribute_for_inspect(:name)
# "\"David Heinemeier Hansson David Heinemeier Hansson D...\""

日時を取得

person.attribute_for_inspect(:created_at)
# => "\"2012-10-22 00:15:07\""

配列を取得

person.attribute_for_inspect(:tag_ids)
# => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"

ソースコード

指定した属性が存在するかチェック

layout: page

説明

指定した属性が存在するかチェック

使い方

モデル.attribute_present?(属性名)

person.attribute_present?(:title)
# true

ソースコード

全てのモデルオブジェクトと属性を取得

layout: page

説明

全てのモデルオブジェクトと属性を取得

使い方

モデル.attributes()

全てのモデルオブジェクトと属性を取得

person = Person.create(name: 'Francesco', age: 22)
person.attributes
# {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}

ソースコード

型に変更前のハッシュを取得

layout: page

説明

型に変更前のハッシュを取得

使い方

モデル.attributes_before_type_cast()

型に変更前のハッシュを取得

task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21')
task.attributes
# {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil}
task.attributes_before_type_cast
# {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}

ソースコード

タイムスタンプの自動保存を停止

layout: page

説明

タイムスタンプの自動保存を停止

使い方

ActiveRecord::Base.record_timestamps = false

モデルに指定した属性が存在するかチェック

layout: page

説明

モデルに指定した属性が存在するかチェック

使い方

モデル.has_attribute?(属性)

指定した属性が存在する場合

person.has_attribute?(:name)
# true

指定した属性が存在しない場合

person.has_attribute?(:nothing)
# false

ソースコード

型に変更前のハッシュを取得

layout: page

説明

型に変更前のハッシュを取得

使い方

モデル.read_attribute_before_type_cast(カラム名)

型に変更前のハッシュを取得

task = Task.new(id: '1', completed_on: '2012-10-21')
task.read_attribute('id')                            # 1
task.read_attribute_before_type_cast('id')           # '1'
task.read_attribute('completed_on')                  # Sun, 21 Oct 2012
task.read_attribute_before_type_cast('completed_on') # "2012-10-21"

ソースコード

指定したモデルオブジェクトの属性が存在するかチェック

layout: page

説明

指定したモデルの属性が存在するかチェック

使い方

モデル.attribute_method?(属性)

true

User.attribute_method?(:name)
# true

false

User.attribute_method?(:age)
# false

ソースコード

レコードの存在を確認

layout: page

説明

指定したレコードが存在するか

使い方

モデル.exists?([条件])

Pagesテーブルに1件でもデータは存在するか確認

Page.exists?
# SELECT 1 FROM "pages" LIMIT 1

categoryがrailsであるデータが存在するか確認

Page.exists?(category: "rails")
# SELECT 1 FROM "pages" WHERE "pages"."category" = "rails" LIMIT 1

ソースコード

取得する時のSQLを表示

layout: page

説明

取得する時のSQLを表示

使い方

モデル.to_sql()

取得する時のSQLを表示

User.where(name: 'Oscar').to_sql
# SELECT "users".* FROM "users"  WHERE "users"."name" = 'Oscar'

ソースコード

新しいレコードかチェック

layout: page

説明

新しいレコードかチェック

使い方

モデル.new_record?()

新しいレコードかチェック

User.new_record?

ソースコード

保存済みかチェック

layout: page

説明

保存済みかチェック

使い方

モデル.persisted?()

保存済みチェック

User.persisted?

ソースコード

削除済みかチェック

layout: page

説明

削除済みかチェック

使い方

モデル.destroyed?()

基本形(オプションなし)

@user.destroyed?

ソースコード

複数カラムをまとめる

layout: page

説明

複数カラムを擬似的に1つにまとめる

使い方

モデル.composed_of(条件 [, オプション])

オプション

オプション 説明
:class_name クラス名
:mapping マッピング
:allow_nil nilのバリデーションをスキップ
:constructor 条件
:converter 新しい値が値オブジェクトに割り当てられたときに呼び出される

複数カラムを擬似的に1つにまとめる

composed_of :temperature, mapping: %w(reading celsius)

マッピング指定

composed_of :balance, class_name: "Money", mapping: %w(balance amount), converter: Proc.new { |balance| balance.to_money }

ソースコード

モデルが空かチェック

layout: page

説明

モデルが空かチェック

使い方

モデル.any?([ブロック])

基本的な使い方

class Person < ActiveRecord::Base
  has_many :pets
end

person.pets.count # 0
person.pets.any?  # false

person.pets << Pet.new(name: 'Snoop')
person.pets.count # 0
person.pets.any?  # true

ブロック

person.pets
# [#<Pet name: "Snoop", group: "dogs">]

person.pets.any? do |pet|
  pet.group == 'cats'
end
# false

person.pets.any? do |pet|
  pet.group == 'dogs'
end
# true

ソースコード

例外が発生するかチェック

layout: page

説明

例外が発生するかチェック

使い方

モデル.instance_method_already_implemented?(属性)

例外が発生する

Person.instance_method_already_implemented?(:save)
# ActiveRecord::DangerousAttributeError: save is defined by ActiveRecord

例外が発生しない

Person.instance_method_already_implemented?(:name)
# false

ソースコード

URLのidの部分にid以外のものを指定

layout: page

説明

URLのidの部分にid以外のものを指定

使い方

class User < ActiveRecord::Base
  def to_param  # overridden
    name
  end
end

URLのidの部分にid以外のものを指定

class User < ActiveRecord::Base
  def to_param  # overridden
    name
  end
end

ソースコード

update_atを更新

layout: page

説明

update_atを現在の時刻でアップデート
バリデーションやコールバックを実施されない

使い方

モデル.touch([名前])

update_atを現在の時刻でアップデート

product.touch

update_atを指定した時刻でアップデート

product.touch(time: Time.new(2015, 2, 16, 0, 0, 0))
# updates updated_at/on with specified time

ソースコード

関連モデルオブジェクトから新しい属性を作成

layout: page

説明

関連モデルオブジェクトから新しい属性を作成
「nil」を引数で渡すと属性をリセット

使い方

モデル.create_with(属性)

関連オブジェクトから新しいモデルを作成

users = users.create_with(name: 'DHH')
users.new.name
# 'DHH'

属性をリセット

users = users.create_with(nil)
users.new.name
# 'Oscar'

ソースコード

左外部結合を使ってすべてのレコードを取得

layout: page

説明

左外部結合を使ってすべてのレコードを取得

使い方

モデル.eager_load(属性)

左外部結合を使ってすべてのレコードを取得

User.eager_load(:posts)
# SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
# FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
# "users"."id"

ソースコード

scopeを使ってモデルを拡張

layout: page

説明

モジュールやブロックメソッドを引数に与えて、モデルをscopeで拡張
返されたオブジェクトをさらに拡張することもできる

使い方

モデル.extending(モジュール)

モジュール

module Pagination
  def page(number)
    # pagination code goes here
  end
end

scope = Model.all.extending(Pagination)
scope.page(params[:page])

ブロック

module Pagination
  def page(number)
    # pagination code goes here
  end
end

scope = Model.all.extending do
  def page(number)
    # pagination code goes here
  end
end
scope.page(params[:page])

ソースコード

モデルをハッシュ形式のJSONに変換

layout: page

説明

モデルをハッシュ形式のJSONに変換

使い方

モデル.as_json([オプション])

オプション

オプション 説明 デフォルト値
:root ノード名を含める false
:only 取得する要素を指定  
:except 取得しない要素を指定  
:methods メソッドの呼び出し結果を含める  
:include 関連付け  

モデルをハッシュ形式のJSONに変換

user.as_json
# {id: 1, name: "Konata Izumi", age: 16, created_at: "2006-08-01T17:27:133.000Z", awesome: true}

ノード名を含める

user.as_json(root: true)
# => { "user" => { "id" => 1, "age" => 16, "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }

取得する要素を指定

user.as_json(only: [:id, :age])
# => { "id" => 1, "age" => 16 }

取得しない要素を指定

user.as_json(except: [:id, :created_at, :name])
# => { "age" => 16, "awesome" => true }

メソッドを指定

user.as_json(methods: :permalink)
# => { "id" => 1, "name" => "Konata Izumi", "age" => 16, "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true, "permalink" => "1-konata-izumi" }

関連付け

user.as_json(include: :posts)
# => { "id" => 1, "name" => "Konata Izumi", "age" => 16, "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true, "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" }, { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }

ソースコード

モデルの変更前と変更後の値をハッシュで取得

layout: page

説明

モデルの変更前と変更後の値をハッシュで取得

使い方

モデル.changes()

person.changes # {}
person.name = 'bob'
person.changes # {name: ["bill", "bob"]}

ソースコード

全てのバリデーションをクリア

layout: page

説明

全てのバリデーションをクリア

使い方

モデル.clear_validators!()

全てのバリデーションをクリア

Person.clear_validators!

ソースコード

SQL文を使って件数を取得

layout: page

説明

SQL文を使って件数を取得

使い方

count_by_sql(SQL文)

Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
# 12

ソースコード

テーブルに外部キー制約が存在するかチェック

layout: page

説明

テーブルに外部キー制約が存在するか

使い方

foreign_key_exists?(from_table [, to_table = nil, オプション])

テーブルに外部キー制約が存在するか

foreign_key_exists?(:accounts, :branches)

カラムを指定

foreign_key_exists?(:accounts, column: :owner_id)

nameを指定

foreign_key_exists?(:accounts, name: "special_fk_name")

ソースコード

分割してレコードを取得して処理

layout: page

説明

分割してレコードを取得して処理
デフォルトで1000件ずつ処理

使い方

モデル.in_batches([オプション]) do |i|
  処理内容
end

オプション

オプション 説明 デフォルト値
:of 同時処理数 1000
:load ロードするか false
:start 処理開始位置  
:finish 終了するときのキー  
:error_on_ignore 例外を発生させる  

分割してレコードを取得して処理

Person.where("age > 21").in_batches do |relation|
  relation.delete_all
  sleep(10) # Throttle the delete queries
end

分割して取得して削除

Person.in_batches.delete_all

分割して取得して更新

Person.in_batches.update_all(awesome: true)

補足

  • find_in_batchesとの違いはActiveRecord::Relationで値を返す

ソースコード

指定されたテーブルのインデックスを配列で取得

layout: page

説明

指定されたテーブルのインデックスを配列で取得

使い方

indexes(テーブル名)

indexes(:pages)

ソースコード

関連するモデルの左外部結合

layout: page

説明

関連するモデルの左外部結合

使い方

モデル.left_outer_joins(モデル名)

関連するモデルの左外部結合

User.left_outer_joins(:posts)
# SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"

ソースコード

主キーを設定して複数のレコードを追加

layout: page

説明

全てのレコード数を取得

使い方

モデル.concat(モデル)

全てのレコード数を取得

person.pets.count
# => 3

ソースコード

重複のない値を取得

layout: page

説明

重複のない値を取得

使い方

モデル.distinct([重複を削除するか])

重複のない値を取得

User.select(:name).distinct

重複も削除

User.select(:name).distinct(false)

ソースコード

OR条件式

layout: page

説明

OR条件式

使い方

モデル.or(条件式)

OR条件式

Post.where("id = 1").or(Post.where("author_id = 3"))
# SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))

ソースコード

条件式を削除

layout: page

説明

条件式を削除

使い方

モデル.unscope(条件式)

条件式を削除

User.order('email DESC').unscope(:order) == User.all

ソースコード

モデルが保存される前に変更された値をハッシュで取得

layout: page

説明

保存される前に変更された値をハッシュで取得

使い方

モデル.previous_changes()

保存される前に変更された値をハッシュで取得

person.name # "bob"
person.name = 'robert'
person.save
person.previous_changes # {name: ["bob", "robert"]}

ソースコード

カラムを指定して取得

layout: page

説明

カラムを指定して取得

使い方

モデル.select(カラム名)

カラムを指定して取得

person.pets.select(:id, :name)
# [
#   <Pet id: 1, name: "Fancy-Fancy">,
#   <Pet id: 2, name: "Spook">,
#   <Pet id: 3, name: "Choo-Choo">
# ]

ブロックで指定

person.pets.select { |pet| pet.name =~ /oo/ }
# [
#   <Pet id: 2, name: "Spook", person_id: 1>,
#   <Pet id: 3, name: "Choo-Choo", person_id: 1>
# ]

ソースコード

指定した条件以外の条件をクエリから削除

layout: page

説明

指定した条件以外の条件をクエリから削除

使い方

モデル.only(条件)

条件式を削除

Post.order('id asc').only(:where)

複数指定

Post.order('id asc').only(:where, :order)

ソースコード

トランザクション

layout: page

説明

分割不可能な複数のレコードの更新を1つの単位にまとめて処理すること

特徴

  • データベース内の情報の整合性を保つための手段
  • 複数のデータベースにまたがる分散トランザクションはサポートしていない
  • 使用するにはデータベースがトランザクションをサポートしていることが必要

使い方

ActiveRecord::Base.transaction do
  例外が発生するかもしれない処理
end
  例外が発生しなかった場合の処理
resque => e
  例外が発生した場合の処理

モデル.transaction do
  例外が発生するかもしれない処理
end
  例外が発生しなかった場合の処理
resque => e
  例外が発生した場合の処理
  • ブロック内のすべての処理が正常に行われた場合に保存
  • エラーが発生した場合は、ロードバック

def new
  User.transaction do
    a1 = User.new(name: 'tarou')
    a1.save!
    a2 = User.new(name: 'jirou')
    a2.save!
  end
  render text: '更新に成功しました'
  rescue: e
  render text: e.message
end

ソースコード

データベースのロック

layout: page

楽観的ロック

あらかじめカラムのロックバージョンを記録しておき、更新時にロックバージョンが変わっていないことをバリデーションして保存

class CreateAddLockColumnToEntries < ActiveRecord::Migration
  def self.up
    create_table :add_lock_column_to_entries do |t|
      t.integer :entries, :lock_version, null: false, default: 0
    end
  end

  def self.down
    drop_table :add_lock_column_to_entries
  end
end

悲観的ロック

データベース側でレコードをロックし、並行に更新できないようにする

locked_entry = Entry.find(1,lock: true)
  • MySQLとPostgreSQLでのみ利用可能