simple_params 1.1.2 → 1.2.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.
@@ -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