reform 1.2.6 → 2.0.0.beta1

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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -1
  3. data/CHANGES.md +14 -0
  4. data/Gemfile +3 -2
  5. data/README.md +225 -283
  6. data/Rakefile +27 -0
  7. data/TODO.md +12 -0
  8. data/database.sqlite3 +0 -0
  9. data/gemfiles/Gemfile.rails-3.0 +1 -0
  10. data/gemfiles/Gemfile.rails-3.1 +1 -0
  11. data/gemfiles/Gemfile.rails-3.2 +1 -0
  12. data/gemfiles/Gemfile.rails-4.0 +1 -0
  13. data/lib/reform.rb +0 -1
  14. data/lib/reform/contract.rb +64 -170
  15. data/lib/reform/contract/validate.rb +10 -13
  16. data/lib/reform/form.rb +74 -19
  17. data/lib/reform/form/active_model.rb +19 -14
  18. data/lib/reform/form/coercion.rb +1 -13
  19. data/lib/reform/form/composition.rb +2 -24
  20. data/lib/reform/form/multi_parameter_attributes.rb +43 -62
  21. data/lib/reform/form/populator.rb +85 -0
  22. data/lib/reform/form/prepopulate.rb +13 -43
  23. data/lib/reform/form/validate.rb +29 -90
  24. data/lib/reform/form/validation/unique_validator.rb +13 -0
  25. data/lib/reform/version.rb +1 -1
  26. data/reform.gemspec +7 -7
  27. data/test/active_model_test.rb +43 -0
  28. data/test/changed_test.rb +23 -51
  29. data/test/coercion_test.rb +1 -7
  30. data/test/composition_test.rb +128 -34
  31. data/test/contract_test.rb +27 -86
  32. data/test/feature_test.rb +43 -6
  33. data/test/fields_test.rb +2 -12
  34. data/test/form_builder_test.rb +28 -25
  35. data/test/form_option_test.rb +19 -0
  36. data/test/from_test.rb +0 -75
  37. data/test/inherit_test.rb +178 -117
  38. data/test/model_reflections_test.rb +1 -1
  39. data/test/populate_test.rb +226 -0
  40. data/test/prepopulator_test.rb +112 -0
  41. data/test/readable_test.rb +2 -4
  42. data/test/save_test.rb +56 -112
  43. data/test/setup_test.rb +48 -0
  44. data/test/skip_if_test.rb +5 -2
  45. data/test/skip_setter_and_getter_test.rb +54 -0
  46. data/test/test_helper.rb +3 -1
  47. data/test/uniqueness_test.rb +41 -0
  48. data/test/validate_test.rb +325 -289
  49. data/test/virtual_test.rb +1 -3
  50. data/test/writeable_test.rb +3 -4
  51. metadata +35 -39
  52. data/lib/reform/composition.rb +0 -63
  53. data/lib/reform/contract/setup.rb +0 -50
  54. data/lib/reform/form/changed.rb +0 -9
  55. data/lib/reform/form/sync.rb +0 -116
  56. data/lib/reform/representer.rb +0 -84
  57. data/test/empty_test.rb +0 -58
  58. data/test/form_composition_test.rb +0 -145
  59. data/test/nested_form_test.rb +0 -197
  60. data/test/prepopulate_test.rb +0 -85
  61. data/test/sync_option_test.rb +0 -83
  62. data/test/sync_test.rb +0 -56
@@ -3,18 +3,16 @@ require 'reform/form/active_model/model_validations'
3
3
  module Reform::Form::ActiveModel
4
4
  module FormBuilderMethods # TODO: rename to FormBuilderCompat.
5
5
  def self.included(base)
6
- base.class_eval do
7
- extend ClassMethods # ::model_name
8
- register_feature FormBuilderMethods
9
- end
6
+ base.extend ClassMethods # ::model_name
10
7
  end
11
8
 
12
9
  module ClassMethods
13
- private
10
+ private
14
11
 
