mutations 0.5.10 → 0.5.11
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.lock +1 -1
- data/README.md +2 -2
- data/TODO +12 -0
- data/lib/mutations/command.rb +45 -81
- data/lib/mutations/float_filter.rb +0 -1
- data/lib/mutations/hash_filter.rb +0 -2
- data/lib/mutations/integer_filter.rb +0 -1
- data/lib/mutations/model_filter.rb +17 -9
- data/lib/mutations/outcome.rb +4 -10
- data/lib/mutations/version.rb +1 -1
- data/spec/command_spec.rb +9 -0
- data/spec/default_spec.rb +33 -0
- data/spec/model_filter_spec.rb +10 -7
- metadata +3 -3
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -114,7 +114,7 @@ That being said, you can pass multiple hashes to run, and they are merged togeth
|
|
114
114
|
```ruby
|
115
115
|
# A user comments on an article
|
116
116
|
class CreateComment < Mutations::Command
|
117
|
-
|
117
|
+
required do
|
118
118
|
model :user
|
119
119
|
model :article
|
120
120
|
string :comment, max_length: 500
|
@@ -138,7 +138,7 @@ Here, we pass two hashes to CreateComment. Even if the params[:comment] hash has
|
|
138
138
|
1. Subclass Mutations::Command
|
139
139
|
|
140
140
|
```ruby
|
141
|
-
class YourMutation <
|
141
|
+
class YourMutation < Mutations::Command
|
142
142
|
# ...
|
143
143
|
end
|
144
144
|
```
|
data/TODO
CHANGED
@@ -1,2 +1,14 @@
|
|
1
1
|
- Document default behavior discarding of nils for optional params
|
2
2
|
- document string.discard_empty
|
3
|
+
|
4
|
+
- protected parameters:
|
5
|
+
optional do
|
6
|
+
boolean :skip_confirmation, protected: true
|
7
|
+
end
|
8
|
+
Given the above, skip_confirmation is only accepted as a parameter if it's passed in a later hash, eg this would make it take:
|
9
|
+
User::ChangeEmail.run!(params, user: current_user, skip_confirmation: true)
|
10
|
+
But this would not:
|
11
|
+
params = {user: current_user, skip_confirmation: true}
|
12
|
+
User::ChangeEmail.run!(params)
|
13
|
+
|
14
|
+
|
data/lib/mutations/command.rb
CHANGED
@@ -1,25 +1,10 @@
|
|
1
|
-
# IDEA i just had (protected parameters):
|
2
|
-
# optional do
|
3
|
-
# boolean :skip_confirmation, protected: true
|
4
|
-
# end
|
5
|
-
# Given the above, skip_confirmation is only accepted as a parameter if it's passed in a later hash, eg this would make it take:
|
6
|
-
# User::ChangeEmail.run!(params, user: current_user, skip_confirmation: true)
|
7
|
-
# But this would not:
|
8
|
-
# params = {user: current_user, skip_confirmation: true}
|
9
|
-
# User::ChangeEmail.run!(params)
|
10
|
-
|
11
|
-
|
12
1
|
module Mutations
|
13
2
|
class Command
|
14
|
-
|
15
|
-
##
|
16
|
-
##
|
17
|
-
##
|
18
3
|
class << self
|
19
|
-
def
|
20
|
-
self.input_filters.
|
21
|
-
|
22
|
-
|
4
|
+
def create_attr_methods(meth, &block)
|
5
|
+
self.input_filters.send(meth, &block)
|
6
|
+
keys = self.input_filters.send("#{meth}_keys")
|
7
|
+
keys.each do |key|
|
23
8
|
define_method(key) do
|
24
9
|
@filtered_input[key]
|
25
10
|
end
|
@@ -33,41 +18,27 @@ module Mutations
|
|
33
18
|
end
|
34
19
|
end
|
35
20
|
end
|
21
|
+
private :create_attr_methods
|
36
22
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
self.input_filters.optional_keys.each do |key|
|
41
|
-
define_method(key) do
|
42
|
-
@filtered_input[key]
|
43
|
-
end
|
44
|
-
|
45
|
-
define_method("#{key}_present?") do
|
46
|
-
@filtered_input.has_key?(key)
|
47
|
-
end
|
23
|
+
def required(&block)
|
24
|
+
create_attr_methods(:required, &block)
|
25
|
+
end
|
48
26
|
|
49
|
-
|
50
|
-
|
51
|
-
end
|
52
|
-
end
|
27
|
+
def optional(&block)
|
28
|
+
create_attr_methods(:optional, &block)
|
53
29
|
end
|
54
30
|
|
55
31
|
def run(*args)
|
56
|
-
new(*args).
|
32
|
+
new(*args).run
|
57
33
|
end
|
58
34
|
|
59
35
|
def run!(*args)
|
60
|
-
|
61
|
-
if m.success?
|
62
|
-
m.result
|
63
|
-
else
|
64
|
-
raise ValidationException.new(m.errors)
|
65
|
-
end
|
36
|
+
new(*args).run!
|
66
37
|
end
|
67
38
|
|
68
39
|
# Validates input, but doesn't call execute. Returns an Outcome with errors anyway.
|
69
40
|
def validate(*args)
|
70
|
-
new(*args).
|
41
|
+
new(*args).validate
|
71
42
|
end
|
72
43
|
|
73
44
|
def input_filters
|
@@ -84,19 +55,10 @@ module Mutations
|
|
84
55
|
|
85
56
|
# Instance methods
|
86
57
|
def initialize(*args)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
@original_hash = args.shift
|
91
|
-
raise ArgumentError.new("All arguments must be hashes") unless @original_hash.is_a?(Hash)
|
92
|
-
@original_hash = @original_hash.with_indifferent_access
|
58
|
+
@original_hash = args.each_with_object({}.with_indifferent_access) do |arg, h|
|
59
|
+
raise ArgumentError.new("All arguments must be hashes") unless arg.is_a?(Hash)
|
60
|
+
h.merge!(arg)
|
93
61
|
end
|
94
|
-
|
95
|
-
args.each do |a|
|
96
|
-
raise ArgumentError.new("All arguments must be hashes") unless a.is_a?(Hash)
|
97
|
-
@original_hash.merge!(a)
|
98
|
-
end
|
99
|
-
|
100
62
|
@filtered_input, @errors = self.input_filters.filter(@original_hash)
|
101
63
|
end
|
102
64
|
|
@@ -104,26 +66,37 @@ module Mutations
|
|
104
66
|
self.class.input_filters
|
105
67
|
end
|
106
68
|
|
107
|
-
def
|
108
|
-
|
69
|
+
def has_errors?
|
70
|
+
!@errors.nil?
|
71
|
+
end
|
109
72
|
|
110
|
-
|
73
|
+
def run
|
74
|
+
return validation_outcome if has_errors?
|
75
|
+
validation_outcome(execute)
|
76
|
+
end
|
111
77
|
|
112
|
-
|
113
|
-
|
114
|
-
|
78
|
+
def run!
|
79
|
+
outcome = run
|
80
|
+
if outcome.success?
|
81
|
+
outcome.result
|
115
82
|
else
|
116
|
-
|
83
|
+
raise ValidationException.new(outcome.errors)
|
117
84
|
end
|
118
85
|
end
|
119
86
|
|
87
|
+
def validate
|
88
|
+
validation_outcome
|
89
|
+
end
|
90
|
+
|
120
91
|
# Runs input thru the filter and sets @filtered_input and @errors
|
121
|
-
def validation_outcome
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
92
|
+
def validation_outcome(result = nil)
|
93
|
+
Outcome.new(!has_errors?, has_errors? ? nil : result, @errors, @filtered_input)
|
94
|
+
end
|
95
|
+
|
96
|
+
protected
|
97
|
+
|
98
|
+
def execute
|
99
|
+
# Meant to be overridden
|
127
100
|
end
|
128
101
|
|
129
102
|
# add_error("name", :too_short)
|
@@ -134,18 +107,13 @@ module Mutations
|
|
134
107
|
raise ArgumentError.new("Invalid kind") unless kind.is_a?(Symbol)
|
135
108
|
|
136
109
|
@errors ||= ErrorHash.new
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
if parts.length > 0
|
142
|
-
cur_errors[part] = ErrorHash.new unless cur_errors[part].is_a?(ErrorHash)
|
143
|
-
cur_errors = cur_errors[part]
|
144
|
-
else
|
145
|
-
cur_errors[part] = ErrorAtom.new(key, kind, message: message)
|
110
|
+
@errors.tap do |errs|
|
111
|
+
*path, last = key.to_s.split(".")
|
112
|
+
inner = path.inject(errs) do |cut_errors,part|
|
113
|
+
cur_errors[part.to_sym] ||= ErrorHash.new
|
146
114
|
end
|
115
|
+
inner[last] = ErrorAtom.new(key, kind, message: message)
|
147
116
|
end
|
148
|
-
@errors
|
149
117
|
end
|
150
118
|
|
151
119
|
def merge_errors(hash)
|
@@ -156,9 +124,5 @@ module Mutations
|
|
156
124
|
def inputs
|
157
125
|
@filtered_input
|
158
126
|
end
|
159
|
-
|
160
|
-
def execute
|
161
|
-
# Meant to be overridden
|
162
|
-
end
|
163
127
|
end
|
164
128
|
end
|
@@ -31,13 +31,11 @@ module Mutations
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def required(&block)
|
34
|
-
# TODO: raise if nesting is wrong
|
35
34
|
@current_inputs = @required_inputs
|
36
35
|
instance_eval &block
|
37
36
|
end
|
38
37
|
|
39
38
|
def optional(&block)
|
40
|
-
# TODO: raise if nesting is wrong
|
41
39
|
@current_inputs = @optional_inputs
|
42
40
|
instance_eval &block
|
43
41
|
end
|
@@ -10,18 +10,26 @@ module Mutations
|
|
10
10
|
def initialize(name, opts = {})
|
11
11
|
super(opts)
|
12
12
|
@name = name
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
end
|
14
|
+
|
15
|
+
# Initialize the model class and builder
|
16
|
+
def initialize_constants!
|
17
|
+
@initialize_constants ||= begin
|
18
|
+
class_const = options[:class] || @name.to_s.camelize
|
19
|
+
class_const = class_const.constantize if class_const.is_a?(String)
|
20
|
+
self.options[:class] = class_const
|
21
|
+
|
22
|
+
if options[:builder]
|
23
|
+
options[:builder] = options[:builder].constantize if options[:builder].is_a?(String)
|
24
|
+
end
|
25
|
+
|
26
|
+
true
|
20
27
|
end
|
21
28
|
end
|
22
29
|
|
23
30
|
def filter(data)
|
24
|
-
|
31
|
+
initialize_constants!
|
32
|
+
|
25
33
|
# Handle nil case
|
26
34
|
if data.nil?
|
27
35
|
return [nil, nil] if options[:nils]
|
@@ -49,4 +57,4 @@ module Mutations
|
|
49
57
|
end
|
50
58
|
|
51
59
|
end
|
52
|
-
end
|
60
|
+
end
|
data/lib/mutations/outcome.rb
CHANGED
@@ -1,19 +1,13 @@
|
|
1
1
|
module Mutations
|
2
2
|
class Outcome
|
3
|
-
|
4
|
-
|
3
|
+
attr_reader :result, :errors, :inputs
|
4
|
+
|
5
|
+
def initialize(is_success, result, errors, inputs)
|
6
|
+
@success, @result, @errors, @inputs = is_success, result, errors, inputs
|
5
7
|
end
|
6
8
|
|
7
9
|
def success?
|
8
10
|
@success
|
9
11
|
end
|
10
|
-
|
11
|
-
def result
|
12
|
-
@result
|
13
|
-
end
|
14
|
-
|
15
|
-
def errors
|
16
|
-
@errors
|
17
|
-
end
|
18
12
|
end
|
19
13
|
end
|
data/lib/mutations/version.rb
CHANGED
data/spec/command_spec.rb
CHANGED
@@ -72,11 +72,20 @@ describe "Command" do
|
|
72
72
|
assert_raises ArgumentError do
|
73
73
|
outcome = SimpleCommand.run(1)
|
74
74
|
end
|
75
|
+
|
76
|
+
assert_raises ArgumentError do
|
77
|
+
outcome = SimpleCommand.run({name: "John"}, 1)
|
78
|
+
end
|
75
79
|
end
|
76
80
|
|
77
81
|
it "should accept nothing at all" do
|
78
82
|
SimpleCommand.run # make sure nothing is raised
|
79
83
|
end
|
84
|
+
|
85
|
+
it "should return the filtered inputs in the outcome" do
|
86
|
+
outcome = SimpleCommand.run(name: " John ", email: "john@gmail.com", amount: "5")
|
87
|
+
assert_equal ({name: "John", email: "john@gmail.com", amount: 5}).stringify_keys, outcome.inputs
|
88
|
+
end
|
80
89
|
end
|
81
90
|
|
82
91
|
describe "EigenCommand" do
|
data/spec/default_spec.rb
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require 'simple_command'
|
3
|
+
|
4
|
+
describe 'Mutations - defaults' do
|
5
|
+
|
6
|
+
class DefaultCommand < Mutations::Command
|
7
|
+
required do
|
8
|
+
string :name, default: "Bob Jones"
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
inputs
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should have a default if no value is passed" do
|
17
|
+
outcome = DefaultCommand.run
|
18
|
+
assert_equal true, outcome.success?
|
19
|
+
assert_equal ({"name" => "Bob Jones"}), outcome.result
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should have the passed value if a value is passed" do
|
23
|
+
outcome = DefaultCommand.run(name: "Fred")
|
24
|
+
assert_equal true, outcome.success?
|
25
|
+
assert_equal ({"name" => "Fred"}), outcome.result
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should be an error if nil is passed on a required field with a default" do
|
29
|
+
outcome = DefaultCommand.run(name: nil)
|
30
|
+
assert_equal false, outcome.success?
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/spec/model_filter_spec.rb
CHANGED
@@ -27,21 +27,24 @@ describe "Mutations::ModelFilter" do
|
|
27
27
|
# it "disallows different types of models" do
|
28
28
|
# end
|
29
29
|
|
30
|
-
it "raises an exception during
|
30
|
+
it "raises an exception during filtering if constantization fails" do
|
31
|
+
f = Mutations::ModelFilter.new(:complex_model)
|
31
32
|
assert_raises NameError do
|
32
|
-
|
33
|
+
f.filter(nil)
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
36
|
-
it "raises an exception during
|
37
|
+
it "raises an exception during filtering if constantization of class fails" do
|
38
|
+
f = Mutations::ModelFilter.new(:simple_model, class: "ComplexModel")
|
37
39
|
assert_raises NameError do
|
38
|
-
|
40
|
+
f.filter(nil)
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
42
|
-
it "raises an exception during
|
44
|
+
it "raises an exception during filtering if constantization of builder fails" do
|
45
|
+
f = Mutations::ModelFilter.new(:simple_model, builder: "ComplexModel")
|
43
46
|
assert_raises NameError do
|
44
|
-
|
47
|
+
f.filter(nil)
|
45
48
|
end
|
46
49
|
end
|
47
50
|
|
@@ -89,4 +92,4 @@ describe "Mutations::ModelFilter" do
|
|
89
92
|
# it "makes sure that if you build a record from a hash, it still has to be of the right class" do
|
90
93
|
# end
|
91
94
|
|
92
|
-
end
|
95
|
+
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.
|
4
|
+
version: 0.5.11
|
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-02
|
12
|
+
date: 2013-03-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -123,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
123
|
version: '0'
|
124
124
|
requirements: []
|
125
125
|
rubyforge_project:
|
126
|
-
rubygems_version: 1.8.
|
126
|
+
rubygems_version: 1.8.23
|
127
127
|
signing_key:
|
128
128
|
specification_version: 3
|
129
129
|
summary: Compose your business logic into commands that sanitize and validate input.
|