Rails save vs save! の違い|戻り値・例外・関連メソッド・使い分けを徹底解説

Rails save vs save! の違い|戻り値・例外・関連メソッド・使い分けを徹底解説

Rails でレコードを保存する2大メソッド:

user = User.new(name: "Alice", email: "alice@example.com")

user.save    # 通常版
user.save!   # ビックリマーク版(例外発生版)

! 1つの違いですが、挙動が決定的に違う:

  • save: バリデーション失敗で false を返す
  • save!: バリデーション失敗で ActiveRecord::RecordInvalid 例外

しかし、

  • どちらをいつ使うべき?
  • create! / update! / destroy! も同じパターン?
  • コントローラとモデルで使い分けはある?
  • トランザクション内では?
  • エラーをどう処理すべき?

など、Rails 開発で必ず出会う判断基準が多くあります。

本記事では、save / save!すべての違いを、戻り値、発生例外、関連メソッド比較、コールバックとの関係、トランザクション内での挙動、コントローラ・モデル・ジョブごとの使い分け、エラーハンドリングパターン、ありがちな間違いまで完全網羅します。この1本で Rails の保存処理を安全かつ意図通りに書けるようになります。


目次

結論:違いを一発で理解

時間がない方向けに、最重要ポイントを先に示します。

早見表

観点savesave!
戻り値(成功)truetrue
戻り値(バリデーション失敗)false
発生例外(バリデーション失敗)なしActiveRecord::RecordInvalid
戻り値(DB保存失敗)false
発生例外(DB保存失敗)なしActiveRecord::RecordNotSaved
callback halt時false 返却ActiveRecord::RecordNotSaved
トランザクション内成功するなら commit同上、失敗で自動 rollback
エラー情報の取得record.errors例外+ record.errors
適している場面エラーで分岐したいエラーは異常とみなす

一言で言うと

  • save: 失敗を戻り値で判定したい時(フォーム送信等)
  • save!: 失敗が**想定外(バグ)**である時(内部処理・トランザクション)

詳細は以下で解説します。


まず押さえる:save の基本

save の使い方

user = User.new(name: "Alice", email: "alice@example.com")

if user.save
  puts "保存成功"
else
  puts "保存失敗"
  puts user.errors.full_messages
end

save の戻り値

結果戻り値
成功true
バリデーション失敗false
DB保存失敗false
before_save コールバックで haltfalse

バリデーション失敗時

class User < ApplicationRecord
  validates :email, presence: true
end

user = User.new(name: "Alice")
user.save
# => false

user.errors.full_messages
# => ["Email can't be blank"]

Controller での典型パターン

class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    
    if @user.save
      redirect_to @user, notice: "登録成功"
    else
      render :new, status: :unprocessable_entity
    end
  end
end

save の戻り値で分岐し、失敗時は再表示。これは Rails 標準のパターン。


まず押さえる:save! の基本

save! の使い方

user = User.new(name: "Alice", email: "alice@example.com")

user.save!   # 失敗すると例外

失敗時の例外

user = User.new(name: "Alice")   # email 無し
user.save!
# => ActiveRecord::RecordInvalid: Validation failed: Email can't be blank

例外の捕捉

begin
  user.save!
rescue ActiveRecord::RecordInvalid => e
  puts "Validation failed: #{e.message}"
  puts e.record.errors.full_messages
end

e.record で問題のレコードオブジェクトにアクセス可能。

save! が向くケース

  • 失敗が想定外な場面(内部処理)
  • トランザクション内(失敗で自動rollback)
  • rake task / job で確実にエラーを通知したい
  • テストで「絶対に成功するはず」を強制したい

戻り値 vs 例外、どちらが良いか

save(戻り値)パターン

def signup(params)
  user = User.new(params)
  
  if user.save
    UserMailer.welcome(user).deliver_later
    { success: true, user: user }
  else
    { success: false, errors: user.errors.full_messages }
  end
end

メリット:

  • エラー処理が戻り値で完結
  • 通常フローと例外フローが線形

デメリット:

  • 判定漏れしやすい(save の戻り値を見ない)
  • ネストが深くなる