12
+ # TODO: add that shit in Form#present, not by overriding ::property.
15
13
  def property(name, options={}, &block)
16
14
  super.tap do |definition|
17
- add_nested_attribute_compat(name) if definition[:form] # TODO: fix that in Rails FB#1832 work.
15
+ add_nested_attribute_compat(name) if definition[:twin] # TODO: fix that in Rails FB#1832 work.
18
16
  end
19
17
  end
20
18
 
@@ -25,11 +23,11 @@ module Reform::Form::ActiveModel
25
23
  end
26
24
 
27
25
  # Modify the incoming Rails params hash to be representable compliant.
28
- def update!(params)
29
- return super unless params.is_a?(Hash)
30
- # TODO: run this only for hash deserialization, but generically (#deserialize_hash ?).
31
-
32
- self.class.representer { |dfn| rename_nested_param_for!(params, dfn) }
26
+ def deserialize!(params)
27
+ # this only happens in a Hash environment. other engines have to overwrite this method.
28
+ schema.each do |dfn|
29
+ rename_nested_param_for!(params, dfn)
30
+ end
33
31
 
34
32
  super
35
33
  end
@@ -75,6 +73,11 @@ module Reform::Form::ActiveModel
75
73
  #
76
74
  # class CoverSongForm < Reform::Form
77
75
  # model :song
76
+ #
77
+ # or we can setup a isolated namespace model ( which defined in isolated rails egine )
78
+ #
79
+ # class CoverSongForm < Reform::Form
80
+ # model "api/v1/song", namespace: "api"
78
81
  def model(main_model, options={})
79
82
  self.model_options = [main_model, options]
80
83
  end
@@ -82,17 +85,19 @@ module Reform::Form::ActiveModel
82
85
  def model_name
83
86
  if model_options
84
87
  form_name = model_options.first.to_s.camelize
88
+ namespace = model_options.last[:namespace].present? ? model_options.last[:namespace].to_s.camelize.constantize : nil
85
89
  else
86
90
  form_name = name.sub(/(::)?Form$/, "") # Song::Form => "Song"
91
+ namespace = nil
87
92
  end
88
93
 
89
- active_model_name_for(form_name)
94
+ active_model_name_for(form_name, namespace)
90
95
  end
91
96
 
92
97
  private
93
- def active_model_name_for(string)
98
+ def active_model_name_for(string, namespace=nil)
94
99
  return ::ActiveModel::Name.new(OpenStruct.new(:name => string)) if Reform.rails3_0?
95
- ::ActiveModel::Name.new(self, nil, string)
100
+ ::ActiveModel::Name.new(self, namespace, string)
96
101
  end
97
102
  end
98
103
  end
@@ -1,17 +1,5 @@
1
1
  require 'representable/coercion'
2
2
 
3
+ # TODO: make optional.
3
4
  module Reform::Form::Coercion
4
- def self.included(base)
5
- base.extend(ClassMethods)
6
- base.send(:register_feature, self)
7
- end
8
-
9
- module ClassMethods
10
- def representer_class # TODO: check out how we can utilise Config#features.
11
- super.class_eval do
12
- include Representable::Coercion
13
- self
14
- end
15
- end
16
- end
17
5
  end
@@ -1,4 +1,5 @@
1
1
  require "reform/form/active_model"
2
+ require "disposable/twin/composition"
2
3
 
3
4
  module Reform::Form::Composition
4
5
  # Automatically creates a Composition object for you when initializing the form.
@@ -6,16 +7,11 @@ module Reform::Form::Composition
6
7
  base.class_eval do
7
8
  extend Reform::Form::ActiveModel::ClassMethods # ::model.
8
9
  extend ClassMethods
10
+ include Disposable::Twin::Composition
9
11
  end
10
12
  end
11
13
 
12
14
  module ClassMethods
