simple_params 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,18 +4,15 @@ module SimpleParams
4
4
  class Errors < ActiveModel::Errors
5
5
  attr_reader :base
6
6
 
7
- def initialize(base, nested_hash_errors = {}, nested_array_errors = {})
7
+ def initialize(base, nested_classes = {})
8
8
  super(base)
9
9
  @base = base
10
- @nested_hash_errors = symbolize_nested(nested_hash_errors)
11
- @nested_array_errors = symbolize_nested(nested_array_errors)
10
+ @nested_classes = symbolize_nested(nested_classes)
12
11
  end
13
12
 
14
13
  def [](attribute)
15
- if is_a_nested_hash_error_attribute?(attribute)
16
- set(attribute.to_sym, @nested_hash_errors[attribute.to_sym])
17
- elsif is_a_nested_array_error_attribute?(attribute)
18
- set(attribute.to_sym, @nested_array_errors[attribute.to_sym])
14
+ if nested_attribute?(attribute)
15
+ set_nested(attribute)
19
16
  else
20
17
  get(attribute.to_sym) || set(attribute.to_sym, [])
21
18
  end
@@ -37,18 +34,22 @@ module SimpleParams
37
34
 
38
35
  def clear
39
36
  super
40
- @nested_hash_errors.map { |attribute, errors| errors.clear }
37
+ @nested_classes.map do |attribute, klass|
38
+ run_or_mapped_run(klass) { |k| k.errors.clear }
39
+ end
41
40
  end
42
41
 
43
42
  def empty?
44
43
  super &&
45
- @nested_hash_errors.all? { |attribute, errors| errors.empty? }
44
+ @nested_classes.all? do |attribute, klass|
45
+ run_or_mapped_run(klass) { |k| k.errors.empty? }
46
+ end
46
47
  end
47
48
  alias_method :blank?, :empty?
48
49
 
49
50
  def include?(attribute)
50
- if is_a_nested_hash_error_attribute?(attribute)
51
- !@nested_hash_errors[attribute.to_sym].empty?
51
+ if nested_attribute?(attribute)
52
+ !nested_class(attribute).errors.empty?
52
53
  else
53
54
  messages[attribute].present?
54
55
  end
@@ -58,39 +59,33 @@ module SimpleParams
58
59
 
59
60
  def values
60
61
  messages.values +
61
- @nested_hash_errors.map do |attribute, errors|
62
- errors.values
62
+ @nested_classes.map do |key, klass|
63
+ run_or_mapped_run(klass) { |k| k.errors.values }
63
64
  end
64
65
  end
65
66
 
66
67
  def full_messages
67
68
  parent_messages = map { |attribute, message| full_message(attribute, message) }
68
- nested_messages = @nested_hash_errors.map do |attribute, errors|
69
- unless errors.full_messages.nil?
70
- errors.full_messages.map { |message| "#{attribute} " + message }
69
+ nested_messages = @nested_classes.map do |attribute, klass|
70
+ run_or_mapped_run(klass) do |k|
71
+ unless k.errors.full_messages.nil?
72
+ k.errors.full_messages.map { |message| "#{attribute} " + message }
73
+ end
71
74
  end
72
75
  end
73
76
  (parent_messages + nested_messages).flatten
74
77
  end
75
78
 
76
79
  def to_hash(full_messages = false)
77
- messages = if full_messages
78
- msgs = {}
79
- self.messages.each do |attribute, array|
80
- msgs[attribute] = array.map { |message| full_message(attribute, message) }
81
- end
82
- msgs
83
- else
84
- self.messages.dup
85
- end
80
+ msgs = get_messages(self, full_messages)
86
81
 
