• Stars
    star
    124
  • Rank 288,207 (Top 6 %)
  • Language
    Ruby
  • License
    MIT License
  • Created about 13 years ago
  • Updated over 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Validation frontend for models.

Scrivener

Validation frontend for models.

Description

Scrivener removes the validation responsibility from models and acts as a filter for whitelisted attributes. Read about the motivation to understand why this separation of concerns is important.

Usage

A very basic example would be creating a blog post:

class CreateBlogPost < Scrivener
  attr_accessor :title
  attr_accessor :body

  def validate
    assert_present :title
    assert_present :body
  end
end

In order to use it, you have to create an instance of CreateBlogPost by passing a hash with the attributes title and body and their corresponding values:

params = {
  title: "Bartleby",
  body: "I am a rather elderly man..."
}

filter = CreateBlogPost.new(params)

Now you can run the validations by calling filter.valid?, and you can retrieve the attributes by calling filter.attributes. If the validation fails, a hash of attributes and error codes will be available by calling filter.errors. For example:

if filter.valid?
  puts filter.attributes
else
  puts filter.errors
end

For now, we are just printing the attributes and the list of errors, but often you will use the attributes to create an instance of a model, and you will display the error messages in a view.

Let's consider the case of creating a new user:

class CreateUser < Scrivener
  attr_accessor :email
  attr_accessor :password
  attr_accessor :password_confirmation

  def validate
    assert_email :email

    if assert_present :password
      assert_equal :password, password_confirmation
    end
  end
end

The filter looks very similar, but as you can see the validations return booleans, thus they can be nested. In this example, we don't want to bother asserting if the password and the password confirmation are equal if the password was not provided.

Let's instantiate the filter:

params = {
  email: "[email protected]",
  password: "monkey",
  password_confirmation: "monkey"
}

filter = CreateUser.new(params)

If the validation succeeds, we only need email and password to create a new user, and we can discard the password_confirmation. The filter.slice method receives a list of attributes and returns the attributes hash with any other attributes removed. In this example, the hash returned by filter.slice will contain only the email and password fields:

if filter.valid?
  User.create(filter.slice(:email, :password))
end

Sometimes we might want to use parameters from the outside for validation, but don't want the validator to treat them as attributes. In that case we can pass arguments to #valid?, and they will be forwarded to #validate.

class CreateComment < Scrivener
  attr_accessor :content
  attr_accessor :article_id

  def validate(available_articles:)
    assert_present :content
    assert_member :article_id, available_articles.map(&:id)
  end
end
params = {
  content:    "this is a comment",
  article_id: 57,
}

filter = CreateComment.new(params)

filter.valid?(available_articles: user.articles)

Assertions

Scrivener ships with some basic assertions.

assert

The assert method is used by all the other assertions. It pushes the second parameter to the list of errors if the first parameter evaluates to false or nil.

def assert(value, error)
   value or errors[error.first].push(error.last) && false
end

New assertions can be built upon existing ones. For example, let's define an assertion for positive numbers:

def assert_positive(att, error = [att, :not_positive])
  assert(send(att) > 0, error)
end

This assertion calls assert and passes both the result of evaluating send(att) > 0 and the array with the attribute and the error code. All assertions respect this API.

assert_present

Checks that the given field is not nil or empty. The error code for this assertion is :not_present.

assert_equal

Check that the attribute has the expected value. It uses === for comparison, so type checks are possible too. Note that in order to make the case equality work, the check inverts the order of the arguments: assert_equal :foo, Bar is translated to the expression Bar === send(:foo).

assert_format

Checks that the given field matches the provided regular expression. The error code for this assertion is :format.

assert_numeric

Checks that the given field holds a number as a Fixnum or as a string representation. The error code for this assertion is :not_numeric.

assert_url

Provides a pretty general URL regular expression match. An important point to make is that this assumes that the URL should start with http:// or https://. The error code for this assertion is :not_url.

assert_email

In this current day and age, almost all web applications need to validate an email address. This pretty much matches 99% of the emails out there. The error code for this assertion is :not_email.

assert_member

Checks that a given field is contained within a set of values (i.e. like an ENUM).

def validate
  assert_member :state, %w{pending paid delivered}
end

The error code for this assertion is :not_valid

assert_length

Checks that a given field's length falls under a specified range.

def validate
  assert_length :username, 3..20
end

The error code for this assertion is :not_in_range.

assert_decimal

Checks that a given field looks like a number in the human sense of the word. Valid numbers are: 0.1, .1, 1, 1.1, 3.14159, etc.

The error code for this assertion is :not_decimal.

Motivation

A model may expose different APIs to satisfy different purposes. For example, the set of validations for a User in a sign up process may not be the same as the one exposed to an Admin when editing a user profile. While you want the User to provide an email, a password and a password confirmation, you probably don't want the admin to mess with those attributes at all.

In a wizard, different model states ask for different validations, and a single set of validations for the whole process is not the best solution.

This library exists to satisfy the need for extracting Ohm's validations for reuse in other scenarios.

Using Scrivener feels very natural no matter what underlying model you are using. As it provides its own validation and whitelisting features, you can choose to ignore those that come bundled with ORMs.

See also

