# All Futures

A Redis ORM for reactive applications. Quacks just like Active Record. 🦆

Rails developers can use the [all\_futures](https://github.com/leastbad/all_futures) gem to persist data **across multiple requests**. It leverages Redis to provide an ephemeral model that you can use just like an Active Record model.

It's perfect for building faceted search interfaces, multi-step forms, real-time input validation and persisting the display state of UI elements.

Try a demo, here: 👉 [Beast Mode StimulusReflex](https://beastmode.leastbad.com/) 👈

[![GitHub stars](https://img.shields.io/github/stars/leastbad/all_futures?style=social)](https://github.com/leastbad/all_futures) [![GitHub forks](https://img.shields.io/github/forks/leastbad/all_futures?style=social)](https://github.com/leastbad/all_futures) [![Twitter follow](https://img.shields.io/twitter/follow/theleastbad?style=social)](https://twitter.com/theleastbad) [![Discord](https://img.shields.io/discord/629472241427415060)](https://discord.gg/stimulus-reflex)

## Is All Futures for you?

Do you ever find yourself:

* building complex search interfaces
* creating multi-stage data entry processes
* frustrated by the limitations of classic form submission
* wanting to save data even if the model is currently invalid
* reinventing the wheel every time you need field validation
* needing granular dirty checking and state management for every attribute

If you answered yes to any of the above... you are every Rails developer, and you're not crazy. This functionality has been a blind-spot in the framework for a long time.

Yes, All Futures is for **you**.

## Why use All Futures?

Many reactive UI concepts are a pain in the ass to implement using the classic Rails request/response pattern, which was created at a time before developers started using Ajax to update portions of a page. ActionController is amazing, but if a user interaction doesn't fit cleanly into a single form submission, the developer now has to maintain UI state across multiple atomic requests. Naturally, this leads to abuse of the session object and awkward hacks to validate and persist models.

{% hint style="danger" %}
In vanilla Rails, it's very difficult to incrementally save models that require the presence of multiple attributes to be valid.
{% endhint %}

The combination of ActionCable and Turbo Drive creates a persistent Connection that blurs the line between session and request, forcing a new mental model that is poorly served by ActionDispatch and the conventions which drove Rails to success... in 2005.

Moving forward, new tooling is required to take full advantage of reactive possibilities.

All Futures presents a flexible and lightweight mechanism to refine a model that persists its attributes across multiple updates, and even multiple servers.

## Key features and advantages

* A natural fit with [StimulusReflex](https://stimulusreflex.com/), [Stimulus](https://stimulus.hotwired.dev/), [Turbo Drive](https://turbo.hotwired.dev/handbook/drive) and [mrujs](https://mrujs.com)
* No reliance on sessions, so it works across servers
* Easy to learn, quick to implement
* Supports model attributes with defaults, arrays and associations
* Per-attribute dirty checking and state management with rollbacks
* Remembers previous model state across multiple requests
* Automatic versioning allows time travel views
* Model validations, errors and associations
* Can be added as attributes in your Active Record model classes
* No more temporary database tables that need to be purged later

## How does All Futures work?

All Futures is the fusion of [Active Entity](https://github.com/jasl/activeentity) and [Kredis](https://github.com/rails/kredis). It is similar to using a **properly juiced** [ActiveModel::Model](https://api.rubyonrails.org/classes/ActiveModel/Model.html), except that it has full support for [Attributes](https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute), including arrays and nested attributes. All Futures classes behave like Active Record model instances as much as possible.

```ruby
class Example < AllFutures::Base
  attribute :name, :string
  validates :name, presence: true
end

example = Example.create
example.valid? # false
example.errors # @errors=[#<ActiveModel::Error attribute=name, type=blank, options={}>]
```

Unlike an Active Record model, All Futures instances can persist their attributes even if the attributes are currently invalid. This design allows you to resolve any errors present, even if it takes several distinct operations to do so.

Once the state of your attributes is valid, you can pass the `attributes` from your All Futures model right into the constructor of a real Active Record model.

{% hint style="danger" %}
All Futures v1 persisted the attributes every time you set the value of an attribute using bracket notation. **This behavior has been removed.** An explicit `save` operation is now required to persist changes.
{% endhint %}

## Who makes this?

First, All Futures wouldn't exist without [Active Entity](https://github.com/jasl/activeentity) and [Kredis](https://github.com/rails/kredis). Thank you, [Jun Jiang](https://twitter.com/jasl9187) and [Kasper Timm Hansen](https://twitter.com/kaspth).

All Futures was originally created by [leastbad](https://twitter.com/theleastbad), who continues to serve as the primary developer and writer of words. :wave:

v2 welcomes pivotal contributions from key members of the [StimulusReflex](https://stimulusreflex.com/) core and moderation teams. [Stephen Margheim](https://twitter.com/fractaledmind) heroically made sure that callbacks work as expected, *twice*. [Julian Rubisch](https://twitter.com/julian_rubisch) is the reason All Futures models are usually interchangeable with Active Record models. [Nate Hopkins](https://twitter.com/hopsoft/), [Marco Roth](https://twitter.com/marcoroth_) and [Konnor Rogers](https://twitter.com/rogerskonnor/) have been generous with their feedback and suggestions.

We realized that this library needed to exist and had a deep understanding of how it should work *only* because we have spent years helping thousands of Rails developers figure out the right way to develop reactive UIs.

All Futures truly was born in fire. :fire::hammer:

## Try it now

You can experiment with [Beast Mode StimulusReflex](https://beastmode.leastbad.com/), a live demonstration of using All Futures to drill down into a tabular dataset, [**right now**](https://beastmode.leastbad.com/). 👈

The Beast Mode [codebase](https://github.com/leastbad/beast_mode) [![GitHub stars](https://img.shields.io/github/stars/leastbad/beast_mode?style=social)](https://github.com/leastbad/beast_mode) [![GitHub forks](https://img.shields.io/github/forks/leastbad/beast_mode?style=social)](https://github.com/leastbad/beast_mode) is set up as a **template repo** which I recommend that you clone and experiment with.

The three key files are the [CustomerFilter](https://github.com/leastbad/beast_mode/blob/master/app/models/customer_filter.rb), the [Reflex](https://github.com/leastbad/beast_mode/blob/master/app/reflexes/customers_reflex.rb) and the [Model](https://github.com/leastbad/beast_mode/blob/master/app/models/customer.rb). You can read the tutorial post behind this example on my blog [here](https://leastbad.com/beast-mode/).

Assuming you're running at least Ruby 2.7.3, Postgres and have Redis running on your system, you can just run `bin/setup` to install it, including migrations and the DB seed file.

{% embed url="<https://www.youtube.com/watch?v=Fbo21aWFbhQ>" %}
REFRACT
{% endembed %}


# Setup

Installation is straight-forward: just add the gem to your `Gemfile`:

```ruby
gem "all_futures", "~> 2.0"
```

All Futures relies on Redis via the `kredis` gem. Make sure that you have a Redis server running and that you have followed the [Kredis installation instructions](https://github.com/rails/kredis#installation) to set up your `config/redis/shared.yml`.

Thankfully, Rails 7 now ships with Kredis installed, which means you should be able to use All Futures going forward. Note that **Kredis requires Ruby 2.7 or above**.

## Redis Cache Eviction Policy

All Futures is designed to create Redis keys on an as-needed basis. No attempt is made to clear keys, as there is an expectation that you will set an [eviction policy](https://docs.redislabs.com/latest/rs/administering/database-operations/eviction-policy/) which will remove old keys to make room for new ones.

The `allkeys-lru` or `volatile-lru` policy is likely your best bet for an All Futures configuration, depending on whether you use the `expire` option.

## Configuring Redis

If possible, consider two Redis instances for your application; one with a `noeviction` policy for Sidekiq and other queues that you want to complain loudly if they are filling up, and one `` allkeys-lru` `` for Rails caching and All Futures.

This will allow maximum flexibility and takes advantage of the automatic cache expiration to ensure that your Redis instance will always remain available with a minimum of oversight required, even under load.

### Hiredis

[`hiredis-rb`](https://github.com/redis/hiredis-rb) is billed as a wrapper around the high-performance native Redis library. For a long time, it seemed like a no-brainer to use it because who doesn't love "fast"?

However, for reasons that are not entirely clear, at the time of this writing, the `hiredis` gem still doesn't appear to support SSL connections. This is problematic in many deployment environments, and the delay has caused many Rails developers to question whether they *really* need the added complexity, given Redis is usually the fastest part of a request anyhow.


# Usage

Working with All Futures is intentionally very similar to working with Active Record, and most of the same methods will work. You will create a class and define the scopes, validations, callbacks and instance methods you need.

All Futures models persist to Redis instead of your relational database. Place All Futures classes in `app/models`, alongside your Active Record models.

### Hello World

For this example, we'll use [StimulusReflex](https://stimulusreflex.com) to send updates to the server when the user enters data into either of two text input elements.

The most visible difference between an Active Record model and an All Futures model is that instead of migrations and a schema, you need to declare your [attributes](https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute) in the class:

{% code title="app/models/example.rb" %}

```ruby
class Example < AllFutures::Base
  attribute :name, :string
  attribute :age, :integer, default: 21
end
```

{% endcode %}

Let's use our new `Example` model to respond to a page request.

First, create an instance and assign it to an instance variable in the controller action:

{% code title="app/controllers/example\_controller.rb" %}

```ruby
class ExampleController < ApplicationController
  def index
    @example = Example.create
  end
end
```

{% endcode %}

Emit the instance id as a data attribute on every element which can update your model.

```
Name: <input type="text" data-id="<%= @example.id %>" data-reflex="input->Example#name" /><br/>
Age: <input type="text" data-id="<%= @example.id %>" data-reflex="input->Example#age" />
```

Since all attributes are gathered and sent to the server during a Reflex operation, it's easy to retrieve the instance id from the Reflex element accessor and use it to `find` the correct All Futures object and make changes to it.

The following methods both find a record and update it, using StimulusReflex:

{% code title="app/reflexes/example\_reflex.rb" %}

```ruby
class ExampleReflex < ApplicationReflex
  def name
    example = Example.find(element.dataset.id)
    example.name = element.value
    example.save
  end

  def age
    Example.find(element.dataset.id).update age: element.value
  end
end
```

{% endcode %}

You can now update your instance across multiple calls or requests, regardless of whether the user refreshes or navigates away from the page. So long as you have the `id` of the instance you need, you can access it until your Redis cache expiry policy purges it at some point in the distant future.

{% hint style="danger" %}
All Futures v1 persisted the attributes every time you set the value of an attribute using bracket notation. **This behavior has been removed.** An explicit `save` operation is now required to persist changes.
{% endhint %}

### Creating model instances

There are two ways to create an All Futures class instance: [`new`](/api-reference/class-methods#new-attributes) and [`create`](/api-reference/class-methods#create-attributes). Both methods accept an optional Hash of attributes:

```ruby
Example.new # no values set, and not yet persisted
Example.new name: "Steve" # not yet persisted
example = Example.new name: "Steve"
example.save # now it's persisted
puts example.id # you'll need the id to access this instance later

Example.create # no values set; persisted but no way to access the id
example_id = Example.create(name: "Bob").id # winning
```

`create` is exactly like `new`, except that the model instance is persisted to Redis before it returns. **If you want to set your own `id` when calling `create`, it's important to specify an `id` value.**

```ruby
Example.create name: "Bob", id: 555
```

### Finding model instances

Retrieving an instance later just requires passing an `id` to the [`find`](/api-reference/class-methods#find-id-find-id1-id2-find-id1-id2) method. Numeric values will be converted to String type for performing the lookup.

```ruby
example = Example.find(example_id)
```

{% hint style="success" %}
In All Futures, [`id`](/api-reference/getter-methods#id) is not an attribute. It's not treated as data.
{% endhint %}

### Reserved words

An incomplete list of attribute/method names that you shouldn't use as attributes:

* id
* created\_at
* updated\_at

{% hint style="info" %}
If you are experiencing strange behaviour with an attribute, consider using the `respond_to?` method the see if there is a naming conflict.
{% endhint %}

### Internationalization

I18n in All Futures is similar to [Active Record](https://guides.rubyonrails.org/i18n.html#translations-for-active-record-models).

The root node in your locale YAML is `allfutures` instead of `activerecord`.


# 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.

```ruby
class Post < ApplicationRecord
  has_future :draft, PostDraft
end

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

{% hint style="danger" %}
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.
{% endhint %}

You can provide your own custom key:

```ruby
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:

```ruby
draft = PostDraft.new Post.last
```

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

{% hint style="danger" %}
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.
{% endhint %}

### 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:

```ruby
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:

```ruby
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)
```

{% hint style="danger" %}
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.
{% endhint %}

### 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:

```ruby
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:

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

{% hint style="info" %}
Remember to use String-based keys when accessing items in your `attributes` collection.
{% endhint %}


# Attributes

All Futures stands on the shoulders of two giants, [Active Entity](https://github.com/jasl/activeentity) and [Kredis](https://github.com/rails/kredis).

Active Entity presents a "virtual model" that is backed by [Redis](https://redis.io/) instead of a relational database. By implementing [Active Model](https://guides.rubyonrails.org/active_model_basics.html), an All Futures model should be useful everywhere that you might usually use an Active Record model in your Rails app.

Instead of creating migrations, All Futures model attributes are declared in the class using the `attribute` method. At minimum, you must specify a Symbol parameter for the name.

You can specify attributes with the following types: `:string`, `:text`, `:integer`, `:float`, `:decimal`, `:datetime`, `:date`, `:time`, and `:boolean`.

Attributes will automatically be `:string` type unless you pass a second Symbol parameter.

Optionally, you can define a `default` value, as well as flag an attribute as an `array`. Other types have their own special options, such as `Decimal`. Unfortunately, the `limit` and `null` options are disregarded.

So far as I can tell, `:text` is a `:string` and `:time` is a `:datetime`. While you can specify type `:binary`, Active Entity gets a bit salty, throwing an `Encoding::UndefinedConversionError` exception when you attempt to save.

### Aggregations

Active Model classes include a `composed_of` method which provides model instances with dynamic methods that map attributes to complex classes.

For example, you could map an address to an `Address` class:

```ruby
composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
```

And then you can call these getters and setters to access the members of the `Address` class:

```ruby
customer.address_street = "Hyancintvej"
customer.address_city   = "Copenhagen"
customer.address        # => Address.new("Hyancintvej", "Copenhagen")

customer.address = Address.new("May Street", "Chicago")
customer.address_street # => "May Street"
customer.address_city   # => "Chicago"
```

You can [learn more](https://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html) in the Rails Guide.

### Arrays

```ruby
attribute :tags, :string, array: true, default: []
```

When defining an Array, it's good practice to make sure that your `default` value is an empty Array. This is true with Postgres-backed Active Record migrations as well.

### Decimal

```ruby
attribute :lat, :decimal, :precision => 15, :scale => 10
attribute :lng, :decimal, :precision => 15, :scale => 10
```

The precision represents the total number of digits in the number, whereas scale represents the number of digits following the decimal point.

### Enums

You can use the `enum` class method to define a set of possible values for an attribute. It is similar to the `enum` functionality in Active Model, but has significant enough quirks that you should think of them as distinct.

```ruby
class Example < AllFutures::Base
  attribute :steve, :integer
  enum steve: [:martin, :carell, :buscemi]
end

example = Example.new
example.attributes # => {"steve"=>nil}
example.steve = :carell
example.carell? # => true
example.attributes # => {"steve"=>"carell"}
example.steve = 2
example.attributes # => {"steve"=>"buscemi"}
example.martin! # => {"steve"=>"martin"} (attributes saved to Redis)
example.steve = :bannon # ArgumentError ('bannon' is not a valid steve)
```

The first thing you'll notice about the `:steve` attribute is that it is an "Integer", even though it might seem logical to define it as a String... TL;DR: **don't do this**. Even though the attribute is ultimately stored in Redis as a String, internally `enum` tracks the possible values based on their index position in the array. It's also possible to provide a Hash of possible values:

```ruby
class Example < AllFutures::Base
  attribute :steve, :integer, default: 9
  enum steve: {martin: 5, carell: 12, buscemi: 9}
end

example = Example.new
example.attributes # => {"steve"=>"buscemi"}
```

The other quirk of this implementation is that you must create your `attribute` **before** you call `enum`.

`enum` does not create the search scopes that might be familar to Active Model users, since there is no `ActiveRecord::Relation` or scope concept in All Futures. You can, however, access the mapping directly to obtain the index number for a given value:

```ruby
Example.steves[:buscemi] # => 9
```

You can define prefixes and suffixes for your enum attributes. Note the underscores:

```ruby
class Conversation < AllFutures::Base
  attribute :status, :integer
  attribute :comments_status, :integer
  enum status: [ :active, :archived ], _suffix: true
  enum comments_status: [ :active, :inactive ], _prefix: :comments
end

conversation = Conversation.new
conversation.active_status!
conversation.archived_status? # => false

conversation.comments_inactive!
conversation.comments_active? # => false
```

### Kredis attributes

![](https://70018364-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MYvadIZ2i2M8o8gLctH%2Fuploads%2FlhtHKCwB3mAIapeBUItd%2Fkredis.jpg?alt=media\&token=00435022-a29a-4c65-91f2-e9075e43bbb8)

Kredis attributes can be used in an All Futures model in the exact same way they are used in Active Record models. The format follows a predictable pattern: `kredis_datatype`:

```ruby
class Example < AllFutures::Base
  kredis_string :foo
  kredis_flag :bar
end
```

This will create a new Redis key/value pair that is fully managed by Kredis, and accessed via the `foo` accessor of your `Example` instances. The `kredis_counter`, `kredis_unique_list` and `kredis_slots` are all really useful tools.

You can set and retrieve values from Kredis attributes using the `value` method:

```ruby
example = Example.new
example.foo.value = "baz"
```

{% hint style="warning" %}
All instances of your All Futures model will **share** the same Kredis attribute values. Advanced users can experiment with using a Proc to pass a unique value when the class is instantiated.
{% endhint %}

It's important to remember that Kredis attributes are **not** tracked as All Futures attributes, and they are stored in entirely different Redis keys.

### Secure Passwords

All Futures models can store and authenticate [secure passwords](https://guides.rubyonrails.org/active_model_basics.html#securepassword). You store the digest value in an attribute instead of using `attr_accessor`.

```ruby
class Example < AllFutures::Base
  has_secure_password
  attribute :password_digest
```

You can now set values for the `password` and `password_confirmation` attributes. `valid?` will return `false` and the `errors` accessor will contain an `ActiveModel::Error` exception if the two values aren't present or don't match.

You can check to see if a password is valid using the `authenticate(value)` method.

{% hint style="warning" %}
In order to use the secure password mechanism, your application must require the `bcrypt` gem. If you use Devise, it's already in your project.
{% endhint %}


# Associations

All Futures supports its own variant of [nested attributes](https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html) via the `embeds_one` / `embeds_many` methods provided by Active Entity. They are mostly compatible with Active Record's `accepts_nested_attributes_for` functionality.

```ruby
class Holiday < AllFutures::Base
  attribute :date, :date
  validates :date, presence: true
end

class HolidaysForm < AllFutures::Base
  embeds_many :holidays
  accepts_nested_attributes_for :holidays, reject_if: :all_blank
end
```


# Aggressions

Open it.

{% embed url="<https://www.youtube.com/watch?v=PFVyFS0ZGA0>" %}


# Callbacks


# Dirty

Attribute and object level dirty checking with rollback support

This is an area of functionality that you might not ever need. If you do need it, you will be endlessly thankful that it is here.

> You can't go back and change the beginning, but you can start where you are and change the ending.
>
> C.S. Lewis

All Futures provides an obsessively complete API for inspecting, manipulating and reversing changes to your model's attributes.

If history is written by the winner, there are three high-level concepts that you need to :bulb: so that you can win:

1. 3-stage timeline: previously, was and \[future] changes
2. Changes are tracked, but you can mess with the tapes
3. Single-attribute and record-level methods are available

### Timeline

Imagine that you create a new instance of your `Example` model:

```ruby
class Example < AllFutures::Base
  attribute :name, :string
  attribute :age, :integer, default: 21
end

example = Example.new
puts example.name # => nil
puts example.age  # => 21
```

Now, we're going to change the `name` to "Steve", `save` it, and then change the `name` to "Fred".

```ruby
puts example.name        # Previously nil
example.name = "Steve"
example.save
puts example.name        # Was Steve
example.name = "Fred"
puts example.name        # Changed to Fred
```

All Futures is able to keep track of the **previous** value before an attribute was saved, the value it became when it **was** saved, and the value it **changed** to after it was saved. Only the most recent current value is stored, and if nothing changes, it's possible two or all three \[of the previous/was/changed] values could be the same.

### Tracking

Internally, the `changes_applied` method is called when the current state of the attributes is saved to Redis. This is done for you with the standard CRUD methods like `save` and `update`.

You will find methods for interrogating the values for each attribute at every stage, as well as tools for rolling back to previous versions. **There are also methods which direct All Futures to forget that changes happened at all.**

### Forest or Trees

It might seem like there's a lot of methods in this module, but for maximum developer silkiness there are often several methods per concept. Take the *fictional* method `pound`:

* pound :name
* pound! :name
* pound\_all
* pound\_all!
* pound\_name
* pound\_name!

As you see, we have generic methods, collection methods and dynamic methods. The convention is that **if it ends with a `!`, it gets saved to Redis**.

### Instance methods

Generally, you use the generic version eg. `attribute_will_change?(:name)` when you are going to iterate over your `attributes` Hash, while you use the specific version eg. `name_will_change?` when you are writing custom business rules.

#### attribute\_change(attribute), attribute\_changed?(attribute)

If `attribute` has changed since the last `save`, returns an Array where the first element is the saved value and the second element is the value it will change to if saved. Otherwise, returns `nil`.

`?` form interrogates dirty tracking for `attribute` and returns `true` or `false`.

#### attribute\_present?(attribute)

Returns `true` if the specified attribute exists and has been set by the user and is neither `nil` nor `empty?` (the latter only applies to objects that respond to `empty?`, most notably Strings). Otherwise, it returns `false`. Note that it always returns `true` with Boolean attributes.

#### attribute\_previous\_change(attribute), attribute\_previously\_changed?(attribute)

Returns an Array where the first element is the value of the `attribute` before it was saved, and the second element is the value after it was saved.

`?` form returns `true` or `false` depending on whether `attribute` was changed to a new value at the time of the last `save` operation.

#### attribute\_previously\_was(attribute)

Returns the value of `attribute` **before** the last `save` operation.

#### attribute\_was(attribute)

Returns the value of `attribute` **after** the last `save` operation, before it was changed.

#### attribute\_will\_change!(attribute), attribute\_will\_change?(attribute)

Forces dirty tracking to report that the value of `attribute` has changed, even if it has not. `?` form is an alias for `attribute_changed?`

#### changed

Returns an Array of Strings, showing all attributes that have changed **after** the most recent `save` operation.

#### changed\_attributes, changed\_attributes?

Returns a Hash of all changes **after** the most recent `save` operation, where the key is the attribute name as a String, and the value is the **former** value, which the attribute was changed from.

For example, if you change the `search` attribute from `nil` to `"foo"` and call `changed_attributes`, it will return `{"search"=>nil}`

`changed_attributes?` is an alias for `dirty?`

#### changes

Returns a Hash of all changes **after** the most recent `save` operation, where the attribute is the String key and the value is is an Array containing the value before and the value after it was saved.

For example, if you change the `search` attribute from `nil` to `"foo"` and call `changes`, it will return `{"search"=>[nil, "foo"]}`

#### changes\_applied

Clears dirty data and moves changes to previous changes.

#### clear\_attribute\_change(attribute), clear\_attribute\_changes(Array)

Remove dirty tracking data for an attribute or an Array of attributes, without modifying the value itself. This has the effect of fooling All Futures into forgetting that data was changed.

Singluar form returns `nil` while plural form returns an Array of attributes that had their dirty tracking data removed.

#### clear\_changes\_information

Clears all dirty data: current changes and previous changes.

#### dirty?

Reports `true` or `false` depending on whether there any attributes with data that have changed since the last `save` operation.

#### previous\_attributes

Returns a Hash of the attributes on your All Futures model instance, with the values reflecting their state before the last `save` operation.

#### previous\_changes, previous\_changes?

Returns a Hash of all changes that were persisted with the most recent `save` operation, where the key is the name of the attribute in String form and the value is is an Array containing the value before and the value after it was saved.

For example, if you change the `search` attribute from `nil` to `"foo"`, call `save` and then `previous_changes`, it will return `{"search"=>[nil, "foo"]}`

`previous_changes?` is an alias for `saved_changes?`

#### restore\_attribute(attribute)

Change an `attribute` to the value it was **after** the last `save` operation. It will return an Array of Symbols for the attributes that were restored.

#### restore\_attributes(Array = changed)

Change attributes to the value that they were **after** the last `save` operation. You can either specify an Array of attributes to restore or it will default to all of the attributes with values that have changed since the last `save` operation. It will return an Array of Symbols for the attributes that were restored.

#### rollback\_attribute(attribute), rollback\_attribute!(attribute)

Change an `attribute` to the value it was **before** the last `save` operation. It will return the restored value that the attribute was returned to.

If you use the `!` version, it will `save` after rolling back the value, and return `true` or `false`.

#### rollback\_attributes(Array = changed), rollback\_attributes!(Array = changed)

Change attributes to the value that they were **before** the last `save` operation. You can either specify an Array of attributes to rollback, or it will default to all of the attributes with values that have changed since the last `save` operation. It will return an Array of Symbols for the attributes that were rolled back.

If you use the `!` version, it will `save` after rolling back the values, and return `true` or `false`.

#### saved\_changes, saved\_changes?

Returns a Hash of all changes **after** the most recent `save` operation, where the key is the attribute name as a String, and the value is the **new** value, which the attribute was changed to.

For example, if you change the `search` attribute from `nil` to `"foo"` and call `saved_changes`, it will return `{"search"=>"foo"}`

`saved_changes?` returns `true` or `false` depending on whether any attribute value changes have been persisted with a `save` call.

### Per-attribute instance methods

When you see an attribute that contains the upper-case word `ATTR`, this means that there is a separate version of the method available for every attribute defined on your model.

For example, if you have a `search` attribute, you will have methods such as `search_will_change?` and `rollback_search!` available.

#### ATTR?

Returns `true` or `false` depending on standard Ruby evaluation of attribute value. This is most useful for Boolean type attributes.

#### ATTR\_change, ATTR\_changed?

Attribute method version of `attribute_change` and `attribute_changed?`

#### ATTR\_previous\_change, ATTR\_previously\_changed?

Attribute method versions of `attribute_previous_change` and `attribute_previously_changed?`

#### ATTR\_previously\_was

Attribute method version of `attribute_previously_was`

#### ATTR\_was

Attribute method version of `attribute_was`

#### ATTR\_will\_change!, ATTR\_will\_change?

Attribute method version of `attribute_will_change!` and `attribute_will_change?`

#### clear\_ATTR\_change

Attribute method version of `clear_attribute_change`

#### restore\_ATTR

Attribute method version of `restore_attribute`

#### rollback\_ATTR, rollback\_ATTR!

Attribute method version of `rollback_attribute` and `rollback_attribute!`


# Errors

All Futures supports the same error handling pipeline as Active Record, `errors`, which is an enumerable instance of `ActiveModel::Errors`.

When validations fail, `ActiveModel::Error` instances will be added to the `errors.objects` Array. `objects` is actually just an alias for `errors.errors`, which is *just not pretty enough* for Rails. :see\_no\_evil:

{% hint style="success" %}
In the interest of brevity and readability, the receiver and `errors` object have been omitted from every reference to a method in this chapter.

When you read `full_messages_for :name`, it is a stand-in for `record.errors.full_messages_for :name`.
{% endhint %}

When a model is initialized, its `objects` Array is empty until you either call `save` / `update` or invoke `valid?` directly. `valid?` returns `false` if at least one validation failed, and there will now be at least one `ActiveModel::Error` in the `objects` Array.

### Successful failure

The `valid?` method clears the `objects` Array, which means that **adding errors will not make a model invalid**. Instead, it's failed validations that typically add the errors.

```ruby
class Teenager < AllFutures::Base
  validate :designated_driver?

  def :designated_driver?
    errors.add(:base, "is drunk") unless sober?
  end
end

```

Our goal is to respond to this invalid state as part of the normal user experience, *without actually raising an application level exception*. To achieve this "successful failure", Rails gives us a family of methods that operate on the `objects` Array.&#x20;

Many Rails developers think of `errors` as "the thing generated resources use to show validation failure messages". All Futures offers developers several compelling reasons to learn what `ActiveModel::Errors` has to offer someone building a reactive UI.

### So, you think you have some errors

Let's start with a basic assumption: if your model is `valid?` - that is, `true` - then you shouldn't have any errors.

However, you might not want to run `valid?` because it runs all of your validations again, and that might be undesirable. The [valid\_email2](https://github.com/lisinge/valid_email2) gem actually tests for valid MX servers, which could be slow. Or perhaps your validations connect with a [paid API](https://github.com/stripe-samples/identity)? (**Don't** actually do this!)

You can use the `any?` method to check for the presence of errors in the `objects` Array, and `size` to get a count. You can use `attribute_names` to access an Array of Symbols representing - you guessed it - attribute names that have errors associated with them.

There's also a `details` method that returns a Hash structure which conveys all error types for all attributes, eg. `{:name=>[{:error=>:blank}]}`

### Manipulating errors

`objects` is an Array, so you should be able to just add and remove Error objects, right?

Actually, no - we don't want to manually instantiate `ActiveModel::Error` objects. Instead, we can make use of [`add`](https://api.rubyonrails.org/v7.0.0/classes/ActiveModel/Errors.html#method-i-add) , [`delete`](https://api.rubyonrails.org/v7.0.0/classes/ActiveModel/Errors.html#method-i-delete) which make it easy to work with errors, even across multiple versions of Rails when the internal structure of `Error` objects might change over time.

You can `clear` your `objects` Array, but that won't make your model valid. To do that, you have to call `valid?` again - hopefully with the errors fixed.

### `full_messages` vs `messages`

The error messages generated by `ActiveModel::Errors` are available with and without the attribute name prefixed, offering you a choice between "Name is invalid" (`full_messages`) and "is invalid" (`messages`).

Both are useful in different situations; you might not want an awkwardly-named attribute being converted into a proper noun, such as "State Province can't be empty".

The key to enlightenment is to spend time studying (and potentially modifying) [the locale file](https://github.com/rails/rails/blob/main/activemodel/lib/active_model/locale/en.yml)s for the languages that you support. Of course, you can also [specify messages](https://guides.rubyonrails.org/active_record_validations.html#message) on a per-validation basis, but that can add complexity to your internationalization strategy.

### Errors, for a specific attribute

The payoff for this chapter is that you can access the errors for a specific attribute, or for the `:base` model instance. When paired with All Future's ability to tell you [if a given attribute is valid](/usage/validations#attribute_valid-attribute-attr_valid) - even when the model itself might not be - this is a major level-up for reactive UI developers looking to give real-time feedback on an input field.

You can pass an attribute name as a Symbol to `include?` (aliased as both `key?` and `has_key?`) and receive a Boolean indicating whether that attribute has at least one error.

Pass a Symbol to `messages_for(:attribute)` or `full_messages_for(:attribute)` and get the errors for that attribute.

![](https://70018364-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MYvadIZ2i2M8o8gLctH%2Fuploads%2FQk1N0M1eHPZv7KQWpGNn%2Fgas.jpg?alt=media\&token=1ad1e1cf-610b-4002-b70d-7f8a9689d9de)

You can also access the `ActiveModel::Error` instances using the [`where`](https://api.rubyonrails.org/v7.0.0/classes/ActiveModel/Errors.html#method-i-where) method:

```ruby
where(:name) # all name errors
where(:name, :too_short) # all name errors being too short
where(:name, :too_short, minimum: 2) # all name errors being too short and minimum is 2
```

To get the first `full_message` error for `:name` you would do something like this:

```ruby
record.errors.where(:name).first.full_message # => "Name can't be blank"
```

### Acting on specific errors

Most applications have two exception layers: the "[successful failure](#successful-failure)" that comes from processing user input within predetermined constraints, and everything else. You need to determine which category of error that you're dealing with so you remain in control of the user experience, even when things go wrong.

There's actually *two* methods that test for the presence of specific errors. In the beginning, there was [`added?`](https://api.rubyonrails.org/v7.0.0/classes/ActiveModel/Errors.html#method-i-added-3F), while [`of_kind?`](https://api.rubyonrails.org/v7.0.0/classes/ActiveModel/Errors.html#method-i-of_kind-3F) was added in Rails 6.

`added?` is designed to use the same syntax as the [`add`](https://api.rubyonrails.org/v7.0.0/classes/ActiveModel/Errors.html#method-i-add) method. It matches against the **specific** options used to generate an error, or the final string generated by the error. It will **not** detect errors that do not match the *exact* signature of the query.

`of_kind?` is much more forgiving, in that it only accepts the attribute and error type but will return `true` if that error occurs, regardless of the options.

We find it confusing that there are two methods for this, too. Think of it like a John Hughes movie: `added?` is the uptight math teacher, while `of_kind?` is the coach who doles out tough love, but won't rat you out to the Vice Principal.


# Readonly

You can use `attr_readonly :title, :author` to prevent assign value to attribute after initialized.

You can use `enable_readonly!` and `disable_readonly!` to control the behavior.

**Important: It's no effect with embeds or array attributes !!!**

#### enable\_attr\_readonly!, disable\_attr\_readonly!

#### attr\_readonly\_enabled?

#### without\_attr\_readonly(\&blk)

#### readonly\_attribute?(attribute)

#### readonly!, readonly?

Mark the current model instance as `readonly`, which prevents any future attempts to save or update. The instance is still accessible, just frozen.

The transition to `readonly`, is one-directional and cannot be reversed. If you need to write to this instance again, you'll have to `find` it again. This is different from marking an individual attribute as `readonly`, which can be reversed.

### Class methods

#### readonly\_attributes

Returns a Set of attributes that are marked with `attr_readonly` in your All Futures class. Attributes in the Set are presented as Strings.


# Validations

One of the original Rails features that drew many to the framework was its powerful business rule validation system for Active Record. Controller actions that call `save` and use the return value to drive application behaviour are an enduring part of what makes Rails...Rails.

However, there is some rigidity in the approach Rails uses to test for model validity that frustrate the implementation of reactive applications. Even though Active Model has support for [form-level error messages](https://guides.rubyonrails.org/active_record_validations.html#errors-base), the standard request-based approach provided by Action Dispatch makes no distinction between field-level and form-level business rules. It's assumed that validation occurs when a form has been completed and the user submits it by clicking a button, 2004 style.

Let's say that you want to incrementally build a model by pushing updates every time the user changes a form input value. Perhaps you don't even have a traditional "submit" button! If your Active Record model has multiple mandatory attributes, it's not currently possible to do reactive validation on a per-attribute basis. Pushing an interim update to Mandatory Attribute A would immediately raise a presence validation error from Mandatory Attribute B.

All Futures makes incremental model creation and validation easy.

### Validations 101

Active Record models cannot be saved unless they are in a valid state. `save` operations (including methods like `update`) call `valid?` and if any validations fail, then `save` returns `false`. `ActiveModel::Error` objects are added to the `errors` collection of the model.

What's interesting about the Active Record's design is that `valid?` is not just some getter that queries an internal state variable; it's actually the method that performs the validations process! If you inspect your model instance before `valid?` is called, you'll notice that the `errors` collection will be empty even if the model has values that would fail validation.

**All Futures does not call `valid?` before saving.** Instead, `valid?` is a tool that you can call *when you need it*, and it is not tied to the persistance layer in any way. :zap:

{% hint style="success" %}
`validate` is aliased to `valid?` and might be grammatically satisfying in some contexts.
{% endhint %}

### Implementing validations

The All Futures validations are really just [Active Entity validations](https://github.com/jasl/activeentity/#validations), which means that [Jun Jiang](https://twitter.com/jasl9187) did all of the hard work. I am including a slightly edited copy of his instructions here for convenience.

{% hint style="success" %}
All Futures (and Active Entity) support many 3rd-party Active Model extensions, such as [adzap/validates\_timeliness](https://github.com/adzap/validates_timeliness).
{% endhint %}

Defining All Futures validations works just like it does in an Active Record model:

```ruby
class Book < AllFutures::Base
  attribute :title, :string
  validates :title, presence: true
end
```

Many Active Record validations are directly supported:

* [acceptance](https://guides.rubyonrails.org/active_record_validations.html#acceptance)
* [confirmation](https://guides.rubyonrails.org/active_record_validations.html#confirmation)
* [exclusion](https://guides.rubyonrails.org/active_record_validations.html#exclusion)
* [format](https://guides.rubyonrails.org/active_record_validations.html#format)
* [inclusion](https://guides.rubyonrails.org/active_record_validations.html#inclusion)
* [length](https://guides.rubyonrails.org/active_record_validations.html#length)
* [numericality](https://guides.rubyonrails.org/active_record_validations.html#numericality)
* [presence](https://guides.rubyonrails.org/active_record_validations.html#presence)
* [absence](https://guides.rubyonrails.org/active_record_validations.html#absence)

Validation options are supported too:

* [allow\_nil](https://guides.rubyonrails.org/active_record_validations.html#allow-nil)
* [allow\_blank](https://guides.rubyonrails.org/active_record_validations.html#allow-blank)
* [message](https://guides.rubyonrails.org/active_record_validations.html#message)
* [on](https://guides.rubyonrails.org/active_record_validations.html#on)

You can use [strict mode](https://guides.rubyonrails.org/active_record_validations.html#strict-validations), which causes raises an `ActiveModel::StrictValidationFailed` exception if you attempt to `validate` a model with \[failing] strict validations:

```ruby
validates :title, presence: {strict: true}
```

You can also include your own [custom validator classes](https://guides.rubyonrails.org/active_record_validations.html#custom-validators), and call [custom validator methods](https://guides.rubyonrails.org/active_record_validations.html#custom-methods) with the `validates` method.

### All Futures validations

All Futures also provides several validator methods, courtesy of Active Entity:

**`subset` validation**

All Futures supports array attributes, so you may want to ensure that the elements of an array attribute are included in a given set.

The `subset` validation has syntax similar to `inclusion` or `exclusion`:

```ruby
class Steak < AllFutures::Base
  attribute :side_dishes, :string, array: true, default: []
  validates :side_dishes, subset: { in: %w(chips mashed_potato salad) }
end
```

**`uniqueness_in_embeds` validation**

The `uniqueness_in_embeds` validation ensures that you have only unique virtual records when working with embedded (nested) models.

`key` is the attribute name of the nested model. Test multiple attributes by passing an Array.

```ruby
class Category < AllFutures::Base
  attribute :name, :string
end

class Reviewer < AllFutures::Base
  attribute :first_name, :string
  attribute :last_name, :string
end

class Book < AllFutures::Base
  embeds_many :categories
  validates :categories, uniqueness_in_embeds: {key: :name}

  embeds_many :reviewers
  validates :categories, uniqueness_in_embeds: {key: [:first_name, :last_name]}
end
```

**`uniqueness_in_active_record` validation**

The `uniqueness_in_active_record` validation is All Futures' answer to Active Record's [uniqueness](https://guides.rubyonrails.org/active_record_validations.html#uniqueness) validation. It will perform a query against a `scope` to ensure that the attribute value is not already present in your relational datastore.

In addition to the standard `uniqueness` options, you are required to specify an Active Record model `class_name` for it to perform the query.

```ruby
class Candidate < AllFutures::Base
  attribute :name, :string

  validates :name,
            uniqueness_on_active_record: {
              class_name: "Staff"
            }
end
```

### Conditional validations

The only way to implement incremental validations with vanilla Active Record is to use its powerful conditional validation mechanisms. Historically, this has been the only way to build "wizard" style UIs. Use `:if` or `:unless` to evaluate a function that decides if the validation is applied.

You can [pass a Symbol](https://guides.rubyonrails.org/active_record_validations.html#using-a-symbol-with-if-and-unless) to call a method, or [provide a Lambda](https://guides.rubyonrails.org/active_record_validations.html#using-a-proc-with-if-and-unless) to evaluate in-line:

```ruby
class Order < AllFutures::Base
  validates :password, confirmation: true, unless: -> { password.blank? }
  validates :card_number, presence: true, if: :paid_with_card?

  def paid_with_card?
    payment_type == "card"
  end
end
```

It's possible to define [groups of validations](#validations-101) that are only applied if a condition is met e.g. if the `is_author` Boolean attribute is `true`:

```ruby
class DraftPost < AllFutures::Base
  with_options if: :is_author? do
    validates :title, presence: true
    validates :body, length: { minimum: 10 }
  end
end

```

You can even construct unholy mashups of all these techniques, using `:if` and `:unless` [in combination](https://guides.rubyonrails.org/active_record_validations.html#combining-validation-conditions). There's a "great" example in the Rails Guide:

```ruby
class Computer < ApplicationRecord
  validates :mouse, presence: true,
                    if: [Proc.new { |c| c.market.retail? }, :desktop?],
                    unless: Proc.new { |c| c.trackpad.present? }
end

```

Unfortunately, the code required to use conditional validation logic in a complex scenario quickly becomes **brittle** and **difficult to maintain**. All Futures *supports* Active Record's conditional mechanisms for compatibility, but you are strongly encouraged to consider a new approach...

### The All Futures way

Cooking shows would be really boring (and short) if all of the recipes were ready to go into the oven at the beginning of the episode. We trust that the ultimate application of heat will be successful, but watch the chef because the important parts are all in the middle.

Active Record wants fully assembled dishes that are ready to go into the oven. AllFutures is all about how you slice the onions and blend the sauces.

{% embed url="<https://www.youtube.com/watch?v=HgG_b9L7dwo>" %}
I'm not really familiar with mustard in my Russian
{% endembed %}

The key design difference that sets All Futures apart from Active Record is that a model instance does not have to be valid to be persisted to Redis.

This means that it's perfectly okay for you to work iteratively, tweaking attributes and providing an infrastructure upon which a reactive UI can be quickly built.

**All Futures sits in front of Active Record like a firewall, meaning that it can remove much of the complexity that led to conditional validations in the first place.**

While you should still have validations in place to ensure the integrity of your Active Record model, having All Futures in your pipeline means that you'll be passing data to Active Record that's already valid, outside of \[literally] exceptional cases.

You will reduce the overall complexity of your Active Record model classes, which can now focus on persistence, while delegating the workflow of your business objects to All Futures.

How cool is that?

When All Futures is in the kitchen, conditional Active Record validations can go in the compost.

### Programmatic validations

All Futures adds the ability to see if a single given attribute is currently valid. **This is exceptionally useful for building reactive interfaces.** :bulb:

Note that any errors on the `:base` have no impact individual attribute validity.

#### attribute\_valid?(attribute), ATTR\_valid?

Just like calling `valid?`, but for one attribute. Returns `true` if the specified attribute passes all validation helpers.

```ruby
post_draft.attribute_valid? :name
post_draft.name_valid?
```

#### Meta-programming validations

You can introspect the validations on a model with the `validators` class method, which returns a Hash that is keyed to the attributes. Get an Array of validation objects for the `name` attribute with `validators_on` class method.

```ruby
PostDraft.validators
PostDraft.validators_on :name
```

### Sharing validations with Active Record

Hopefully, it's self-evident that defining the same validations on both All Futures and Active Record models would be repetitive today and a maintenance burden tomorrow. Instead, create a Concern that you can include in both models.

Let's create a `Postable` Concern that we can use in our `Post` Active Record model and our `PostDraft` All Futures model:

{% code title="app/models/concerns/postable.rb" %}

```ruby
module Postable
  extend ActiveSupport::Concern

  included do
    validates :name, presence: true
  end
end
```

{% endcode %}

Now you can just include `Postable` in both models:

```ruby
class Post < ApplicationModel
  include Postable
end

class PostDraft < AllFutures::Base
  include Postable
end
```

If you have any validations that are only intended to run in one of the models, you can just keep it in the appropriate class. However, there's also another technique that could be helpful in advanced scenarios: you can selectively include class method calls based on the class of the object that is calling it.

```ruby
module Postable
  extend ActiveSupport::Concern

  included do |base|
    validates :name, presence: true
    if base < ActiveRecord::Base
      validates :name, uniqueness: true
    end
  end
end
```


# Versioning


# API Reference


# Class Methods

As powerful as Redis is, it is not a drop-in replacement for a relational database.

Methods like `all`, `find_by` and `where` all utilize Redis keyspace scanning, which is slow and thread-blocking. If you have a large number of records, use these methods with care - especially in production environments.

What is "large" in this context? It's impossible to say because every application is different, but we urge you to not build significant functionality that relies on keyspace scans.

You can use All Futures without any performance hit by tracking the `id` of the records that you are working with.

{% hint style="warning" %}
All Futures does not currently implement `ActiveRecord::Relation` or have a "scope" concept, although this functionality *is* planned for a future release.

Unlike Active Record models, class names are not Relations and are not composable. You cannot build a method chain or specify multiple `where` clauses, *yet*.

**If you are planning to do multiple operations that utilize the results of a keyspace scan, you are strongly advised to store the return value as there is no caching.**
{% endhint %}

#### all

Perform a keyspace scan and return an Array containing all records that have been created. Records are sorted in the order of their creation, oldest to newest.

#### any?(\&block)

Returns `true` or `false` depending on whether there are any records in the Redis keyspace for this model. This will trigger a keyspace scan, so you might be better off using `all` or `where` and testing the Array returned with `any?` instead of using this method.

If you pass a block, it will be evaluated for every record until one returns `true`.

#### attribute\_names

Returns an Array of Strings containing the attributes on your All Futures model instance, as defined in your model class when you use the [`attribute`](https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute) method.

{% hint style="info" %}
`attribute_names` is also available as an instance getter method.
{% endhint %}

#### create(attributes = {}, \&block)

Pass a Hash of attributes to create an instance of your All Futures model and persist it to Redis. Attributes need to be valid; that is, defined on your model using the `attribute` class method.

If you want to set an `id` for the instance, you will need to pass it in via the Hash; otherwise, a UUID will be assigned as the `id` automatically.

If you pass a block, it will be called after the record is initialized and before it is saved.

#### count

Return an Integer reporting the total number of records stored in Redis. A keyspace scan is required.

#### delete\_all

Deletes every record from Redis without creating instances in memory.

Callbacks will not be run. A keyspace scan is required.

#### delete\_by(attributes = {}, \&block)

Calls `where` and then deletes matching records. Models are instantiated.

Callbacks will not be run. A keyspace scan is required.

#### destroy\_all

`destroy` is called on every record, after models are instantiated.

Callbacks will be run. A keyspace scan is required.

#### destroy\_by(attributes = {}, \&block)

Calls `where` and then `destroy`s matching records. Models are instantiated.

Callbacks will be run. A keyspace scan is required.

#### exists?(arg)

Returns `true` or `false` depending on whether a record has been persisted to Redis. Typically, this is used by passing an `id`. This *will not* trigger a keyspace scan.

If a Hash or Array is passed, it is passed to the `where` method, then `any?` on the Array that is returned. This *will* trigger a keyspace scan.

If you pass `false`, it will return `false`. Finally, if you pass nothing, it calls `any?`.

#### find(id), find(id1, id2), find(\[id1, id2])

Retrieve one or more AllFutures model instances. If you pass one `id`, it will return the model instance.

If you pass either a list of `id`s or an Array of `id`s, you will receive an Array of model instances.

Regardless of how many `id`s that you pass into `find`, all of them must be available or an `AllFutures::RecordNotFound` exception will be raised.

#### find\_by(attributes = {})

Perform a `where` operation (which might require a keyspace scan, depending on what you pass) and return either the first record with attributes that match, or `nil`.

#### find\_by!(attributes = {})

Perform a `where` operation (which might require a keyspace scan, depending on what you pass) and return either the first record with attributes that match or raise an `AllFutures::RecordNotFound` exception.

#### find\_or\_create\_by(attributes = {}, \&block)

Perform a `find_by` operation (which might require a keyspace scan, depending on what you pass) and if a record is not retrieved, `create` a new record with the attributes you specified.

If you pass a block and a new record is created, the block will be called after the record is initialized and before it is saved.

#### find\_or\_initialize\_by(attributes = {}, \&block)

Perform a `find_by` operation (which might require a keyspace scan, depending on what you pass) and if a record is not retrieved, initialize a `new` record with the attributes you specified.

If you pass a block and a new record is initialized, the block will be called at the end of the initialization process.

#### ids

Returns an array containing the key of every record saved to Redis. This requires a keyspace scan, which will slow down the server when there's a large number of records.

#### new(attributes = {}, \&block)

Pass a Hash of attributes to initialize an instance of your All Futures model that has not been persisted to Redis. Attributes need to be valid; that is, defined on your model using the `attribute` class method.

Optionally, you may pass an `id` in the Hash, alongside the attributes.

If you pass a block, it will be called at the end of the initialization process.

#### valid\_attribute?(attribute)

Return `true` or `false` depending on whether the `attribute` provided is either `id` or a valid attribute that has been defined in your All Futures class.

#### where(attributes = {}, \&block)

Perform a keyspace scan and return an Array containing the records which match the attributes provided. This is an all-or-nothing comparison; records must match all attributes specified.

```ruby
Example.where name: "Steve"
```

String and Array parameters are not supported at this time; however, in a departure from the `where` method in Active Record, the All Futures `where` method does accept an optional block which will be evaluated in addition to any attribute comparisons.

```ruby
Example.where do |record|
  record.name.starts_with? "S"
end
```

You can combine attribute and block queries:

```ruby
Example.where name: "Steve" do |record|
  record.email.include? "@"
end
```

If no records match, `where` will return an empty Array.

{% hint style="warning" %}
Remember, `where` returns an Array, not an `ActiveRecord::Relation`. You can chain the usual `Enumerable` methods, but you cannot specify complex queries use scopes at this time.
{% endhint %}

If you specify an attribute that is not present on the model, it will raise an `AllFutures::InvalidAttribute` exception.


# Instance Methods

{% hint style="success" %}
`attribute` name parameters can be passed as a Symbol or String.
{% endhint %}

#### ==(comparison\_object)

Returns `true` if `comparison_object` is the same exact All Futures model instance **or** `comparison_object` is of the same type and has the same `id`.

Note also that destroying a record preserves its `id` in the model instance, so deleted models are still comparable.

#### assign\_attributes(Hash)

Update the current value of one or several attributes without committing them to Redis.

#### attribute\_present?(attribute)

Returns `true` if the specified attribute has been set by the user or by a Redis load and is neither `nil` nor `empty?` (the latter only applies to objects that respond to `empty?`, most notably Strings). Otherwise, `false`.

Note that it always returns `true` with Boolean attributes.

#### attribute\_valid?(attribute), ATTR\_valid?

Just like calling `valid?`, but for one attribute. Returns `true` if the specified attribute passes all validation helpers.

Also available as a dynamic method (created for every attribute in your model).

#### decrement(attribute, by = 1), decrement!(attribute, by = 1), increment(attribute, by = 1), increment!(attribute, by = 1)

Increase or decrease an Integer attribute by 1, or any number you provide as an optional second parameter. `decrement` and `increment` work without writing to Redis, while `decrement!` and `increment!` both commit all outstanding changes.

#### destroy, destroy!, delete

`destroy` will attempt to remove the current instance from Redis and mark the instance as destroyed, which prevents further attempts to `save`.

All three methods return the `attributes` Hash when successful.

`destroy!` functions the same way as `destroy`, except that it will raise a `RecordNotDestroyed` exception if no data was deleted.

`delete` will remove the record even if the `readonly?` method returns `true`. The `before_destroy` and `after_destroy` callbacks are not called.

#### freeze, frozen?

Freeze the attributes hash such that associations are still accessible, even on destroyed records. Cloned models will not be frozen.

#### has\_attribute?(attribute)

Returns `true` or `false` depending on whether `attribute` has been defined in your model.

#### id=(String)

`id` is a String that uniquely identifies an All Futures class instance. If you do not set an `id` before your instance is saved, it will be assigned a unique UUIDv4 code.

If you assign an Integer or other value to `id`, it will be converted to a String.

{% hint style="info" %}
You cannot change the primary key All Futures uses to be something other than `id`.

Once the instance has been saved, the `id` is permanent. Attempts to change it will raise a `FrozenError`.
{% endhint %}

#### reload

This will refresh all attributes and previous attributes with the current data from Redis. It will return the model instance with the current values.

{% hint style="info" %}
Unfortunately, it's not currently possible for an All Futures instance to track changes in Redis that are made after the attributes are loaded. While [I have written about how this problem could be solved](https://dev.to/leastbad/async-redis-key-mutation-notifications-in-rails-4hng) with Redis pubsub, it really seemed as though people didn't understand why this would be useful. If you are equally excited about a **reactive** All Futures *in the future*, please let me know on [Discord](https://discord.gg/stimulus-reflex).
{% endhint %}

#### save, save!

A true classic - accept no substitutes! `save` will persist the current state of the attributes and inform the dirty checking mechanism that changes are now past-tense.

Both methods return `true` if the operation is successful. If unsuccessful, `save` will return `false` while `save!` will raise an `AllFutures::RecordNotSaved` exception.

#### slice(\*methods)

Returns a Hash of the given methods with their names as keys and returned values as values.

```ruby
example = Example.new page: 3
example.slice :id, :page, :to_partial_path
# {"id"=>nil, "page"=>3, "to_partial_path"=>"examples/example"}
```

#### toggle(attribute), toggle!(attribute)

Flip the value of a Boolean attribute to the opposite of its current value. `toggle` changes the attribute but does not persist, and returns the model instance. `toggle!` changes the attribute and saves the instance, returning `true` or `false` based on the success of the operation.

#### to\_json

Returns the `attributes` as a JSON-serialized String.

#### update(attributes = {}), update!(attributes = {})

This should be familiar to Active Record users, as it accepts a Hash of attributes to persist. Internally, the `save` method will not be called unless there are changes to at least one attribute. If you attempt to pass an invalid attribute, it will raise an `AllFutures::InvalidAttribute` exception.

Both methods return `true` if the operation is successful. If unsuccessful, `update` will return `false` while `update!` will raise an `AllFutures::RecordNotSaved` exception.

#### update\_attribute(attribute, value)

Use this method to programmatically update attributes. No callbacks will be executed. Attributes must exist and not be marked `readonly` to be updated. Returns `true` or `false` depending on the success of the operation.


# Getter Methods

#### attribute\_names

Returns an Array of Strings containing the attributes on your All Futures model instance, as defined in your model class when you use the [`attribute`](https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute) method.

{% hint style="info" %}
`attribute_names` is also available as a class method.
{% endhint %}

#### attributes

Returns a Hash of the attributes on your All Futures model instance, as defined in your model class when you use the [`attribute`](https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute) method. You can pass this Hash to the `new` or `create` method of an Active Record model class.

The `attributes` Hash will not contain `id`, which is a property.

#### destroyed?

Returns `true` or `false`, depending on whether the current instance has been `destroy`ed.

#### id

`id` is a String that uniquely identifies an All Futures class instance. When combined with the name of your All Futures class, it is mapped directly to a Redis key. For example, if you have a `DraftPost` class with an id of `bdef228c-248c-4a50-abf0-6942353962bf`, your instance is stored in Redis as `DraftPost:bdef228c-248c-4a50-abf0-6942353962bf`.

#### new\_record?

Returns `true` if the current instance has not yet been saved to Redis.

#### persisted?

Returns `true` if the current instance is not a `new_record?` and has not been `destroyed?`.

#### previously\_new\_record?

Returns `true` if the current instance was a `new_record?` before it was saved to Redis. A record retrieved with `find` cannot have been "previously new".


# Overwritable Methods

### Methods to overwrite

These methods are already defined on your All Futures class. 90% of the time, these defaults are great. If you have complex needs, you can redefine them with your own logic.

#### to\_dom\_id

Responsible for converting the model instance into a valid DOM `id`. Can be passed to a StimulusReflex Morph and used as a CableReady `selector`. Converts namespaced classes to double-dashes.

```ruby
def to_dom_id
  [self.class.name.underscore.dasherize.gsub("/", "--"), id].join("-")
end
```

{% hint style="warning" %}
Only [ASCII](https://developer.mozilla.org/en-US/docs/Glossary/ASCII) letters, digits, `_`, and `-` should be used for an `id`. The `id` attribute should start with a letter.

**Do** **not** add a `#` prefix to the return value.
{% endhint %}

#### to\_key

If you attempt to sort two objects of the same class, Ruby will call the `to_key` method and use the Array it returns to sort. By default, the `to_key` Array contains the `id`. Since All Futures models frequently have a UUIDv4 `id`, this isn't a useful sorting criteria.

You can specify one or more attributes - or other values - to sort by instead.

```ruby
def to_key
  [name, age, id]
end
```

#### to\_param

Returns a String representing the model's key suitable for use in URLs, or `nil` if `persisted?` is `false`. The key is usually the `id`, but this can be overwritten to provide vanity URL slugs.

```ruby
def to_param
  "#{id}-#{title}"
end
```

#### to\_partial\_path

Active Record model instances can be passed to Rails' `render` method, and if ActionPack can locate a partial in the correct location based on that model, it will render that partial. `to_partial_path` is responsible for this magic.

All Futures models can also be passed to `render`. If you have a `Drafts` model, `to_partial_path` returns `drafts/draft` and ActionPack will look for `app/views/drafts/_draft.html.erb`. If this isn't where the partial for your model is located, define your own:

```ruby
def to_partial_path
  "article_drafts/preview"
end
```

ActionPack will now attempt to render `app/views/article_drafts/_preview.html.erb`.

#### readonly?

Query `readonly?` to see if this model instance has been marked as `readonly`, which prevents all `save`, `update` and `destroy` operations.

Want to ensure that no changes are written to Redis for this model class, ever?

```ruby
def readonly?
  true
end
```


# Examples


# Faceted Search

Filtering, Pagination and Sorting

Define a class that inherits from `All Futures`, in a location that makes sense for your application. Many times, `app/models` is a suitable home but in the example, an `app/filters` folder was created.

Your first task is to define attributes representing the data structure you intend to persist. `attribute` supports all of the same data types you could use in a migration:

{% code title="app/filters/customer\_filter.rb" %}

```ruby
class CustomerFilter < AllFutures::Base
  # Facets
  attribute :search, :string
  attribute :threshold, :float, default: 0.1
  attribute :status, :string
  attribute :lawyers, :boolean, default: false
  attribute :low, :integer, default: 21
  attribute :high, :integer, default: 65
  
  # Pagination
  attribute :items, :integer, default: 10
  attribute :page, :integer, default: 1
  
  # Sorting
  attribute :order, :string, default: "name"
  attribute :direction, :string, default: "asc"
end
```

{% endcode %}

The above code is an example of using All Futures to implement an exclusion filter. It's taken from the [Beast Mode repo](https://github.com/leastbad/beast_mode), and is used to hold the values required to create a faceted search UI for a tabular dataset.

### Filtering, Pagination and Sorting

When working with tabular data, there are typically three concerns:

1. **Filtering**: attributes used to reduce and exclude data from the total pool of possible values
2. **Pagination**: attributes used to track the current page and number of items per page
3. **Sorting**: attributes used to sort the filtered results in a specific direction (ASC vs DESC)

The `CustomerFilter` doesn't describe the data - that's the model's job. Instead, facets describe the ways a user might exclude rows. Facets are composable, meaning that you can add them together to remove more data. Ultimately, the filter that is applied is the sum total of all active facets.

![Hole In The Wall](https://70018364-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MYvadIZ2i2M8o8gLctH%2F-MZy_07GNJ3SEglrdLMO%2F-MZzHZDyWiHkk4YsAKUK%2Fhole.jpg?alt=media\&token=5252bf73-957f-4447-b81a-9a1f13dcc092)

For example, the `lawyers` attribute is used to reduce the results to only rows where the name of the employer has the string `and` in it. `threshold` is used to alternate between loose and strict text matching.

{% hint style="info" %}
You can [see this for yourself](https://beastmode.leastbad.com/) if you search for "ste". Initially, you'll see 7 matches. If you turn on *Uptight* *mode*, it reduces the results to 3 matches.
{% endhint %}

{% hint style="success" %}
When designing faceted search UIs, it's important that you handle impossible states so that there are no combinations of filters which could produce invalid combinations or even errors.

For example, it's recommended that you configure [Pagy](https://github.com/ddnexus/pagy) so that a user viewing page 10 is automatically taken to page 5 if the user adjust the number of records per-page from 10 to 20. Set `Pagy::DEFAULT[:overflow] = :last_page` in your `pagy.rb` initializer.
{% endhint %}

### Providing a scope

Since this example doesn't require any attribute validation, we complete the Filter by defining a `scope` method to return an `ActiveRecord::Relation` object. You can pass this relation directly into [Pagy](https://github.com/ddnexus/pagy) to perform the search, or additional scope clauses can be added to suit the needs of your application.

```ruby
class CustomerFilter < AllFutures::Base

  # Attribute definitions cut for brevity

  def scope
    Customer
      .with_status(status)
      .only_lawyers(lawyers)
      .between(low, high)
      .order(order => direction)
      .search_for(search, threshold)
  end
  
end
```

The business logic required to filter the data is fully contained in the model as a set of scopes. This `CustomerFilter#scope` method simply connects the dots to provide access to a relation for *this* filter instance.

### Draw the rest of the owl

Going through building the rest of a faceted search is beyond the scope of this document, but you are encouraged to clone and explore the Beast Mode [codebase](https://github.com/leastbad/beast_mode) and/or follow along with the [tutorial blog post](https://leastbad.com/beast-mode).