87
- @nested_hash_errors.map do |attribute, errors|
88
- error_messages = nested_error_messages(attribute, full_messages)
89
- unless errors.empty?
90
- messages.merge!(attribute.to_sym => error_messages)
82
+ @nested_classes.map do |attribute, klass|
83
+ nested_msgs = run_or_mapped_run(klass) { |k| get_messages(k.errors, full_messages) }
84
+ unless empty_messages?(nested_msgs)
85
+ msgs.merge!(attribute.to_sym => nested_msgs)
91
86
  end
92
87
  end
93
- messages
88
+ msgs
94
89
  end
95
90
 
96
91
  def to_s(full_messages = false)
@@ -99,35 +94,48 @@ module SimpleParams
99
94
  end
100
95
 
101
96
  private
102
- def nested_error_messages(attribute, full_messages = false)
103
- if is_a_nested_hash_error_attribute?(attribute)
104
- errors = @nested_hash_errors[attribute.to_sym]
105
- if full_messages
106
- errors.messages.each_with_object({}) do |(attr, array), messages|
107
- messages[attr] = array.map { |message| errors.full_message(attr, message) }
108
- end
109
- else
110
- errors.messages.dup
111
- end
112
- else
113
- {}
114
- end
97
+ def nested_class(key)
98
+ @nested_classes[key.to_sym]
99
+ end
100
+
101
+ def nested_attribute?(attribute)
102
+ @nested_classes.keys.include?(attribute.to_sym)
103
+ end
104
+
105
+ def set_nested(attribute)
106
+ klass = nested_class(attribute)
107
+ errors = run_or_mapped_run(klass) { |k| k.errors }
108
+ set(attribute.to_sym, errors)
115
109
  end
116
110
 
117
111
  def add_error_to_attribute(attribute, error)
118
- if is_a_nested_hash_error_attribute?(attribute)
119
- @nested_hash_errors[attribute.to_sym][:base] = error
112
+ if nested_attribute?(attribute)
113
+ @nested_classes[attribute].errors.add(:base, error)
120
114
  else
121
115
  self[attribute] << error
122
116
  end
123
117
  end
124
118
 
125
- def is_a_nested_hash_error_attribute?(attribute)
126
- @nested_hash_errors.keys.include?(attribute.to_sym)
119
+ def get_messages(object, full_messages = false)
120
+ if full_messages
121
+ object.messages.each_with_object({}) do |(attribute, array), messages|
122
+ messages[attribute] = array.map { |message| object.full_message(attribute, message) }
123
+ end
124
+ else
125
+ object.messages.dup
126
+ end
127
+ end
128
+
129
+ def empty_messages?(msgs)
130
+ msgs.nil? || msgs.empty? || (msgs.is_a?(Array) && msgs.all?(&:empty?))
127
131
  end
128
132
 
129
- def is_a_nested_array_error_attribute?(attribute)
130
- @nested_array_errors.keys.include?(attribute.to_sym)
133
+ def run_or_mapped_run(object, &block)
134
+ if object.is_a?(Array)
135
+ object.map { |obj| yield obj }
136
+ else
137
+ yield object
138
+ end
131
139
  end
132
140
 
133
141
  def symbolize_nested(nested)