13
- #include Reform::Form::ActiveModel::ClassMethods # ::model.
14
-
15
- def model_class # DISCUSS: needed?
16
- @model_class ||= Reform::Composition.from(representer_class)
17
- end
18
-
19
15
  # Same as ActiveModel::model but allows you to define the main model in the composition
20
16
  # using +:on+.
21
17
  #
@@ -36,22 +32,4 @@ module Reform::Form::Composition
36
32
  self
37
33
  end
38
34
  end
39
-
40
- def initialize(models)
41
- composition = self.class.model_class.new(models)
42
- super(composition)
43
- end
44
-
45
- def to_nested_hash
46
- model.nested_hash_for(to_hash) # use composition to compute nested hash.
47
- end
48
-
49
- def to_hash(*args)
50
- mapper.new(fields).to_hash(*args) # do not map names, yet. this happens in #to_nested_hash
51
- end
52
-
53
- private
54
- def aliased_model # we don't need an Expose as we save the Composition instance in the constructor.
55
- model
56
- end
57
35
  end
@@ -1,72 +1,53 @@
1
- Reform::Form.class_eval do
2
- # TODO: this must be implemented with a parse_filter that is only applied to type: Time or Datetime properties.
3
- # we then simply add the converted attribute to params.
4
- module MultiParameterAttributes
5
- # TODO: implement this with parse_filter, so we don't have to manually walk through the hash, etc.
6
- def self.included(base)
7
- base.send(:register_feature, self)
8
- end
9
-
10
- class DateTimeParamsFilter
11
- def call(params)
12
- params = params.dup # DISCUSS: not sure if that slows down form processing?
13
- date_attributes = {}
14
-
15
- params.each do |attribute, value|
16
- if value.is_a?(Hash)
17
- params[attribute] = call(value) # TODO: #validate should only handle local form params.
18
- elsif matches = attribute.match(/^(\w+)\(.i\)$/)
19
- date_attribute = matches[1]
20
- date_attributes[date_attribute] = params_to_date(
21
- params.delete("#{date_attribute}(1i)"),
22
- params.delete("#{date_attribute}(2i)"),
23
- params.delete("#{date_attribute}(3i)"),
24
- params.delete("#{date_attribute}(4i)"),
25
- params.delete("#{date_attribute}(5i)")
26
- )
27
- end
1
+ module Reform::Form::MultiParameterAttributes
2
+ # TODO: implement this with parse_filter, so we don't have to manually walk through the hash, etc.
3
+ class DateTimeParamsFilter
4
+ def call(params)
5
+ params = params.dup # DISCUSS: not sure if that slows down form processing?
6
+ date_attributes = {}
7
+
8
+ params.each do |attribute, value|
9
+ if value.is_a?(Hash)
10
+ params[attribute] = call(value) # TODO: #validate should only handle local form params.
11
+ elsif matches = attribute.match(/^(\w+)\(.i\)$/)
12
+ date_attribute = matches[1]
13
+ date_attributes[date_attribute] = params_to_date(
14
+ params.delete("#{date_attribute}(1i)"),
15
+ params.delete("#{date_attribute}(2i)"),
16
+ params.delete("#{date_attribute}(3i)"),
17
+ params.delete("#{date_attribute}(4i)"),
18
+ params.delete("#{date_attribute}(5i)")
19
+ )
28
20
  end
29
- params.merge!(date_attributes)
30
21
  end
22
+ params.merge!(date_attributes)
23
+ end
31
24
 
32
- private
33
- def params_to_date(year, month, day, hour, minute)
34
- return nil if [year, month, day].any?(&:blank?)
25
+ private
26
+ def params_to_date(year, month, day, hour, minute)
27
+ return nil if [year, month, day].any?(&:blank?)
35
28
 