Scrivener is Bureaucrat's little brother. It draws all the inspiration from it and its features are a subset of Bureaucrat's.

Installation

$ gem install scrivener

More Repositories

1

cuba

Rum based microframework for web development.
Ruby
1,438
star
2

ohm

Object-Hash Mapping for Redis
Ruby
1,390
star
3

micromachine

Minimal Finite State Machine
Ruby
522
star
4

clac

Command-line, stack-based calculator with postfix notation
C
350
star
5

map

Map lines from stdin to commands
C
221
star
6

mote

Minimum Operational Template
Ruby
217
star
7

nest

Generate nested namespaced keys for key-value databases.
Ruby
185
star
8

ost

Redis based queues and workers.
Ruby
166
star
9

toro

Tree oriented routing
Crystal
144
star
10

syro

Simple router for web applications
Ruby
135
star
11

cargo

Require libraries without cluttering your namespace.
Ruby
127
star
12

clap

Command line argument parsing
Ruby
90
star
13

ohm-crystal

Ohm for Crystal
Crystal
71
star
14

disque-rb

Disque client for Ruby
Ruby
68
star
15

gs

Gemset management
Ruby
67
star
16

totp

Time-based One-Time Passwords
Ruby
44
star
17

mailcat

Fake SMTP server that prints emails to stdout
C
38
star
18

chen

Change directory entries with your text editor
C
37
star
19

spawn

A ridiculously simple fixtures replacement for your web framework of choice.
Ruby
36
star
20

finist

Redis based Finite State Machine
Ruby
36
star
21

redisurl

Connect to Redis using a REDIS_URL and the redigo client.
Go
33
star
22

hart

Hash router
Ruby
24
star
23

stal-ruby

Set algebra solver for Redis
Ruby
22
star
24

redisent

Sentinels aware Redis client.
Ruby
22
star
25

relay

Relay commands over SSH
Ruby
21
star
26

resp

Lightweight RESP client for Lua
Lua
21
star
27

lomo

Apply a lomo filter to your pictures from the command line using ImageMagick.
Ruby
19
star
28

terco

Obstinate DNS
Ruby
16
star
29

override

The as-simple-as-possible-but-not-simpler stubbing library.
Ruby
16
star
30

ox

Skeleton for a Cuba-based JSON API.
Ruby
16
star
31

nido

Structured keys helper
Ruby
14
star
32

syro-demo

Demo application with Syro
Ruby
14
star
33

resp-crystal

Lightweight RESP client
Crystal
14
star
34

mt

Mail tester daemon.
Ruby
13
star
35

stringent

Generate a string with a target entropy
Ruby
12
star
36

rediscan

Scanner for Redis keyspace
Ruby
12
star
37

hypertext

Hypertext authoring with Ruby
Ruby
11
star
38

filmo

A single page presentation tool.
11
star
39

cuba-book

Cuba Book
11
star
40

sc

List of HTTP status codes.
Ruby
11
star
41

finist.lua

Redis based Finite State Machine
Lua
10
star
42

drawer

Ultra slim file-based cache
Ruby
10
star
43

nest-crystal

Object Oriented Keys for Redis
Crystal
9
star
44

basica

Basic authentication library.
Ruby
8
star
45

rino

Remove a file by its inode number
C
7
star
46

seg.rb

Segment matcher for paths
Ruby
7
star
47

seg

Segment matcher for paths
Crystal
7
star
48

trim

Read from stdin and remove a prefix from each line
C
7
star
49

tas

Trees as strings
Ruby
6
star
50

stal

Set algebra solver for Redis
Lua
6
star
51

pac

Package management for Lua libraries.
Shell
6
star
52

prep

Read from stdin and prepend a string to each line while preserving identation.
C
6
star
53

m4s2

Static Site Generator
HTML
5
star
54

contract

Contract helper
Ruby
5
star
55

stal-crystal

Set algebra solver for Redis
Crystal
5
star
56

loco

Lines of code counter
C
4
star
57

ook

Object Oriented Keys for Redis
Ruby
4
star
58

walk

Walk a directory tree and print the name of every regular file
C
4
star
59

rel

Ruby client for the Bandicoot database.
Ruby
4
star
60

textorama

Text slides for the terminal
Shell
4
star
61

homebrew-tools

Formulae for Homebrew
Ruby
4
star
62

rediscan.lua

Scanner for Redis keyspace in Lua
Lua
3
star
63

seg.go

Segment matcher for paths
Go
3
star
64

look

Add a vendor directory to your load path.
Ruby
3
star
65

app

Cuba application template for gn, the file generator.
Ruby
2
star
66

packer

Require different versions of the same Cargo-compatible gem
Ruby
2
star
67

tele.sh

2
star
68

ers

AWK script easy replacements in templates
Awk
2
star
69

soveran.github.io

2
star
70

filter

Workflow template for gn.
Ruby
1
star
71

remoto

Ruby
1
star
72

gem

Gem template for gn, the file generator.
Ruby
1
star
73

ohm-scripts

Lua scripts for Ohm compatible libraries
Lua
1
star
74

ostgo

Ost client.
Go
1
star
75

miga

Breadcrumb name of the working directory
C
1
star