scrivener 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +86 -2
- data/lib/scrivener.rb +2 -2
- data/lib/scrivener/validations.rb +44 -2
- data/test/scrivener_test.rb +102 -0
- metadata +5 -7
data/README.md
CHANGED
@@ -37,6 +37,7 @@ chose to ignore the ones that come bundled with ORMs.
|
|
37
37
|
This short example illustrates how to move the validation and whitelisting
|
38
38
|
responsibilities away from the model and into Scrivener:
|
39
39
|
|
40
|
+
```ruby
|
40
41
|
# We use Sequel::Model in this example, but it applies to other ORMs such
|
41
42
|
# as Ohm or ActiveRecord.
|
42
43
|
class Article < Sequel::Model
|
@@ -60,15 +61,17 @@ responsibilities away from the model and into Scrivener:
|
|
60
61
|
|
61
62
|
article.valid? #=> false
|
62
63
|
article.errors.on(:state) #=> ["cannot be empty"]
|
64
|
+
```
|
63
65
|
|
64
66
|
Of course, what you would do instead is declare `:title` and `:body` as allowed
|
65
67
|
columns, then assign `:state` using the attribute accessor. The reason for this
|
66
68
|
example is to show how you need to work around the fact that there's a single
|
67
69
|
declaration for allowed columns and validations, which in many cases is a great
|
68
|
-
feature and in
|
70
|
+
feature and in others is a minor obstacle.
|
69
71
|
|
70
72
|
Now see what happens with Scrivener:
|
71
73
|
|
74
|
+
```ruby
|
72
75
|
# Now the model has no validations or whitelists. It may still have schema
|
73
76
|
# constraints, which is a good practice to enforce data integrity.
|
74
77
|
class Article < Sequel::Model
|
@@ -110,13 +113,94 @@ Now see what happens with Scrivener:
|
|
110
113
|
article.update_attributes(publish.attributes)
|
111
114
|
|
112
115
|
# If we try to change other fields...
|
113
|
-
publish = Publish.new(status: "published", title:
|
116
|
+
publish = Publish.new(status: "published", title: "foo")
|
114
117
|
#=> NoMethodError: undefined method `title=' for #<Publish...>
|
118
|
+
```
|
115
119
|
|
116
120
|
It's important to note that using Scrivener implies a greater risk than using
|
117
121
|
the model validations. Having a central repository of mass assignable
|
118
122
|
attributes and validations is more secure in most scenarios.
|
119
123
|
|
124
|
+
|
125
|
+
Assertions
|
126
|
+
-----------
|
127
|
+
|
128
|
+
Scrivener ships with some basic assertions. The following is a brief description
|
129
|
+
for each of them:
|
130
|
+
|
131
|
+
### assert
|
132
|
+
|
133
|
+
The `assert` method is used by all the other assertions. It pushes the
|
134
|
+
second parameter to the list of errors if the first parameter evaluates
|
135
|
+
to false.
|
136
|
+
|
137
|
+
``` ruby
|
138
|
+
def assert(value, error)
|
139
|
+
value or errors[error.first].push(error.last) && false
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
### assert_present
|
144
|
+
|
145
|
+
Checks that the given field is not nil or empty. The error code for this
|
146
|
+
assertion is `:not_present`.
|
147
|
+
|
148
|
+
### assert_format
|
149
|
+
|
150
|
+
Checks that the given field matches the provided regular expression.
|
151
|
+
The error code for this assertion is `:format`.
|
152
|
+
|
153
|
+
### assert_numeric
|
154
|
+
|
155
|
+
Checks that the given field holds a number as a Fixnum or as a string
|
156
|
+
representation. The error code for this assertion is `:not_numeric`.
|
157
|
+
|
158
|
+
### assert_url
|
159
|
+
|
160
|
+
Provides a pretty general URL regular expression match. An important
|
161
|
+
point to make is that this assumes that the URL should start with
|
162
|
+
`http://` or `https://`. The error code for this assertion is
|
163
|
+
`:not_url`.
|
164
|
+
|
165
|
+
### assert_email
|
166
|
+
|
167
|
+
In this current day and age, almost all web applications need to
|
168
|
+
validate an email address. This pretty much matches 99% of the emails
|
169
|
+
out there. The error code for this assertion is `:not_email`.
|
170
|
+
|
171
|
+
### assert_member
|
172
|
+
|
173
|
+
Checks that a given field is contained within a set of values (i.e.
|
174
|
+
like an `ENUM`).
|
175
|
+
|
176
|
+
``` ruby
|
177
|
+
def validate
|
178
|
+
assert_member :state, %w{pending paid delivered}
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
The error code for this assertion is `:not_valid`
|
183
|
+
|
184
|
+
### assert_length
|
185
|
+
|
186
|
+
Checks that a given field's length falls under a specified range.
|
187
|
+
|
188
|
+
``` ruby
|
189
|
+
def validate
|
190
|
+
assert_length :username, 3..20
|
191
|
+
end
|
192
|
+
```
|
193
|
+
|
194
|
+
The error code for this assertion is `:not_in_range`.
|
195
|
+
|
196
|
+
### assert_decimal
|
197
|
+
|
198
|
+
Checks that a given field looks like a number in the human sense
|
199
|
+
of the word. Valid numbers are: 0.1, .1, 1, 1.1, 3.14159, etc.
|
200
|
+
|
201
|
+
The error code for this assertion is `:not_decimal`.
|
202
|
+
|
203
|
+
|
120
204
|
Installation
|
121
205
|
------------
|
122
206
|
|
data/lib/scrivener.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require File.expand_path("scrivener/validations", File.dirname(__FILE__))
|
2
2
|
|
3
3
|
class Scrivener
|
4
|
-
VERSION = "0.0.
|
4
|
+
VERSION = "0.0.3"
|
5
5
|
|
6
6
|
include Validations
|
7
7
|
|
@@ -49,7 +49,7 @@ class Scrivener
|
|
49
49
|
instance_variables.each do |ivar|
|
50
50
|
next if ivar == :@errors
|
51
51
|
|
52
|
-
att = ivar.
|
52
|
+
att = ivar[1..-1].to_sym
|
53
53
|
atts[att] = send(att)
|
54
54
|
end
|
55
55
|
end
|
@@ -7,6 +7,11 @@ class Scrivener
|
|
7
7
|
# * assert_present
|
8
8
|
# * assert_format
|
9
9
|
# * assert_numeric
|
10
|
+
# * assert_url
|
11
|
+
# * assert_email
|
12
|
+
# * assert_member
|
13
|
+
# * assert_length
|
14
|
+
# * assert_decimal
|
10
15
|
#
|
11
16
|
# The core tenets that Scrivener::Validations advocates can be summed up in a
|
12
17
|
# few bullet points:
|
@@ -121,10 +126,48 @@ class Scrivener
|
|
121
126
|
# @see http://cyx.github.com/ohm-contrib/doc/Ohm/NumberValidations.html
|
122
127
|
def assert_numeric(att, error = [att, :not_numeric])
|
123
128
|
if assert_present(att, error)
|
124
|
-
assert_format(att,
|
129
|
+
assert_format(att, /\A\d+\z/, error)
|
125
130
|
end
|
126
131
|
end
|
127
132
|
|
133
|
+
URL = /\A(http|https):\/\/([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}|(2
|
134
|
+
5[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}
|
135
|
+
|localhost)(:[0-9]{1,5})?(\/.*)?\z/ix
|
136
|
+
|
137
|
+
def assert_url(att, error = [att, :not_url])
|
138
|
+
if assert_present(att, error)
|
139
|
+
assert_format(att, URL, error)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
EMAIL = /\A([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*
|
144
|
+
[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@
|
145
|
+
((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+
|
146
|
+
[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)\z/ix
|
147
|
+
|
148
|
+
def assert_email(att, error = [att, :not_email])
|
149
|
+
if assert_present(att, error)
|
150
|
+
assert_format(att, EMAIL, error)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def assert_member(att, set, err = [att, :not_valid])
|
155
|
+
assert(set.include?(send(att)), err)
|
156
|
+
end
|
157
|
+
|
158
|
+
def assert_length(att, range, error = [att, :not_in_range])
|
159
|
+
if assert_present(att, error)
|
160
|
+
val = send(att).to_s
|
161
|
+
assert range.include?(val.length), error
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
DECIMAL = /\A(\d+)?(\.\d+)?\z/
|
166
|
+
|
167
|
+
def assert_decimal(att, error = [att, :not_decimal])
|
168
|
+
assert_format att, DECIMAL, error
|
169
|
+
end
|
170
|
+
|
128
171
|
# The grand daddy of all assertions. If you want to build custom
|
129
172
|
# assertions, or even quick and dirty ones, you can simply use this method.
|
130
173
|
#
|
@@ -149,4 +192,3 @@ class Scrivener
|
|
149
192
|
end
|
150
193
|
end
|
151
194
|
end
|
152
|
-
|
data/test/scrivener_test.rb
CHANGED
@@ -89,3 +89,105 @@ scope do
|
|
89
89
|
assert_equal [:not_present], q.errors[:foo]
|
90
90
|
end
|
91
91
|
end
|
92
|
+
|
93
|
+
class Post < Scrivener
|
94
|
+
attr_accessor :url, :email
|
95
|
+
|
96
|
+
def validate
|
97
|
+
assert_url :url
|
98
|
+
assert_email :email
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
scope do
|
103
|
+
test "email & url" do
|
104
|
+
p = Post.new({})
|
105
|
+
|
106
|
+
assert ! p.valid?
|
107
|
+
assert_equal [:not_url], p.errors[:url]
|
108
|
+
assert_equal [:not_email], p.errors[:email]
|
109
|
+
|
110
|
+
p = Post.new(url: "google.com", email: "egoogle.com")
|
111
|
+
|
112
|
+
assert ! p.valid?
|
113
|
+
assert_equal [:not_url], p.errors[:url]
|
114
|
+
assert_equal [:not_email], p.errors[:email]
|
115
|
+
|
116
|
+
p = Post.new(url: "http://google.com", email: "me@google.com")
|
117
|
+
assert p.valid?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Person < Scrivener
|
122
|
+
attr_accessor :username
|
123
|
+
|
124
|
+
def validate
|
125
|
+
assert_length :username, 3..10
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
scope do
|
130
|
+
test "length validation" do
|
131
|
+
p = Person.new({})
|
132
|
+
|
133
|
+
assert ! p.valid?
|
134
|
+
assert p.errors[:username].include?(:not_in_range)
|
135
|
+
|
136
|
+
p = Person.new(username: "fo")
|
137
|
+
assert ! p.valid?
|
138
|
+
assert p.errors[:username].include?(:not_in_range)
|
139
|
+
|
140
|
+
p = Person.new(username: "foofoofoofo")
|
141
|
+
assert ! p.valid?
|
142
|
+
assert p.errors[:username].include?(:not_in_range)
|
143
|
+
|
144
|
+
p = Person.new(username: "foo")
|
145
|
+
assert p.valid?
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Order < Scrivener
|
150
|
+
attr_accessor :status
|
151
|
+
|
152
|
+
def validate
|
153
|
+
assert_member :status, %w{pending paid delivered}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
scope do
|
158
|
+
test "member validation" do
|
159
|
+
o = Order.new({})
|
160
|
+
assert ! o.valid?
|
161
|
+
assert_equal [:not_valid], o.errors[:status]
|
162
|
+
|
163
|
+
o = Order.new(status: "foo")
|
164
|
+
assert ! o.valid?
|
165
|
+
assert_equal [:not_valid], o.errors[:status]
|
166
|
+
|
167
|
+
%w{pending paid delivered}.each do |status|
|
168
|
+
o = Order.new(status: status)
|
169
|
+
assert o.valid?
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class Product < Scrivener
|
175
|
+
attr_accessor :price
|
176
|
+
|
177
|
+
def validate
|
178
|
+
assert_decimal :price
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
scope do
|
183
|
+
test "decimal validation" do
|
184
|
+
p = Product.new({})
|
185
|
+
assert ! p.valid?
|
186
|
+
assert_equal [:not_decimal], p.errors[:price]
|
187
|
+
|
188
|
+
%w{10 10.1 10.100000 0.100000 .1000}.each do |price|
|
189
|
+
p = Product.new(price: price)
|
190
|
+
assert p.valid?
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scrivener
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,12 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
default_executable:
|
12
|
+
date: 2012-01-24 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: cutest
|
17
|
-
requirement: &
|
16
|
+
requirement: &2156191960 !ruby/object:Gem::Requirement
|
18
17
|
none: false
|
19
18
|
requirements:
|
20
19
|
- - ! '>='
|
@@ -22,7 +21,7 @@ dependencies:
|
|
22
21
|
version: '0'
|
23
22
|
type: :development
|
24
23
|
prerelease: false
|
25
|
-
version_requirements: *
|
24
|
+
version_requirements: *2156191960
|
26
25
|
description: Scrivener removes the validation responsibility from models and acts
|
27
26
|
as a filter for whitelisted attributes.
|
28
27
|
email:
|
@@ -39,7 +38,6 @@ files:
|
|
39
38
|
- lib/scrivener.rb
|
40
39
|
- scrivener.gemspec
|
41
40
|
- test/scrivener_test.rb
|
42
|
-
has_rdoc: true
|
43
41
|
homepage: http://github.com/soveran/scrivener
|
44
42
|
licenses: []
|
45
43
|
post_install_message:
|
@@ -60,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
58
|
version: '0'
|
61
59
|
requirements: []
|
62
60
|
rubyforge_project:
|
63
|
-
rubygems_version: 1.
|
61
|
+
rubygems_version: 1.8.10
|
64
62
|
signing_key:
|
65
63
|
specification_version: 3
|
66
64
|
summary: Validation frontend for models.
|