36
- if hour.blank? && minute.blank?
37
- Date.new(year.to_i, month.to_i, day.to_i) # TODO: test fails.
38
- else
39
- args = [year, month, day, hour, minute].map(&:to_i)
40
- Time.zone ? Time.zone.local(*args) :
41
- Time.new(*args)
42
- end
29
+ if hour.blank? && minute.blank?
30
+ Date.new(year.to_i, month.to_i, day.to_i) # TODO: test fails.
31
+ else
32
+ args = [year, month, day, hour, minute].map(&:to_i)
33
+ Time.zone ? Time.zone.local(*args) :
34
+ Time.new(*args)
43
35
  end
44
36
  end
37
+ end
45
38
 
46
- def validate(params)
47
- # TODO: make it cleaner to hook into essential reform steps.
48
- params = DateTimeParamsFilter.new.call(params) if params.is_a?(Hash) # this currently works for hash, only.
49
- super
50
- end
51
-
52
-
53
- # module ClassMethods
54
- # def representer_class # TODO: check out how we can utilise Config#features.
55
- # super.class_eval do
56
- # extend BuildDefinition
57
- # self
58
- # end
59
- # end
60
- # end
61
-
39
+ # this hooks into the format-specific #deserialize! method.
40
+ def deserialize!(params)
41
+ params = DateTimeParamsFilter.new.call(params) if params.is_a?(Hash) # this currently works for hash, only.
42
+ super
43
+ end
62
44
 
63
- # module BuildDefinition
64
- # def build_definition(name, options, &block)
65
- # return super unless options[:multi_params]
45
+ # module BuildDefinition
46
+ # def build_definition(name, options, &block)
47
+ # return super unless options[:multi_params]
66
48
 
67
- # options[:parse_filter] << DateParamsFilter.new
68
- # super
69
- # end
70
- # end
71
- end
49
+ # options[:parse_filter] << DateParamsFilter.new
50
+ # super
51
+ # end
52
+ # end
72
53
  end
@@ -0,0 +1,85 @@
1
+ # TODO: move somewhere else!
2
+ # TODO: make inheritable? and also, there's a lot of noise. shorten.
3
+ # Implements the :populator option.
4
+ #
5
+ # populator: -> (fragment, twin, options)
6
+ # populator: -> (fragment, collection[twin], index, options)
7
+ #
8
+ # For collections, the entire collection and the currently deserialised index is passed in.
9
+ class Reform::Form::Populator
10
+ include Uber::Callable
11
+
12
+ def initialize(user_proc)
13
+ @user_proc = user_proc # the actual `populator: ->{}` block from the user, via ::property.
14
+ @value = Uber::Options::Value.new(user_proc) # we can now process Callable, procs, :symbol.
15
+ end
16
+
17
+ def call(form, fragment, *args)
18
+ options = args.last
19
+
20
+ # FIXME: the optional index parameter SUCKS.
21
+ twin = call!(form, fragment, options.binding.get, *args)
22
+
23
+ # this kinda sucks. the proc may call self.composer = Artist.new, but there's no way we can
24
+ # return the twin instead of the model from the #composer= setter.
25
+ twin = options.binding.get unless options.binding.array?
26
+
27
+ # since Populator#call is invoked as :instance, we always need to return a twin/form here.
28
+ handle_fail(twin, options)
29
+
30
+ twin
31
+ end
32
+
33
+ private
34
+ # DISCUSS: this signature could change soon.
35
+ # FIXME: the optional index parameter SUCKS.
36
+ def call!(form, fragment, model, *args)
37
+ # FIXME: use U:::Value.
38
+ form.instance_exec(fragment, model, *args, &@user_proc)
39
+ end
40
+
41
+ def handle_fail(twin, options)
42
+ raise "[Reform] Your :populator did not return a Reform::Form instance for `#{options.binding.name}`." if options.binding[:twin] && !twin.is_a?(Reform::Form)
43
+ end
44
+
45
+
46
+ class IfEmpty < self # Populator
47
+ # FIXME: the optional index parameter SUCKS.
48
+ def call!(form, fragment, twin, *args)
49
+ options = args.last
50
+
51
+ if options.binding.array? # FIXME: ifs suck.
52
+ index = args.first
53
+ item = twin.original[index] and return item
54
+
55
+ twin.insert(index, run!(form, fragment, options)) # form.songs.insert(Song.new)
56
+ else
57
+ return if twin
58
+
59
+ form.send(options.binding.setter, run!(form, fragment, options)) # form.artist=(Artist.new)
60
+ end
61
+ end
62
+
63
+ private
64
+ def run!(form, fragment, options)
65
+ return @user_proc.new if @user_proc.is_a?(Class) # handle populate_if_empty: Class. this excludes using Callables, though.
66
+
67
+ @value.evaluate(form, fragment, options.user_options)
68
+ end
69
+ end
70
+
71
+ # Sync (default) blindly grabs the corresponding form twin and returns it. This might imply that nil is returned,
72
+ # and in turn #validate! is called on nil.
73
+ class Sync < self
74
+ def call!(form, fragment, model, *args)
75
+ options = args.last
76
+
77
+ if options.binding.array?
78
+ index = args.first
79
+ return model[index]
80
+ else
81
+ model
82
+ end
83
+ end
84
+ end
85
+ end
@@ -1,54 +1,24 @@
1
- # this will soon be handled in Disposable.
1
+ # prepopulate!(options)
2
+ # prepopulator: ->(model, user_options)
2
3
  module Reform::Form::Prepopulate
3
- def prepopulate!
4
- # TODO: representer.new(fields).from_object(fields)
5
- hash = prepopulate_representer.new(fields).to_hash(:parent_form => self)
6
- prepopulate_representer.new(fields).from_hash(hash)
4
+ def prepopulate!(options={})
5
+ prepopulate_local!(options) # call #prepopulate! on local properties.
6
+ prepopulate_nested!(options) # THEN call #prepopulate! on nested forms.
7
7
 
8
- recursive_prepopulate_representer.new(fields).to_hash # not sure if i leave that like this, consider private.
9
8
  self
10
9
  end
11
- private
12
- def prepopulate_representer
13
- self.class.representer(:prepopulate, :all => true) do |dfn|
14
- next unless block = dfn[:prepopulate] or dfn[:form]
15
-
16
- if dfn[:form]
17
- dfn.merge!(
18
- :render_filter => lambda do |v, h, options|
19
- parent_form = options.user_options[:parent_form] # TODO: merge with Validate/populate_if_empty.
20
-
21
- # execute in form context, pass user optioins.
22
- object = parent_form.instance_exec(options.user_options, &options.binding[:prepopulate])
23
-
24
- if options.binding.array?
25
- object.collect { |item| options.binding[:form].new(item) }
26
- else
27
- options.binding[:form].new(object)
28
- end
29
- end,
30
- :representable => false,
31
10
 
32
- :instance => lambda { |obj, *| obj } # needed for from_hash. TODO: make that in one go.
33
- )
34
- else
35
- dfn.merge!(:render_filter => lambda do |v, h, options|
36
- parent_form = options.user_options[:parent_form] # TODO: merge with Validate/populate_if_empty.
37
-
38
- # execute in form context, pass user optioins.
39
- parent_form.instance_exec(options.user_options, &options.binding[:prepopulate])
40
- end)
41
- end
11
+ private
12
+ def prepopulate_local!(options)
13
+ schema.each do |dfn|
14
+ next unless block = dfn[:prepopulator]
15
+ Uber::Options::Value.new(block).evaluate(self, options)
42
16
  end
43
17
  end
44
18
 
45
- def recursive_prepopulate_representer
46
- self.class.representer(:recursive_prepopulate_representer) do |dfn|
47
- dfn.merge!(
48
- :serialize => lambda { |object, *| model = object.prepopulate! } # sync! returns the synced model.
49
- # representable's :setter will do collection=([..]) or property=(..) for us on the model.
50
- )
19
+ def prepopulate_nested!(options)
20
+ schema.each(twin: true) do |dfn|
21
+ Disposable::Twin::PropertyProcessor.new(dfn, self).() { |form| form.prepopulate!(options) }
51
22
  end
