mutations 0.5.11 → 0.5.12

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.
@@ -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