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 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, <tt>#{method}_gagged?</tt> and <tt>#{method}_not_gagged?</tt> methods are added to the class.
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 <tt>ActiveModel</tt> attributes are acceptable. First, add the gem to your <tt>Gemfile</tt>
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 <tt>NotGaggedValidator</tt> is available which you can use like this.
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 <tt>call</tt> can be used as an engine.
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 <tt>call</tt> is the value, or hash of values to be checked for acceptability.
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 <tt>call</tt> method is invoked with the value of the attribute. If multiple attributes are gagged, the <tt>call</tt> method is invoked with a hash whose keys are the gagged attribute names and whose values are the attributes' values.
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 <tt>call</tt> is a hash of options. This hash has the following entries:
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><tt><strong>:instance</strong></tt></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><tt><strong>:options</strong></tt></td>
81
- <td>The options supplied to the specific <tt>gag</tt> call.</td>
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><tt><strong>:single</strong></tt></td>
85
- <td>A boolean indicating whether the <tt>gag</tt> method was invoked with only one attribute.</td>
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><tt><strong>:attr</strong></tt></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 <tt>gag</tt> method are passed through to your back-end, you can use them when building up a request to send to a 3rd-party.
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 <tt>:single</tt> key of the options it's called with.
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 <tt>BowlingBall</tt> is provided. Include <tt>BowlingBall</tt> in your models to get access to the same functionality you would get with <tt>BallGag</tt>.
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
 
@@ -1,13 +1,11 @@
1
1
  module BallGag
2
2
  module Engine
3
3
  module ClassMethods
4
- def engine
5
- @engine
6
- end
4
+ attr_accessor(
5
+ :engine,
6
+ :only_validate_on_attribute_changed
7
+ )
7
8
 
8
- def engine= new_engine
9
- @engine = new_engine
10
- end
11
9
  end
12
10
  end
13
11
 
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
- unless @gagged_attribute_results.has_key?(attributes)
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 false unless call_result
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?(:[])
@@ -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
@@ -1,4 +1,4 @@
1
1
  module BallGag
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.5'
3
3
  end
4
4
 
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
@@ -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
 
@@ -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
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-08 00:00:00.000000000 Z
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: &70274670743440 !ruby/object:Gem::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: *70274670743440
24
+ version_requirements: *70215291665000
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &70274670742620 !ruby/object:Gem::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: *70274670742620
35
+ version_requirements: *70215291664360
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: activemodel
38
- requirement: &70274670741600 !ruby/object:Gem::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: *70274670741600
46
+ version_requirements: *70215291663520
47
47
  description: Validate user input using pluggable back-ends
48
48
  email:
49
49
  - duncan@dweebd.com