52
23
  end
53
-
54
24
  end
@@ -1,53 +1,13 @@
1
1
  # Mechanics for writing to forms in #validate.
2
2
  module Reform::Form::Validate
3
- module Populator
4
- # This might change soon (e.g. moved into disposable).
5
- class PopulateIfEmpty
6
- include Uber::Callable
7
-
8
- def call(fields, fragment, *args)
9
- index = args.first
10
- options = args.last
11
- binding = options.binding
12
- form = binding.get
13
-
14
- parent_form = options.user_options[:parent_form]
15
-
16
- # FIXME: test those cases!!!
17
- return form[index] if binding.array? and form and form[index] # TODO: this should be handled by the Binding.
18
- return form if !binding.array? and form
19
- # only get here when above form is nil.
20
-
21
-
22
- if binding[:populate_if_empty].is_a?(Proc)
23
- model = parent_form.instance_exec(fragment, options.user_options, &binding[:populate_if_empty]) # call user block.
24
- else
25
- model = binding[:populate_if_empty].new
26
- end
27
-
28
- form = binding[:form].new(model) # free service: wrap model with Form. this usually happens in #setup.
29
-
30
- if binding.array?
31
- # TODO: please extract this into Disposable.
32
- fields = fields.send(:fields)
33
-
34
- fields.send("#{binding.setter}", []) unless fields.send("#{binding.getter}") # DISCUSS: why do I have to initialize this here?
35
- fields.send("#{binding.getter}")[index] = form
36
- else
37
- fields.send("#{binding.setter}", form) # :setter is currently overwritten by :parse_strategy.
38
- end
39
- end
40
- end # PopulateIfEmpty
41
- end
42
-
43
-
44
3
  module Skip
45
4
  class AllBlank
46
5
  include Uber::Callable
47
6
 
48
7
  def call(form, params, options)
49
8
  # TODO: hahahahahaha.
50
- properties = options.binding.representer_module.representer_class.representable_attrs[:definitions].keys
9
+ # FIXME: this is a bit ridiculous.
10
+ properties = options.binding[:twin].representer_class.representable_attrs[:definitions].keys
51
11
 
52
12
  properties.each { |name| params[name].present? and return false }
53
13
  true # skip
@@ -56,18 +16,6 @@ module Reform::Form::Validate
56
16
  end
57
17
 
58
18
 
59
- class Changed
60
- def call(fragment, params, options)
61
- # options is a Representable::Options object holding all the stakeholders. this is here becaues of pass_options: true.
62
- form = options.represented
63
- name = options.binding.name
64
-
65
- form.changed[name] = form.send(name) != fragment
66
-
67
- fragment
68
- end
69
- end
70
-
71
19
  # 1. Populate the form object graph so that each incoming object has a representative form object.
72
20
  # 2. Deserialize. This is wrong and should be done in 1.
73
21
  # 3. Validate the form object graph.
@@ -76,61 +24,52 @@ module Reform::Form::Validate
76
24
 
77
25
  super() # run the actual validation on self.
78
26
 
79
- rescue Representable::DeserializeError
80
- raise DeserializeError.new("[Reform] Deserialize error: You probably called #validate without setting up your nested models. Check https://github.com/apotonick/reform#populating-forms-for-validation on how to use populators.")
27
+ # rescue Representable::DeserializeError
28
+ # raise DeserializeError.new("[Reform] Deserialize error: You probably called #validate without setting up your nested models. Check https://github.com/apotonick/reform#populating-forms-for-validation on how to use populators.")
81
29
  end
82
30
 
83
31
  # Some users use this method to pre-populate a form. Not saying this is right, but we'll keep
84
32
  # this method here.
33
+ # DISCUSS: this is only called once, on the top-level form.
85
34
  def update!(params)
