mutations 0.5.12 → 0.6.0
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/{.travis → .travis.yml} +3 -0
- data/CHANGELOG.md +10 -2
- data/Gemfile +5 -2
- data/README.md +1 -0
- data/TODO +1 -3
- data/lib/mutations.rb +14 -1
- data/lib/mutations/additional_filter.rb +13 -0
- data/lib/mutations/array_filter.rb +12 -30
- data/lib/mutations/boolean_filter.rb +3 -3
- data/lib/mutations/command.rb +5 -4
- data/lib/mutations/date_filter.rb +52 -0
- data/lib/mutations/duck_filter.rb +4 -4
- data/lib/mutations/errors.rb +24 -20
- data/lib/mutations/exception.rb +1 -1
- data/lib/mutations/file_filter.rb +7 -7
- data/lib/mutations/float_filter.rb +5 -5
- data/lib/mutations/hash_filter.rb +12 -29
- data/lib/mutations/integer_filter.rb +5 -5
- data/lib/mutations/model_filter.rb +13 -8
- data/lib/mutations/outcome.rb +1 -1
- data/lib/mutations/string_filter.rb +10 -10
- data/lib/mutations/version.rb +2 -2
- data/mutations.gemspec +0 -2
- data/spec/additional_filter_spec.rb +76 -0
- data/spec/array_filter_spec.rb +25 -17
- data/spec/boolean_filter_spec.rb +5 -5
- data/spec/command_spec.rb +30 -30
- data/spec/date_filter_spec.rb +146 -0
- data/spec/default_spec.rb +10 -10
- data/spec/duck_filter_spec.rb +6 -6
- data/spec/errors_spec.rb +8 -9
- data/spec/file_filter_spec.rb +26 -22
- data/spec/float_filter_spec.rb +8 -8
- data/spec/hash_filter_spec.rb +31 -23
- data/spec/inheritance_spec.rb +9 -9
- data/spec/integer_filter_spec.rb +9 -9
- data/spec/model_filter_spec.rb +26 -6
- data/spec/mutations_spec.rb +1 -1
- data/spec/simple_command.rb +2 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/string_filter_spec.rb +17 -17
- metadata +8 -4
data/{.travis → .travis.yml}
RENAMED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
0.6.0
|
2
|
+
-----------
|
3
|
+
|
4
|
+
- Add pluggable filters.
|
5
|
+
- Add ruby 1.8.7 support [#19]
|
6
|
+
- Add a date filter: ```date :start_date``` (/via @eliank and @geronimo)
|
7
|
+
- ```Mutations.cache_constants = false``` if you want to work in Rails dev mode which redefines constants. [#23]
|
8
|
+
|
1
9
|
0.5.12
|
2
10
|
-----------
|
3
11
|
|
@@ -15,8 +23,8 @@
|
|
15
23
|
- Model filters should lazily resolve their classes so that mocks can be used (@edwinv)
|
16
24
|
- Filtered inputs should be included in the Outcome
|
17
25
|
- Fix typos (@frodsan)
|
18
|
-
|
26
|
+
|
19
27
|
0.5.10 and earlier
|
20
28
|
-----------
|
21
29
|
|
22
|
-
- Initial versions
|
30
|
+
- Initial versions
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Mutations
|
2
2
|
|
3
3
|
[](https://travis-ci.org/cypriss/mutations)
|
4
|
+
[](https://codeclimate.com/github/cypriss/mutations)
|
4
5
|
|
5
6
|
Compose your business logic into commands that sanitize and validate input. Write safe, reusable, and maintainable code for Ruby and Rails apps.
|
6
7
|
|
data/TODO
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
- Document default behavior discarding of nils for optional params
|
2
2
|
- document string.discard_empty
|
3
|
-
|
3
|
+
|
4
4
|
- protected parameters:
|
5
5
|
optional do
|
6
6
|
boolean :skip_confirmation, protected: true
|
@@ -10,5 +10,3 @@
|
|
10
10
|
But this would not:
|
11
11
|
params = {user: current_user, skip_confirmation: true}
|
12
12
|
User::ChangeEmail.run!(params)
|
13
|
-
|
14
|
-
|
data/lib/mutations.rb
CHANGED
@@ -1,16 +1,19 @@
|
|
1
1
|
require 'active_support'
|
2
2
|
require 'active_support/core_ext/hash/indifferent_access'
|
3
3
|
require 'active_support/core_ext/string/inflections'
|
4
|
+
require 'date'
|
4
5
|
|
5
6
|
require 'mutations/version'
|
6
7
|
require 'mutations/exception'
|
7
8
|
require 'mutations/errors'
|
8
9
|
require 'mutations/input_filter'
|
10
|
+
require 'mutations/additional_filter'
|
9
11
|
require 'mutations/string_filter'
|
10
12
|
require 'mutations/integer_filter'
|
11
13
|
require 'mutations/float_filter'
|
12
14
|
require 'mutations/boolean_filter'
|
13
15
|
require 'mutations/duck_filter'
|
16
|
+
require 'mutations/date_filter'
|
14
17
|
require 'mutations/file_filter'
|
15
18
|
require 'mutations/model_filter'
|
16
19
|
require 'mutations/array_filter'
|
@@ -27,5 +30,15 @@ module Mutations
|
|
27
30
|
def error_message_creator=(creator)
|
28
31
|
@error_message_creator = creator
|
29
32
|
end
|
33
|
+
|
34
|
+
def cache_constants=(val)
|
35
|
+
@cache_constants = val
|
36
|
+
end
|
37
|
+
|
38
|
+
def cache_constants?
|
39
|
+
@cache_constants
|
40
|
+
end
|
30
41
|
end
|
31
|
-
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Mutations.cache_constants = true
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'mutations/hash_filter'
|
2
|
+
require 'mutations/array_filter'
|
3
|
+
|
4
|
+
module Mutations
|
5
|
+
class AdditionalFilter < InputFilter
|
6
|
+
def self.inherited(subclass)
|
7
|
+
type_name = subclass.name[/^Mutations::([a-zA-Z]*)Filter$/, 1].downcase
|
8
|
+
|
9
|
+
Mutations::HashFilter.register_additional_filter(subclass, type_name)
|
10
|
+
Mutations::ArrayFilter.register_additional_filter(subclass, type_name)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,9 +1,16 @@
|
|
1
1
|
module Mutations
|
2
2
|
class ArrayFilter < InputFilter
|
3
|
+
def self.register_additional_filter(type_class, type_name)
|
4
|
+
define_method(type_name) do | *args |
|
5
|
+
options = args[0] || {}
|
6
|
+
@element_filter = type_class.new(options)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
3
10
|
@default_options = {
|
4
|
-
nils
|
5
|
-
class
|
6
|
-
arrayize
|
11
|
+
:nils => false, # true allows an explicit nil to be valid. Overrides any other options
|
12
|
+
:class => nil, # A constant or string indicates that each element of the array needs to be one of these classes
|
13
|
+
:arrayize => false # true will convert "hi" to ["hi"]. "" converts to []
|
7
14
|
}
|
8
15
|
|
9
16
|
def initialize(name, opts = {}, &block)
|
@@ -19,35 +26,10 @@ module Mutations
|
|
19
26
|
raise ArgumentError.new("Can't supply both a class and a filter") if @element_filter && self.options[:class]
|
20
27
|
end
|
21
28
|
|
22
|
-
def string(options = {})
|
23
|
-
@element_filter = StringFilter.new(options)
|
24
|
-
end
|
25
|
-
|
26
|
-
def integer(options = {})
|
27
|
-
@element_filter = IntegerFilter.new(options)
|
28
|
-
end
|
29
|
-
|
30
|
-
def float(options = {})
|
31
|
-
@element_filter = FloatFilter.new(options)
|
32
|
-
end
|
33
|
-
|
34
|
-
def boolean(options = {})
|
35
|
-
@element_filter = BooleanFilter.new(options)
|
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
|
45
|
-
|
46
29
|
def hash(options = {}, &block)
|
47
30
|
@element_filter = HashFilter.new(options, &block)
|
48
31
|
end
|
49
32
|
|
50
|
-
# Advanced types
|
51
33
|
def model(name, options = {})
|
52
34
|
@element_filter = ModelFilter.new(name.to_sym, options)
|
53
35
|
end
|
@@ -74,7 +56,7 @@ module Mutations
|
|
74
56
|
found_error = false
|
75
57
|
data.each_with_index do |el, i|
|
76
58
|
el_filtered, el_error = filter_element(el)
|
77
|
-
el_error = ErrorAtom.new(@name, el_error, index
|
59
|
+
el_error = ErrorAtom.new(@name, el_error, :index => i) if el_error.is_a?(Symbol)
|
78
60
|
|
79
61
|
errors << el_error
|
80
62
|
found_error = true if el_error
|
@@ -110,4 +92,4 @@ module Mutations
|
|
110
92
|
[data, nil]
|
111
93
|
end
|
112
94
|
end
|
113
|
-
end
|
95
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Mutations
|
2
|
-
class BooleanFilter <
|
2
|
+
class BooleanFilter < AdditionalFilter
|
3
3
|
@default_options = {
|
4
|
-
nils
|
4
|
+
:nils => false # true allows an explicit nil to be valid. Overrides any other options
|
5
5
|
}
|
6
6
|
|
7
7
|
BOOL_MAP = {"true" => true, "1" => true, "false" => false, "0" => false}
|
@@ -30,4 +30,4 @@ module Mutations
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
33
|
-
end
|
33
|
+
end
|
data/lib/mutations/command.rb
CHANGED
@@ -55,11 +55,11 @@ module Mutations
|
|
55
55
|
|
56
56
|
# Instance methods
|
57
57
|
def initialize(*args)
|
58
|
-
@raw_inputs = args.
|
58
|
+
@raw_inputs = args.inject({}.with_indifferent_access) do |h, arg|
|
59
59
|
raise ArgumentError.new("All arguments must be hashes") unless arg.is_a?(Hash)
|
60
60
|
h.merge!(arg)
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
@inputs, @errors = self.input_filters.filter(@raw_inputs)
|
64
64
|
end
|
65
65
|
|
@@ -110,11 +110,12 @@ module Mutations
|
|
110
110
|
|
111
111
|
@errors ||= ErrorHash.new
|
112
112
|
@errors.tap do |errs|
|
113
|
-
|
113
|
+
path = key.to_s.split(".")
|
114
|
+
last = path.pop
|
114
115
|
inner = path.inject(errs) do |cut_errors,part|
|
115
116
|
cur_errors[part.to_sym] ||= ErrorHash.new
|
116
117
|
end
|
117
|
-
inner[last] = ErrorAtom.new(key, kind, message
|
118
|
+
inner[last] = ErrorAtom.new(key, kind, :message => message)
|
118
119
|
end
|
119
120
|
end
|
120
121
|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Mutations
|
2
|
+
class DateFilter < AdditionalFilter
|
3
|
+
@default_options = {
|
4
|
+
:nils => false, # true allows an explicit nil to be valid. Overrides any other options
|
5
|
+
:format => nil, # If nil, Date.parse will be used for coercsion. If something like "%Y-%m-%d", Date.strptime is used
|
6
|
+
:after => nil, # A date object, representing the minimum date allowed, inclusive
|
7
|
+
:before => nil # A date object, representing the maximum date allowed, inclusive
|
8
|
+
}
|
9
|
+
|
10
|
+
def filter(data)
|
11
|
+
# Handle nil case
|
12
|
+
if data.nil?
|
13
|
+
return [nil, nil] if options[:nils]
|
14
|
+
return [nil, :nils]
|
15
|
+
end
|
16
|
+
|
17
|
+
if data.is_a?(Date) # Date and DateTime
|
18
|
+
actual_date = data
|
19
|
+
elsif data.is_a?(String)
|
20
|
+
begin
|
21
|
+
actual_date = if options[:format]
|
22
|
+
Date.strptime(data, options[:format])
|
23
|
+
else
|
24
|
+
Date.parse(data)
|
25
|
+
end
|
26
|
+
rescue ArgumentError
|
27
|
+
return [nil, :date]
|
28
|
+
end
|
29
|
+
elsif data.respond_to?(:to_date) # Time
|
30
|
+
actual_date = data.to_date
|
31
|
+
else
|
32
|
+
return [nil, :date]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Ok, its a valid date, check if it falls within the range
|
36
|
+
if options[:after]
|
37
|
+
if actual_date <= options[:after]
|
38
|
+
return [nil, :after]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
if options[:before]
|
43
|
+
if actual_date >= options[:before]
|
44
|
+
return [nil, :before]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# We win, it's valid!
|
49
|
+
[actual_date, nil]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Mutations
|
2
|
-
class DuckFilter <
|
2
|
+
class DuckFilter < AdditionalFilter
|
3
3
|
@default_options = {
|
4
|
-
nils
|
5
|
-
methods
|
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
6
|
}
|
7
7
|
|
8
8
|
def filter(data)
|
@@ -22,4 +22,4 @@ module Mutations
|
|
22
22
|
[data, nil]
|
23
23
|
end
|
24
24
|
end
|
25
|
-
end
|
25
|
+
end
|
data/lib/mutations/errors.rb
CHANGED
@@ -6,33 +6,38 @@ module Mutations
|
|
6
6
|
MESSAGES = Hash.new("is invalid").tap do |h|
|
7
7
|
h.merge!(
|
8
8
|
# General
|
9
|
-
nils
|
10
|
-
required
|
9
|
+
:nils => "can't be nil",
|
10
|
+
:required => "is required",
|
11
11
|
|
12
12
|
# Datatypes
|
13
|
-
string
|
14
|
-
integer
|
15
|
-
boolean
|
16
|
-
hash
|
17
|
-
array
|
18
|
-
model
|
13
|
+
:string => "isn't a string",
|
14
|
+
:integer => "isn't an integer",
|
15
|
+
:boolean => "isn't a boolean",
|
16
|
+
:hash => "isn't a hash",
|
17
|
+
:array => "isn't an array",
|
18
|
+
:model => "isn't the right class",
|
19
|
+
|
20
|
+
# Date
|
21
|
+
:date => "date doesn't exist",
|
22
|
+
:before => "isn't before given date",
|
23
|
+
:after => "isn't after given date",
|
19
24
|
|
20
25
|
# String
|
21
|
-
empty
|
22
|
-
max_length
|
23
|
-
min_length
|
24
|
-
matches
|
25
|
-
in
|
26
|
+
:empty => "can't be blank",
|
27
|
+
:max_length => "is too long",
|
28
|
+
:min_length => "is too short",
|
29
|
+
:matches => "isn't in the right format",
|
30
|
+
:in => "isn't an option",
|
26
31
|
|
27
32
|
# Array
|
28
|
-
class
|
33
|
+
:class => "isn't the right class",
|
29
34
|
|
30
35
|
# Integer
|
31
|
-
min
|
32
|
-
max
|
36
|
+
:min => "is too small",
|
37
|
+
:max => "is too big",
|
33
38
|
|
34
39
|
# Model
|
35
|
-
new_records
|
40
|
+
:new_records => "isn't a saved model"
|
36
41
|
)
|
37
42
|
end
|
38
43
|
|
@@ -49,7 +54,6 @@ module Mutations
|
|
49
54
|
end
|
50
55
|
end
|
51
56
|
|
52
|
-
|
53
57
|
class ErrorAtom
|
54
58
|
|
55
59
|
# NOTE: in the future, could also pass in:
|
@@ -70,7 +74,7 @@ module Mutations
|
|
70
74
|
end
|
71
75
|
|
72
76
|
def message
|
73
|
-
@message ||= Mutations.error_message_creator.message(@key, @symbol, index
|
77
|
+
@message ||= Mutations.error_message_creator.message(@key, @symbol, :index => @index)
|
74
78
|
end
|
75
79
|
|
76
80
|
def message_list
|
@@ -152,4 +156,4 @@ module Mutations
|
|
152
156
|
compact.map {|e| e.message_list }.flatten
|
153
157
|
end
|
154
158
|
end
|
155
|
-
end
|
159
|
+
end
|
data/lib/mutations/exception.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module Mutations
|
2
|
-
class FileFilter <
|
2
|
+
class FileFilter < AdditionalFilter
|
3
3
|
@default_options = {
|
4
|
-
nils
|
5
|
-
upload
|
6
|
-
size
|
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
7
|
}
|
8
8
|
|
9
9
|
def filter(data)
|
@@ -20,13 +20,13 @@ module Mutations
|
|
20
20
|
methods.each do |method|
|
21
21
|
return [data, :file] unless data.respond_to?(method)
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
if options[:size].is_a?(Fixnum)
|
25
|
-
return [data, :size] if data.size > options[:size]
|
25
|
+
return [data, :size] if data.size > options[:size]
|
26
26
|
end
|
27
27
|
|
28
28
|
# We win, it's valid!
|
29
29
|
[data, nil]
|
30
30
|
end
|
31
31
|
end
|
32
|
-
end
|
32
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module Mutations
|
2
|
-
class FloatFilter <
|
2
|
+
class FloatFilter < AdditionalFilter
|
3
3
|
@default_options = {
|
4
|
-
nils
|
5
|
-
min
|
6
|
-
max
|
4
|
+
:nils => false, # true allows an explicit nil to be valid. Overrides any other options
|
5
|
+
:min => nil, # lowest value, inclusive
|
6
|
+
:max => nil # highest value, inclusive
|
7
7
|
}
|
8
8
|
|
9
9
|
def filter(data)
|
@@ -32,4 +32,4 @@ module Mutations
|
|
32
32
|
[data, nil]
|
33
33
|
end
|
34
34
|
end
|
35
|
-
end
|
35
|
+
end
|