PassiveModel
PassiveModel provides lightweight model objects for Rails applications when you want ActiveModel-style behavior without a database table.
It is useful for form objects, service inputs, or other plain Ruby objects that need validations, naming, translation, conversion, and callback hooks similar to Active Record models.
Installation
Add this line to your application's Gemfile:
gem "passive_model"
And then execute:
bundle install
Or install it yourself:
gem install passive_model
PassiveModel depends on ActiveModel 7.0 or newer. It does not depend on Rails or ActiveRecord.
Usage
Create a class that inherits from PassiveModel::Base and define the attributes
your object exposes.
class ContactForm < PassiveModel::Base
attr_accessor :first_name, :last_name
validates :first_name, presence: true
validates :last_name, presence: true
validate :first_name_is_not_spam
private
def first_name_is_not_spam
return unless first_name.to_s.downcase == "test"
errors.add(:first_name, "is not valid")
end
end
Creating Objects
Pass initial attributes to .new:
form = ContactForm.new(first_name: "John", last_name: "Doe")
Assigning Attributes
Assign multiple attributes with attributes=:
form.attributes = { first_name: "Lewis" }
The current implementation copies each key directly to an instance variable. For
example, first_name: "Lewis" sets @first_name. Define attr_accessor,
attr_reader, or explicit methods for values you need to read later.
This assignment style does not call custom setters and does not reject unknown attribute names.
Validations
PassiveModel::Base includes ActiveModel validations, so standard Rails
validators work:
form = ContactForm.new(last_name: "Doe")
form.valid? # => false
form.errors[:first_name] # => ["can't be blank"]
ActiveModel validation callbacks such as before_validation and
after_validation are also available.
Save
save validates the object. If the object is invalid, it returns false. If the
object is valid, it runs registered before_save callbacks and returns true.
invalid_form = ContactForm.new(last_name: "Doe")
valid_form = ContactForm.new(first_name: "John", last_name: "Doe")
invalid_form.save # => false
valid_form.save # => true
Save Bang
save! calls save and raises PassiveModel::ValidationError when the object
cannot be saved. PassiveModel::ValidationError inherits from
ActiveModel::ValidationError; it does not require ActiveRecord.
form = ContactForm.new
form.save! # raises PassiveModel::ValidationError
before_save Callback
Register a before_save callback with the class method:
class ContactForm < PassiveModel::Base
attr_accessor :first_name
validates :first_name, presence: true
before_save :send_info_to_mailchimp
private
def send_info_to_mailchimp
# API call
end
end
The callback is executed only after validations pass.
before_save callbacks are stored per class, so callbacks registered on one
PassiveModel::Base subclass do not run for unrelated subclasses.
persisted?
persisted? is intended to report whether save has been called successfully.
In the current implementation it returns false unless an instance sets
@persisted itself.
Behavior Notes
These notes describe the current gem behavior and should be reviewed before changing runtime code:
persisted?does not currently switch totrueafter a successfulsave.- Attribute assignment writes instance variables directly instead of using ActiveModel attributes or custom setters.
Contributing
For bug reports or feature requests, open an issue at https://github.com/RocketApex/passive_model.