save!(例外)パターン

def signup(params)
  user = User.new(params)
  user.save!
  UserMailer.welcome(user).deliver_later
  user
rescue ActiveRecord::RecordInvalid => e
  Rails.logger.error "Signup failed: #{e.message}"
  raise
end

メリット:

  • 失敗を明示的に通知(無視できない)
  • トランザクション内で自動 rollback
  • 通常フローがシンプル

デメリット:

  • 例外捕捉漏れで500エラーに
  • パフォーマンス(例外コスト)

Rails 公式の指針

The bang versions (methods that end with an exclamation mark, like save!) raise an exception if the record is invalid. The non-bang versions – save and update returns false, and create returns the object.

フォームのバリデーションは save、内部処理は save!」が一般的なベストプラクティス。


関連メソッドの早見表

save 以外にも bang/non-bang のペアが多数存在。

create vs create!

# create: 失敗時はオブジェクト返却(new_record? == true)
user = User.create(email: "")
user.persisted?
# => false(保存されてない)
user.errors.full_messages
# => ["Email can't be blank"]

# create!: 失敗時は例外
user = User.create!(email: "")
# => ActiveRecord::RecordInvalid

update vs update!

# update: 失敗時に false
result = user.update(email: "")
# => false

# update!: 失敗時に例外
user.update!(email: "")
# => ActiveRecord::RecordInvalid

update_attribute(要注意)

# update_attribute: バリデーションスキップで保存(危険)
user.update_attribute(:email, "")
# 検証なしで保存される!

⚠️ update_attributeバリデーションを無視するため、業務コードでは基本使わない。

destroy vs destroy!

# destroy: 失敗時に false(before_destroy コールバックで halt等)
result = user.destroy
# => false(halt時)

# destroy!: 失敗時に例外
user.destroy!
# => ActiveRecord::RecordNotDestroyed

完全比較表

通常版bang版失敗時挙動
savesave!false / RecordInvalid
createcreate!オブジェクト / RecordInvalid
updateupdate!false / RecordInvalid
destroydestroy!false / RecordNotDestroyed
find_byfind_by!nil / RecordNotFound
firstfirst!nil / RecordNotFound
lastlast!nil / RecordNotFound
taketake!nil / RecordNotFound
toggletoggle!保存しない / 保存(save!)

Rubyの慣習

! 付きメソッドは Ruby全般で「より破壊的または例外発生」を意味する慣習:

# String
"abc".upcase   # => "ABC"(新オブジェクト)
"abc".upcase!  # 自身を変更(または変化なしで nil)

# Array
arr.compact    # 新配列
arr.compact!   # 自身から nil 除去

# ActiveRecord
user.save      # 戻り値で結果通知
user.save!     # 失敗で例外

ActiveRecord 例外の詳細

ActiveRecord::RecordInvalid

begin
  user.save!
rescue ActiveRecord::RecordInvalid => e
  e.message       # => "Validation failed: Email can't be blank"
  e.record        # => 該当のモデルインスタンス
  e.record.errors # => ActiveModel::Errors
end

ActiveRecord::RecordNotSaved

class User < ApplicationRecord
  before_save :prevent_save
  
  def prevent_save
    throw(:abort)   # コールバックで halt
  end
end

User.new(email: "...").save
# => false(バリデーションはOKだがコールバックでhalt)

User.new(email: "...").save!
# => ActiveRecord::RecordNotSaved

ActiveRecord::RecordNotDestroyed

class User < ApplicationRecord
  before_destroy :prevent_destroy
  
  def prevent_destroy
    throw(:abort) if admin?
  end
end

User.find(1).destroy
# => false(halt)

User.find(1).destroy!
# => ActiveRecord::RecordNotDestroyed

例外の継承関係

ActiveRecord::ActiveRecordError
  ├── ActiveRecord::RecordNotFound
  ├── ActiveRecord::RecordInvalid
  ├── ActiveRecord::RecordNotSaved
  ├── ActiveRecord::RecordNotDestroyed
  └── ActiveRecord::StatementInvalid

詳細はActiveRecord::RecordNotFound の記事も参照。


