attribute_sanitizer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -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
@@ -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
+