@@ -0,0 +1,73 @@
1
+ module SimpleParams
2
+ class NestedParams < Params
3
+ class << self
4
+ def type
5
+ options[:type]
6
+ end
7
+
8
+ def array?
9
+ type.to_sym == :array
10
+ end
11
+
12
+ def hash?
13
+ type.to_sym == :hash
14
+ end
15
+
16
+ def with_ids?
17
+ !!options[:with_ids]
18
+ end
19
+
20
+ def define_new_hash_class(parent, name, options, &block)
21
+ options = options.merge(type: :hash)
22
+ define_new_class(parent, name, options, &block)
23
+ end
24
+
25
+ def define_new_array_class(parent, name, options, &block)
26
+ options = options.merge(type: :array)
27
+ define_new_class(parent, name, options, &block)
28
+ end
29
+
30
+ private
31
+ def define_new_class(parent, name, options, &block)
32
+ klass_name = name.to_s.split('_').collect(&:capitalize).join
33
+ Class.new(self).tap do |klass|
34
+ parent.const_set(klass_name, klass)
35
+ extend ActiveModel::Naming
36
+ klass.class_eval(&block)
37
+ klass.class_eval("self.options = #{options}")
38
+ end
39
+ end
40
+ end
41
+
42
+ def initialize(params={}, parent = nil)
43
+ @parent = parent
44
+ super(params)
45
+ end
46
+
47
+ def id
48
+ @id
49
+ end
50
+
51
+ def set_accessors(params={})
52
+ if class_has_ids?
53
+ @id = params.keys.first
54
+ params = params.values.first
55
+ end
56
+
57
+ super(params)
58
+ end
59
+
60
+ private
61
+ def class_has_ids?
62
+ self.class.with_ids?
63
+ end
64
+
65
+ def hash_class?
66
+ self.class.hash?
67
+ end
68
+
69
+ def array_class?
70
+ self.class.array?
71
+ end
72
+ end
73
+ end
@@ -7,30 +7,15 @@ module SimpleParams
7
7
  include ActiveModel::Validations
8
8
  extend ActiveModel::Naming
9
9
  include SimpleParams::Validations
10
-
11
- TYPES = [
12
- :integer,
13
- :string,
14
- :decimal,
15
- :datetime,
16
- :date,
17
- :time,
18
- :float,
19
- :boolean,
20
- :array,
21
- :hash,
22
- :object
23
- ]
10
+ include SimpleParams::HasAttributes
11
+ include SimpleParams::HasTypedParams
12
+ include SimpleParams::HashHelpers
13
+ include SimpleParams::DateTimeHelpers
14
+ include SimpleParams::RailsHelpers
15
+ include SimpleParams::StrictParams
24
16
 
25
17
  class << self
26
-
27
- TYPES.each do |sym|
28
- define_method("#{sym}_param") do |name, opts={}|
29
- param(name, opts.merge(type: sym))
30
- end
31
- end
32
-
33
- attr_accessor :strict_enforcement, :options
18
+ attr_accessor :options
34
19
 
35
20
  def model_name
36
21
  ActiveModel::Name.new(self)
@@ -40,200 +25,85 @@ module SimpleParams
40
25
  SimpleParams::ApiPieDoc.new(self).build
41
26
  end
42
27
 
43
- def strict
44
- @strict_enforcement = true
45
- end
46
-
47
- def allow_undefined_params
48
- @strict_enforcement = false
49
- end
50
-
51
28
  def param(name, opts={})
52
29
  define_attribute(name, opts)
53
30
  add_validations(name, opts)
54
31
  end
55
32
 
56
- def nested_hash(name, opts={}, &block)
57
- attr_accessor name
58
- nested_class = define_nested_class(name, opts, &block)
59
- @nested_hashes ||= {}
60
- @nested_hashes[name.to_sym] = nested_class
33
+ def nested_classes
34
+ @nested_classes ||= {}
61
35
  end
62
- alias_method :nested_param, :nested_hash
63
- alias_method :nested, :nested_hash
64
36
 
65
37
  def nested_hashes
66
- @nested_hashes ||= {}
38
+ nested_classes.select { |key, klass| klass.hash? }
67
39
  end
68
40
 
69
- def nested_array(name, opts={}, &block)
70
- attr_accessor name
71
- nested_array_class = define_nested_class(name, opts, &block)
72
- @nested_arrays ||= {}
73
- @nested_arrays[name.to_sym] = nested_array_class
41
+ def nested_arrays
42
+ nested_classes.select { |key, klass| klass.array? }
74
43
  end
75
44
 
76
- def nested_arrays
77
- @nested_arrays ||= {}
45
+ def nested_hash(name, opts={}, &block)
46
+ klass = NestedParams.define_new_hash_class(self, name, opts, &block)
47
+ add_nested_class(name, klass)
78
48
  end
