Socket
Book a DemoInstallSign in
Socket

activerecord-changesets

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

activerecord-changesets

bundlerRubygems
Version
1.0.3
Version published
Maintainers
1
Created
Source

ActiveRecord Changesets

Make your model updates explicit and predictable.

Instead of scattering validations, strong parameters, and business rules across controllers and models, changesets give you one clear pipeline for handling data before it touches the database.

  • 🔍 Make model operations clear and intentional
  • 🔒 Scope attribute filtering and validation per operation
  • ✨ Reduce coupling between controllers and models

Quick start

Install the gem:

bundle add activerecord-changesets

Use it in your model:

class User < ApplicationRecord
  # Or include it in your ApplicationRecord class
  include ActiveRecordChangesets
  
  changeset :create_user do
    # Only allow the email and password fields to be changed
    expect :email, :password
    
    # Run validation rules specifically for this changeset
    validates :email, presence: true, uniqueness: true, format: {with: URI::MailTo::EMAIL_REGEXP}
    validate :must_have_secure_password
  end
end

User.create_user(email: "bob@example.com", password: "password1234")

Why use changesets?

Reduce boilerplate and business logic in controllers

Validations are defined in our models, but we still need to use strong parameters in our controllers to filter incoming parameters. This approach leads to extra boilerplate and a tighter coupling between controllers and models. If you make a change to the model, you need to update the controller.

Changesets solve this problem by moving the behaviour of strong parameters to the model: each changeset defines which parameters are allowed to change. This means that controllers no longer need to know anything about model attributes - they can just focus on the HTTP request.

Show code example
# Model
class User < ApplicationRecord
    changeset :create_user do
        expect :name
        validates :name, presence: true
    end
end

# Controller
class UsersController < ApplicationController
    def new
        render :new, locals: { changeset: User.create_user }
    end
    
    def create
        # Notice how the controller doesn't need to know about the model's attributes
        changeset = User.create_user(params)
        
        if changeset.save
            redirect_to changeset
        else
            render :new, locals: { changeset: changeset }, status: :unprocessable_content
        end
    end
end

# View
<%= form_with changeset do |f| %>
    <%= f.text_field :name %>
    <%= f.submit %>
<% end %>

Prevent validation changes from causing unintended consequences

If you ever need to change the validation logic in your model, you may end up with unintended consequences for your existing data. For example, if you start validating that all users have a phone number, if you're not careful, an existing record without a phone number may be marked invalid when they go to change their password.

Changesets let you define validations that are specific to each operation, so you can be sure that your validation logic is only applied when it makes sense.

Although this is possible using contexts in vanilla Rails, it can be difficult to see which validations apply to which operations.

Show code example
# Model
class User < ApplicationRecord
    changeset :edit_name do
        expect :name
        
        validates_name
    end
    
    changeset :edit_phone do
        expect :phone_number
        
        validates_phone_number
    end
    
    def self.validates_name
        validates :name, presence: true
    end
    
    def self.validates_phone_number
        validates :phone_number, presence: true
    end
end

Because the validations are scoped to the changeset, you won't get an unexpected phone number error when you try to change your name.

Simplify nested attributes

Nested attributes are a common pattern in Rails, but it can be tricky to permit the right parameters using strong parameters. Nested changesets let you define a changeset for each association so that the changeset controls which parameters are allowed.

Show code example
class User < ApplicationRecord
    has_many :accounts
    
    changeset :edit_user do
        expect :email
        validates :email, presence: true
        
        nested_changeset :accounts, :edit_account
    end
end

class Account < ApplicationRecord
    belongs_to :user
    
    changeset :edit_account do
        expect :name
        validates :name, presence: true
    end
end

def update
    changeset = @user.edit_user(params)
    changeset.save!
end

Getting started

Install activerecord-changesets in your Rails project:

# Gemfile
gem "activerecord-changesets"

Or using bundler:

bundle add activerecord-changesets

Usage

To start using changesets, add it to your model:

class ApplicationRecord < ActiveRecord::Base
  include ActiveRecordChangesets
end

Defining Changesets

Use the class-level changeset method to define a changeset:

class User < ApplicationRecord
  # Changeset for user creation
  changeset :create_user do
    # Required parameters
    expect :first_name, :last_name, :email, :password

    # Validations specific to this changeset
    validates :first_name, presence: true
    validates :last_name, presence: true
    validates :email, presence: true, uniqueness: true, format: {with: URI::MailTo::EMAIL_REGEXP}
    validate :must_have_secure_password
  end

  # Changeset for updating user's name
  changeset :edit_name do
    # Optional parameter
    permit :first_name
    # Required parameter
    expect :last_name

    # Validations specific to this changeset
    validates :first_name, presence: true
    validates :last_name, presence: true
  end

  # Changeset for updating user's email
  changeset :edit_email do
    expect :email

    validates :email, presence: true, uniqueness: true, format: {with: URI::MailTo::EMAIL_REGEXP}
  end

  private

  def must_have_secure_password
    errors.add(:password, "can't be blank") unless self.password.present? && self.password.is_a?(String)
    errors.add(:password, "must be at least 10 characters") unless self.password.is_a?(String) && self.password.length >= 10
  end
end

Using Changesets

Creating a new record with a changeset

# Class-level method
changeset = User.create_user({
  first_name: "Bob", 
  last_name: "Ross", 
  email: "bob@example.com", 
  password: "password1234"
})

# Save the changeset to create the record
changeset.save!

Updating an existing record with a changeset

user = User.find(params[:id])

# Instance-level method
changeset = user.edit_name({
  first_name: "Rob", 
  last_name: "Boss"
})

# Save the changeset to update the record
changeset.save!

Automatical parameter unwrapping

If parameters are wrapped under the model parameter key (e.g., { user: { ... } }), they will be unwrapped automatically. The following two calls are equivalent:

user.edit_email({user: {email: "..."}})
user.edit_email(email: "...")

Working with Rails Strong Parameters

Changesets work seamlessly with Rails' strong parameters:

# In a controller
def create
  changeset = User.create_user(params)

  if changeset.save
    redirect_to user_path(changeset)
  else
    render :new
  end
end

API Reference

Changeset Definition

changeset :name do
  # Changeset configuration
end

Parameter Control

  • expect :param1, :param2, ... - Define required parameters
  • permit :param1, :param2, ... - Define optional parameters

Nested Changesets

For associations, you can define nested changesets:

changeset :create_post do
  expect :title, :content

  # Define a nested changeset for the comments association
  # This will use the Comment model's :create_comment changeset
  nested_changeset :comments, :create_comment, optional: true
end

Note: nested_changeset forwards any additional options to ActiveRecord's accepts_nested_attributes_for (e.g., :allow_destroy, :limit, :update_only, :reject_if). The :optional flag controls whether [association]_attributes is expected (required) or merely permitted for this changeset.

Configuration

These global settings can be overridden in a Rails initializer:

# Whether to raise an error if unexpected parameters are passed to a changeset
# Defaults to true
ActiveRecordChangesets.strict_mode = false

# Parameter keys that are ignored when strict mode is enabled
# Defaults to [:authenticity_token, :_method]
ActiveRecordChangesets.ignored_attributes = [:authenticity_token, :_method, :utf8]

These options can also be overridden on a per-changeset basis:

class User < ApplicationRecord
  include ActiveRecordChangesets

  changeset :register, strict: false, ignore: [:utf8, :commit] do
    expect :email, :password
    permit :name
  end
end

Error Handling

If required parameters are missing, an ActiveRecordChangesets::MissingParametersError will be raised:

begin
  User.create_user({}) # Missing all required parameters
rescue ActiveRecordChangesets::MissingParametersError => e
  puts e.message
  # => "User::Changesets::CreateUser: Expected parameters were missing: first_name, last_name, email, password"
end

If unexpected parameters are provided while strict mode is enabled (globally or for a specific changeset), an ActiveRecordChangesets::StrictParametersError will be raised:

begin
  User.register(email: "a@b.com", password: "secret", extra: "nope")
rescue ActiveRecordChangesets::StrictParametersError => e
  puts e.message
  # => "User::Changesets::Register: Unexpected parameters passed to changeset: extra"
end

If you reference a changeset that hasn't been defined, an ActiveRecordChangesets::UnknownChangeset will be raised:

begin
  User.changeset_class(:does_not_exist)
rescue ActiveRecordChangesets::UnknownChangeset => e
  puts e.message
end

FAQs

Package last updated on 02 Nov 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts