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 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
+