49
+ alias_method :nested_param, :nested_hash
50
+ alias_method :nested, :nested_hash
79
51
 
80
- def defined_attributes
81
- @define_attributes ||= {}
52
+ def nested_array(name, opts={}, &block)
53
+ klass = NestedParams.define_new_array_class(self, name, opts, &block)
54
+ add_nested_class(name, klass)
82
55
  end
83
56
 
84
57
  private
85
- def define_attribute(name, opts = {})
86
- opts[:type] ||= :string
87
- defined_attributes[name.to_sym] = opts
88
- attr_accessor "#{name}_attribute"
89
-
90
- define_method("#{name}") do
91
- attribute = send("#{name}_attribute")
92
- attribute.send("value")
93
- end
94
-
95
- define_method("raw_#{name}") do
96
- attribute = send("#{name}_attribute")
97
- attribute.send("raw_value")
98
- end
99
-
100
- define_method("#{name}=") do |val|
101
- attribute = send("#{name}_attribute")
102
- attribute.send("value=", val)
103
- end
104
-
105
- if [Date, 'Date', :date].include?(opts[:type])
106
- define_date_helper_methods(name)
107
- elsif [DateTime, 'DateTime', :datetime, Time, 'Time', :time].include?(opts[:type])
108
- define_datetime_helper_methods(name)
109
- end
58
+ def add_nested_class(name, klass)
59
+ @nested_classes ||= {}
60
+ @nested_classes[name.to_sym] = klass
61
+ define_nested_accessor(name, klass)
62
+ define_rails_helpers(name, klass)
110
63
  end
111
64
 
112
- def add_validations(name, opts = {})
113
- validations = opts[:validations] || {}
114
- has_default = opts.has_key?(:default) # checking has_key? because :default may be nil
115
- optional = opts[:optional]
116
- if !validations.empty?
117
- if optional || has_default
118
- validations.merge!(allow_nil: true)
65
+ def define_nested_accessor(name, klass)
66
+ define_method("#{name}") do
67
+ if instance_variable_defined?("@#{name}")
68
+ instance_variable_get("@#{name}")
119
69
  else
120
- validations.merge!(presence: true)
70
+ init_value = klass.hash? ? klass.new({}, self) : []
71
+ instance_variable_set("@#{name}", init_value)
121
72
  end
122
- else
123
- if !optional && !has_default
124
- validations.merge!(presence: true)
125
- end
126
- end
127
- validates name, validations unless validations.empty?
128
- end
129
-
130
- def define_nested_class(name, options, &block)
131
- klass_name = name.to_s.split('_').collect(&:capitalize).join
132
- Class.new(Params).tap do |klass|
133
- self.const_set(klass_name, klass)
134
- extend ActiveModel::Naming
135
- klass.class_eval(&block)
136
- klass.class_eval("self.options = #{options}")
137
- end
138
- end
139
-
140
- def define_date_helper_methods(name)
141
- define_method("#{name}(3i)=") do |day|
142
- attribute = send("#{name}_attribute")
143
- value = attribute.send("value") || Date.today
144
- attribute.send("value=", Date.new(value.year, value.month, day.to_i))
145
- end
146
-
147
- define_method("#{name}(2i)=") do |month|
148
- attribute = send("#{name}_attribute")
149
- value = attribute.send("value") || Date.today
150
- attribute.send("value=", Date.new(value.year, month.to_i, value.day))
151
- end
152
-
153
- define_method("#{name}(1i)=") do |year|
154
- attribute = send("#{name}_attribute")
155
- value = attribute.send("value") || Date.today
156
- attribute.send("value=", Date.new(year.to_i, value.month, value.day))
157
- end
158
- end
159
-
160
- def define_datetime_helper_methods(name)
161
- define_method("#{name}(6i)=") do |sec|
162
- attribute = send("#{name}_attribute")
163
- value = attribute.send("value") || Time.now.utc
164
- attribute.send("value=", Time.new(value.year, value.month, value.day, value.hour, value.min, sec.to_i, value.utc_offset))
165
- end
166
-
167
- define_method("#{name}(5i)=") do |minute|
168
- attribute = send("#{name}_attribute")
169
- value = attribute.send("value") || Time.now.utc
170
- attribute.send("value=", Time.new(value.year, value.month, value.day, value.hour, minute.to_i, value.sec, value.utc_offset))
171
- end
172
-
173
- define_method("#{name}(4i)=") do |hour|
174
- attribute = send("#{name}_attribute")
175
- value = attribute.send("value") || Time.now.utc
176
- attribute.send("value=", Time.new(value.year, value.month, value.day, hour.to_i, value.min, value.sec, value.utc_offset))
177
- end
178
-
179
- define_method("#{name}(3i)=") do |day|
180
- attribute = send("#{name}_attribute")
181
- value = attribute.send("value") || Time.now.utc
182
- attribute.send("value=", Time.new(value.year, value.month, day.to_i, value.hour, value.min, value.sec, value.utc_offset))
183
73
  end
