mutations 0.6.0 → 0.7.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/.gitignore +2 -0
- data/.travis.yml +2 -5
- data/CHANGELOG.md +11 -0
- data/Gemfile +6 -0
- data/README.md +17 -2
- data/lib/mutations.rb +2 -3
- data/lib/mutations/additional_filter.rb +1 -1
- data/lib/mutations/array_filter.rb +4 -4
- data/lib/mutations/command.rb +14 -8
- data/lib/mutations/hash_filter.rb +7 -3
- data/lib/mutations/input_filter.rb +4 -0
- data/lib/mutations/string_filter.rb +1 -1
- data/lib/mutations/version.rb +1 -1
- data/mutations.gemspec +1 -1
- data/spec/additional_filter_spec.rb +56 -3
- data/spec/array_filter_spec.rb +8 -0
- data/spec/command_spec.rb +45 -0
- data/spec/hash_filter_spec.rb +44 -0
- data/spec/simple_command.rb +7 -0
- data/spec/string_filter_spec.rb +42 -0
- metadata +2 -3
- data/Gemfile.lock +0 -25
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
0.7.0
|
2
|
+
-----------
|
3
|
+
|
4
|
+
- Ruby 2.1 support added.
|
5
|
+
- Ruby 1.8.7 support removed.
|
6
|
+
- Rubinius support updated.
|
7
|
+
- Gemfile.lock removed (Rails 4 support, etc)
|
8
|
+
- API change: Add ability to implement a 'validate' method
|
9
|
+
- ```discard_invalid``` option added
|
10
|
+
- AdditionFilters: Gain ability to pass blocks to filters.
|
11
|
+
|
1
12
|
0.6.0
|
2
13
|
-----------
|
3
14
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -233,7 +233,23 @@ unless outcome.success?
|
|
233
233
|
end
|
234
234
|
```
|
235
235
|
|
236
|
-
You can add errors
|
236
|
+
You can add errors in a validate method if the default validations are insufficient. Errors added by validate will prevent the execute method from running.
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
#...
|
240
|
+
def validate
|
241
|
+
if password != password_confirmation
|
242
|
+
add_error(:password_confirmation, :doesnt_match, "Your passwords don't match")
|
243
|
+
end
|
244
|
+
end
|
245
|
+
# ...
|
246
|
+
|
247
|
+
# That error would show up in the errors hash:
|
248
|
+
outcome.errors.symbolic # => {password_confirmation: :doesnt_match}
|
249
|
+
outcome.errors.message # => {password_confirmation: "Your passwords don't match"}
|
250
|
+
```
|
251
|
+
|
252
|
+
Alternatively you can also add these validations in the execute method:
|
237
253
|
|
238
254
|
```ruby
|
239
255
|
#...
|
@@ -271,4 +287,3 @@ Yes, but I don't think it's a very good idea. Better to compose.
|
|
271
287
|
### Can I use this with Rails forms helpers?
|
272
288
|
|
273
289
|
Somewhat. Any form can submit to your server, and mutations will happily accept that input. However, if there are errors, there's no built-in way to bake the errors into the HTML with Rails form tag helpers. Right now this is really designed to support a JSON API. You'd probably have to write an adapter of some kind.
|
274
|
-
|
data/lib/mutations.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'active_support'
|
2
1
|
require 'active_support/core_ext/hash/indifferent_access'
|
3
2
|
require 'active_support/core_ext/string/inflections'
|
4
3
|
require 'date'
|
@@ -30,11 +29,11 @@ module Mutations
|
|
30
29
|
def error_message_creator=(creator)
|
31
30
|
@error_message_creator = creator
|
32
31
|
end
|
33
|
-
|
32
|
+
|
34
33
|
def cache_constants=(val)
|
35
34
|
@cache_constants = val
|
36
35
|
end
|
37
|
-
|
36
|
+
|
38
37
|
def cache_constants?
|
39
38
|
@cache_constants
|
40
39
|
end
|
@@ -4,7 +4,7 @@ require 'mutations/array_filter'
|
|
4
4
|
module Mutations
|
5
5
|
class AdditionalFilter < InputFilter
|
6
6
|
def self.inherited(subclass)
|
7
|
-
type_name = subclass.name[/^Mutations::([a-zA-Z]*)Filter$/, 1].
|
7
|
+
type_name = subclass.name[/^Mutations::([a-zA-Z]*)Filter$/, 1].underscore
|
8
8
|
|
9
9
|
Mutations::HashFilter.register_additional_filter(subclass, type_name)
|
10
10
|
Mutations::ArrayFilter.register_additional_filter(subclass, type_name)
|
@@ -57,15 +57,15 @@ module Mutations
|
|
57
57
|
data.each_with_index do |el, i|
|
58
58
|
el_filtered, el_error = filter_element(el)
|
59
59
|
el_error = ErrorAtom.new(@name, el_error, :index => i) if el_error.is_a?(Symbol)
|
60
|
-
|
61
60
|
errors << el_error
|
62
|
-
|
63
|
-
|
61
|
+
if el_error
|
62
|
+
found_error = true
|
63
|
+
else
|
64
64
|
filtered_data << el_filtered
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
if found_error
|
68
|
+
if found_error && !(@element_filter && @element_filter.discard_invalid?)
|
69
69
|
[data, errors]
|
70
70
|
else
|
71
71
|
[filtered_data, nil]
|
data/lib/mutations/command.rb
CHANGED
@@ -38,7 +38,7 @@ module Mutations
|
|
38
38
|
|
39
39
|
# Validates input, but doesn't call execute. Returns an Outcome with errors anyway.
|
40
40
|
def validate(*args)
|
41
|
-
new(*args).
|
41
|
+
new(*args).validation_outcome
|
42
42
|
end
|
43
43
|
|
44
44
|
def input_filters
|
@@ -60,7 +60,11 @@ module Mutations
|
|
60
60
|
h.merge!(arg)
|
61
61
|
end
|
62
62
|
|
63
|
+
# Do field-level validation / filtering:
|
63
64
|
@inputs, @errors = self.input_filters.filter(@raw_inputs)
|
65
|
+
|
66
|
+
# Run a custom validation method if supplied:
|
67
|
+
validate unless has_errors?
|
64
68
|
end
|
65
69
|
|
66
70
|
def input_filters
|
@@ -85,10 +89,6 @@ module Mutations
|
|
85
89
|
end
|
86
90
|
end
|
87
91
|
|
88
|
-
def validate
|
89
|
-
validation_outcome
|
90
|
-
end
|
91
|
-
|
92
92
|
def validation_outcome(result = nil)
|
93
93
|
Outcome.new(!has_errors?, has_errors? ? nil : result, @errors, @inputs)
|
94
94
|
end
|
@@ -97,6 +97,10 @@ module Mutations
|
|
97
97
|
|
98
98
|
attr_reader :inputs, :raw_inputs
|
99
99
|
|
100
|
+
def validate
|
101
|
+
# Meant to be overridden
|
102
|
+
end
|
103
|
+
|
100
104
|
def execute
|
101
105
|
# Meant to be overridden
|
102
106
|
end
|
@@ -112,7 +116,7 @@ module Mutations
|
|
112
116
|
@errors.tap do |errs|
|
113
117
|
path = key.to_s.split(".")
|
114
118
|
last = path.pop
|
115
|
-
inner = path.inject(errs) do |
|
119
|
+
inner = path.inject(errs) do |cur_errors,part|
|
116
120
|
cur_errors[part.to_sym] ||= ErrorHash.new
|
117
121
|
end
|
118
122
|
inner[last] = ErrorAtom.new(key, kind, :message => message)
|
@@ -120,8 +124,10 @@ module Mutations
|
|
120
124
|
end
|
121
125
|
|
122
126
|
def merge_errors(hash)
|
123
|
-
|
124
|
-
|
127
|
+
if hash.any?
|
128
|
+
@errors ||= ErrorHash.new
|
129
|
+
@errors.merge!(hash)
|
130
|
+
end
|
125
131
|
end
|
126
132
|
|
127
133
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module Mutations
|
2
2
|
class HashFilter < InputFilter
|
3
3
|
def self.register_additional_filter(type_class, type_name)
|
4
|
-
define_method(type_name) do | *args |
|
4
|
+
define_method(type_name) do | *args, &block |
|
5
5
|
name = args[0]
|
6
6
|
options = args[1] || {}
|
7
7
|
|
8
|
-
@current_inputs[name.to_sym] = type_class.new(options)
|
8
|
+
@current_inputs[name.to_sym] = type_class.new(options, &block)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
@@ -103,7 +103,7 @@ module Mutations
|
|
103
103
|
|
104
104
|
# First, discard optional nils/empty params
|
105
105
|
data.delete(key) if !is_required && data.has_key?(key) && filterer.discard_nils? && data_element.nil?
|
106
|
-
data.delete(key) if !is_required && data.has_key?(key) && filterer.discard_empty? && data_element == ""
|
106
|
+
data.delete(key) if !is_required && data.has_key?(key) && filterer.discard_empty? && data_element == "" # BUG: this doesn't account for data_elem being " "
|
107
107
|
|
108
108
|
default_used = false
|
109
109
|
if !data.has_key?(key) && filterer.has_default?
|
@@ -116,6 +116,8 @@ module Mutations
|
|
116
116
|
|
117
117
|
if sub_error.nil?
|
118
118
|
filtered_data[key] = sub_data
|
119
|
+
elsif !is_required && filterer.discard_invalid?
|
120
|
+
data.delete(key)
|
119
121
|
else
|
120
122
|
sub_error = ErrorAtom.new(key, sub_error) if sub_error.is_a?(Symbol)
|
121
123
|
errors[key] = sub_error
|
@@ -139,6 +141,8 @@ module Mutations
|
|
139
141
|
sub_data, sub_error = wildcard_filterer.filter(data_element)
|
140
142
|
if sub_error.nil?
|
141
143
|
filtered_data[key] = sub_data
|
144
|
+
elsif wildcard_filterer.discard_invalid?
|
145
|
+
data.delete(key)
|
142
146
|
else
|
143
147
|
sub_error = ErrorAtom.new(key, sub_error) if sub_error.is_a?(Symbol)
|
144
148
|
errors[key] = sub_error
|
@@ -2,7 +2,7 @@ module Mutations
|
|
2
2
|
class StringFilter < AdditionalFilter
|
3
3
|
@default_options = {
|
4
4
|
:strip => true, # true calls data.strip if data is a string
|
5
|
-
:strict => false, # If false, then symbols, numbers, and booleans are converted to a string with to_s.
|
5
|
+
:strict => false, # If false, then symbols, numbers, and booleans are converted to a string with to_s.
|
6
6
|
:nils => false, # true allows an explicit nil to be valid. Overrides any other options
|
7
7
|
:empty => false, # false disallows "". true allows "" and overrides any other validations (b/c they couldn't be true if it's empty)
|
8
8
|
:min_length => nil, # Can be a number like 5, meaning that 5 codepoints are required
|
data/lib/mutations/version.rb
CHANGED
data/mutations.gemspec
CHANGED
@@ -13,20 +13,31 @@ describe "Mutations::AdditionalFilter" do
|
|
13
13
|
return [data, nil]
|
14
14
|
end
|
15
15
|
end
|
16
|
+
|
17
|
+
class MultiWordTestFilter < Mutations::AdditionalFilter
|
18
|
+
@default_options = {
|
19
|
+
:nils => false
|
20
|
+
}
|
21
|
+
|
22
|
+
def filter(data)
|
23
|
+
return [data, nil]
|
24
|
+
end
|
25
|
+
end
|
16
26
|
end
|
17
27
|
|
18
28
|
class TestCommandUsingAdditionalFilters < Mutations::Command
|
19
29
|
required do
|
20
30
|
sometest :first_name
|
31
|
+
multi_word_test :last_name
|
21
32
|
end
|
22
33
|
|
23
34
|
def execute
|
24
|
-
{ :first_name => first_name }
|
35
|
+
{ :first_name => first_name, :last_name => last_name }
|
25
36
|
end
|
26
37
|
end
|
27
38
|
|
28
39
|
it "should recognize additional filters" do
|
29
|
-
outcome = TestCommandUsingAdditionalFilters.run(:first_name => "John")
|
40
|
+
outcome = TestCommandUsingAdditionalFilters.run(:first_name => "John", :last_name => "Doe")
|
30
41
|
assert outcome.success?
|
31
42
|
assert_equal nil, outcome.errors
|
32
43
|
end
|
@@ -45,7 +56,7 @@ describe "Mutations::AdditionalFilter" do
|
|
45
56
|
|
46
57
|
it "should be useable in hashes" do
|
47
58
|
outcome = TestCommandUsingAdditionalFiltersInHashes.run(
|
48
|
-
:a_hash => { :first_name => "John" }
|
59
|
+
:a_hash => { :first_name => "John" }
|
49
60
|
)
|
50
61
|
|
51
62
|
assert outcome.success?
|
@@ -72,5 +83,47 @@ describe "Mutations::AdditionalFilter" do
|
|
72
83
|
assert outcome.success?
|
73
84
|
assert_equal nil, outcome.errors
|
74
85
|
end
|
86
|
+
|
87
|
+
module Mutations
|
88
|
+
class AdditionalWithBlockFilter < Mutations::AdditionalFilter
|
89
|
+
|
90
|
+
def initialize(opts={}, &block)
|
91
|
+
super(opts)
|
92
|
+
|
93
|
+
if block_given?
|
94
|
+
instance_eval &block
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def should_be_called
|
99
|
+
@was_called = true
|
100
|
+
end
|
101
|
+
|
102
|
+
def filter(data)
|
103
|
+
if @was_called
|
104
|
+
[true, nil]
|
105
|
+
else
|
106
|
+
[nil, :not_called]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class TestCommandUsingBlockArgument < Mutations::Command
|
113
|
+
required do
|
114
|
+
additional_with_block :foo do
|
115
|
+
should_be_called
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def execute
|
120
|
+
true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it "can have a block constructor" do
|
125
|
+
assert_equal true, TestCommandUsingBlockArgument.run!(:foo => 'bar')
|
126
|
+
end
|
127
|
+
|
75
128
|
end
|
76
129
|
end
|
data/spec/array_filter_spec.rb
CHANGED
@@ -181,4 +181,12 @@ describe "Mutations::ArrayFilter" do
|
|
181
181
|
assert_equal ["Array[2] isn't a string", "Array[0] can't be blank"], errors.message_list
|
182
182
|
end
|
183
183
|
|
184
|
+
it "strips invalid elements" do
|
185
|
+
f = Mutations::ArrayFilter.new(:arr) do
|
186
|
+
integer :discard_invalid => true
|
187
|
+
end
|
188
|
+
filtered, errors = f.filter([1, "2", "three", "4", 5, [6]])
|
189
|
+
assert_equal [1,2,4,5], filtered
|
190
|
+
assert_equal nil, errors
|
191
|
+
end
|
184
192
|
end
|
data/spec/command_spec.rb
CHANGED
@@ -50,6 +50,29 @@ describe "Command" do
|
|
50
50
|
assert_equal :max_length, outcome.errors.symbolic[:name]
|
51
51
|
end
|
52
52
|
|
53
|
+
it "should execute a custom validate method" do
|
54
|
+
outcome = SimpleCommand.validate(:name => "JohnLong", :email => "xxxx")
|
55
|
+
|
56
|
+
assert !outcome.success?
|
57
|
+
assert_equal :invalid, outcome.errors.symbolic[:email]
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should execute custom validate method during run" do
|
61
|
+
outcome = SimpleCommand.run(:name => "JohnLong", :email => "xxxx")
|
62
|
+
|
63
|
+
assert !outcome.success?
|
64
|
+
assert_nil outcome.result
|
65
|
+
assert_equal :invalid, outcome.errors.symbolic[:email]
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should execute custom validate method only if regular validations succeed" do
|
69
|
+
outcome = SimpleCommand.validate(:name => "JohnTooLong", :email => "xxxx")
|
70
|
+
|
71
|
+
assert !outcome.success?
|
72
|
+
assert_equal :max_length, outcome.errors.symbolic[:name]
|
73
|
+
assert_equal nil, outcome.errors.symbolic[:email]
|
74
|
+
end
|
75
|
+
|
53
76
|
it "should merge multiple hashes" do
|
54
77
|
outcome = SimpleCommand.run({:name => "John", :email => "john@gmail.com"}, {:email => "bob@jones.com", :amount => 5})
|
55
78
|
|
@@ -145,6 +168,28 @@ describe "Command" do
|
|
145
168
|
end
|
146
169
|
end
|
147
170
|
|
171
|
+
describe "NestingErrorfulCommand" do
|
172
|
+
class NestingErrorfulCommand < Mutations::Command
|
173
|
+
|
174
|
+
required { string :name }
|
175
|
+
optional { string :email }
|
176
|
+
|
177
|
+
def execute
|
178
|
+
add_error("people.bob", :is_a_bob)
|
179
|
+
|
180
|
+
1
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should let you add errors nested under a namespace" do
|
185
|
+
outcome = NestingErrorfulCommand.run(:name => "John", :email => "john@gmail.com")
|
186
|
+
|
187
|
+
assert !outcome.success?
|
188
|
+
assert_nil outcome.result
|
189
|
+
assert :is_a_bob, outcome.errors[:people].symbolic[:bob]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
148
193
|
describe "MultiErrorCommand" do
|
149
194
|
class ErrorfulCommand < Mutations::Command
|
150
195
|
|
data/spec/hash_filter_spec.rb
CHANGED
@@ -186,4 +186,48 @@ describe "Mutations::HashFilter" do
|
|
186
186
|
end
|
187
187
|
end
|
188
188
|
|
189
|
+
describe "discarding invalid values" do
|
190
|
+
it "should discard invalid optional values" do
|
191
|
+
hf = Mutations::HashFilter.new do
|
192
|
+
required do
|
193
|
+
string :foo
|
194
|
+
end
|
195
|
+
optional do
|
196
|
+
integer :bar, :discard_invalid => true
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
filtered, errors = hf.filter(:foo => "bar", :bar => "baz")
|
201
|
+
assert_equal ({"foo" => "bar"}), filtered
|
202
|
+
assert_equal nil, errors
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should discard invalid optional values for wildcards" do
|
206
|
+
hf = Mutations::HashFilter.new do
|
207
|
+
required do
|
208
|
+
string :foo
|
209
|
+
end
|
210
|
+
optional do
|
211
|
+
integer :*, :discard_invalid => true
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
filtered, errors = hf.filter(:foo => "bar", :bar => "baz", :wat => 1)
|
216
|
+
assert_equal ({"foo" => "bar", "wat" => 1}), filtered
|
217
|
+
assert_equal nil, errors
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
it "should not discard invalid require values" do
|
222
|
+
hf = Mutations::HashFilter.new do
|
223
|
+
required do
|
224
|
+
integer :foo, :discard_invalid => true
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
filtered, errors = hf.filter(:foo => "bar")
|
229
|
+
assert_equal ({"foo" => :integer}), errors.symbolic
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
189
233
|
end
|
data/spec/simple_command.rb
CHANGED
data/spec/string_filter_spec.rb
CHANGED
@@ -135,4 +135,46 @@ describe "Mutations::StringFilter" do
|
|
135
135
|
assert_equal "red", filtered
|
136
136
|
assert_equal nil, errors
|
137
137
|
end
|
138
|
+
|
139
|
+
it "converts symbols to strings" do
|
140
|
+
sf = Mutations::StringFilter.new(:strict => false)
|
141
|
+
filtered, errors = sf.filter(:my_sym)
|
142
|
+
assert_equal "my_sym", filtered
|
143
|
+
assert_equal nil, errors
|
144
|
+
end
|
145
|
+
|
146
|
+
it "converts integers to strings" do
|
147
|
+
sf = Mutations::StringFilter.new(:strict => false)
|
148
|
+
filtered, errors = sf.filter(1)
|
149
|
+
assert_equal "1", filtered
|
150
|
+
assert_equal nil, errors
|
151
|
+
end
|
152
|
+
|
153
|
+
it "converts booleans to strings" do
|
154
|
+
sf = Mutations::StringFilter.new(:strict => false)
|
155
|
+
filtered, errors = sf.filter(true)
|
156
|
+
assert_equal "true", filtered
|
157
|
+
assert_equal nil, errors
|
158
|
+
end
|
159
|
+
|
160
|
+
it "disallows symbols" do
|
161
|
+
sf = Mutations::StringFilter.new(:strict => true)
|
162
|
+
filtered, errors = sf.filter(:my_sym)
|
163
|
+
assert_equal :my_sym, filtered
|
164
|
+
assert_equal :string, errors
|
165
|
+
end
|
166
|
+
|
167
|
+
it "disallows integers" do
|
168
|
+
sf = Mutations::StringFilter.new(:strict => true)
|
169
|
+
filtered, errors = sf.filter(1)
|
170
|
+
assert_equal 1, filtered
|
171
|
+
assert_equal :string, errors
|
172
|
+
end
|
173
|
+
|
174
|
+
it "disallows booleans" do
|
175
|
+
sf = Mutations::StringFilter.new(:strict => true)
|
176
|
+
filtered, errors = sf.filter(true)
|
177
|
+
assert_equal true, filtered
|
178
|
+
assert_equal :string, errors
|
179
|
+
end
|
138
180
|
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.
|
4
|
+
version: 0.7.0
|
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-
|
12
|
+
date: 2013-12-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -70,7 +70,6 @@ files:
|
|
70
70
|
- .travis.yml
|
71
71
|
- CHANGELOG.md
|
72
72
|
- Gemfile
|
73
|
-
- Gemfile.lock
|
74
73
|
- MIT-LICENSE
|
75
74
|
- README.md
|
76
75
|
- Rakefile
|
data/Gemfile.lock
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
mutations (0.5.12)
|
5
|
-
activesupport
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: http://rubygems.org/
|
9
|
-
specs:
|
10
|
-
activesupport (3.2.8)
|
11
|
-
i18n (~> 0.6)
|
12
|
-
multi_json (~> 1.0)
|
13
|
-
i18n (0.6.1)
|
14
|
-
minitest (4.1.0)
|
15
|
-
multi_json (1.3.6)
|
16
|
-
rake (0.9.2.2)
|
17
|
-
|
18
|
-
PLATFORMS
|
19
|
-
ruby
|
20
|
-
|
21
|
-
DEPENDENCIES
|
22
|
-
activesupport
|
23
|
-
minitest (~> 4.0)
|
24
|
-
mutations!
|
25
|
-
rake
|