Active Record

All Futures is designed to compliment Active Record and make it easier to use in a reactive context. To this end, it implements many of the same interfaces and features - allowing you to use an All Futures model just about anywhere that you can use an Active Record model.

Association-style accessors: has_future

You can mount an All Futures record as an accessor in an Active Record model using the has_future class method, which is conceptually similar to the has_one association. It requires that you provide an accessor name and an All Futures model class as parameters.

class Post < ApplicationRecord
  has_future :draft, PostDraft
end

post = Post.find(params[:id])
post.draft.title = "nihil admirari"
post.draft.save

All Futures models provided by has_future can only be accessed after the parent Active Record model has been persisted. If you attempt to access the model before the parent is persisted, an AllFutures::ParentModelNotSavedYet exception will be raised.

You can provide your own custom key:

class Post < ApplicationRecord
  has_future :draft, PostDraft, key: ->(p) { "posts:#{p.id}:draft" }
end 

Attached All Futures models have not been persisted to Redis when they are first accessed. You must call save on them if you want attribute data to persist. Of course, you might not! Such is the flexibility you have at your disposal.

Creating or updating from an Active Record model

Assuming that you have compatible attributes, you can pass an Active Record model as a parameter to an All Futures model's create or update method:

draft = PostDraft.new Post.last

Behind the scenes, PostDraft strips out the :id, :created_at and :updated_at attributes, if they exist.

If your Active Record model has attributes that your All Futures model does not, passing it to create or update will raise an AllFutures::InvalidAttribute exception.

Creating or updating from an All Futures model

Assuming that you have compatible attributes, you can pass an All Futures model as a parameter to an Active Record model's create or update method:

class PostDraft < AllFutures::Base
  attribute :title, :string
  attribute :body, :string
end

draft = PostDraft.new title: "hello", body: "tbd"

post = Post.create draft

Behind the scenes, Post is actually calling reject on our PostDraft model; All Futures implements a reject method that returns attributes. In the example above, you could also pass the full draft.attributes to Post.create if you hate brevity.

If you are using the All Futures versioning mechanism, you can pass a version to create or update in the same manner:

class PostDraft < AllFutures::Base
  attribute :title, :string
  attribute :body, :string
  enable_versioning!
end

draft = PostDraft.create title: "hello", body: "tbd"
draft.update! body: "still thinking"

post = Post.create draft.version(2)

If your All Futures model has attributes that your Active Record model does not, passing it to create or update will raise an AllFutures::InvalidAttribute exception.

Cache Keys

All Futures models maintain an internal @updated_at accessor so that they can be used as cache keys and invalidate themselves when appropriate.

Excluding attributes

You might encounter scenarios where you have models that are close to identical but might have additional attributes. This will cause issues if you attempt to pass the attributes of the superset model into the constructor of the subset.

This can be remedied by excluding the attributes you don't want to pass:

Post.create PostDraft.find(3).attributes.except("attribute1", "attribute2")

If you find that you're accessing this subset of attributes often, you could create a method on your model to DRY up your code:

def without_attrs
  attributes.except("attribute1", "attribute2")
end

Remember to use String-based keys when accessing items in your attributes collection.

Last updated