184
74
 
185
- define_method("#{name}(2i)=") do |month|
186
- attribute = send("#{name}_attribute")
187
- value = attribute.send("value") || Time.now.utc
188
- attribute.send("value=", Time.new(value.year, month.to_i, value.day, value.hour, value.min, value.sec, value.utc_offset))
189
- end
190
-
191
- define_method("#{name}(1i)=") do |year|
192
- attribute = send("#{name}_attribute")
193
- value = attribute.send("value") || Time.now.utc
194
- attribute.send("value=", Time.new(year.to_i, value.month, value.day, value.hour, value.min, value.sec, value.utc_offset))
75
+ define_method("#{name}=") do |initializer|
76
+ init_value = if initializer.is_a?(Array)
77
+ if klass.with_ids?
78
+ initializer.first.each_pair.inject([]) do |array, (key, val)|
79
+ array << klass.new({key => val}, self)
80
+ end
81
+ else
82
+ initializer.map { |val| klass.new(val, self) }
83
+ end
84
+ else
85
+ klass.new(initializer, self)
86
+ end
87
+ instance_variable_set("@#{name}", init_value)
195
88
  end
196
89
  end
197
90
  end
198
91
 
199
- def initialize(params={}, parent = nil)
200
- # Set default strict params
201
- if self.class.strict_enforcement.nil?
202
- self.class.strict_enforcement = true
203
- end
92
+ attr_accessor :original_params
93
+ alias_method :original_hash, :original_params
94
+ alias_method :raw_params, :original_params
95
+
96
+ def initialize(params={})
97
+ set_strictness
204
98
 
205
- @parent = parent
206
- # Initializing Params
207
99
  @original_params = hash_to_symbolized_hash(params)
208
100
  define_attributes(@original_params)
209
101
 
210
- # Nested Hashes
211
- @nested_params = nested_hashes.keys
212
-
213
- # Nested Arrays
214
- @nested_arrays = nested_arrays.keys
215
-
216
102
  # Nested Classes
217
- set_accessors(params)
218
- initialize_nested_classes
219
- initialize_nested_array_classes
220
- end
103
+ @nested_classes = nested_classes.keys
221
104
 
222
- def define_attributes(params)
223
- self.class.defined_attributes.each_pair do |key, opts|
224
- send("#{key}_attribute=", Attribute.new(self, key, opts))
225
- end
226
- end
227
-
228
- def attributes
229
- (defined_attributes.keys + nested_hashes.keys + nested_arrays.keys).flatten
230
- end
231
-
232
- def original_params
233
- @original_params ||= {}
105
+ set_accessors(params)
234
106
  end
235
- alias_method :original_hash, :original_params
236
- alias_method :raw_params, :original_params
237
107
 
238
108
  def to_hash
239
109
  hash = {}
@@ -256,115 +126,27 @@ module SimpleParams
256
126
  end
