ball_gag 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Readme.md +34 -16
- data/lib/ball_gag/bowling_ball.rb +9 -0
- data/lib/ball_gag/engine.rb +4 -6
- data/lib/ball_gag/gag.rb +13 -2
- data/lib/ball_gag/validations.rb +7 -0
- data/lib/ball_gag/version.rb +1 -1
- data/spec/cloak_spec.rb +6 -0
- data/spec/gag_spec.rb +36 -0
- data/spec/support/models.rb +9 -1
- data/spec/validations_spec.rb +18 -0
- metadata +8 -8
data/Readme.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Validate user-generated content against acceptable content rules. BallGag provides a convenient interface for describing the acceptability of the attributes of Ruby objects.
|
4
4
|
|
5
|
-
When an attribute is gagged,
|
5
|
+
When an attribute is gagged, `#{method}_gagged?` and `#{method}_not_gagged?` methods are added to the class.
|
6
6
|
|
7
7
|
## Example
|
8
8
|
Here's a simple example demonstrating gagging of a single attribute.
|
@@ -29,13 +29,13 @@ Post.new('That was some fine watermelon').body_gagged?
|
|
29
29
|
````
|
30
30
|
|
31
31
|
## Using it with Rails
|
32
|
-
BallGag integrates easily into your Rails app, allowing you validate that
|
32
|
+
BallGag integrates easily into your Rails app, allowing you validate that `ActiveModel` attributes are acceptable. First, add the gem to your `Gemfile`
|
33
33
|
|
34
34
|
````ruby
|
35
35
|
gem 'ball_gag'
|
36
36
|
````
|
37
37
|
|
38
|
-
Now a
|
38
|
+
Now a `NotGaggedValidator` is available which you can use like this.
|
39
39
|
|
40
40
|
````ruby
|
41
41
|
class Post < ActiveRecord::Base
|
@@ -61,42 +61,60 @@ Each gagged attribute can be provided its own block to call when checking accept
|
|
61
61
|
BallGag.engine = lambda { |content| !/damn/.match content }
|
62
62
|
````
|
63
63
|
|
64
|
-
Any object that responds to
|
64
|
+
Any object that responds to `call` can be used as an engine.
|
65
|
+
|
66
|
+
Another global option that can be set is `<BallGag.only_validate_on_attribute_changed/tt>. When this is set to true, `:not_gagged` validations will not be run unless the attribute has changed. This can help you avoid writing boilerplate and can make conditionals provided to your validations more concise. Compare the following two examples to see how specifying this option simplifies validations.
|
67
|
+
|
68
|
+
````ruby
|
69
|
+
BallGag.only_validate_on_attribute_changed = true
|
70
|
+
|
71
|
+
class Post < ActiveRecord::Base
|
72
|
+
validates :text, not_gagged: { unless: -> { author.admin? } }
|
73
|
+
end
|
74
|
+
````
|
75
|
+
|
76
|
+
vs.
|
77
|
+
|
78
|
+
````ruby
|
79
|
+
class Post < ActiveRecord::Base
|
80
|
+
validates :text, not_gagged: { unless: -> { author.admin? || !text_changed? } }
|
81
|
+
end
|
82
|
+
````
|
65
83
|
|
66
84
|
## Signature
|
67
85
|
|
68
|
-
The first argument to
|
86
|
+
The first argument to `call` is the value, or hash of values to be checked for acceptability.
|
69
87
|
|
70
|
-
If a single attribute is gagged, the
|
88
|
+
If a single attribute is gagged, the `call` method is invoked with the value of the attribute. If multiple attributes are gagged, the `call` method is invoked with a hash whose keys are the gagged attribute names and whose values are the attributes' values.
|
71
89
|
|
72
|
-
The second argument to
|
90
|
+
The second argument to `call` is a hash of options. This hash has the following entries:
|
73
91
|
<table>
|
74
92
|
<tbody>
|
75
93
|
<tr>
|
76
|
-
<td
|
94
|
+
<td>`<strong>:instance</strong>`</td>
|
77
95
|
<td>The instance on which the attribute checked was invoked.</td>
|
78
96
|
</tr>
|
79
97
|
<tr>
|
80
|
-
<td
|
81
|
-
<td>The options supplied to the specific
|
98
|
+
<td>`<strong>:options</strong>`</td>
|
99
|
+
<td>The options supplied to the specific `gag` call.</td>
|
82
100
|
</tr>
|
83
101
|
<tr>
|
84
|
-
<td
|
85
|
-
<td>A boolean indicating whether the
|
102
|
+
<td>`<strong>:single</strong>`</td>
|
103
|
+
<td>A boolean indicating whether the `gag` method was invoked with only one attribute.</td>
|
86
104
|
</tr>
|
87
105
|
<tr>
|
88
|
-
<td
|
106
|
+
<td>`<strong>:attr</strong>`</td>
|
89
107
|
<td>The name of the attribute being checked. This is only useful for single-attribute gags.</td>
|
90
108
|
</tr>
|
91
109
|
</tbody>
|
92
110
|
</table>
|
93
111
|
|
94
|
-
Since options provided to the
|
112
|
+
Since options provided to the `gag` method are passed through to your back-end, you can use them when building up a request to send to a 3rd-party.
|
95
113
|
|
96
114
|
## Integration with 3rd-party tools
|
97
115
|
The real power of BallGag is in how it integrates with 3rd-party moderation services. BallGag helps you thread model-specific information through to a remote back-end.
|
98
116
|
|
99
|
-
An advanced engine may need to handle accepability of single and multiple attributes. The handler can differentiate between the two types of calls by inspecting the
|
117
|
+
An advanced engine may need to handle accepability of single and multiple attributes. The handler can differentiate between the two types of calls by inspecting the `:single` key of the options it's called with.
|
100
118
|
|
101
119
|
Here's an example engine that accepts single and multi-attribute calls and validates against a made-up RESTful moderation service.
|
102
120
|
|
@@ -126,7 +144,7 @@ BallGag.engine = ModerationServiceEngine
|
|
126
144
|
|
127
145
|
## Cloaking
|
128
146
|
If you need flexibility in how the library is used, you can reprogram the API.
|
129
|
-
First, an alternative top-level module called
|
147
|
+
First, an alternative top-level module called `BowlingBall` is provided. Include `BowlingBall` in your models to get access to the same functionality you would get with `BallGag`.
|
130
148
|
You can also specify an alternate verb for gagging attributes, and an alternate preterite form of the verb for querying the attribute. The preterite form supplied also makes positive and negative validations of the same name available.
|
131
149
|
|
132
150
|
````ruby
|
@@ -31,6 +31,15 @@ module BowlingBall
|
|
31
31
|
def negative_preterite= preterite
|
32
32
|
BallGag.negative_preterite = preterite
|
33
33
|
end
|
34
|
+
|
35
|
+
def only_validate_on_attribute_changed
|
36
|
+
BallGag.only_validate_on_attribute_changed
|
37
|
+
end
|
38
|
+
|
39
|
+
def only_validate_on_attribute_changed= bool
|
40
|
+
BallGag.only_validate_on_attribute_changed = bool
|
41
|
+
end
|
42
|
+
|
34
43
|
end
|
35
44
|
end
|
36
45
|
|
data/lib/ball_gag/engine.rb
CHANGED
data/lib/ball_gag/gag.rb
CHANGED
@@ -7,6 +7,10 @@ module BallGag
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
+
def invalidate_gag_cache attributes
|
11
|
+
@gagged_attribute_results.delete(attributes)
|
12
|
+
end
|
13
|
+
|
10
14
|
module ClassMethods
|
11
15
|
def gagged_attributes
|
12
16
|
clear_gagged_attributes unless @gagged_attributes
|
@@ -79,8 +83,15 @@ module BallGag
|
|
79
83
|
gagged_attribute_negative_interpellation_name(attr)) do
|
80
84
|
@gagged_attribute_results ||= {}
|
81
85
|
|
86
|
+
# Is the attribute dirty?
|
87
|
+
any_changed = attributes.any? do |attribute|
|
88
|
+
dirty_method_name = "#{attribute}_changed?"
|
89
|
+
respond_to?(dirty_method_name) &&
|
90
|
+
method(dirty_method_name).call
|
91
|
+
end
|
92
|
+
|
82
93
|
# Have we performed this call already?
|
83
|
-
|
94
|
+
if any_changed || !@gagged_attribute_results.has_key?(attributes)
|
84
95
|
@gagged_attribute_results[attributes] = fn.call(self, attr)
|
85
96
|
end
|
86
97
|
|
@@ -89,7 +100,7 @@ module BallGag
|
|
89
100
|
if one_attribute
|
90
101
|
# If only one attribute was supplied, a simple
|
91
102
|
# boolean response is sufficient
|
92
|
-
return
|
103
|
+
return unless call_result
|
93
104
|
return true unless call_result.respond_to?(:[])
|
94
105
|
else
|
95
106
|
raise BadResultsMappingError unless call_result.respond_to?(:[])
|
data/lib/ball_gag/validations.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
class GaggedValidator < ActiveModel::EachValidator
|
2
2
|
def validate_each object, attribute, value
|
3
|
+
if BallGag.only_validate_on_attribute_changed
|
4
|
+
dirty_method_name = "#{attribute}_changed?"
|
5
|
+
if object.respond_to?(dirty_method_name)
|
6
|
+
return unless object.method(dirty_method_name).call
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
3
10
|
unless object.method(condition_method_name(attribute)).call
|
4
11
|
object.errors[attribute] << (options[:message] || default_message)
|
5
12
|
end
|
data/lib/ball_gag/version.rb
CHANGED
data/spec/cloak_spec.rb
CHANGED
@@ -7,6 +7,7 @@ describe 'BallGag cloaking' do
|
|
7
7
|
ExampleActiveModel.clear_gagged_attributes
|
8
8
|
BallGag.verb = nil
|
9
9
|
BallGag.preterite = nil
|
10
|
+
BallGag.only_validate_on_attribute_changed = false
|
10
11
|
end
|
11
12
|
|
12
13
|
after do
|
@@ -147,6 +148,11 @@ describe 'BallGag cloaking' do
|
|
147
148
|
BowlingBall.engine = mock_engine
|
148
149
|
BallGag.engine.should eq mock_engine
|
149
150
|
end
|
151
|
+
|
152
|
+
it 'should set only_validate_on_attribute_changed' do
|
153
|
+
BowlingBall.only_validate_on_attribute_changed = true
|
154
|
+
BallGag.only_validate_on_attribute_changed.should be_true
|
155
|
+
end
|
150
156
|
end
|
151
157
|
end
|
152
158
|
|
data/spec/gag_spec.rb
CHANGED
@@ -238,6 +238,42 @@ describe ExampleModel do
|
|
238
238
|
2.times { instance.words_gagged? }
|
239
239
|
2.times { instance.email_gagged? }
|
240
240
|
end
|
241
|
+
|
242
|
+
it 'can invalidate cache' do
|
243
|
+
callable = lambda {}
|
244
|
+
mock_words = mock('words')
|
245
|
+
ExampleModel.gag :words, callable
|
246
|
+
|
247
|
+
|
248
|
+
instance = ExampleModel.new
|
249
|
+
instance.stub!(words: mock_words)
|
250
|
+
callable.should_receive(:call).
|
251
|
+
with(mock_words, anything).
|
252
|
+
exactly(2).times
|
253
|
+
|
254
|
+
instance.words_gagged?
|
255
|
+
|
256
|
+
instance.invalidate_gag_cache([:words])
|
257
|
+
|
258
|
+
instance.words_gagged?
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'should should invalidate cache when attribute changes' do
|
262
|
+
callable = lambda {}
|
263
|
+
ExampleActiveModel.gag :words, callable
|
264
|
+
instance = ExampleActiveModel.new
|
265
|
+
|
266
|
+
callable.should_receive(:call).
|
267
|
+
with(instance.words, anything).once
|
268
|
+
callable.should_receive(:call).
|
269
|
+
with(instance.words + ' you see', anything).once
|
270
|
+
|
271
|
+
instance.words_gagged?
|
272
|
+
instance.words = instance.words + ' you see'
|
273
|
+
instance.should be_words_changed
|
274
|
+
|
275
|
+
instance.words_gagged?
|
276
|
+
end
|
241
277
|
end
|
242
278
|
|
243
279
|
describe 'error cases' do
|
data/spec/support/models.rb
CHANGED
@@ -12,10 +12,18 @@ end
|
|
12
12
|
|
13
13
|
class ExampleActiveModel
|
14
14
|
include ActiveModel::Validations
|
15
|
+
include ActiveModel::Dirty
|
15
16
|
include BallGag
|
16
17
|
|
18
|
+
define_attribute_methods [ :words ]
|
19
|
+
|
17
20
|
def words
|
18
|
-
'Welcome to the place where all the creatures meet.'
|
21
|
+
@words || 'Welcome to the place where all the creatures meet.'
|
22
|
+
end
|
23
|
+
|
24
|
+
def words= words
|
25
|
+
words_will_change!
|
26
|
+
@words = words
|
19
27
|
end
|
20
28
|
end
|
21
29
|
|
data/spec/validations_spec.rb
CHANGED
@@ -4,6 +4,7 @@ describe 'ActiveModel::Validations integration' do
|
|
4
4
|
before do
|
5
5
|
ExampleActiveModel.reset_callbacks :validate
|
6
6
|
ExampleActiveModel.clear_gagged_attributes
|
7
|
+
BallGag.only_validate_on_attribute_changed = false
|
7
8
|
end
|
8
9
|
|
9
10
|
describe NotGaggedValidator do
|
@@ -47,6 +48,23 @@ describe 'ActiveModel::Validations integration' do
|
|
47
48
|
instance.should_not_receive(:words_not_gagged?)
|
48
49
|
instance.valid?
|
49
50
|
end
|
51
|
+
|
52
|
+
context 'when configured to only check on attribute changed' do
|
53
|
+
before { BallGag.only_validate_on_attribute_changed = true }
|
54
|
+
|
55
|
+
it 'should not call #{attribute}_not_gagged? if attribute is not changed' do
|
56
|
+
callable = lambda {}
|
57
|
+
ExampleActiveModel.gag :words, callable
|
58
|
+
ExampleActiveModel.validates :words,
|
59
|
+
not_gagged: true
|
60
|
+
|
61
|
+
instance = ExampleActiveModel.new
|
62
|
+
instance.should_not be_words_changed
|
63
|
+
|
64
|
+
callable.should_not_receive(:call)
|
65
|
+
instance.valid?
|
66
|
+
end
|
67
|
+
end
|
50
68
|
end
|
51
69
|
|
52
70
|
describe GaggedValidator do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ball_gag
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-11-
|
12
|
+
date: 2011-11-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &70215291665000 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70215291665000
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
requirement: &
|
27
|
+
requirement: &70215291664360 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70215291664360
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: activemodel
|
38
|
-
requirement: &
|
38
|
+
requirement: &70215291663520 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70215291663520
|
47
47
|
description: Validate user input using pluggable back-ends
|
48
48
|
email:
|
49
49
|
- duncan@dweebd.com
|