86
35
  deserialize!(params)
87
36
  end
88
37
 
89
38
  private
90
39
  def deserialize!(params)
91
- # using self here will call the form's setters like title= which might be overridden.
92
- # from_hash(params, parent_form: self)
93
- # Go through all nested forms and call form.update!(hash).
94
- populate_representer.new(self).send(deserialize_method, params, :parent_form => self)
95
- end
40
+ require "disposable/twin/schema"
41
+ require "reform/form/coercion" # DISCUSS: make optional?
96
42
 
97
- def deserialize_method
98
- :from_hash
99
- end
43
+ # NOTE: it is completely up to the form user how they want to deserialize (e.g. using an external JSON-API representer).
100
44
 
101
- # IDEA: what if Populate was a Decorator that simply knows how to setup the Form object graph, nothing more? That would decouple
102
- # the population from the validation (good and bad as less customizable).
103
-
104
- # Don't get scared by this method. All this does is create a new representer class for this form.
105
- # It then configures each property so the population of the form can happen in #validate.
106
- # A lot of this code is simply renaming from Reform's API to representable's. # FIXME: unify that?
107
- def populate_representer
108
- self.class.representer(:populate, :all => true) do |dfn|
109
- if dfn[:form]
110
- dfn.merge!(
111
- # set parse_strategy: sync> # DISCUSS: that kills the :setter directive, which usually sucks. at least document this in :populator.
112
- :collection => dfn[:collection], # TODO: Def#merge! doesn't consider :collection if it's already set in dfn YET.
113
- :parse_strategy => :sync, # just use nested objects as they are.
114
-
115
- # :getter grabs nested forms directly from fields bypassing the reader method which could possibly be overridden for presentation.
116
- :getter => lambda { |options| fields.send(options.binding.name) },
117
- :deserialize => lambda { |object, params, args| object.update!(params) },
45
+ deserializer = Disposable::Twin::Schema.from(self.class,
46
+ include: [Representable::Hash::AllowSymbols, Representable::Hash, Representable::Coercion], # FIXME: how do we get this info?
47
+ superclass: Representable::Decorator,
48
+ representer_from: lambda { |inline| inline.representer_class },
49
+ options_from: :deserializer
118
50
  )
119
51
 
120
- # TODO: :populator now is just an alias for :instance. handle in ::property.
121
- dfn.merge!(:instance => dfn[:populator]) if dfn[:populator]
52
+ deserializer.representable_attrs.each do |dfn|
53
+ next unless dfn[:_inline] # FIXME: we have to standardize that!
122
54
 
123
- dfn.merge!(:instance => Populator::PopulateIfEmpty.new) if dfn[:populate_if_empty]
124
- end
55
+ # FIXME: collides with Schema?
56
+ dfn.merge!(
57
+ deserialize: lambda { |decorator, params, options|
58
+ decorator.represented.validate(params)
125
59
 
60
+ decorator.represented
61
+ }
62
+ )
63
+ end
126
64
 
127
- dfn.merge!(:parse_filter => Representable::Coercion::Coercer.new(dfn[:coercion_type])) if dfn[:coercion_type]
65
+ deserializer.new(self).
66
+ # extend(Representable::Debug).
67
+ from_hash(params)
128
68
 
129
- dfn.merge!(:skip_if => Skip::AllBlank.new) if dfn[:skip_if] == :all_blank
130
- dfn.merge!(:skip_parse => dfn[:skip_if]) if dfn[:skip_if]
69
+ # use the deserializer as an external instance to operate on the Twin API,
70
+ # e.g. adding new items in collections using #<< etc.
131
71
 
132
- dfn.merge!(:parse_filter => Changed.new) unless dfn[:form] # TODO: make changed? work for nested forms.
133
- end
72
+ # DISCUSS: using self here will call the form's setters like title= which might be overridden.
134
73
  end
135
74
 
136
75
  class DeserializeError < RuntimeError