257
127
 
258
128
  def errors
259
- nested_errors_hash = {}
260
- @nested_params.each do |param|
261
- nested_errors_hash[param.to_sym] = send(param).errors
262
- end
263
-
264
- nested_arrays_hash = {}
265
- @nested_arrays.each do |array|
266
- nested_arrays_hash[array.to_sym] = send(array).map(&:errors)
267
- end
268
-
269
- @errors ||= SimpleParams::Errors.new(self, nested_errors_hash, nested_arrays_hash)
270
- end
271
-
272
- # Overriding this method to allow for non-strict enforcement!
273
- def method_missing(method_name, *arguments, &block)
274
- if strict_enforcement?
275
- raise SimpleParamsError, "parameter #{method_name} is not defined."
276
- else
277
- if @original_params.include?(method_name.to_sym)
278
- value = @original_params[method_name.to_sym]
279
- if value.is_a?(Hash)
280
- define_anonymous_class(method_name, value)
281
- else
282
- Attribute.new(self, method_name).value = value
283
- end
284
- end
129
+ nested_class_hash = {}
130
+ @nested_classes.each do |param|
131
+ nested_class_hash[param.to_sym] = send(param)
285
132
  end
286
- end
287
133
 
288
- def respond_to?(method_name, include_private = false)
289
- if strict_enforcement?
290
- super
291
- else
292
- @original_params.include?(method_name.to_sym) || super
293
- end
134
+ @errors ||= SimpleParams::Errors.new(self, nested_class_hash)
294
135
  end
295
136
 
296
137
  private
297
- def strict_enforcement?
298
- self.class.strict_enforcement
299
- end
300
-
301
138
  def set_accessors(params={})
302
139
  params.each do |attribute_name, value|
303
- # Don't set accessors for nested classes
304
- unless value.is_a?(Hash)
305
- send("#{attribute_name}=", value)
306
- end
140
+ send("#{attribute_name}=", value)
307
141
  end
308
142
  end
309
143
 
310
- def hash_to_symbolized_hash(hash)
311
- hash.inject({}){|result, (key, value)|
312
- new_key = case key
313
- when String then key.to_sym
314
- else key
315
- end
316
- new_value = case value
317
- when Hash then hash_to_symbolized_hash(value)
318
- else value
319
- end
320
- result[new_key] = new_value
321
- result
322
- }
323
- end
324
-
325
144
  def defined_attributes
326
145
  self.class.defined_attributes
327
146
  end
328
147
 
329
- def nested_hashes
330
- self.class.nested_hashes
331
- end
332
-
333
- def nested_arrays
334
- self.class.nested_arrays
335
- end
336
-
337
- def initialize_nested_classes
338
- nested_hashes.each do |key, klass|
339
- initialization_params = @original_params[key.to_sym] || {}
340
- send("#{key}=", klass.new(initialization_params, self))
341
- end
342
- end
343
-
344
- def initialize_nested_array_classes
345
- nested_arrays.each do |key, klass|
346
- initialization_params = @original_params[key.to_sym] || []
347
- initialization_array = []
348
- initialization_params.each do |initialization_param|
349
- initialization_array << klass.new(initialization_param, self)
350
- end
351
- send("#{key}=", initialization_array)
352
- end
353
- end
354
-
355
- def define_anonymous_class(name, hash)
356
- klass_name = name.to_s.split('_').collect(&:capitalize).join
357
- anonymous_klass = Class.new(Params).tap do |klass|
358
- if self.class.const_defined?(klass_name)
359
- begin
360
- self.class.send(:remove_const, klass_name)
361
- rescue NameError
362
- end
363
- end
364
- self.class.const_set(klass_name, klass)
365
- end
366
- anonymous_klass.allow_undefined_params
367
- anonymous_klass.new(hash)
148
+ def nested_classes
149
+ self.class.nested_classes
368
150
  end
369
151
  end
370
152
  end