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.
- 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
|