0.0
No release in over 3 years
Siftly::Rails lets Rails applications validate model attributes with Siftly and inspect flagged attributes through the model instance.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

= 6.0.2
~> 13.0
~> 2.0

Runtime

 Project Readme

Siftly::Rails

siftly-rails wires Siftly.check into Active Model and Active Record validations. Use it when you want attributes checked automatically during validation and want the results attached to the model instance.

Installation

gem "siftly-rails"
require "siftly/rails"

Requiring siftly/rails auto-includes Siftly::Rails::Model into all Active Record models via ActiveSupport.on_load(:active_record). You still need to require whichever plugin gems provide the filters you want — siftly-rails only adds the model integration layer.

For plain Active Model objects that don't inherit from ActiveRecord::Base, include the concern manually with include Siftly::Rails::Model.

Quick Start

First configure the normal Siftly pipeline:

Siftly.configure do |config|
  config.aggregator = :score
  config.threshold = 1.0
  config.use :keyword_pack

  config.filter :keyword_pack do |filter|
    filter.keywords = ["spam", "buy now"]
    filter.weight = 1.0
  end
end

Then declare the attributes to check:

class ContactRequest < ApplicationRecord
  siftly_check :email, :message, message: "contains spam"
end

At validation time, each declared attribute is checked with:

  • value: the attribute value
  • attribute: the attribute name
  • record: the model instance
  • context: resolved from the check options
  • filter_overrides: resolved from the check options

Example:

request = ContactRequest.new(email: "spam@example.com", message: "buy now")

request.valid?                           # => false
request.siftly_spam?                     # => true
request.siftly_spam_attributes           # => [:email, :message]
request.siftly_results.keys              # => [:email, :message]
request.siftly_spam_result_for(:message) # => #<Siftly::Result ...>

What siftly_check Does

Each siftly_check call:

  • registers one validation callback for the declared attributes
  • runs Siftly.check once per attribute
  • stores the latest result per attribute in @siftly_results
  • adds an Active Model error when a result is spam

The spam-tracking state is reset in before_validation, so old results do not leak across revalidation.

Supported Options

Siftly options passed through to Siftly.check:

  • filters
  • filter_overrides
  • aggregator
  • threshold
  • failure_mode
  • instrumenter
  • context

Model-side validation behavior:

  • allow_nil
  • allow_blank
  • message
  • strict

Active Model callback options:

  • if
  • unless
  • on

Option Resolution

Every non-callback option can be a literal value or a callable. This is broader than just context and filter_overrides.

Callables are executed via instance_exec on the model instance, so self inside the block is the record. Arity determines which explicit arguments are passed:

  • arity 0: no arguments (access the record through self)
  • arity 1: attribute
  • arity 2+: attribute and record

Example:

class Lead < ApplicationRecord
  siftly_check :message,
    filters: -> { [:keyword_pack] },
    filter_overrides: ->(attribute) { { keyword_pack: { keywords: [trigger_phrase], weight: 1.2 } } },
    context: ->(attribute, record) { { source: "#{attribute}:#{record.source_name}" } },
    allow_blank: true
end

Skip Behavior

A check is skipped entirely when:

  • allow_nil is truthy and the value is nil
  • allow_blank is truthy and the value is blank

Skipped checks:

  • do not call Siftly.check
  • do not store a result for that attribute
  • do not add errors

Exposed Instance Helpers

Instances get:

  • siftly_spam?
  • siftly_results
  • siftly_result_for(attribute)
  • siftly_spam_attributes
  • siftly_spam_results
  • siftly_spam_result_for(attribute)

Practical Advice

  • Scope filters: per model when different models need different spam rules. Relying only on global configuration is how you end up checking everything with everything.
  • Remember that siftly-rails does not decide what counts as spam. It just runs the configured pipeline during validation.
  • If a filter is flaky or network-backed, set failure_mode deliberately. Pretending the default is fine without thinking about it is lazy.