form_obj 0.1.0 → 0.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.
data/lib/form_obj.rb CHANGED
@@ -1,433 +1,7 @@
1
+ module FormObj
2
+ end
3
+
1
4
  require "form_obj/version"
2
- require 'form_obj/attribute'
3
- require 'form_obj/array'
5
+ require 'form_obj/form'
6
+ require 'form_obj/mappable'
4
7
  require 'result_obj'
5
- require 'active_model'
6
-
7
- class FormObj
8
- class UnknownAttributeError < RuntimeError; end
9
- class WrongHashAttributeValue < RuntimeError; end
10
- class WrongArrayAttributeValue < RuntimeError; end
11
-
12
- extend ::ActiveModel::Naming
13
- extend ::ActiveModel::Translation
14
-
15
- include ::ActiveModel::Conversion
16
- include ::ActiveModel::Validations
17
-
18
- private
19
-
20
- class_attribute :_attributes, instance_predicate: false, instance_reader: false, instance_writer: false
21
- self._attributes = []
22
-
23
- public
24
-
25
- attr_accessor :persisted
26
- attr_reader :errors
27
-
28
- class_attribute :primary_key, instance_predicate: false, instance_reader: false, instance_writer: false
29
- self.primary_key = :id
30
-
31
- def self.model_name
32
- @model_name || super
33
- end
34
-
35
- def self.model_primary_key
36
- primary_key_attributes = self._attributes.find { |attr| attr.name == self.primary_key.to_s }.model_attributes
37
- raise 'Primary key could not be mapped to nested model' if primary_key_attributes.size > 1
38
- primary_key_attributes.first
39
- end
40
-
41
- def self.attribute(name, opts = {}, &block)
42
- primary_key = opts.delete(:primary_key)
43
- hash = opts[:hash]
44
- klass = opts.delete(:class)
45
- if !klass && block_given?
46
- klass = Class.new(FormObj, &block)
47
- klass.instance_variable_set(:@model_name, ActiveModel::Name.new(klass, nil, name.to_s))
48
- end
49
- self._attributes += [attr = Attribute.new(name, klass, opts)]
50
-
51
- if klass
52
- klass.primary_key = primary_key if primary_key
53
-
54
- if opts[:array]
55
- define_method name do
56
- instance_variable_get("@#{name}") || instance_variable_set("@#{name}", FormObj::Array.new(klass, hash: hash, model_class: attr.model_class.last))
57
- end
58
- define_method "#{name}=" do |val|
59
- unless val.class == FormObj::Array
60
- raise ArgumentError.new(":#{name} attribute value should be of class #{self.class.name}::Array while attempt to assign value of class #{val.class.name}")
61
- end
62
- unless val.item_class == klass
63
- raise ArgumentError.new(":#{name} attribute value should be a form array with items of class #{klass.name} attempt to assign a form array with items of class #{val.item_class.name}")
64
- end
65
-
66
- @persisted = false
67
- instance_variable_set("@#{name}", val)
68
- end
69
-
70
- else
71
- define_method name do
72
- instance_variable_get("@#{name}") || instance_variable_set("@#{name}", klass.new({}, hash: hash))
73
- end
74
- define_method "#{name}=" do |val|
75
- unless val.class == klass
76
- raise ArgumentError.new(":#{name} attribute value should be of class #{klass.name} while attempt to assign value of class #{val.class.name}")
77
- end
78
-
79
- @persisted = false
80
- instance_variable_set("@#{name}", val)
81
- end
82
- end
83
-
84
- else
85
- attr_reader name
86
- define_method "#{name}=" do |val|
87
- @persisted = false
88
- instance_variable_set("@#{name}", val)
89
- end
90
- self.primary_key = name.to_sym if primary_key
91
- end
92
- end
93
-
94
- def initialize(models = {}, opts = { hash: false })
95
- @errors = ActiveModel::Errors.new(self)
96
- @persisted = false
97
- @hash = opts[:hash]
98
- load_from_models(models) if models.present?
99
- end
100
-
101
- def persisted?
102
- @persisted
103
- end
104
-
105
- def primary_key
106
- send(self.class.primary_key)
107
- end
108
-
109
- def primary_key=(val)
110
- send("#{self.class.primary_key}=", val)
111
- end
112
-
113
- def load_from_models(models)
114
- attributes.each { |attribute| load_attribute_from_model(attribute, models) }
115
- @persisted = true
116
- self
117
- end
118
-
119
- def save_to_models(models)
120
- attributes.each { |attribute | save_attribute_to_model(attribute, models) }
121
- @persisted = true
122
- self
123
- end
124
-
125
- def load_from_model(model)
126
- load_from_models(default: model)
127
- end
128
-
129
- def save_to_model(model)
130
- save_to_models(default: model)
131
- end
132
-
133
- def update_attributes(new_attrs, raise_if_not_found: true)
134
- @persisted = false
135
- new_attrs.each_pair do |new_attr, new_val|
136
- attr = attributes.find { |attr| attr.name == new_attr.to_s }
137
- if attr.nil?
138
- raise UnknownAttributeError.new(new_attr) if raise_if_not_found
139
- else
140
- if attr.subform && attr.array?
141
- if new_val.is_a?(Enumerable)
142
- self.send(new_attr).update_attributes(new_val)
143
- else
144
- raise WrongArrayAttributeValue.new("#{new_attr}: #{new_val.inspect}")
145
- end
146
- elsif attr.subform
147
- if new_val.is_a? Hash
148
- self.send(new_attr).update_attributes(new_val)
149
- else
150
- raise WrongHashAttributeValue.new("#{new_attr}: #{new_val.inspect}")
151
- end
152
- else
153
- self.send("#{new_attr}=", new_val)
154
- end
155
- end
156
- end
157
- self
158
- end
159
-
160
- def to_hash
161
- Hash[attributes.map { |attribute| [attribute.name.to_sym, attribute.subform ? send(attribute.name).to_hash : send(attribute.name)] }]
162
- end
163
-
164
- def to_model_hash(model = :default)
165
- export_to_model_hash(model => (hash = {}))
166
- hash
167
- end
168
-
169
- def export_to_model_hash(models)
170
- attributes.each do |attribute|
171
- if attribute.array?
172
- value = []
173
- if models[attribute.model]
174
- val = if attribute.model_attributes.present?
175
- attribute
176
- .model_attributes
177
- .map { |ma| ma.to_s[0] == ':' ? ma[1..-1] : ma }
178
- .reverse
179
- .reduce(value) { |h, k| { k.to_sym => h } }
180
- else
181
- { self: value }
182
- end
183
- models[attribute.model].merge!(val)
184
- end
185
- nested_models = models.merge(default: value)
186
- send(attribute.name).export_to_model_hash(nested_models)
187
-
188
- elsif attribute.subform # && !attribute.array?
189
- value = {}
190
- if models[attribute.model]
191
- if attribute.model_attributes.present?
192
- val = attribute
193
- .model_attributes
194
- .map { |ma| ma.to_s[0] == ':' ? ma[1..-1] : ma }
195
- .reverse
196
- .reduce(value) { |h, k| { k.to_sym => h } }
197
- models[attribute.model].merge!(val)
198
- nested_models = models.merge(default: value)
199
- else
200
- if attribute.model == :default
201
- nested_models = models
202
- else
203
- nested_models = models.merge(default: models[attribute.model])
204
- end
205
- end
206
- else
207
- nested_models = models.merge(default: value)
208
- end
209
- send(attribute.name).export_to_model_hash(nested_models)
210
-
211
- else
212
- if models[attribute.model]
213
- if attribute.model_attributes.present?
214
- value = send(attribute.name)
215
- val = attribute
216
- .model_attributes
217
- .map { |ma| ma.to_s[0] == ':' ? ma[1..-1] : ma }
218
- .reverse
219
- .reduce(value) { |h, k| { k.to_sym => h } }
220
- models[attribute.model].merge!(val)
221
- end
222
- end
223
- end
224
- end
225
- models
226
- end
227
-
228
- def copy_errors_from_model(model)
229
- copy_errors_from_models(default: model)
230
- end
231
-
232
- def copy_errors_from_models(models)
233
- attributes.each do |attribute|
234
- if attribute.subform
235
- else
236
- @errors[attribute.name].push(*read_attribute_errors_from_model(attribute, models[attribute.model]))
237
- end
238
- end
239
- self
240
- end
241
-
242
- private
243
-
244
- def attributes
245
- self.class._attributes.clone
246
- end
247
-
248
- def load_attribute_from_model(attribute, models)
249
- if attribute.subform
250
- if attribute.array?
251
- self.send(attribute.name).clear
252
- if (model_array = if attribute.model_attributes.present?
253
- read_attribute_from_model(attribute, models[attribute.model])
254
- else
255
- models[attribute.model]
256
- end)
257
- model_array.each do |model|
258
- self.send(attribute.name).create.load_from_models(models.merge(default: model))
259
- end
260
- end
261
- else
262
- if attribute.model_attributes.present?
263
- self.send(attribute.name).load_from_models(models.merge(default: read_attribute_from_model(attribute, models[attribute.model])))
264
- else
265
- self.send(attribute.name).load_from_models(models.merge(default: models[attribute.model]))
266
- end
267
- end
268
- else
269
- if attribute.model_attributes.present?
270
- self.send("#{attribute.name}=", read_attribute_from_model(attribute, models[attribute.model]))
271
- end
272
- end
273
- end
274
-
275
- def read_attribute_from_model(attribute, model, create_nested_form_if_nil: false)
276
- m = attribute
277
- .model_attributes[1..-1]
278
- .reduce(
279
- {
280
- index: 0,
281
- model: _read_attribute(
282
- model: model,
283
- model_attr: attribute.model_attributes.first,
284
- hash: @hash,
285
- nested_form_class: create_nested_form_if_nil ? ((attribute.model_class.size == 1 && attribute.array?) ? ::Array : attribute.model_class.first) : nil
286
- )
287
- }
288
- ) { |a, m_attr|
289
- {
290
- index: a[:index] + 1,
291
- model: _read_attribute(
292
- model: a[:model],
293
- model_attr: m_attr,
294
- nested_form_class: create_nested_form_if_nil ? ((attribute.model_class.size == a[:index] + 2 && attribute.array?) ? ::Array : attribute.model_class[a[:index] + 1]) : nil
295
- )
296
- }
297
- }[:model]
298
- end
299
-
300
- def _read_attribute(model_attr:, model:, hash: false, nested_form_class: nil)
301
- return nil if model.nil?
302
-
303
- result = if hash
304
- model[model_attr.to_sym].nil? ? model[model_attr.to_s] : model[model_attr.to_sym]
305
- elsif model_attr.to_s[0] == ':'
306
- model[model_attr[1..-1].to_sym].nil? ? model[model_attr[1..-1].to_s] : model[model_attr[1..-1].to_sym]
307
- else
308
- model.send(model_attr)
309
- end
310
-
311
- if result.nil? && nested_form_class
312
- result = (nested_form_class.is_a?(String) ? nested_form_class.constantize : nested_form_class).try(:new)
313
- if hash
314
- model[model_attr.to_sym] = result
315
- elsif model_attr.to_s[0] == ':'
316
- model[model_attr[1..-1].to_sym] = result
317
- else
318
- model.send("#{model_attr}=", result)
319
- end
320
- end
321
-
322
- result
323
- end
324
-
325
- def read_attribute_errors_from_model(attribute, model)
326
- m = if attribute.model_attributes.size > 1
327
- attribute
328
- .model_attributes[1..-2]
329
- .reduce(
330
- {
331
- index: 0,
332
- model: _read_attribute(
333
- model: model,
334
- model_attr: attribute.model_attributes.first,
335
- hash: @hash,
336
- )
337
- }
338
- ) { |a, m_attr|
339
- {
340
- index: a[:index] + 1,
341
- model: _read_attribute(
342
- model: a[:model],
343
- model_attr: m_attr,
344
- )
345
- }
346
- }[:model]
347
-
348
- elsif attribute.model_attributes.size == 1
349
- model
350
- else
351
- nil
352
- end
353
-
354
- if m.nil?
355
- []
356
- else
357
- _read_attribute_error(model_attr: attribute.model_attributes.last, model: m, hash: (attribute.model_attributes.size == 1) && @hash)
358
- end
359
- end
360
-
361
- def _read_attribute_error(model_attr:, model:, hash: false)
362
- if hash || model_attr.to_s[0] == ':'
363
- []
364
- else
365
- model.errors[model_attr.to_sym]
366
- end
367
- end
368
-
369
- def save_attribute_to_model(attribute, models)
370
- if attribute.subform
371
- if attribute.array?
372
- if attribute.model_attributes.present?
373
- self.send(attribute.name).save_to_models(models.merge(default: read_attribute_from_model(attribute, models[attribute.model], create_nested_form_if_nil: true)))
374
- else
375
- self.send(attribute.name).save_to_models(models.merge(default: models[attribute.model]))
376
- end
377
- else
378
- if attribute.model_attributes.present?
379
- self.send(attribute.name).save_to_models(models.merge(default: read_attribute_from_model(attribute, models[attribute.model], create_nested_form_if_nil: true)))
380
- else
381
- self.send(attribute.name).save_to_models(models.merge(default: models[attribute.model]))
382
- end
383
- end
384
- else
385
- if attribute.model_attributes.present?
386
- write_attribute_to_model(attribute, models[attribute.model], self.send(attribute.name))
387
- end
388
- end
389
- end
390
-
391
- def write_attribute_to_model(attribute, model, value)
392
- path = attribute.model_attributes[0..-2]
393
- model_attr = attribute.model_attributes.last
394
-
395
- if path.present?
396
- _write_attribute(
397
- model: path[1..-1]
398
- .reduce({
399
- index: 0,
400
- model: _read_attribute(
401
- model: model,
402
- model_attr: attribute.model_attributes.first,
403
- hash: @hash,
404
- nested_form_class: attribute.model_class.first
405
- )
406
- }) { |a, m_attr|
407
- {
408
- index: a[:index] + 1,
409
- model: _read_attribute(
410
- model: a[:model],
411
- model_attr: m_attr,
412
- nested_form_class: attribute.model_class[a[:index] + 1]
413
- )
414
- }
415
- }[:model],
416
- model_attr: model_attr,
417
- value: value
418
- )
419
- else
420
- _write_attribute(model: model, model_attr: model_attr, hash: @hash, value: value)
421
- end
422
- end
423
-
424
- def _write_attribute(model_attr:, model:, hash: false, value:)
425
- if hash
426
- model[(model.key?(model_attr.to_s) && !model.key?(model_attr.to_sym)) ? model_attr.to_s : model_attr.to_sym] = value
427
- elsif model_attr.to_s[0] == ':'
428
- model[(model.key?(model_attr[1..-1].to_s) && !model.key?(model_attr[1..-1].to_sym)) ? model_attr[1..-1].to_s : model_attr[1..-1].to_sym] = value
429
- else
430
- model.send("#{model_attr}=", value)
431
- end
432
- end
433
- end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: form_obj
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Koltun
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-04-12 00:00:00.000000000 Z
11
+ date: 2018-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: typed_array
14
+ name: tree_struct
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.0.0
19
+ version: 1.0.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.0.0
26
+ version: 1.0.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activemodel
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -120,6 +134,14 @@ files:
120
134
  - lib/form_obj.rb
121
135
  - lib/form_obj/array.rb
122
136
  - lib/form_obj/attribute.rb
137
+ - lib/form_obj/attributes.rb
138
+ - lib/form_obj/form.rb
139
+ - lib/form_obj/mappable.rb
140
+ - lib/form_obj/mappable/array.rb
141
+ - lib/form_obj/mappable/attribute.rb
142
+ - lib/form_obj/mappable/model_attribute.rb
143
+ - lib/form_obj/mappable/model_attribute/item.rb
144
+ - lib/form_obj/mappable/model_primary_key.rb
123
145
  - lib/form_obj/version.rb
124
146
  - lib/result_obj.rb
125
147
  homepage: https://github.com/akoltun/form_obj