bellmyer-validates_blacklist 0.1.1

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 ADDED
@@ -0,0 +1,148 @@
1
+ validates_blacklist
2
+ ===================
3
+
4
+ The ridiculously paranoid don't want anything getting past them. Wouldn't you
5
+ feel the same if everyone was out to get you? How do you know they're not?
6
+
7
+ When it comes to validations, paranoia is pricey. You need a tool to simplify
8
+ the process. Time is of the essence, and those aluminum foil hats aren't going to
9
+ fashion themselves.
10
+
11
+ In all seriousness, validations are great for general rules that describe what type
12
+ of data is or isn't acceptable. What they're not good for is muddying up your
13
+ model with lists, possibly large lists, of specifically blacklisted data.
14
+
15
+ You may not want chunkeymonkey47@aol.com joining your online forum because you know
16
+ they're just going to spam your users with viagra ads. Maybe there are dozens of
17
+ chunkymonkeys out there, as it were, and that sort of config data is just
18
+ hideous in your model, if you care anything about beautiful code. I say one
19
+ chunkymonkey is too much.
20
+
21
+
22
+ What can be done?
23
+ =================
24
+
25
+ What if we create a list *outside* the model, that can be validated against? What
26
+ if we gave each model its own blacklist file in the config folder so you knew
27
+ exactly where to find them? Maybe a yaml file would make it easy to blacklist at
28
+ the attribute level. And what if I wrote a nify little mixin for you, so calling
29
+ "validates_blacklist" inside a model would automatically read this list and handle
30
+ the validation for you?
31
+
32
+ Well, I'd be a swell guy. And as it turns out, I kind of am.
33
+
34
+
35
+ Installation
36
+ ============
37
+
38
+ Stick this little line in your environment.rb:
39
+
40
+ config.gem 'bellmyer-validates_blacklist', :lib => 'validates_blacklist', :source => 'http://gems.github.com'
41
+
42
+ Then, for fun, run these two rake tasks:
43
+
44
+ rake gems:install
45
+ rake gems:unpack
46
+
47
+
48
+ Usage
49
+ =====
50
+
51
+ Run this command to create blacklist files for all your models in the config/blacklists/ folder:
52
+
53
+ script/generate blacklists
54
+
55
+ Don't worry, it won't overwrite your existing blacklist files, only create the ones
56
+ you're missing. This means you can run it after every new model is added, no worries.
57
+
58
+ Now, call this in your models:
59
+
60
+ validates_blacklist
61
+
62
+ And populate the yaml files using the example below to guide you. Have fun!
63
+
64
+
65
+ Example
66
+ =======
67
+
68
+ As an example, we'll imagine a high school girl named Heather. Although she has
69
+ mad Ruby skillz (perhaps BECAUSE) she's a snob. All her potential friends must
70
+ submit applications for friendship via her Rails website. She uses
71
+ validates_blacklist to weed out the "undesireables".
72
+
73
+ # config/blacklists/user_blacklist.yml
74
+ email:
75
+ - greasy_pete@yahoo.com # We kissed once when we were 5. Get over it already.
76
+ - /@aol.com$/ # Like, yeah right, AOL is for losers!
77
+
78
+ age:
79
+ - <14 # Junior high? Need not apply.
80
+ - >25 # Gross, you perv!
81
+
82
+ parents_salary:
83
+ - 50_000..150_000 # Poor kids are gullible. Rich kids have money. Middle class is so passe.
84
+
85
+ # app/models/user.rb
86
+ class User < ActiveRecord::Base
87
+ validates_blacklist
88
+ end
89
+
90
+
91
+ Explanation
92
+ ===========
93
+
94
+ Basically, under each attribute (email, age, parents_salary, etc) you can list
95
+ as many blacklisted values as you like. They can be a simple strings:
96
+
97
+ greasy_pete@aol.com
98
+
99
+ or regular expressions, if you wrap them in forward slashes:
100
+
101
+ /aol.com$/
102
+
103
+ or numbers (not very exciting):
104
+
105
+ 5
106
+
107
+ or ranges of numbers:
108
+
109
+ 5..15
110
+
111
+ or any other conditionals you can think of:
112
+
113
+ != 'sadness'
114
+ =~ /happiness/
115
+ !~ /sad/
116
+ > 12
117
+ < 99
118
+
119
+ The only limits are your imagination, and my forethought. You can even specify
120
+ the error message that is generated:
121
+
122
+ email:
123
+ - [greasy_pete@aol.com, cannot be greasy pete from biology]
124
+
125
+ age:
126
+ - [>25, cannot be a pervert]
127
+
128
+ parents_salary
129
+ - [50_000..150_000, cannot be middle class]
130
+
131
+ Otherwise, the user will receive a generic message like:
132
+
133
+ Email is not allowed
134
+
135
+ Of course, you can change that for an entire model with the mixin call:
136
+
137
+ validates_blacklist :message => 'is not valid'
138
+
139
+ or per attribute:
140
+
141
+ validates_blacklist :message => "is not valid',
142
+ :attributes => { :email => 'has been banned', :age => 'is not allowed' }
143
+
144
+ For the record, I think even a 21 year old hitting on high school girls is a
145
+ pervert, but I'm trying to approach this thing from Heather's point of view.
146
+
147
+
148
+ Copyright (c) 2009 Jaime Bellmyer, released under the MIT license
@@ -0,0 +1,27 @@
1
+ module ActiveRecord
2
+ module Validates #:nodoc:
3
+ module Blacklist #:nodoc:
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ validates_blacklist(options = {})
10
+ options.reverse_merge!(:message => 'is not allowed')
11
+
12
+ include ActiveRecord::Validates::Blacklist::InstanceMethods
13
+ end
14
+ end
15
+
16
+ module InstanceMethods
17
+ def blacklist(attribute, value, message = nil)
18
+ puts "blacklisted!"
19
+ end
20
+
21
+ def unblacklist(attribute, value)
22
+ puts "unblacklisted!"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,2 @@
1
+ require 'active_record/validates/blacklist'
2
+ ActiveRecord::Base.class_eval { include ActiveRecord::Validates::Blacklist }
@@ -0,0 +1,332 @@
1
+ require 'test/unit'
2
+
3
+ require 'rubygems'
4
+ gem 'activerecord', '>= 1.15.4.7794'
5
+ require 'active_record'
6
+
7
+ require "#{File.dirname(__FILE__)}/../init"
8
+
9
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
10
+
11
+ def setup_db
12
+ ActiveRecord::Schema.define(:version => 1) do
13
+ create_table :mixins do |t|
14
+ t.column :pos, :integer
15
+ t.column :parent_id, :integer
16
+ t.column :created_at, :datetime
17
+ t.column :updated_at, :datetime
18
+ end
19
+ end
20
+ end
21
+
22
+ def teardown_db
23
+ ActiveRecord::Base.connection.tables.each do |table|
24
+ ActiveRecord::Base.connection.drop_table(table)
25
+ end
26
+ end
27
+
28
+ class Mixin < ActiveRecord::Base
29
+ end
30
+
31
+ class ListMixin < Mixin
32
+ acts_as_list :column => "pos", :scope => :parent
33
+
34
+ def self.table_name() "mixins" end
35
+ end
36
+
37
+ class ListMixinSub1 < ListMixin
38
+ end
39
+
40
+ class ListMixinSub2 < ListMixin
41
+ end
42
+
43
+ class ListWithStringScopeMixin < ActiveRecord::Base
44
+ acts_as_list :column => "pos", :scope => 'parent_id = #{parent_id}'
45
+
46
+ def self.table_name() "mixins" end
47
+ end
48
+
49
+
50
+ class ListTest < Test::Unit::TestCase
51
+
52
+ def setup
53
+ setup_db
54
+ (1..4).each { |counter| ListMixin.create! :pos => counter, :parent_id => 5 }
55
+ end
56
+
57
+ def teardown
58
+ teardown_db
59
+ end
60
+
61
+ def test_reordering
62
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
63
+
64
+ ListMixin.find(2).move_lower
65
+ assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
66
+
67
+ ListMixin.find(2).move_higher
68
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
69
+
70
+ ListMixin.find(1).move_to_bottom
71
+ assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
72
+
73
+ ListMixin.find(1).move_to_top
74
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
75
+
76
+ ListMixin.find(2).move_to_bottom
77
+ assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
78
+
79
+ ListMixin.find(4).move_to_top
80
+ assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
81
+ end
82
+
83
+ def test_move_to_bottom_with_next_to_last_item
84
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
85
+ ListMixin.find(3).move_to_bottom
86
+ assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
87
+ end
88
+
89
+ def test_next_prev
90
+ assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
91
+ assert_nil ListMixin.find(1).higher_item
92
+ assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
93
+ assert_nil ListMixin.find(4).lower_item
94
+ end
95
+
96
+ def test_injection
97
+ item = ListMixin.new(:parent_id => 1)
98
+ assert_equal "parent_id = 1", item.scope_condition
99
+ assert_equal "pos", item.position_column
100
+ end
101
+
102
+ def test_insert
103
+ new = ListMixin.create(:parent_id => 20)
104
+ assert_equal 1, new.pos
105
+ assert new.first?
106
+ assert new.last?
107
+
108
+ new = ListMixin.create(:parent_id => 20)
109
+ assert_equal 2, new.pos
110
+ assert !new.first?
111
+ assert new.last?
112
+
113
+ new = ListMixin.create(:parent_id => 20)
114
+ assert_equal 3, new.pos
115
+ assert !new.first?
116
+ assert new.last?
117
+
118
+ new = ListMixin.create(:parent_id => 0)
119
+ assert_equal 1, new.pos
120
+ assert new.first?
121
+ assert new.last?
122
+ end
123
+
124
+ def test_insert_at
125
+ new = ListMixin.create(:parent_id => 20)
126
+ assert_equal 1, new.pos
127
+
128
+ new = ListMixin.create(:parent_id => 20)
129
+ assert_equal 2, new.pos
130
+
131
+ new = ListMixin.create(:parent_id => 20)
132
+ assert_equal 3, new.pos
133
+
134
+ new4 = ListMixin.create(:parent_id => 20)
135
+ assert_equal 4, new4.pos
136
+
137
+ new4.insert_at(3)
138
+ assert_equal 3, new4.pos
139
+
140
+ new.reload
141
+ assert_equal 4, new.pos
142
+
143
+ new.insert_at(2)
144
+ assert_equal 2, new.pos
145
+
146
+ new4.reload
147
+ assert_equal 4, new4.pos
148
+
149
+ new5 = ListMixin.create(:parent_id => 20)
150
+ assert_equal 5, new5.pos
151
+
152
+ new5.insert_at(1)
153
+ assert_equal 1, new5.pos
154
+
155
+ new4.reload
156
+ assert_equal 5, new4.pos
157
+ end
158
+
159
+ def test_delete_middle
160
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
161
+
162
+ ListMixin.find(2).destroy
163
+
164
+ assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
165
+
166
+ assert_equal 1, ListMixin.find(1).pos
167
+ assert_equal 2, ListMixin.find(3).pos
168
+ assert_equal 3, ListMixin.find(4).pos
169
+
170
+ ListMixin.find(1).destroy
171
+
172
+ assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
173
+
174
+ assert_equal 1, ListMixin.find(3).pos
175
+ assert_equal 2, ListMixin.find(4).pos
176
+ end
177
+
178
+ def test_with_string_based_scope
179
+ new = ListWithStringScopeMixin.create(:parent_id => 500)
180
+ assert_equal 1, new.pos
181
+ assert new.first?
182
+ assert new.last?
183
+ end
184
+
185
+ def test_nil_scope
186
+ new1, new2, new3 = ListMixin.create, ListMixin.create, ListMixin.create
187
+ new2.move_higher
188
+ assert_equal [new2, new1, new3], ListMixin.find(:all, :conditions => 'parent_id IS NULL', :order => 'pos')
189
+ end
190
+
191
+
192
+ def test_remove_from_list_should_then_fail_in_list?
193
+ assert_equal true, ListMixin.find(1).in_list?
194
+ ListMixin.find(1).remove_from_list
195
+ assert_equal false, ListMixin.find(1).in_list?
196
+ end
197
+
198
+ def test_remove_from_list_should_set_position_to_nil
199
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
200
+
201
+ ListMixin.find(2).remove_from_list
202
+
203
+ assert_equal [2, 1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
204
+
205
+ assert_equal 1, ListMixin.find(1).pos
206
+ assert_equal nil, ListMixin.find(2).pos
207
+ assert_equal 2, ListMixin.find(3).pos
208
+ assert_equal 3, ListMixin.find(4).pos
209
+ end
210
+
211
+ def test_remove_before_destroy_does_not_shift_lower_items_twice
212
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
213
+
214
+ ListMixin.find(2).remove_from_list
215
+ ListMixin.find(2).destroy
216
+
217
+ assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
218
+
219
+ assert_equal 1, ListMixin.find(1).pos
220
+ assert_equal 2, ListMixin.find(3).pos
221
+ assert_equal 3, ListMixin.find(4).pos
222
+ end
223
+
224
+ end
225
+
226
+ class ListSubTest < Test::Unit::TestCase
227
+
228
+ def setup
229
+ setup_db
230
+ (1..4).each { |i| ((i % 2 == 1) ? ListMixinSub1 : ListMixinSub2).create! :pos => i, :parent_id => 5000 }
231
+ end
232
+
233
+ def teardown
234
+ teardown_db
235
+ end
236
+
237
+ def test_reordering
238
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
239
+
240
+ ListMixin.find(2).move_lower
241
+ assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
242
+
243
+ ListMixin.find(2).move_higher
244
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
245
+
246
+ ListMixin.find(1).move_to_bottom
247
+ assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
248
+
249
+ ListMixin.find(1).move_to_top
250
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
251
+
252
+ ListMixin.find(2).move_to_bottom
253
+ assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
254
+
255
+ ListMixin.find(4).move_to_top
256
+ assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
257
+ end
258
+
259
+ def test_move_to_bottom_with_next_to_last_item
260
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
261
+ ListMixin.find(3).move_to_bottom
262
+ assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
263
+ end
264
+
265
+ def test_next_prev
266
+ assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
267
+ assert_nil ListMixin.find(1).higher_item
268
+ assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
269
+ assert_nil ListMixin.find(4).lower_item
270
+ end
271
+
272
+ def test_injection
273
+ item = ListMixin.new("parent_id"=>1)
274
+ assert_equal "parent_id = 1", item.scope_condition
275
+ assert_equal "pos", item.position_column
276
+ end
277
+
278
+ def test_insert_at
279
+ new = ListMixin.create("parent_id" => 20)
280
+ assert_equal 1, new.pos
281
+
282
+ new = ListMixinSub1.create("parent_id" => 20)
283
+ assert_equal 2, new.pos
284
+
285
+ new = ListMixinSub2.create("parent_id" => 20)
286
+ assert_equal 3, new.pos
287
+
288
+ new4 = ListMixin.create("parent_id" => 20)
289
+ assert_equal 4, new4.pos
290
+
291
+ new4.insert_at(3)
292
+ assert_equal 3, new4.pos
293
+
294
+ new.reload
295
+ assert_equal 4, new.pos
296
+
297
+ new.insert_at(2)
298
+ assert_equal 2, new.pos
299
+
300
+ new4.reload
301
+ assert_equal 4, new4.pos
302
+
303
+ new5 = ListMixinSub1.create("parent_id" => 20)
304
+ assert_equal 5, new5.pos
305
+
306
+ new5.insert_at(1)
307
+ assert_equal 1, new5.pos
308
+
309
+ new4.reload
310
+ assert_equal 5, new4.pos
311
+ end
312
+
313
+ def test_delete_middle
314
+ assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
315
+
316
+ ListMixin.find(2).destroy
317
+
318
+ assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
319
+
320
+ assert_equal 1, ListMixin.find(1).pos
321
+ assert_equal 2, ListMixin.find(3).pos
322
+ assert_equal 3, ListMixin.find(4).pos
323
+
324
+ ListMixin.find(1).destroy
325
+
326
+ assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
327
+
328
+ assert_equal 1, ListMixin.find(3).pos
329
+ assert_equal 2, ListMixin.find(4).pos
330
+ end
331
+
332
+ end
@@ -0,0 +1,28 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'validates_blacklist'
3
+ s.version = '0.1.1'
4
+ s.date = '2009-08-26'
5
+
6
+ s.summary = "Allows models to be validated against yaml-based blacklists"
7
+ s.description = ""
8
+
9
+ s.authors = ['Jaime Bellmyer']
10
+ s.email = 'ruby@bellmyer.com'
11
+ s.homepage = 'http://github.com/bellmyer/validates_blacklist'
12
+
13
+ s.has_rdoc = true
14
+ s.rdoc_options = ["--main", "README"]
15
+ s.extra_rdoc_files = ["README"]
16
+
17
+ s.add_dependency 'rails', ['>= 2.1']
18
+
19
+ s.files = [
20
+ "README",
21
+ "validates_blacklist.gemspec",
22
+ "lib/bellmyer-validates_blacklist.rb",
23
+ "lib/active_record/validates/blacklist.rb",
24
+ ]
25
+
26
+ s.test_files = ["test/blacklist_test.rb"]
27
+
28
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bellmyer-validates_blacklist
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Jaime Bellmyer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-26 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "2.1"
24
+ version:
25
+ description: ""
26
+ email: ruby@bellmyer.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ files:
34
+ - README
35
+ - validates_blacklist.gemspec
36
+ - lib/bellmyer-validates_blacklist.rb
37
+ - lib/active_record/validates/blacklist.rb
38
+ has_rdoc: true
39
+ homepage: http://github.com/bellmyer/validates_blacklist
40
+ licenses:
41
+ post_install_message:
42
+ rdoc_options:
43
+ - --main
44
+ - README
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.3.5
63
+ signing_key:
64
+ specification_version: 2
65
+ summary: Allows models to be validated against yaml-based blacklists
66
+ test_files:
67
+ - test/blacklist_test.rb