ball_gag 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +72 -0
- data/Guardfile +12 -0
- data/License +8 -0
- data/Rakefile +8 -0
- data/Readme.md +122 -0
- data/ball_gag.gemspec +23 -0
- data/lib/ball_gag.rb +10 -0
- data/lib/ball_gag/engine.rb +16 -0
- data/lib/ball_gag/errors.rb +8 -0
- data/lib/ball_gag/gag.rb +139 -0
- data/lib/ball_gag/validations.rb +8 -0
- data/lib/ball_gag/version.rb +4 -0
- data/spec/engine_spec.rb +22 -0
- data/spec/gag_spec.rb +267 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/models.rb +29 -0
- data/spec/validations_spec.rb +33 -0
- metadata +80 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
source 'http://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
group :development do
|
6
|
+
gem 'rake', '>= 0.9.2'
|
7
|
+
gem 'rspec', '>=2.6.0'
|
8
|
+
gem 'spork', '~> 0.9.0.rc9'
|
9
|
+
gem 'guard'
|
10
|
+
gem 'rb-fsevent'
|
11
|
+
gem 'growl_notify'
|
12
|
+
gem 'guard-rspec'
|
13
|
+
gem 'guard-spork'
|
14
|
+
gem 'pry'
|
15
|
+
gem 'pry-remote'
|
16
|
+
gem 'activemodel'
|
17
|
+
end
|
18
|
+
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
ball_gag (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
activemodel (3.1.1)
|
10
|
+
activesupport (= 3.1.1)
|
11
|
+
builder (~> 3.0.0)
|
12
|
+
i18n (~> 0.6)
|
13
|
+
activesupport (3.1.1)
|
14
|
+
multi_json (~> 1.0)
|
15
|
+
builder (3.0.0)
|
16
|
+
coderay (0.9.8)
|
17
|
+
diff-lcs (1.1.3)
|
18
|
+
growl_notify (0.0.3)
|
19
|
+
rb-appscript
|
20
|
+
guard (0.8.4)
|
21
|
+
thor (~> 0.14.6)
|
22
|
+
guard-rspec (0.5.0)
|
23
|
+
guard (>= 0.8.4)
|
24
|
+
guard-spork (0.3.1)
|
25
|
+
guard (>= 0.8.4)
|
26
|
+
spork (>= 0.8.4)
|
27
|
+
i18n (0.6.0)
|
28
|
+
method_source (0.6.6)
|
29
|
+
ruby_parser (~> 2.0.5)
|
30
|
+
multi_json (1.0.3)
|
31
|
+
pry (0.9.6.2)
|
32
|
+
coderay (~> 0.9.8)
|
33
|
+
method_source (~> 0.6.5)
|
34
|
+
ruby_parser (~> 2.0.5)
|
35
|
+
slop (~> 2.1.0)
|
36
|
+
pry-remote (0.1.0)
|
37
|
+
pry (~> 0.9.6)
|
38
|
+
slop (~> 2.1)
|
39
|
+
rake (0.9.2)
|
40
|
+
rb-appscript (0.6.1)
|
41
|
+
rb-fsevent (0.4.3.1)
|
42
|
+
rspec (2.6.0)
|
43
|
+
rspec-core (~> 2.6.0)
|
44
|
+
rspec-expectations (~> 2.6.0)
|
45
|
+
rspec-mocks (~> 2.6.0)
|
46
|
+
rspec-core (2.6.4)
|
47
|
+
rspec-expectations (2.6.0)
|
48
|
+
diff-lcs (~> 1.1.2)
|
49
|
+
rspec-mocks (2.6.0)
|
50
|
+
ruby_parser (2.0.6)
|
51
|
+
sexp_processor (~> 3.0)
|
52
|
+
sexp_processor (3.0.7)
|
53
|
+
slop (2.1.0)
|
54
|
+
spork (0.9.0.rc9)
|
55
|
+
thor (0.14.6)
|
56
|
+
|
57
|
+
PLATFORMS
|
58
|
+
ruby
|
59
|
+
|
60
|
+
DEPENDENCIES
|
61
|
+
activemodel
|
62
|
+
ball_gag!
|
63
|
+
growl_notify
|
64
|
+
guard
|
65
|
+
guard-rspec
|
66
|
+
guard-spork
|
67
|
+
pry
|
68
|
+
pry-remote
|
69
|
+
rake (>= 0.9.2)
|
70
|
+
rb-fsevent
|
71
|
+
rspec (>= 2.6.0)
|
72
|
+
spork (~> 0.9.0.rc9)
|
data/Guardfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
guard 'spork' do
|
2
|
+
watch('spec/spec_helper.rb')
|
3
|
+
end
|
4
|
+
|
5
|
+
guard 'rspec', version: 2, cli: '--drb --color --format nested', all_after_pass: false do
|
6
|
+
watch(/^spec\/.+_spec\.rb$/)
|
7
|
+
watch(/^lib\/ball_gag\/(.+)\.rb$/) { |m| "spec/#{m[1]}_spec.rb" }
|
8
|
+
watch(/^lib\/ball_gag\.rb$/) { 'spec' }
|
9
|
+
watch('spec/spec_helper.rb') { 'spec' }
|
10
|
+
watch(/spec\/support\/.+\.rb$/) { 'spec' }
|
11
|
+
end
|
12
|
+
|
data/License
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
Copyright (c) 2011 Duncan Beevers - Dweebd LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
8
|
+
|
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# BallGag Pluggable User-Content Validation
|
2
|
+
|
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
|
+
|
5
|
+
When an attribute is gagged, <tt>#{method}_gagged?</tt> and <tt>#{method}_not_gagged?</tt> methods are added to the class.
|
6
|
+
|
7
|
+
## Example
|
8
|
+
Here's a simple example demonstrating gagging of a single attribute.
|
9
|
+
|
10
|
+
````ruby
|
11
|
+
class Post
|
12
|
+
include BallGag
|
13
|
+
attr_accessor :body
|
14
|
+
|
15
|
+
gag(:body) { |body| !/damn/.match body }
|
16
|
+
|
17
|
+
def initialize body
|
18
|
+
self.body = body
|
19
|
+
end
|
20
|
+
end
|
21
|
+
````
|
22
|
+
|
23
|
+
````ruby
|
24
|
+
Post.new('That was some damn good watermelon').body_gagged?
|
25
|
+
# true
|
26
|
+
|
27
|
+
Post.new('That was some fine watermelon.').body_gagged?
|
28
|
+
# false
|
29
|
+
````
|
30
|
+
|
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>
|
33
|
+
|
34
|
+
````ruby
|
35
|
+
gem 'ball_gag'
|
36
|
+
````
|
37
|
+
|
38
|
+
Now a <tt>NotGaggedValidator</tt> is available which you can use like this.
|
39
|
+
|
40
|
+
````ruby
|
41
|
+
class Post < ActiveRecord::Base
|
42
|
+
include BallGag
|
43
|
+
gag :body { |body| !/damn/.match body }
|
44
|
+
|
45
|
+
validates :body, :not_gagged => true
|
46
|
+
end
|
47
|
+
````
|
48
|
+
|
49
|
+
## Engines / Global configuration
|
50
|
+
Each gagged attribute can be provided its own block to call when checking acceptability, or a global engine can be provided. Set a global engine like this.
|
51
|
+
|
52
|
+
````ruby
|
53
|
+
BallGag.engine = lambda { |content| !/damn/.match content }
|
54
|
+
````
|
55
|
+
|
56
|
+
Any object that responds to <tt>call</tt> can be used as an engine.
|
57
|
+
|
58
|
+
## Signature
|
59
|
+
|
60
|
+
The first argument to <tt>call</tt> is the value, or hash of values to be checked for acceptability.
|
61
|
+
|
62
|
+
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.
|
63
|
+
|
64
|
+
The second argument to <tt>call</tt> is a hash of options. This hash has the following entries:
|
65
|
+
<table>
|
66
|
+
<tbody>
|
67
|
+
<tr>
|
68
|
+
<td><tt><strong>:instance</strong></tt></td>
|
69
|
+
<td>The instance on which the attribute checked was invoked.</td>
|
70
|
+
</tr>
|
71
|
+
<tr>
|
72
|
+
<td><tt><strong>:options</strong></tt></td>
|
73
|
+
<td>The options supplied to the specific <tt>gag</tt> call.</td>
|
74
|
+
</tr>
|
75
|
+
<tr>
|
76
|
+
<td><tt><strong>:single</strong></tt></td>
|
77
|
+
<td>A boolean indicating whether the <tt>gag</tt> method was invoked with only one attribute.</td>
|
78
|
+
</tr>
|
79
|
+
<tr>
|
80
|
+
<td><tt><strong>:attr</strong></tt></td>
|
81
|
+
<td>The name of the attribute being checked. This is only useful for single-attribute gags.</td>
|
82
|
+
</tr>
|
83
|
+
</tbody>
|
84
|
+
</table>
|
85
|
+
|
86
|
+
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.
|
87
|
+
|
88
|
+
## Integration with 3rd-party tools
|
89
|
+
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.
|
90
|
+
|
91
|
+
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.
|
92
|
+
|
93
|
+
Here's an example engine that accepts single and multi-attribute calls and validates against a made-up RESTful moderation service.
|
94
|
+
|
95
|
+
````ruby
|
96
|
+
class ModerationServiceEngine
|
97
|
+
include HTTParty
|
98
|
+
base_uri MyApp::Application.config.moderation_service_uri
|
99
|
+
|
100
|
+
def self.call fields, options
|
101
|
+
if options.delete(:single)
|
102
|
+
return call(options[:attr] => fields, options)
|
103
|
+
end
|
104
|
+
|
105
|
+
gag_options = options[:options]
|
106
|
+
|
107
|
+
sender = gag_options[:sender].
|
108
|
+
bind(instance).call
|
109
|
+
|
110
|
+
response = new(*MyApp::ApplicationConfig.moderation_service_credentials).
|
111
|
+
get('/moderate', query: { fields: fields, user: sender })
|
112
|
+
|
113
|
+
response['fields']
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
BallGag.engine = ModerationServiceEngine
|
118
|
+
````
|
119
|
+
|
120
|
+
## License
|
121
|
+
|
122
|
+
MIT License
|
data/ball_gag.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'ball_gag/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'ball_gag'
|
7
|
+
s.version = BallGag::VERSION
|
8
|
+
s.authors = ['Duncan Beevers']
|
9
|
+
s.email = ['duncan@dweebd.com']
|
10
|
+
s.homepage = ''
|
11
|
+
s.summary = 'Pluggable User-Content Validation'
|
12
|
+
s.description = 'Validate user input using pluggable back-ends'
|
13
|
+
|
14
|
+
s.rubyforge_project = 'ball_gag'
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ['lib']
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency 'rspec'
|
23
|
+
end
|
data/lib/ball_gag.rb
ADDED
data/lib/ball_gag/gag.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
module BallGag
|
2
|
+
def self.included base
|
3
|
+
@gagged_inclusions ||= {}
|
4
|
+
if !@gagged_inclusions[base]
|
5
|
+
@gagged_inclusions[base] = true
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def gagged_attributes
|
12
|
+
clear_gagged_attributes unless @gagged_attributes
|
13
|
+
@gagged_attributes.keys
|
14
|
+
end
|
15
|
+
|
16
|
+
def gag *arguments, &block
|
17
|
+
define_and_mixin_gagged_attributes_methods
|
18
|
+
|
19
|
+
callable = arguments.pop if arguments.last.respond_to? :call
|
20
|
+
options = arguments.pop if arguments.last.kind_of? Hash
|
21
|
+
|
22
|
+
to_call = callable || block || BallGag.engine ||
|
23
|
+
lambda { |*| raise NoEngineConfiguredError }
|
24
|
+
|
25
|
+
define_not_gagged_interpellations(arguments, to_call, options)
|
26
|
+
|
27
|
+
arguments.each do |attribute|
|
28
|
+
@gagged_attributes[attribute] = to_call
|
29
|
+
define_gagged_interpellation attribute
|
30
|
+
end
|
31
|
+
|
32
|
+
arguments
|
33
|
+
end
|
34
|
+
|
35
|
+
def clear_gagged_attributes
|
36
|
+
undefine_gagged_attributes_methods
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def define_and_mixin_gagged_attributes_methods
|
41
|
+
return if @gagged_attributes_methods
|
42
|
+
|
43
|
+
undefine_gagged_attributes_methods
|
44
|
+
end
|
45
|
+
|
46
|
+
def define_not_gagged_interpellations attributes, callable, gag_options = nil
|
47
|
+
one_attribute = 1 == attributes.length
|
48
|
+
unsanitized_values = lambda { |it|
|
49
|
+
attributes.inject({}) do |a, attribute|
|
50
|
+
a[attribute] = it.send(attribute)
|
51
|
+
a
|
52
|
+
end
|
53
|
+
}
|
54
|
+
|
55
|
+
output = one_attribute ?
|
56
|
+
lambda { |it, attr| unsanitized_values.call(it)[attr] } :
|
57
|
+
lambda { |it, attr| unsanitized_values.call(it) }
|
58
|
+
|
59
|
+
arity = callable.respond_to?(:arity) ?
|
60
|
+
callable.arity :
|
61
|
+
callable.method(:call).arity.abs
|
62
|
+
|
63
|
+
fn = nil
|
64
|
+
if 1 == arity
|
65
|
+
fn = lambda { |it, attr| callable.call(output.call(it, attr)) }
|
66
|
+
else
|
67
|
+
options = {}
|
68
|
+
fn = lambda do |it, attr|
|
69
|
+
callable.call(output.call(it, attr),
|
70
|
+
{ options: gag_options,
|
71
|
+
instance: it,
|
72
|
+
attr: attr,
|
73
|
+
single: one_attribute
|
74
|
+
}
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
attributes.each do |attr|
|
81
|
+
@gagged_attributes_methods.send(:define_method,
|
82
|
+
gagged_attribute_negative_interpellation_name(attr)) do
|
83
|
+
@gagged_attribute_results ||= {}
|
84
|
+
|
85
|
+
# Have we performed this call already?
|
86
|
+
unless @gagged_attribute_results.has_key?(attributes)
|
87
|
+
@gagged_attribute_results[attributes] = fn.call(self, attr)
|
88
|
+
end
|
89
|
+
|
90
|
+
call_result = @gagged_attribute_results[attributes]
|
91
|
+
|
92
|
+
if one_attribute
|
93
|
+
# If only one attribute was supplied, a simple
|
94
|
+
# boolean response is sufficient
|
95
|
+
return false unless call_result
|
96
|
+
return true unless call_result.respond_to?(:[])
|
97
|
+
else
|
98
|
+
raise BadResultsMappingError unless call_result.respond_to?(:[])
|
99
|
+
end
|
100
|
+
|
101
|
+
@gagged_attribute_results[attributes][attr]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def define_gagged_interpellation attribute
|
107
|
+
neg_method = gagged_attribute_negative_interpellation_name(attribute)
|
108
|
+
@gagged_attributes_methods.send(:define_method,
|
109
|
+
gagged_attribute_interpellation_name(attribute)) do
|
110
|
+
!method(neg_method).call
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def gagged_attribute_interpellation_name attribute
|
115
|
+
"#{attribute}_gagged?"
|
116
|
+
end
|
117
|
+
|
118
|
+
def gagged_attribute_negative_interpellation_name attribute
|
119
|
+
"#{attribute}_not_gagged?"
|
120
|
+
end
|
121
|
+
|
122
|
+
def undefine_gagged_attributes_methods
|
123
|
+
@gagged_attributes.keys.each do |attribute|
|
124
|
+
|
125
|
+
@gagged_attributes_methods.send(:remove_method,
|
126
|
+
gagged_attribute_interpellation_name(attribute))
|
127
|
+
|
128
|
+
@gagged_attributes_methods.send(:remove_method,
|
129
|
+
gagged_attribute_negative_interpellation_name(attribute))
|
130
|
+
|
131
|
+
end if @gagged_attributes
|
132
|
+
|
133
|
+
@gagged_attributes_methods = Module.new
|
134
|
+
include @gagged_attributes_methods
|
135
|
+
@gagged_attributes = {}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
data/spec/engine_spec.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BallGag do
|
4
|
+
before { BallGag.engine = nil }
|
5
|
+
|
6
|
+
it 'should have no engine' do
|
7
|
+
BallGag.engine.should be_nil
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should have configurable engine' do
|
11
|
+
mock_engine = mock('engine')
|
12
|
+
BallGag.engine = mock_engine
|
13
|
+
BallGag.engine.should eq mock_engine
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should use engine' do
|
17
|
+
BallGag.engine = ExampleEngine
|
18
|
+
ExampleModel.gag :words, { strict: true }
|
19
|
+
ExampleModel.new.words_gagged?.should be_false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
data/spec/gag_spec.rb
ADDED
@@ -0,0 +1,267 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ExampleModel do
|
4
|
+
before do
|
5
|
+
BallGag.engine = nil
|
6
|
+
ExampleModel.clear_gagged_attributes
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#gag' do
|
10
|
+
it 'should add #{attribute}_gagged? method' do
|
11
|
+
ExampleModel.gag :words
|
12
|
+
ExampleModel.new.should respond_to :words_gagged?
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should add #{attribute}_not_gagged? method' do
|
16
|
+
ExampleModel.gag :words
|
17
|
+
ExampleModel.new.should respond_to :words_not_gagged?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#gagged_attributes' do
|
22
|
+
it 'should have no gagged attributes' do
|
23
|
+
ExampleModel.gagged_attributes.should be_empty
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should gag attribute' do
|
27
|
+
ExampleModel.gag :words
|
28
|
+
ExampleModel.gagged_attributes.should include :words
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should gag multiple attributes' do
|
32
|
+
ExampleModel.gag :words, :email
|
33
|
+
ExampleModel.gagged_attributes.should include :words
|
34
|
+
ExampleModel.gagged_attributes.should include :email
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#clear_gagged_attributes' do
|
39
|
+
it 'should clear gagged attributes' do
|
40
|
+
ExampleModel.gag :words
|
41
|
+
ExampleModel.clear_gagged_attributes
|
42
|
+
ExampleModel.gagged_attributes.should be_empty
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should remove #{attribute}_not_gagged? method' do
|
46
|
+
ExampleModel.gag :words
|
47
|
+
ExampleModel.new.should respond_to(:words_not_gagged?)
|
48
|
+
ExampleModel.clear_gagged_attributes
|
49
|
+
ExampleModel.new.should_not respond_to(:words_not_gagged?)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should remove #{attribute}_gagged? method' do
|
53
|
+
ExampleModel.gag :words
|
54
|
+
ExampleModel.new.should respond_to(:words_gagged?)
|
55
|
+
ExampleModel.clear_gagged_attributes
|
56
|
+
ExampleModel.new.should_not respond_to(:words_gagged?)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'single attribute gagged' do
|
61
|
+
describe 'return values' do
|
62
|
+
context 'when callable returns' do
|
63
|
+
context 'true' do
|
64
|
+
before do
|
65
|
+
ExampleModel.gag :words, lambda { |*| true }
|
66
|
+
end
|
67
|
+
|
68
|
+
it '#{attribute}_gagged? should be false' do
|
69
|
+
ExampleModel.new.words_gagged?.should be_false
|
70
|
+
end
|
71
|
+
it '#{attribute}_not_gagged? should be true' do
|
72
|
+
ExampleModel.new.words_not_gagged?.should be_true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'false' do
|
77
|
+
before do
|
78
|
+
ExampleModel.gag :words, lambda { |*| false }
|
79
|
+
end
|
80
|
+
|
81
|
+
it '#{attribute}_gagged? should be true' do
|
82
|
+
ExampleModel.new.words_gagged?.should be_true
|
83
|
+
end
|
84
|
+
it '#{attribute}_not_gagged? should be false' do
|
85
|
+
ExampleModel.new.words_not_gagged?.should be_false
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
specify 'callable is called with attribute value' do
|
92
|
+
callable = lambda { |words| }
|
93
|
+
ExampleModel.gag :words, callable
|
94
|
+
|
95
|
+
mock_words = mock('words')
|
96
|
+
|
97
|
+
callable.should_receive(:call).
|
98
|
+
with(mock_words)
|
99
|
+
|
100
|
+
instance = ExampleModel.new
|
101
|
+
instance.stub!(words: mock_words)
|
102
|
+
|
103
|
+
instance.words_gagged?
|
104
|
+
end
|
105
|
+
|
106
|
+
specify 'callable is called with single: true in options' do
|
107
|
+
callable = lambda { |words, options| }
|
108
|
+
ExampleModel.gag :words, callable
|
109
|
+
callable.should_receive(:call).
|
110
|
+
with(anything, hash_including(single: true))
|
111
|
+
ExampleModel.new.words_gagged?
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe 'multiple attributes gagged' do
|
116
|
+
describe 'return values' do
|
117
|
+
context 'when results map returns true for attribute' do
|
118
|
+
before { ExampleModel.gag :words, :email do |*| { words: true } end }
|
119
|
+
|
120
|
+
it 'should return false for #{attribute}_gagged?' do
|
121
|
+
ExampleModel.new.words_gagged?.should be_false
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should return true for #{attribute}_not_gagged?' do
|
125
|
+
ExampleModel.new.words_not_gagged?.should be_true
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
specify 'callable is called with map of attributes' do
|
131
|
+
callable = lambda { |map, options| }
|
132
|
+
mock_words = mock('words')
|
133
|
+
mock_email = mock('email')
|
134
|
+
|
135
|
+
ExampleModel.gag :words, :email, callable
|
136
|
+
callable.should_receive(:call).
|
137
|
+
with(
|
138
|
+
hash_including(words: mock_words, email: mock_email), anything).
|
139
|
+
and_return({})
|
140
|
+
|
141
|
+
instance = ExampleModel.new
|
142
|
+
instance.stub!(words: mock_words, email: mock_email)
|
143
|
+
instance.words_gagged?
|
144
|
+
end
|
145
|
+
|
146
|
+
specify 'callable is called with attr in options' do
|
147
|
+
callable = lambda { |map, options| }
|
148
|
+
ExampleModel.gag :words, :email, callable
|
149
|
+
|
150
|
+
callable.should_receive(:call).
|
151
|
+
with(anything, hash_including(attr: :words)).
|
152
|
+
and_return({})
|
153
|
+
ExampleModel.new.words_gagged?
|
154
|
+
|
155
|
+
callable.should_receive(:call).
|
156
|
+
with(anything, hash_including(attr: :email)).
|
157
|
+
and_return({})
|
158
|
+
ExampleModel.new.email_gagged?
|
159
|
+
end
|
160
|
+
|
161
|
+
specify 'callable is called with single: false in options' do
|
162
|
+
callable = lambda { |map, options| }
|
163
|
+
ExampleModel.gag :words, :email, callable
|
164
|
+
callable.should_receive(:call).
|
165
|
+
with(anything, hash_including(single: false)).
|
166
|
+
and_return({})
|
167
|
+
ExampleModel.new.words_gagged?
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe 'callable options' do
|
172
|
+
specify 'instance is in options' do
|
173
|
+
callable = lambda { |words, options| }
|
174
|
+
ExampleModel.gag :words, callable
|
175
|
+
|
176
|
+
instance = ExampleModel.new
|
177
|
+
callable.should_receive(:call).
|
178
|
+
with(anything, hash_including(instance: instance))
|
179
|
+
|
180
|
+
instance.words_gagged?
|
181
|
+
end
|
182
|
+
|
183
|
+
specify 'attr is in options' do
|
184
|
+
callable = lambda { |words, options| }
|
185
|
+
ExampleModel.gag :words, callable
|
186
|
+
callable.should_receive(:call).
|
187
|
+
with(anything, hash_including(attr: :words))
|
188
|
+
|
189
|
+
ExampleModel.new.words_gagged?
|
190
|
+
end
|
191
|
+
|
192
|
+
specify 'gag options are in options' do
|
193
|
+
callable = lambda { |words, options| }
|
194
|
+
mock_options = mock('options')
|
195
|
+
mock_options.stub(:kind_of?).
|
196
|
+
with(Hash).and_return(true)
|
197
|
+
|
198
|
+
ExampleModel.gag :words, mock_options, callable
|
199
|
+
callable.should_receive(:call).
|
200
|
+
with(anything, hash_including(options: mock_options))
|
201
|
+
|
202
|
+
ExampleModel.new.words_gagged?
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
describe 'callable result caching' do
|
207
|
+
it 'should cache the result of the callable' do
|
208
|
+
callable = lambda {}
|
209
|
+
ExampleModel.gag :words, callable
|
210
|
+
|
211
|
+
callable.should_receive(:call).once
|
212
|
+
|
213
|
+
instance = ExampleModel.new
|
214
|
+
2.times { instance.words_gagged? }
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'should separate invocations and cache results' do
|
218
|
+
callable = lambda { |words, instance| }
|
219
|
+
mock_words = mock('words')
|
220
|
+
mock_email = mock('email')
|
221
|
+
|
222
|
+
ExampleModel.gag :words, callable
|
223
|
+
ExampleModel.gag :email, callable
|
224
|
+
|
225
|
+
instance = ExampleModel.new
|
226
|
+
instance.stub!(words: mock_words, email: mock_email)
|
227
|
+
|
228
|
+
callable.should_receive(:call).
|
229
|
+
with(mock_words, anything).
|
230
|
+
once
|
231
|
+
|
232
|
+
callable.should_receive(:call).
|
233
|
+
with(mock_email, anything).
|
234
|
+
once
|
235
|
+
|
236
|
+
2.times { instance.words_gagged? }
|
237
|
+
2.times { instance.email_gagged? }
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
describe 'error cases' do
|
242
|
+
context 'when gagged with no callable and no engine is configured' do
|
243
|
+
it 'should raise when gag is checekd' do
|
244
|
+
ExampleModel.gag :words
|
245
|
+
|
246
|
+
-> { ExampleModel.new.words_gagged? }.
|
247
|
+
should raise_error(BallGag::NoEngineConfiguredError)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
context 'when multiple attributes are gagged' do
|
252
|
+
it 'should raise if callable returns non-map result' do
|
253
|
+
mock_result = mock('result')
|
254
|
+
|
255
|
+
mock_result.should_receive(:respond_to?).
|
256
|
+
with(:[]).and_return(false)
|
257
|
+
|
258
|
+
callable = lambda { |*| mock_result }
|
259
|
+
ExampleModel.gag :words, :email, callable
|
260
|
+
|
261
|
+
-> { ExampleModel.new.words_gagged? }.
|
262
|
+
should raise_error(BallGag::BadResultsMappingError)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class ExampleModel
|
2
|
+
include BallGag
|
3
|
+
|
4
|
+
def words
|
5
|
+
'Never has one man rocked so many.'
|
6
|
+
end
|
7
|
+
|
8
|
+
def email
|
9
|
+
'theodore@example.com'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class ExampleActiveModel
|
14
|
+
include ActiveModel::Validations
|
15
|
+
include BallGag
|
16
|
+
|
17
|
+
def words
|
18
|
+
'Welcome to the place where all the creatures meet.'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class ExampleEngine
|
23
|
+
class << self
|
24
|
+
def call value, options = {}
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'ActiveModel::Validations integration' do
|
4
|
+
before do
|
5
|
+
ExampleActiveModel.reset_callbacks :validate
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'should call #{attribute}_gagged?' do
|
9
|
+
ExampleActiveModel.validates :words, not_gagged: true
|
10
|
+
|
11
|
+
instance = ExampleActiveModel.new
|
12
|
+
instance.should_receive(:words_gagged?)
|
13
|
+
instance.valid?
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should add default error message' do
|
17
|
+
ExampleActiveModel.validates :words, not_gagged: true
|
18
|
+
instance = ExampleActiveModel.new
|
19
|
+
instance.stub!(words_gagged?: true)
|
20
|
+
instance.valid?
|
21
|
+
instance.errors[:words].should include 'is gagged'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should respect custom error message' do
|
25
|
+
ExampleActiveModel.validates :words,
|
26
|
+
not_gagged: { message: 'is not acceptable' }
|
27
|
+
instance = ExampleActiveModel.new
|
28
|
+
instance.stub!(words_gagged?: true)
|
29
|
+
instance.valid?
|
30
|
+
instance.errors[:words].should include 'is not acceptable'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ball_gag
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Duncan Beevers
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70285274103000 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70285274103000
|
25
|
+
description: Validate user input using pluggable back-ends
|
26
|
+
email:
|
27
|
+
- duncan@dweebd.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- Gemfile
|
34
|
+
- Gemfile.lock
|
35
|
+
- Guardfile
|
36
|
+
- License
|
37
|
+
- Rakefile
|
38
|
+
- Readme.md
|
39
|
+
- ball_gag.gemspec
|
40
|
+
- lib/ball_gag.rb
|
41
|
+
- lib/ball_gag/engine.rb
|
42
|
+
- lib/ball_gag/errors.rb
|
43
|
+
- lib/ball_gag/gag.rb
|
44
|
+
- lib/ball_gag/validations.rb
|
45
|
+
- lib/ball_gag/version.rb
|
46
|
+
- spec/engine_spec.rb
|
47
|
+
- spec/gag_spec.rb
|
48
|
+
- spec/spec_helper.rb
|
49
|
+
- spec/support/models.rb
|
50
|
+
- spec/validations_spec.rb
|
51
|
+
homepage: ''
|
52
|
+
licenses: []
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project: ball_gag
|
71
|
+
rubygems_version: 1.8.10
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Pluggable User-Content Validation
|
75
|
+
test_files:
|
76
|
+
- spec/engine_spec.rb
|
77
|
+
- spec/gag_spec.rb
|
78
|
+
- spec/spec_helper.rb
|
79
|
+
- spec/support/models.rb
|
80
|
+
- spec/validations_spec.rb
|