attribute_sanitizer 0.0.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/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
|
+
|