active_model_persistence

Gem Version License: MIT Documentation

A gem to add in-memory persistence to Models built with ActiveModel.

The goals of this gem are:

  • Add ActiveRecord-like meta-programming to configure Models used for ETI (Extract, Transform, Load) work where a lot of data is loaded from desparate sources, transformed in memory, and then written to other sources.
  • Make creation of these model objects consistent across several different teams,
  • Make it so easy to create model objects that teams will use these models instead of using hashs
  • Encourage a separation from the models from the business logic of reading, transforming, and writing the data
  • Make it easy to use FactoryBot to generate test data instead of having to maintain a bunch of fixture files.

An example model built with this gem might look like this:

require 'active_model_persistence'

class Employee
  include ActiveModelPersistence::Persistence

  # Use ActiveModel attributes and validations to define the model's state

  attribute :id, :string
  attribute :name, :string
  attribute :manager_id, :string

  validates :id, presence: true
  validates :name, presence: true

  # A unique index is automatically created on the primary key attribute (which is :id by default)
  # index :id, unique: true

  # You can set the primary key attribute if you are using a different attribute for
  # the primary key using the statement `self.primary_key = attribute_name`

  # Indexes are non-unique by default and create a `find_by_{index_name}` method on
  # the model class.
  index :manager_id
end

Use the employee model like you would an ActiveRecord model:

e1 = Enmployee.create(id: 'jdoe1', name: 'John Doe', manager_id: 'boss')
e2 = Enmployee.create(id: 'jdoe2', name: 'Bob Doe', manager_id: 'boss')
e3 = Enmployee.create(id: 'boss', name: 'Boss Person')

# The `find` method looks up objects by the primary key and returns a single object or nil
Employee.find('jdoe1') #=> [e1]

# The `find_by_*` methods return a (possibly empty) object array based on the indexes
# declared in the model class.
Employee.find_by_manager_id('boss') #=> [e1, e2]

# etc.

Inheritance

ActiveModelPersistence supports inheritance in models. Include ActiveModelPersistence::Persistence only in the base class.

Here is an example with a User base class and two derived clases Employee and Member. Each derived class adds different attributes and indexes.

require 'active_model_persistence'

class User
  include ActiveModelPersistence::Persistence

  attribute :id, :integer
  attribute :name, :string
end

class Employee < User
  attribute :manager_id, :integer

  index :manager_id, unique: false
end

class Member < User
  attribute :joined_on, :date, default: Date.today

  index :joined_on, unique: false
end

As an example, say we have one instance of each class:

user = User.create!(id: 1, name: 'User James')
employee = Employee.create!(id: 2, name: 'Employee Bob', manager_id: nil)
member = Member.create!(id: 3, name: 'Member Mary', joined_on: Date.parse('2022-01-01'))

The primary key is shared by all objects of these classes so it must be unique for all objects created from these classes. For instance:

require 'rspec-expectations'
include RSpec::Matchers

# Creating a Member with the same primary key as a User will fail
expect { Member.create!(id: 1, name: 'Member Jason') }.to(
  raise_error(ActiveModelPersistence::UniqueConstraintError)
)

Calling a find method such as find or find_by_* on a class will only return objects that are a kind of that class.

That means calling a find method on User will return users, employees, or members:

expect(User.find(1)).to eq(user)
expect(User.find(2)).to eq(employee)
expect(User.find(3)).to eq(member)

While calling a find method on Employee will only return employees:

expect(Employee.find(1)).to be_nil
expect(Employee.find(2)).to eq(employee)
expect(Employee.find(3)).to be_nil

all, count, delete_all, destroy_all and other methods similarly limit what objects are acted upon based on what class they are called on.

API Documentation

See the full API documentation for more details.

Installation

Add this line to your application's Gemfile (or equivalent comamnd in the project's gemspec):

gem 'active_model_persistence'

And then execute:

bundle install

Or install it manually using the gem command:

gem install active_model_persistence

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/active_model_persistence.

License

The gem is available as open source under the terms of the MIT License.