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.
- checksums.yaml +4 -4
- data/.travis.yml +6 -1
- data/CHANGES.md +14 -0
- data/Gemfile +3 -2
- data/README.md +225 -283
- data/Rakefile +27 -0
- data/TODO.md +12 -0
- data/database.sqlite3 +0 -0
- data/gemfiles/Gemfile.rails-3.0 +1 -0
- data/gemfiles/Gemfile.rails-3.1 +1 -0
- data/gemfiles/Gemfile.rails-3.2 +1 -0
- data/gemfiles/Gemfile.rails-4.0 +1 -0
- data/lib/reform.rb +0 -1
- data/lib/reform/contract.rb +64 -170
- data/lib/reform/contract/validate.rb +10 -13
- data/lib/reform/form.rb +74 -19
- data/lib/reform/form/active_model.rb +19 -14
- data/lib/reform/form/coercion.rb +1 -13
- data/lib/reform/form/composition.rb +2 -24
- data/lib/reform/form/multi_parameter_attributes.rb +43 -62
- data/lib/reform/form/populator.rb +85 -0
- data/lib/reform/form/prepopulate.rb +13 -43
- data/lib/reform/form/validate.rb +29 -90
- data/lib/reform/form/validation/unique_validator.rb +13 -0
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +7 -7
- data/test/active_model_test.rb +43 -0
- data/test/changed_test.rb +23 -51
- data/test/coercion_test.rb +1 -7
- data/test/composition_test.rb +128 -34
- data/test/contract_test.rb +27 -86
- data/test/feature_test.rb +43 -6
- data/test/fields_test.rb +2 -12
- data/test/form_builder_test.rb +28 -25
- data/test/form_option_test.rb +19 -0
- data/test/from_test.rb +0 -75
- data/test/inherit_test.rb +178 -117
- data/test/model_reflections_test.rb +1 -1
- data/test/populate_test.rb +226 -0
- data/test/prepopulator_test.rb +112 -0
- data/test/readable_test.rb +2 -4
- data/test/save_test.rb +56 -112
- data/test/setup_test.rb +48 -0
- data/test/skip_if_test.rb +5 -2
- data/test/skip_setter_and_getter_test.rb +54 -0
- data/test/test_helper.rb +3 -1
- data/test/uniqueness_test.rb +41 -0
- data/test/validate_test.rb +325 -289
- data/test/virtual_test.rb +1 -3
- data/test/writeable_test.rb +3 -4
- metadata +35 -39
- data/lib/reform/composition.rb +0 -63
- data/lib/reform/contract/setup.rb +0 -50
- data/lib/reform/form/changed.rb +0 -9
- data/lib/reform/form/sync.rb +0 -116
- data/lib/reform/representer.rb +0 -84
- data/test/empty_test.rb +0 -58
- data/test/form_composition_test.rb +0 -145
- data/test/nested_form_test.rb +0 -197
- data/test/prepopulate_test.rb +0 -85
- data/test/sync_option_test.rb +0 -83
- 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.
|
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
|
-
|
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[:
|
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
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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,
|
100
|
+
::ActiveModel::Name.new(self, namespace, string)
|
96
101
|
end
|
97
102
|
end
|
98
103
|
end
|
data/lib/reform/form/coercion.rb
CHANGED
@@ -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
|
2
|
-
# TODO: this
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
params
|
18
|
-
|
19
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
25
|
+
private
|
26
|
+
def params_to_date(year, month, day, hour, minute)
|
27
|
+
return nil if [year, month, day].any?(&:blank?)
|
35
28
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
45
|
+
# module BuildDefinition
|
46
|
+
# def build_definition(name, options, &block)
|
47
|
+
# return super unless options[:multi_params]
|
66
48
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
#
|
1
|
+
# prepopulate!(options)
|
2
|
+
# prepopulator: ->(model, user_options)
|
2
3
|
module Reform::Form::Prepopulate
|
3
|
-
def prepopulate!
|
4
|
-
#
|
5
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
46
|
-
|
47
|
-
dfn.
|
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
|
data/lib/reform/form/validate.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
92
|
-
#
|
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
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
-
|
121
|
-
|
52
|
+
deserializer.representable_attrs.each do |dfn|
|
53
|
+
next unless dfn[:_inline] # FIXME: we have to standardize that!
|
122
54
|
|
123
|
-
|
124
|
-
|
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
|
-
|
65
|
+
deserializer.new(self).
|
66
|
+
# extend(Representable::Debug).
|
67
|
+
from_hash(params)
|
128
68
|
|
129
|
-
|
130
|
-
|
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
|
-
|
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
|