reform 1.2.6 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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