バリデーションとコールバックの挙動

バリデーション失敗の流れ

user = User.new(email: "")

# 1. user.save 呼び出し
# 2. before_validation コールバック実行
# 3. validations 実行 → 失敗
# 4. after_validation コールバック実行(失敗時も呼ばれる)
# 5. save: false 返却 / save!: RecordInvalid
class User < ApplicationRecord
  before_validation :normalize_email
  after_validation :log_validation_result
  
  validates :email, presence: true
  
  private
  
  def normalize_email
    self.email = email.downcase.strip if email.present?
  end
  
  def log_validation_result
    Rails.logger.info "Validation result: #{valid?}"
  end
end

before_save での halt

class User < ApplicationRecord
  before_save :check_quota
  
  private
  
  def check_quota
    if User.count >= 1000
      errors.add(:base, "ユーザー上限に達しました")
      throw(:abort)   # ← 保存中止
    end
  end
end

user.save
# => false

user.save!
# => ActiveRecord::RecordNotSaved

⚠️ Rails 5+ では return false ではなく throw(:abort) を使う。

after_save での例外

class User < ApplicationRecord
  after_save :notify_admin
  
  private
  
  def notify_admin
    AdminMailer.user_created(self).deliver_now
    # ↑ ここで例外が出ると…
  end
end

user.save
# => Net::SMTPError等 が propagate される(save が rescue しない)

after_save で例外が出るとそのまま伝搬。save 戻り値では捕捉できないので注意。


トランザクション内での挙動

トランザクションの基本

ActiveRecord::Base.transaction do
  user.save!
  account.save!
  # 両方成功なら commit、片方失敗で rollback
end

save vs save! でのトランザクション挙動

save の場合(暗黙トランザクションなし)

ActiveRecord::Base.transaction do
  user.save        # 失敗しても false が返るだけ
  account.save     # こちらは成功
  # → user は保存されていない、account は保存される
end

save だけだと整合性が崩れる可能性。

save! の場合

ActiveRecord::Base.transaction do
  user.save!       # 失敗で RecordInvalid → トランザクション rollback
  account.save!    # 実行されない
end
# → 両方とも保存されない(整合性保持)

推奨パターン

ActiveRecord::Base.transaction do
  user.save!
  account.save!
rescue ActiveRecord::RecordInvalid => e
  Rails.logger.error e.message
  # トランザクションは既に rollback されている
end

明示的な rollback

ActiveRecord::Base.transaction do
  user.save!
  
  raise ActiveRecord::Rollback if some_condition
  
  account.save!
end

ActiveRecord::Rollback例外を再発生させずに rollback だけ実行。

ネストしたトランザクション

ActiveRecord::Base.transaction do
  user.save!
  
  ActiveRecord::Base.transaction(requires_new: true) do
    # ネストしたトランザクション(SAVEPOINT 使用)
    account.save!
  rescue ActiveRecord::RecordInvalid
    # ネスト内だけ rollback
  end
  
  # ここに続く処理
end

使い分け指針

Controller での使い分け

class UsersController < ApplicationController
  # ✅ フォーム送信 → save(戻り値で分岐)
  def create
    @user = User.new(user_params)
    
    if @user.save
      redirect_to @user
    else
      render :new, status: :unprocessable_entity
    end
  end
  
  # ✅ API モード → save!(rescue_from で統一処理)
  # rescue_from ActiveRecord::RecordInvalid, with: :render_invalid
end

Service Object での使い分け

class UserSignupService
  def call(params)
    user = User.new(params)
    
    if user.save
      UserMailer.welcome(user).deliver_later
      [:ok, user]
    else
      [:error, user.errors.full_messages]
    end
  end
end

または例外パターン:

class UserSignupService
  def call(params)
    user = User.create!(params)
    UserMailer.welcome(user).deliver_later
    user
  end
end

# 呼び出し側
begin
  service.call(params)
rescue ActiveRecord::RecordInvalid => e
  # ...
end

Job での使い分け

