mutations 0.5.11 → 0.5.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ 0.5.12
2
+ -----------
3
+
4
+ - Added a duck filter: ```duck :lengthy, methods: :length``` to ensure all values respond_to? :length [#14]
5
+ - Added a file filter: ```file :data``` to ensure the data is a File-like object [#15]
6
+ - Allow raw_inputs to be used as method inside of execute to access the original data passed in. (@tomtaylor)
7
+ - integer filter now allows the ```in``` option. Eg: ```integer :http_code, in: (200, 404, 401)``` (/via @tomtaylor)
8
+ - Added a changelog. [#10]
9
+
10
+ 0.5.11
11
+ -----------
12
+
13
+ - Float filter (@aq1018)
14
+ - Clean up public API + code (@blambeau)
15
+ - Model filters should lazily resolve their classes so that mocks can be used (@edwinv)
16
+ - Filtered inputs should be included in the Outcome
17
+ - Fix typos (@frodsan)
18
+
19
+ 0.5.10 and earlier
20
+ -----------
21
+
22
+ - Initial versions
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mutations (0.5.11)
4
+ mutations (0.5.12)
5
5
  activesupport
6
6
 
7
7
  GEM
@@ -10,6 +10,8 @@ require 'mutations/string_filter'
10
10
  require 'mutations/integer_filter'
11
11
  require 'mutations/float_filter'
12
12
  require 'mutations/boolean_filter'
13
+ require 'mutations/duck_filter'
14
+ require 'mutations/file_filter'
13
15
  require 'mutations/model_filter'
14
16
  require 'mutations/array_filter'
15
17
  require 'mutations/hash_filter'
@@ -34,6 +34,14 @@ module Mutations
34
34
  def boolean(options = {})
35
35
  @element_filter = BooleanFilter.new(options)
36
36
  end
37
+
38
+ def duck(options = {})
39
+ @element_filter = DuckFilter.new(options)
40
+ end
41
+
42
+ def file(options = {})
43
+ @element_filter = FileFilter.new(options)
44
+ end
37
45
 
38
46
  def hash(options = {}, &block)
39
47
  @element_filter = HashFilter.new(options, &block)
@@ -87,7 +95,6 @@ module Mutations
87
95
 
88
96
  # Returns [filtered, errors]
89
97
  def filter_element(data)
90
-
91
98
  if @element_filter
92
99
  data, el_errors = @element_filter.filter(data)
93
100
  return [data, el_errors] if el_errors
@@ -6,15 +6,15 @@ module Mutations
6
6
  keys = self.input_filters.send("#{meth}_keys")
7
7
  keys.each do |key|
8
8
  define_method(key) do
9
- @filtered_input[key]
9
+ @inputs[key]
10
10
  end
11
11
 
12
12
  define_method("#{key}_present?") do
13
- @filtered_input.has_key?(key)
13
+ @inputs.has_key?(key)
14
14
  end
15
15
 
16
16
  define_method("#{key}=") do |v|
17
- @filtered_input[key] = v
17
+ @inputs[key] = v
18
18
  end
19
19
  end
20
20
  end
@@ -55,11 +55,12 @@ module Mutations
55
55
 
56
56
  # Instance methods
57
57
  def initialize(*args)
58
- @original_hash = args.each_with_object({}.with_indifferent_access) do |arg, h|
58
+ @raw_inputs = args.each_with_object({}.with_indifferent_access) do |arg, h|
59
59
  raise ArgumentError.new("All arguments must be hashes") unless arg.is_a?(Hash)
60
60
  h.merge!(arg)
61
61
  end
62
- @filtered_input, @errors = self.input_filters.filter(@original_hash)
62
+
63
+ @inputs, @errors = self.input_filters.filter(@raw_inputs)
63
64
  end
64
65
 
65
66
  def input_filters
@@ -88,13 +89,14 @@ module Mutations
88
89
  validation_outcome
89
90
  end
90
91
 
91
- # Runs input thru the filter and sets @filtered_input and @errors
92
92
  def validation_outcome(result = nil)
93
- Outcome.new(!has_errors?, has_errors? ? nil : result, @errors, @filtered_input)
93
+ Outcome.new(!has_errors?, has_errors? ? nil : result, @errors, @inputs)
94
94
  end
95
95
 
96
96
  protected
97
97
 
98
+ attr_reader :inputs, :raw_inputs
99
+
98
100
  def execute
99
101
  # Meant to be overridden
100
102
  end
@@ -121,8 +123,5 @@ module Mutations
121
123
  @errors.merge!(hash)
122
124
  end
123
125
 
124
- def inputs
125
- @filtered_input
126
- end
127
126
  end
128
- end
127
+ end
@@ -0,0 +1,25 @@
1
+ module Mutations
2
+ class DuckFilter < InputFilter
3
+ @default_options = {
4
+ nils: false, # true allows an explicit nil to be valid. Overrides any other options
5
+ methods: nil # The object needs to respond to each of the symbols in this array.
6
+ }
7
+
8
+ def filter(data)
9
+
10
+ # Handle nil case
11
+ if data.nil?
12
+ return [nil, nil] if options[:nils]
13
+ return [nil, :nils]
14
+ end
15
+
16
+ # Ensure the data responds to each of the methods
17
+ Array(options[:methods]).each do |method|
18
+ return [data, :duck] unless data.respond_to?(method)
19
+ end
20
+
21
+ # We win, it's valid!
22
+ [data, nil]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ module Mutations
2
+ class FileFilter < InputFilter
3
+ @default_options = {
4
+ nils: false, # true allows an explicit nil to be valid. Overrides any other options
5
+ upload: false, # if true, also checks the file is has original_filename and content_type methods.
6
+ size: nil # An integer value like 1_000_000 limits the size of the file to 1M bytes
7
+ }
8
+
9
+ def filter(data)
10
+
11
+ # Handle nil case
12
+ if data.nil?
13
+ return [nil, nil] if options[:nils]
14
+ return [nil, :nils]
15
+ end
16
+
17
+ # Ensure the data responds to each of the methods
18
+ methods = [:read, :size]
19
+ methods.concat([:original_filename, :content_type]) if options[:upload]
20
+ methods.each do |method|
21
+ return [data, :file] unless data.respond_to?(method)
22
+ end
23
+
24
+ if options[:size].is_a?(Fixnum)
25
+ return [data, :size] if data.size > options[:size]
26
+ end
27
+
28
+ # We win, it's valid!
29
+ [data, nil]
30
+ end
31
+ end
32
+ end
@@ -64,12 +64,19 @@ module Mutations
64
64
  def boolean(name, options = {})
65
65
  @current_inputs[name.to_sym] = BooleanFilter.new(options)
66
66
  end
67
+
68
+ def duck(name, options = {})
69
+ @current_inputs[name.to_sym] = DuckFilter.new(options)
70
+ end
71
+
72
+ def file(name, options = {})
73
+ @current_inputs[name.to_sym] = FileFilter.new(options)
74
+ end
67
75
 
68
76
  def hash(name, options = {}, &block)
69
77
  @current_inputs[name.to_sym] = HashFilter.new(options, &block)
70
78
  end
71
79
 
72
- # Advanced types
73
80
  def model(name, options = {})
74
81
  name_sym = name.to_sym
75
82
  @current_inputs[name_sym] = ModelFilter.new(name_sym, options)
@@ -3,7 +3,8 @@ module Mutations
3
3
  @default_options = {
4
4
  nils: false, # true allows an explicit nil to be valid. Overrides any other options
5
5
  min: nil, # lowest value, inclusive
6
- max: nil # highest value, inclusive
6
+ max: nil, # highest value, inclusive
7
+ in: nil # Can be an array like %w(3 4 5)
7
8
  }
8
9
 
9
10
  def filter(data)
@@ -26,8 +27,11 @@ module Mutations
26
27
  return [data, :min] if options[:min] && data < options[:min]
27
28
  return [data, :max] if options[:max] && data > options[:max]
28
29
 
30
+ # Ensure it matches `in`
31
+ return [data, :in] if options[:in] && !options[:in].include?(data)
32
+
29
33
  # We win, it's valid!
30
34
  [data, nil]
31
35
  end
32
36
  end
33
- end
37
+ end
@@ -1,3 +1,3 @@
1
1
  module Mutations
2
- VERSION = "0.5.11"
2
+ VERSION = "0.5.12"
3
3
  end
@@ -1,4 +1,5 @@
1
1
  require_relative 'spec_helper'
2
+ require 'stringio'
2
3
 
3
4
  describe "Mutations::ArrayFilter" do
4
5
 
@@ -91,6 +92,23 @@ describe "Mutations::ArrayFilter" do
91
92
  assert_equal [5.0,6.0,1.0,"bob"], filtered
92
93
  assert_equal [nil, nil, :min, :float], errors.symbolic
93
94
  end
95
+
96
+ it "lets you pass ducks in arrays" do
97
+ f = Mutations::ArrayFilter.new(:arr) { duck(methods: :length) }
98
+
99
+ filtered, errors = f.filter(["hi", [1], true])
100
+ assert_equal ["hi", [1], true], filtered
101
+ assert_equal [nil, nil, :duck], errors.symbolic
102
+ end
103
+
104
+ it "lets you pass files in arrays" do
105
+ sio = StringIO.new("bob")
106
+ f = Mutations::ArrayFilter.new(:arr) { file }
107
+
108
+ filtered, errors = f.filter([sio, "bob"])
109
+ assert_equal [sio, "bob"], filtered
110
+ assert_equal [nil, :file], errors.symbolic
111
+ end
94
112
 
95
113
  it "lets you pass booleans in arrays" do
96
114
  f = Mutations::ArrayFilter.new(:arr) { boolean }
@@ -201,4 +201,22 @@ describe "Command" do
201
201
  end
202
202
  end
203
203
 
204
+ describe "RawInputsCommand" do
205
+ class RawInputsCommand < Mutations::Command
206
+
207
+ required do
208
+ string :name
209
+ end
210
+
211
+ def execute
212
+ return raw_inputs
213
+ end
214
+ end
215
+
216
+ it "should return the raw input data" do
217
+ input = { "name" => "Hello World", "other" => "Foo Bar Baz" }
218
+ assert_equal input, RawInputsCommand.run!(input)
219
+ end
220
+ end
221
+
204
222
  end
@@ -0,0 +1,49 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe "Mutations::DuckFilter" do
4
+
5
+ it "allows objects that respond to a single specified method" do
6
+ f = Mutations::DuckFilter.new(methods: [:length])
7
+ filtered, errors = f.filter("test")
8
+ assert_equal "test", filtered
9
+ assert_equal nil, errors
10
+
11
+ filtered, errors = f.filter([1, 2])
12
+ assert_equal [1, 2], filtered
13
+ assert_equal nil, errors
14
+ end
15
+
16
+ it "doesn't allow objects that respond to a single specified method" do
17
+ f = Mutations::DuckFilter.new(methods: [:length])
18
+ filtered, errors = f.filter(true)
19
+ assert_equal true, filtered
20
+ assert_equal :duck, errors
21
+
22
+ filtered, errors = f.filter(12)
23
+ assert_equal 12, filtered
24
+ assert_equal :duck, errors
25
+ end
26
+
27
+ it "considers nil to be invalid" do
28
+ f = Mutations::DuckFilter.new(nils: false)
29
+ filtered, errors = f.filter(nil)
30
+ assert_equal nil, filtered
31
+ assert_equal :nils, errors
32
+ end
33
+
34
+ it "considers nil to be valid" do
35
+ f = Mutations::DuckFilter.new(nils: true)
36
+ filtered, errors = f.filter(nil)
37
+ assert_equal nil, filtered
38
+ assert_equal nil, errors
39
+ end
40
+
41
+ it "Allows anything if no methods are specified" do
42
+ f = Mutations::DuckFilter.new
43
+ [true, "hi", 1, [1, 2, 3]].each do |v|
44
+ filtered, errors = f.filter(v)
45
+ assert_equal v, filtered
46
+ assert_equal nil, errors
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,91 @@
1
+ require_relative 'spec_helper'
2
+ require 'stringio'
3
+ require 'tempfile'
4
+
5
+ describe "Mutations::FileFilter" do
6
+
7
+ class UploadedStringIO < StringIO
8
+ attr_accessor :content_type, :original_filename
9
+ end
10
+
11
+ it "allows files - file class" do
12
+ file = File.new("README.md")
13
+ f = Mutations::FileFilter.new
14
+ filtered, errors = f.filter(file)
15
+ assert_equal file, filtered
16
+ assert_equal nil, errors
17
+ end
18
+
19
+ it "allows files - stringio class" do
20
+ file = StringIO.new("bob")
21
+ f = Mutations::FileFilter.new
22
+ filtered, errors = f.filter(file)
23
+ assert_equal file, filtered
24
+ assert_equal nil, errors
25
+ end
26
+
27
+ it "allows files - tempfile" do
28
+ file = Tempfile.new("bob")
29
+ f = Mutations::FileFilter.new
30
+ filtered, errors = f.filter(file)
31
+ assert_equal file, filtered
32
+ assert_equal nil, errors
33
+ end
34
+
35
+ it "doesn't allow non-files" do
36
+ f = Mutations::FileFilter.new
37
+ filtered, errors = f.filter("string")
38
+ assert_equal "string", filtered
39
+ assert_equal :file, errors
40
+
41
+ filtered, errors = f.filter(12)
42
+ assert_equal 12, filtered
43
+ assert_equal :file, errors
44
+ end
45
+
46
+ it "considers nil to be invalid" do
47
+ f = Mutations::FileFilter.new(nils: false)
48
+ filtered, errors = f.filter(nil)
49
+ assert_equal nil, filtered
50
+ assert_equal :nils, errors
51
+ end
52
+
53
+ it "considers nil to be valid" do
54
+ f = Mutations::FileFilter.new(nils: true)
55
+ filtered, errors = f.filter(nil)
56
+ assert_equal nil, filtered
57
+ assert_equal nil, errors
58
+ end
59
+
60
+ it "should allow small files" do
61
+ file = StringIO.new("bob")
62
+ f = Mutations::FileFilter.new(size: 4)
63
+ filtered, errors = f.filter(file)
64
+ assert_equal file, filtered
65
+ assert_equal nil, errors
66
+ end
67
+
68
+ it "shouldn't allow big files" do
69
+ file = StringIO.new("bob")
70
+ f = Mutations::FileFilter.new(size: 2)
71
+ filtered, errors = f.filter(file)
72
+ assert_equal file, filtered
73
+ assert_equal :size, errors
74
+ end
75
+
76
+ it "should require extra methods if uploaded file: accept" do
77
+ file = UploadedStringIO.new("bob")
78
+ f = Mutations::FileFilter.new(upload: true)
79
+ filtered, errors = f.filter(file)
80
+ assert_equal file, filtered
81
+ assert_equal nil, errors
82
+ end
83
+
84
+ it "should require extra methods if uploaded file: deny" do
85
+ file = StringIO.new("bob")
86
+ f = Mutations::FileFilter.new(upload: true)
87
+ filtered, errors = f.filter(file)
88
+ assert_equal file, filtered
89
+ assert_equal :file, errors
90
+ end
91
+ end
@@ -1,4 +1,5 @@
1
1
  require_relative 'spec_helper'
2
+ require 'stringio'
2
3
 
3
4
  describe "Mutations::HashFilter" do
4
5
 
@@ -28,7 +29,7 @@ describe "Mutations::HashFilter" do
28
29
  assert_equal nil, errors
29
30
  end
30
31
 
31
- it "allow floats in hashes" do
32
+ it "allows floats in hashes" do
32
33
  hf = Mutations::HashFilter.new do
33
34
  float :foo
34
35
  end
@@ -36,6 +37,25 @@ describe "Mutations::HashFilter" do
36
37
  assert_equal ({"foo" => 3.14}), filtered
37
38
  assert_equal nil, errors
38
39
  end
40
+
41
+ it "allows ducks in hashes" do
42
+ hf = Mutations::HashFilter.new do
43
+ duck :foo, methods: [:length]
44
+ end
45
+ filtered, errors = hf.filter(foo: "123")
46
+ assert_equal ({"foo" => "123"}), filtered
47
+ assert_equal nil, errors
48
+ end
49
+
50
+ it "allows files in hashes" do
51
+ sio = StringIO.new("bob")
52
+ hf = Mutations::HashFilter.new do
53
+ file :foo
54
+ end
55
+ filtered, errors = hf.filter(foo: sio)
56
+ assert_equal ({"foo" => sio}), filtered
57
+ assert_equal nil, errors
58
+ end
39
59
 
40
60
  it "doesn't allow wildcards in hashes" do
41
61
  hf = Mutations::HashFilter.new do
@@ -10,7 +10,7 @@ describe 'Mutations - inheritance' do
10
10
  end
11
11
 
12
12
  def execute
13
- @filtered_input
13
+ inputs
14
14
  end
15
15
  end
16
16
 
@@ -73,4 +73,18 @@ describe "Mutations::IntegerFilter" do
73
73
  assert_equal nil, errors
74
74
  end
75
75
 
76
- end
76
+ it "considers not matching numbers to be invalid" do
77
+ f = Mutations::IntegerFilter.new(in: [3, 4, 5])
78
+ filtered, errors = f.filter(6)
79
+ assert_equal 6, filtered
80
+ assert_equal :in, errors
81
+ end
82
+
83
+ it "considers matching numbers to be valid" do
84
+ f = Mutations::IntegerFilter.new(in: [3, 4, 5])
85
+ filtered, errors = f.filter(3)
86
+ assert_equal 3, filtered
87
+ assert_nil errors
88
+ end
89
+
90
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mutations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.11
4
+ version: 0.5.12
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-02 00:00:00.000000000 Z
12
+ date: 2013-03-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -68,6 +68,7 @@ extra_rdoc_files: []
68
68
  files:
69
69
  - .gitignore
70
70
  - .travis
71
+ - CHANGELOG.md
71
72
  - Gemfile
72
73
  - Gemfile.lock
73
74
  - MIT-LICENSE
@@ -78,8 +79,10 @@ files:
78
79
  - lib/mutations/array_filter.rb
79
80
  - lib/mutations/boolean_filter.rb
80
81
  - lib/mutations/command.rb
82
+ - lib/mutations/duck_filter.rb
81
83
  - lib/mutations/errors.rb
82
84
  - lib/mutations/exception.rb
85
+ - lib/mutations/file_filter.rb
83
86
  - lib/mutations/float_filter.rb
84
87
  - lib/mutations/hash_filter.rb
85
88
  - lib/mutations/input_filter.rb
@@ -93,7 +96,9 @@ files:
93
96
  - spec/boolean_filter_spec.rb
94
97
  - spec/command_spec.rb
95
98
  - spec/default_spec.rb
99
+ - spec/duck_filter_spec.rb
96
100
  - spec/errors_spec.rb
101
+ - spec/file_filter_spec.rb
97
102
  - spec/float_filter_spec.rb
98
103
  - spec/hash_filter_spec.rb
99
104
  - spec/inheritance_spec.rb