attribute_sanitizer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/Gemfile.lock +22 -0
- data/README.md +57 -0
- data/lib/attribute_sanitizer.rb +23 -0
- data/lib/attribute_sanitizer_dsl.rb +93 -0
- data/lib/attribute_sanitizer_helpers.rb +61 -0
- data/test/attribute_sanitizer_test.rb +100 -0
- metadata +103 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
attribute_sanitizer (0.0.1)
|
5
|
+
activesupport
|
6
|
+
i18n
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: http://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (3.0.9)
|
12
|
+
i18n (0.6.0)
|
13
|
+
mocha (0.9.12)
|
14
|
+
shoulda (2.11.3)
|
15
|
+
|
16
|
+
PLATFORMS
|
17
|
+
ruby
|
18
|
+
|
19
|
+
DEPENDENCIES
|
20
|
+
attribute_sanitizer!
|
21
|
+
mocha
|
22
|
+
shoulda
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
AttributeSanitizer - DSL for specifiying steps to sanitize input.
|
2
|
+
=================================================================
|
3
|
+
|
4
|
+
Example
|
5
|
+
-------
|
6
|
+
|
7
|
+
class Foo
|
8
|
+
include AttributeSanitizer::Helpers
|
9
|
+
|
10
|
+
sanitize_attributes do
|
11
|
+
remap :bagel => :bagels
|
12
|
+
remap :bagels => :bagels_attributes
|
13
|
+
ensure_array :bagels_attributes
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Subsequently calling this method with a hash will apply the sanitization
|
18
|
+
steps, in the order declared, sanitizing the hash.
|
19
|
+
|
20
|
+
attrs = { :bagel => { :flavor => "blueberry" } }
|
21
|
+
|
22
|
+
Foo.sanitize_attributes(attrs)
|
23
|
+
|
24
|
+
attrs # => { :bagels_attributes => [ { :flavor => "blueberry" } ] }
|
25
|
+
|
26
|
+
You can also pass a non-hash value. The steps for sanitization must know
|
27
|
+
what to do with whatever you pass in. Here's an example using a custom
|
28
|
+
defined step:
|
29
|
+
|
30
|
+
class Foo
|
31
|
+
sanitize_attributes do
|
32
|
+
add_step :split_on_commas # bad example
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.split_on_commas(val)
|
36
|
+
if val.is_a?(String)
|
37
|
+
val.split(",")
|
38
|
+
else
|
39
|
+
val
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Foo.sanitize_attributes("1,2,3") # => ["1", "2", "3"]
|
45
|
+
|
46
|
+
Why?
|
47
|
+
----
|
48
|
+
|
49
|
+
I created this for a project where I had to deal with input that didn't
|
50
|
+
quite conform to my models, and I didn't want to change the API of my
|
51
|
+
application to facilitate non-conforming inputs.
|
52
|
+
|
53
|
+
License
|
54
|
+
-------
|
55
|
+
|
56
|
+
AttributeSanitizer is Copyright © 2011 Jim Garvin. It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
|
57
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require "attribute_sanitizer_helpers"
|
3
|
+
require "attribute_sanitizer_dsl"
|
4
|
+
|
5
|
+
class AttributeSanitizer
|
6
|
+
attr_accessor :sanitization_steps, :method_delegate
|
7
|
+
include DSL
|
8
|
+
|
9
|
+
def initialize(method_delegate)
|
10
|
+
self.sanitization_steps = []
|
11
|
+
self.method_delegate = method_delegate
|
12
|
+
end
|
13
|
+
|
14
|
+
# Applies sanitization steps to provided ata.
|
15
|
+
#
|
16
|
+
# @param [Object] the data to be sanitized
|
17
|
+
#
|
18
|
+
# @return [Object] the sanitized data
|
19
|
+
def sanitize(attrs)
|
20
|
+
sanitization_steps.inject(attrs) { |new_attrs, step| step.call(new_attrs) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require "active_support/core_ext/string"
|
3
|
+
|
4
|
+
class AttributeSanitizer
|
5
|
+
module DSL
|
6
|
+
|
7
|
+
# Adds a step to the sanitization routine.
|
8
|
+
#
|
9
|
+
# If passed a block, that block is added as a step.
|
10
|
+
#
|
11
|
+
# If passed a symbol or string, then a custom step will be added that calls
|
12
|
+
# that method on the method_delegate (typically your class).
|
13
|
+
def add_step(method=nil, &block)
|
14
|
+
if block_given?
|
15
|
+
sanitization_steps << block
|
16
|
+
elsif method
|
17
|
+
add_step { |attrs| method_delegate.send(method, attrs) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Adds a step that renames a hash key.
|
22
|
+
#
|
23
|
+
# @parameter [Hash] describing the remap action
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# remap :foo => :bar
|
27
|
+
def remap(mapping)
|
28
|
+
from = mapping.keys.first
|
29
|
+
to = mapping[from]
|
30
|
+
add_step do |attrs|
|
31
|
+
attrs[to] = attrs.delete(from) if attrs.has_key?(from)
|
32
|
+
attrs
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Adds a step that ensures that a hash value is an array
|
37
|
+
#
|
38
|
+
# @parameter [Object] the key whose value we want to be an array
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# ensure_array :bagels_attributes
|
42
|
+
def ensure_array(field)
|
43
|
+
add_step do |attrs|
|
44
|
+
if attrs.has_key?(field) && !attrs[field].is_a?(Array)
|
45
|
+
attrs[field] = [attrs[field]]
|
46
|
+
end
|
47
|
+
attrs
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Adds a step that iterates over items in array and calls sanitize_attributes
|
52
|
+
# using the specified class.
|
53
|
+
#
|
54
|
+
# @parameter [Hash] descriping the field and class
|
55
|
+
#
|
56
|
+
# @example
|
57
|
+
# sanitize_nested_attributes :bagels_attributes => Bagel
|
58
|
+
def sanitize_nested_attributes(mapping)
|
59
|
+
key = mapping.keys.first
|
60
|
+
klass = mapping[key]
|
61
|
+
add_step do |attrs|
|
62
|
+
if attrs.has_key?(key)
|
63
|
+
attrs[key].compact.map! { |h| klass.sanitize_attributes(h) }
|
64
|
+
end
|
65
|
+
attrs
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Adds several steps that can be useful when dealing with
|
70
|
+
# accepts_nested_attributes_for in Rails projects.
|
71
|
+
#
|
72
|
+
# @parameter [Object] association name
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
# sanitize_has_many :bagels
|
76
|
+
#
|
77
|
+
# This is equivalent to:
|
78
|
+
# remap :bagel => :bagels_attributes
|
79
|
+
# remap :bagels => :bagels_attributes
|
80
|
+
# ensure_array :bagels_attributes
|
81
|
+
# sanitize_nested_attributes :bagels_attributes => Bagel
|
82
|
+
def sanitize_has_many(association_name)
|
83
|
+
association_name = association_name.to_s
|
84
|
+
singular_name = association_name.singularize
|
85
|
+
nested_name = "#{association_name}_attributes"
|
86
|
+
klass = association_name.singularize.camelize.constantize
|
87
|
+
remap singular_name => nested_name
|
88
|
+
remap association_name => nested_name
|
89
|
+
ensure_array nested_name
|
90
|
+
sanitize_nested_attributes nested_name => klass
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class AttributeSanitizer
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
# When called with a block this will instantiate a new AttributeSanitizer and
|
5
|
+
# call the block using instance_eval. The DSL methods for sanitizing
|
6
|
+
# attributes may be called inside of the block.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# class Foo
|
11
|
+
# include AttributeSanitizer::Helpers
|
12
|
+
#
|
13
|
+
# sanitize_attributes do
|
14
|
+
# remap :bagel => :bagels
|
15
|
+
# remap :bagels => :bagels_attributes
|
16
|
+
# ensure_array :bagels_attributes
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# Subsequently calling this method with a hash will apply the sanitization
|
21
|
+
# steps, in the order declared, sanitizing the hash.
|
22
|
+
#
|
23
|
+
# attrs = { :bagel => { :flavor => "blueberry" } }
|
24
|
+
#
|
25
|
+
# Foo.sanitize_attributes(attrs)
|
26
|
+
#
|
27
|
+
# attrs # => { :bagels_attributes => [ { :flavor => "blueberry" } ] }
|
28
|
+
#
|
29
|
+
# You can also pass a non-hash value. The steps for sanitization must know
|
30
|
+
# what to do with whatever you pass in. Here's an example using a custom
|
31
|
+
# defined step:
|
32
|
+
#
|
33
|
+
# class Foo
|
34
|
+
# sanitize_attributes do
|
35
|
+
# add_step :split_on_commas # bad example
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# def self.split_on_commas(val)
|
39
|
+
# if val.is_a?(String)
|
40
|
+
# val.split(",")
|
41
|
+
# else
|
42
|
+
# val
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# Foo.sanitize_attributes("1,2,3") # => ["1", "2", "3"]
|
48
|
+
def sanitize_attributes(attrs=nil, &block)
|
49
|
+
if attrs
|
50
|
+
return attrs unless @_attribute_sanitizer
|
51
|
+
@_attribute_sanitizer.sanitize(attrs)
|
52
|
+
elsif block_given?
|
53
|
+
@_attribute_sanitizer = AttributeSanitizer.new(self)
|
54
|
+
@_attribute_sanitizer.instance_eval(&block)
|
55
|
+
else
|
56
|
+
raise "sanitize_attributes needs an argument"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), "..", "lib")
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'shoulda'
|
5
|
+
require 'mocha'
|
6
|
+
require 'attribute_sanitizer'
|
7
|
+
|
8
|
+
class Bagel
|
9
|
+
extend AttributeSanitizer::Helpers
|
10
|
+
end
|
11
|
+
|
12
|
+
class TestAttributeSanitizer < Test::Unit::TestCase
|
13
|
+
def setup
|
14
|
+
@sanitizer = AttributeSanitizer.new(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
context "#sanitize with no steps" do
|
18
|
+
should "do nothing" do
|
19
|
+
sanitized = @sanitizer.sanitize("foo")
|
20
|
+
assert_equal "foo", sanitized
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "#add_steps with a block" do
|
25
|
+
end
|
26
|
+
|
27
|
+
context "#add_steps" do
|
28
|
+
should "add named method as a step" do
|
29
|
+
@sanitizer.add_step :stringify
|
30
|
+
assert_equal "1", @sanitizer.sanitize(1)
|
31
|
+
end
|
32
|
+
|
33
|
+
should "add block as a step" do
|
34
|
+
@sanitizer.add_step { |val| val.to_i }
|
35
|
+
assert_equal 1, @sanitizer.sanitize("1")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "#remap" do
|
40
|
+
should "rename a hash key" do
|
41
|
+
@sanitizer.remap :foo => :bar
|
42
|
+
assert_equal({ bar: 1 }, @sanitizer.sanitize(foo: 1))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "#ensure_array" do
|
47
|
+
setup do
|
48
|
+
@sanitizer.ensure_array :foo
|
49
|
+
end
|
50
|
+
|
51
|
+
should "wrap a non-array value in array" do
|
52
|
+
assert_equal({ foo: [1] }, @sanitizer.sanitize(foo: 1))
|
53
|
+
end
|
54
|
+
|
55
|
+
should "not wrap array value in array" do
|
56
|
+
assert_equal({ foo: [1] }, @sanitizer.sanitize(foo: [1]))
|
57
|
+
end
|
58
|
+
|
59
|
+
should "not mess with nested arrays" do
|
60
|
+
assert_equal({ foo: [[1,2], [3, 4]] }, @sanitizer.sanitize(foo: [[1,2], [3,4]]))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "#sanitize_nested_attributes" do
|
65
|
+
setup do
|
66
|
+
@sanitizer.sanitize_nested_attributes :foos_attributes => Bagel
|
67
|
+
end
|
68
|
+
|
69
|
+
should "iterate over elements and call sanitize_attributes on each one with given class" do
|
70
|
+
Bagel.expects(:sanitize_attributes).times(3)
|
71
|
+
@sanitizer.sanitize(foos_attributes: [1,2,3])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "#sanitize_has_many" do
|
76
|
+
setup do
|
77
|
+
@sanitizer = AttributeSanitizer.new(Bagel)
|
78
|
+
end
|
79
|
+
should "add steps to for sanitizing nested attributes for the typical has many association" do
|
80
|
+
@sanitizer.expects(:remap).with("bagel" => "bagels_attributes")
|
81
|
+
@sanitizer.expects(:remap).with("bagels" => "bagels_attributes")
|
82
|
+
@sanitizer.expects(:ensure_array).with("bagels_attributes")
|
83
|
+
@sanitizer.expects(:sanitize_nested_attributes).with("bagels_attributes" => Bagel)
|
84
|
+
@sanitizer.sanitize_has_many :bagels
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "#sanitize_attributes" do
|
89
|
+
should "let us add sanitization steps and also run sanitization steps on input" do
|
90
|
+
Bagel.sanitize_attributes do
|
91
|
+
remap :foo => :bar
|
92
|
+
end
|
93
|
+
assert_equal({bar: 1}, Bagel.sanitize_attributes(foo: 1))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def stringify(val)
|
98
|
+
val.to_s
|
99
|
+
end
|
100
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: attribute_sanitizer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jim Garvin
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-07-20 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: i18n
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: shoulda
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: mocha
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
type: :development
|
58
|
+
version_requirements: *id004
|
59
|
+
description: DSL for sanitizing inputs.
|
60
|
+
email: jim@thegarvin.com
|
61
|
+
executables: []
|
62
|
+
|
63
|
+
extensions: []
|
64
|
+
|
65
|
+
extra_rdoc_files: []
|
66
|
+
|
67
|
+
files:
|
68
|
+
- Gemfile
|
69
|
+
- Gemfile.lock
|
70
|
+
- README.md
|
71
|
+
- lib/attribute_sanitizer.rb
|
72
|
+
- lib/attribute_sanitizer_dsl.rb
|
73
|
+
- lib/attribute_sanitizer_helpers.rb
|
74
|
+
- test/attribute_sanitizer_test.rb
|
75
|
+
homepage:
|
76
|
+
licenses: []
|
77
|
+
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: "0"
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: "0"
|
95
|
+
requirements: []
|
96
|
+
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.8.5
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: DSL for sanitizing inputs.
|
102
|
+
test_files: []
|
103
|
+
|