class ProcessUserJob < ApplicationJob
  retry_on ActiveRecord::RecordInvalid, wait: 5.seconds
  
  def perform(user_id)
    user = User.find(user_id)
    user.update!(status: :processed)
    # save! で例外発生 → 自動リトライ
  end
end

Job では save! 推奨。失敗を例外で確実にキャッチしてリトライ機構を活用。

詳細はSolid Queue 使い方の記事も参照。

rake task での使い分け

# lib/tasks/users.rake
namespace :users do
  task migrate_data: :environment do
    User.find_each do |user|
      user.update!(processed: true)
      # save! で失敗を即座に通知(ログ + 中断)
    rescue ActiveRecord::RecordInvalid => e
      Rails.logger.error "Failed for user #{user.id}: #{e.message}"
    end
  end
end

詳細はrake task 作り方の記事も参照。

テストでの使い分け

RSpec.describe User do
  it "creates valid user" do
    user = User.new(valid_attrs)
    expect(user.save).to be true
  end
  
  it "raises on invalid" do
    user = User.new(invalid_attrs)
    expect { user.save! }.to raise_error(ActiveRecord::RecordInvalid)
  end
end

テストデータ作成は create! 推奨(失敗を即発見):

# ✅ 失敗を即発見
let(:user) { User.create!(valid_attrs) }

# ❌ 失敗を見逃しがち
let(:user) { User.create(valid_attrs) }

エラーハンドリングのベストプラクティス

API モード:rescue_from で統一

class ApplicationController < ActionController::API
  rescue_from ActiveRecord::RecordInvalid do |e|
    render json: {
      error: "validation_failed",
      details: e.record.errors.as_json
    }, status: :unprocessable_entity
  end
  
  rescue_from ActiveRecord::RecordNotFound do |e|
    render json: { error: "not_found" }, status: :not_found
  end
end

class Api::UsersController < Api::ApplicationController
  def create
    user = User.create!(user_params)
    render json: user, status: :created
  end
end

詳細はActiveRecord::RecordNotFound の記事

Form Object パターン

class UserSignupForm
  include ActiveModel::Model
  
  attr_accessor :name, :email, :password
  
  validates :name, presence: true
  validates :email, presence: true, format: URI::MailTo::EMAIL_REGEXP
  validates :password, length: { minimum: 8 }
  
  def save
    return false unless valid?
    
    ActiveRecord::Base.transaction do
      user = User.create!(name: name, email: email)
      Account.create!(user: user, password: password)
    end
    
    true
  rescue ActiveRecord::RecordInvalid
    false
  end
end

Result オブジェクトパターン

class Result
  attr_reader :data, :error
  
  def initialize(data: nil, error: nil)
    @data = data
    @error = error
  end
  
  def success?
    @error.nil?
  end
  
  def self.ok(data) = new(data: data)
  def self.fail(error) = new(error: error)
end

class CreateUserService
  def call(params)
    user = User.create!(params)
    Result.ok(user)
  rescue ActiveRecord::RecordInvalid => e
    Result.fail(e.record.errors.full_messages)
  end
end

# 呼び出し側
result = CreateUserService.new.call(params)
if result.success?
  redirect_to result.data
else
  render :new, locals: { errors: result.error }
end

ありがちな間違い

1. save の戻り値を見ない

# ❌ 失敗を見逃す
user.save
redirect_to user_path(user)  # user.id は nil!
# ✅
if user.save
  redirect_to user_path(user)
else
  render :new
end

または save! で確実に例外化:

user.save!   # 失敗なら例外で通知
redirect_to user

2. トランザクション内で save(!なし)

# ❌ 整合性崩壊
ActiveRecord::Base.transaction do
  user.save        # 失敗してもrollback されない
  account.save
end

# ✅ save! でロールバック保証
ActiveRecord::Base.transaction do
  user.save!
  account.save!
end

3. RecordInvalid を rescue StandardError で

# ❌ 範囲広すぎ
begin
  user.save!
rescue StandardError => e
  # DB接続エラーまで捕捉してしまう
end

# ✅ 具体的な例外を指定
begin
  user.save!
rescue ActiveRecord::RecordInvalid => e
  # バリデーションエラーのみ
end

