The Abstraction

Share this post

Introducing ActionState for Rails

www.theabstraction.space

Introducing ActionState for Rails

Define Rails model scopes and predicates with the same code.

Joel Drapper
Mar 10, 2022
1
Share this post

Introducing ActionState for Rails

www.theabstraction.space

One of my favourite features in Rails is the ability to quickly define named filters for ActiveRecord models using a simple query language.

If you have an Article model, for example, where records can be published, you could define a scope called published, that allows you to filter just the published articles.

class Article
  scope :published, -> { where(published: true) }
end

Calling Article.published will return all the articles that have the published attribute set to true.

Let’s say you want to be able to check if a specific article is itself published using the same rules you defined in the scope. You could write a predicate method like this:

def published?
  published == true
end

Now calling the published? predicate method on an article will return true if it’s published and false if not.

The problem is we now have two different definitions of what it means to be published.

Let’s say in the future, you decide to make a scheduling feature so articles can be published on a specific date in the future. You add a published_at field to the Article model and change your published scope to this:

scope :published, -> { where(published_at: ..Time.current) }

Now articles are only considered published if the published_at attribute is a date / time before the current time. But unless you remember to update the predicate method, the logic is out-of-sync.

You could write a test to ensure your scope and predicate stay in sync so you never forget to update one or the other, but in the best case you’ve got the same logic defined in two places.

That’s where ActionState comes in. ActionState provides a state method on ActiveRecord objects that defines both a scope and a predicate at the same time using the same logic.

That means this:

state(:published) { where(published_at: ..Time.current }

Is equivalent to this:

scope :published, -> { where(published_at: ..Time.current) }

def published?
  (..Time.current).cover?(published_at)
end 

ActionState supports a small subset of the ActiveRecord query language but I think it’s enough to cover most use-cases it’s designed for. Supported query methods include: where, where.not and excluding. It also supports states with arguments and state composition.

You can find further documentation and installation instructions on GitHub.

I’d love to know if you find it useful or if you have any questions.

— Joel

Share this post

Introducing ActionState for Rails

www.theabstraction.space
Previous
Next
Comments
TopNewCommunity

No posts

Ready for more?

© 2023 The Abstraction
Privacy ∙ Terms ∙ Collection notice
Start WritingGet the app
Substack is the home for great writing