4. update_attribute で意図せずバリデーションスキップ

# ❌ バリデーションスキップ(危険)
user.update_attribute(:email, "")

# ✅ バリデーション実施
user.update(email: "")  # false が返る
user.update!(email: "") # 例外

5. before_save で return false(Rails 5+)

# ❌ Rails 5+ では効かない
class User < ApplicationRecord
  before_save :check
  
  def check
    return false if blocked?
  end
end

# ✅ throw(:abort)
class User < ApplicationRecord
  before_save :check
  
  def check
    throw(:abort) if blocked?
  end
end

6. after_save で例外をスロー

class User < ApplicationRecord
  after_save :notify
  
  def notify
    raise "Failed" if some_condition
    # ↑ save! でも save でも例外が伝搬
  end
end

after_* コールバック内の例外は save の戻り値で捕捉できない。設計時注意。

7. errors を確認しない

# ❌ なぜ失敗したか不明
user.save
puts "failed" unless user.persisted?

# ✅ エラー情報を確認
user.save
unless user.persisted?
  puts user.errors.full_messages
end

8. valid? と save の重複呼び出し

# ❌ バリデーションが2回走る
if user.valid?
  user.save
end

# ✅ save が valid? を内部で呼ぶ
if user.save
  # ...
end

valid? / errors の活用

valid? で事前チェック

user = User.new(email: "")
user.valid?
# => false

user.errors.full_messages
# => ["Email can't be blank"]

save を呼ばずにバリデーションだけ走らせたい時に。

errors の構造

user.errors.full_messages
# => ["Email can't be blank", "Name is too short"]

user.errors[:email]
# => ["can't be blank"]

user.errors.details
# => { email: [{ error: :blank }] }

user.errors.size
# => 2

user.errors.empty?
# => false

カスタムエラー追加

class User < ApplicationRecord
  validate :check_quota
  
  private
  
  def check_quota
    if User.count >= 1000
      errors.add(:base, "登録上限に達しました")
    end
  end
end

:base は特定カラムに紐付かないエラー。


関連メソッド早見表

メソッド用途バリデーション
save保存(new/update両用)走る
save!保存、失敗で例外走る
save(validate: false)バリデーションスキップ保存走らない
createnew + save の一括走る
create!失敗で例外走る
update属性更新 + 保存走る
update!失敗で例外走る
update_attribute単一属性更新(検証スキップ走らない
update_columnsDB直接更新走らない
update_column単一カラムをDB直接走らない
touchupdated_at 更新走らない
valid?バリデーションのみ走る
invalid?valid? の逆走る
destroy削除コールバック走る
destroy!失敗で例外同上
deleteDB直接削除(コールバックスキップ走らない

⚠️ update_attribute / update_columns / update_column / deleteバリデーション/コールバックをスキップするため、業務コードでは慎重に。


よくある質問(FAQ)

Q1. save と save! どちらを使うべき?

状況推奨
フォーム送信save(戻り値で分岐)
トランザクション内save!(自動rollback)
API(rescue_from統一)save!
Service Objectチームの方針次第
Job/Workersave!(リトライ機構活用)
rake tasksave!(失敗即発見)
テストデータ作成create!(バグ早期発見)

Q2. RecordInvalid のメッセージカスタマイズ

class User < ApplicationRecord
  validates :email, presence: { message: "は必須です" }
end

begin
  User.create!(email: "")
rescue ActiveRecord::RecordInvalid => e
  e.message
  # => "Validation failed: Email は必須です"
end

i18n 対応:

# config/locales/ja.yml
ja:
  activerecord:
    errors:
      models:
        user:
          attributes:
            email:
              blank: "を入力してください"

Q3. save(validate: false) はいつ使う?

# バリデーションスキップ
user.save(validate: false)

主な用途:

  • 既存データのマイグレーション
  • レガシーデータの段階移行
  • 内部処理で意図的にスキップ

⚠️ 通常の業務コードではほぼ使わない

Q4. save! の例外を rescue すべきか

ケースバイケース:

# 上位で rescue されるなら下位では何もしない
class Service
  def call
    user.save!   # 例外そのまま伝搬
  end
end

# controllerで rescue_from
class UsersController < ApplicationController
  rescue_from ActiveRecord::RecordInvalid do |e|
    render json: { errors: e.record.errors }, status: 422
  end
end

Q5. update_columns との違い

# save / update: バリデーション・コールバック走る
user.update(name: "")
# => false(バリデーション失敗)

# update_columns: 直接UPDATE、検証なし
user.update_columns(name: "")
# => true(強制的に保存)

update_columns緊急時のデータ修正等限定用途。

Q6. トランザクションの自動 rollback 条件

トランザクション内で例外が出ると自動 rollback:

ActiveRecord::Base.transaction do
  user.save!         # RecordInvalid なら rollback
  raise "error"      # 任意の例外も rollback
end

例外なしで終了すると commit。

Q7. save! が成功するか確認したい

# valid? で事前確認
if user.valid?
  user.save!
else
  # エラー処理
end

# または rescue
begin
  user.save!
rescue ActiveRecord::RecordInvalid
  # ...
end

Q8. 既存レコードの save とは

user = User.find(1)
user.name = "Updated"
user.save
# UPDATE users SET name = 'Updated' WHERE id = 1

save は新規/既存両方対応。new_record? で判定。

Q9. save の前後にロジック

class User < ApplicationRecord
  before_save :normalize
  after_create :send_welcome
  after_update :log_change
  after_destroy :cleanup
  
  private
  
  def normalize
    self.email = email.downcase if email.present?
  end
end

before_* / after_* の活用。

Q10. save 後のエラーチェック

user.save
if user.persisted? && user.errors.empty?
  # 成功
else
  # 失敗
end

通常は if user.save で十分。

Q11. ネストした関連保存

class User < ApplicationRecord
  has_many :posts
  accepts_nested_attributes_for :posts
end

user = User.new(name: "Alice", posts_attributes: [
  { title: "Post 1" },
  { title: "Post 2" }
])
user.save   # user と posts を一括保存

# 失敗時
user.errors          # user のエラー
user.posts.first.errors  # 個別の post のエラー

Q12. saveメソッドの内部動作

# ActiveRecord::Validations#save の概要
def save(**options)
  perform_validations(options) ? super : false
end

def save!(**options)
  perform_validations(options) ? super : raise_validation_error
end

save がバリデーション失敗で false を返し、save! が例外を投げる。本質は同じロジック。


参考リンク・関連資料

Rails 公式

関連記事(本サイト)


まとめ

save / save! の違いを理解することは、Rails 開発の基礎中の基礎。要点を再整理します。

  • save: 失敗で false を返す(戻り値で判定)
  • save!: 失敗で ActiveRecord::RecordInvalid 例外
  • 同じパターン: create/create!update/update!destroy/destroy!

使い分け

# フォーム送信 → save
if @user.save
  redirect_to @user
else
  render :new
end

# トランザクション内 → save!
ActiveRecord::Base.transaction do
  user.save!
  account.save!
end

# API → save! + rescue_from
class ApplicationController < ActionController::API
  rescue_from ActiveRecord::RecordInvalid do |e|
    render json: { errors: e.record.errors }, status: 422
  end
end

避けるべきメソッド(バリデーションスキップ)

  • update_attribute
  • update_columns
  • update_column
  • delete

特別な理由がない限り save / save! / update / update! を使用。

コールバック

  • before_* / after_* で前処理・後処理
  • throw(:abort) で halt(Rails 5+)
  • after_* での例外は伝搬する

トランザクション

  • save! で自動 rollback
  • ActiveRecord::Rollback で例外なし rollback

これらの知識は、Rails アプリの設計・エラーハンドリング・トランザクション管理など、あらゆる場面で活用できます。本記事をブックマークしておけば、save / save! の選択に迷うことが大幅に減ります。


本記事は2026年6月時点の情報をもとに、Ruby on Rails 7.x / 8.x での動作確認・公式ドキュメントに基づき作成しています。Rails のバージョンによって挙動が異なる場合があるため、最新の情報はRails公式ガイドもあわせてご確認ください。