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
data/Rakefile
CHANGED
@@ -5,6 +5,33 @@ task :default => [:test]
|
|
5
5
|
Rake::TestTask.new(:test) do |test|
|
6
6
|
test.libs << 'test'
|
7
7
|
test.test_files = FileList['test/*_test.rb']
|
8
|
+
|
9
|
+
test.test_files = ["test/changed_test.rb",
|
10
|
+
"test/coercion_test.rb",
|
11
|
+
"test/feature_test.rb",
|
12
|
+
|
13
|
+
"test/contract_test.rb",
|
14
|
+
|
15
|
+
"test/populate_test.rb", "test/prepopulator_test.rb",
|
16
|
+
|
17
|
+
"test/readable_test.rb","test/setup_test.rb","test/skip_if_test.rb",
|
18
|
+
|
19
|
+
"test/validate_test.rb", "test/save_test.rb",
|
20
|
+
|
21
|
+
"test/writeable_test.rb","test/virtual_test.rb",
|
22
|
+
|
23
|
+
"test/form_builder_test.rb", "test/active_model_test.rb",
|
24
|
+
|
25
|
+
"test/readonly_test.rb",
|
26
|
+
"test/inherit_test.rb",
|
27
|
+
"test/uniqueness_test.rb",
|
28
|
+
"test/from_test.rb",
|
29
|
+
"test/composition_test.rb",
|
30
|
+
"test/form_option_test.rb"
|
31
|
+
]
|
32
|
+
|
33
|
+
|
34
|
+
|
8
35
|
test.verbose = true
|
9
36
|
end
|
10
37
|
|
data/TODO.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
# 2.0
|
2
|
+
|
3
|
+
* make Coercible optional (include it to activate)
|
4
|
+
* all options Uber:::Value with :method support
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
# NOTES
|
9
|
+
* use the same test setup everywhere (album -> songs -> composer)
|
10
|
+
* copy things in tests
|
11
|
+
* one test file per "feature": sync_test, sync_option_test.
|
12
|
+
|
1
13
|
* fields is a Twin and sorts out all the changed? stuff.
|
2
14
|
* virtual: don't read dont write
|
3
15
|
* empty dont read, but write
|
data/database.sqlite3
CHANGED
Binary file
|
data/gemfiles/Gemfile.rails-3.0
CHANGED
data/gemfiles/Gemfile.rails-3.1
CHANGED
data/gemfiles/Gemfile.rails-3.2
CHANGED
data/gemfiles/Gemfile.rails-4.0
CHANGED
data/lib/reform.rb
CHANGED
data/lib/reform/contract.rb
CHANGED
@@ -1,172 +1,106 @@
|
|
1
1
|
require 'forwardable'
|
2
2
|
require 'uber/inheritable_attr'
|
3
3
|
require 'uber/delegates'
|
4
|
-
require 'ostruct'
|
5
|
-
|
6
|
-
require 'reform/representer'
|
7
4
|
|
8
5
|
module Reform
|
9
6
|
# Gives you a DSL for defining the object structure and its validations.
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
inheritable_attr :representer_class
|
16
|
-
self.representer_class = Reform::Representer.for(:form_class => self) # only happens in Contract/Form.
|
17
|
-
# this should be the only mechanism to inherit, features should be stored in this as well.
|
18
|
-
|
19
|
-
|
20
|
-
# each contract keeps track of its features and passes them onto its local representer_class.
|
21
|
-
# gets inherited, features get automatically included into inline representer.
|
22
|
-
# TODO: the representer class should handle that, e.g. in options (deep-clone when inheriting.)
|
23
|
-
inheritable_attr :features
|
24
|
-
self.features = {}
|
25
|
-
|
26
|
-
|
27
|
-
RESERVED_METHODS = [:model, :aliased_model, :fields, :mapper] # TODO: refactor that so we don't need that.
|
28
|
-
|
29
|
-
|
30
|
-
module PropertyMethods
|
31
|
-
def property(name, options={}, &block)
|
32
|
-
deprecate_as!(options)
|
33
|
-
options[:private_name] = options.delete(:from)
|
34
|
-
options[:coercion_type] = options.delete(:type)
|
35
|
-
options[:features] ||= []
|
36
|
-
options[:features] += features.keys if block_given?
|
37
|
-
options[:pass_options] = true
|
38
|
-
|
39
|
-
# readable and writeable is true as it's not == false
|
7
|
+
require "disposable/twin"
|
8
|
+
require "disposable/twin/setup"
|
9
|
+
class Contract < Disposable::Twin
|
10
|
+
require "disposable/twin/composition" # Expose.
|
11
|
+
include Expose
|
40
12
|
|
41
|
-
|
42
|
-
|
43
|
-
options[:_readable] = false
|
44
|
-
options[:_writeable] = false
|
45
|
-
else
|
46
|
-
options[:_readable] = options.delete(:readable)
|
47
|
-
options[:_writeable] = options.delete(:writeable)
|
48
|
-
end
|
49
|
-
|
50
|
-
else # TODO: remove me in 2.0.
|
51
|
-
deprecate_virtual_and_empty!(options)
|
52
|
-
end
|
53
|
-
|
54
|
-
validates(name, options.delete(:validates).dup) if options[:validates]
|
55
|
-
|
56
|
-
definition = representer_class.property(name, options, &block)
|
57
|
-
setup_form_definition(definition) if block_given? or options[:form]
|
58
|
-
|
59
|
-
create_accessor(name)
|
60
|
-
definition
|
61
|
-
end
|
62
|
-
|
63
|
-
def properties(*args)
|
64
|
-
options = args.extract_options!
|
65
|
-
|
66
|
-
if args.first.is_a? Array # TODO: REMOVE in 2.0.
|
67
|
-
warn "[Reform] Deprecation: Please pass a list of names instead of array to ::properties, like `properties :title, :id`."
|
68
|
-
args = args.first
|
69
|
-
end
|
70
|
-
args.each { |name| property(name, options.dup) }
|
71
|
-
end
|
13
|
+
feature Setup
|
14
|
+
feature Setup::SkipSetter
|
72
15
|
|
73
|
-
|
74
|
-
options[:collection] = true
|
16
|
+
extend Uber::Delegates
|
75
17
|
|
76
|
-
|
18
|
+
representer_class.instance_eval do
|
19
|
+
def default_inline_class
|
20
|
+
Contract
|
77
21
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
:pass_options => true, # new style of passing args
|
84
|
-
:prepare => lambda { |form, args| form }, # always just return the form without decorating.
|
85
|
-
:representable => true, # form: Class must be treated as a typed property.
|
86
|
-
}
|
87
|
-
|
88
|
-
definition.merge!(options)
|
22
|
+
end
|
23
|
+
# FIXME: THIS sucks because we're building two representers.
|
24
|
+
representer_class.instance_eval do
|
25
|
+
def default_inline_class
|
26
|
+
Contract
|
89
27
|
end
|
28
|
+
end
|
90
29
|
|
91
|
-
|
92
|
-
|
93
|
-
def create_accessor(name)
|
94
|
-
handle_reserved_names(name)
|
30
|
+
def self.property(name, options={}, &block)
|
31
|
+
options.merge!(pass_options: true)
|
95
32
|
|
96
|
-
|
33
|
+
if twin = options.delete(:form)
|
34
|
+
options[:twin] = twin
|
97
35
|
end
|
98
36
|
|
99
|
-
|
100
|
-
raise "[Reform] the property name '#{name}' is reserved, please consider something else using :as." if RESERVED_METHODS.include?(name)
|
101
|
-
end
|
37
|
+
super
|
102
38
|
end
|
103
|
-
extend PropertyMethods
|
104
39
|
|
40
|
+
# FIXME: test me.
|
41
|
+
def self.properties(*args)
|
42
|
+
options = args.extract_options!
|
43
|
+
args.each { |name| property(name, options.dup) }
|
44
|
+
end
|
105
45
|
|
106
46
|
# FIXME: make AM optional.
|
107
47
|
require 'active_model'
|
108
48
|
include ActiveModel::Validations
|
109
49
|
|
110
|
-
|
111
|
-
|
112
|
-
attr_accessor :model
|
113
|
-
|
114
|
-
require 'reform/contract/setup'
|
115
|
-
include Setup
|
116
|
-
|
117
|
-
def self.representers # keeps all transformation representers for one class.
|
118
|
-
@representers ||= {}
|
119
|
-
end
|
120
|
-
|
121
|
-
def self.representer(name=nil, options={}, &block)
|
122
|
-
return representer_class.each(&block) if name == nil
|
123
|
-
return representers[name] if representers[name] # don't run block as this representer is already setup for this form class.
|
124
|
-
|
125
|
-
only_forms = options[:all] ? false : true
|
126
|
-
base = options[:superclass] || representer_class
|
127
|
-
|
128
|
-
representers[name] = Class.new(base).each(only_forms, &block) # let user modify representer.
|
129
|
-
end
|
130
|
-
|
131
50
|
require 'reform/contract/validate'
|
132
|
-
include Validate
|
51
|
+
include Reform::Contract::Validate
|
133
52
|
|
134
53
|
def errors # FIXME: this is needed for Rails 3.0 compatibility.
|
135
54
|
@errors ||= Errors.new(self)
|
136
55
|
end
|
137
56
|
|
138
|
-
|
139
57
|
private
|
140
|
-
attr_accessor :fields
|
141
58
|
attr_writer :errors # only used in top form. (is that true?)
|
142
59
|
|
143
|
-
|
144
|
-
|
60
|
+
# DISCUSS: can we achieve that somehow via features in build_inline?
|
61
|
+
# TODO: check out if that is needed with Lotus::Validations and make it a AM feature.
|
62
|
+
def self.process_inline!(mod, definition)
|
63
|
+
_name = definition.name
|
64
|
+
mod.instance_eval do
|
65
|
+
@_name = _name.singularize.camelize
|
66
|
+
def name # this adds Form::name for AM::Validations and I18N. i know it's retarded.
|
67
|
+
# something weird happens here: somewhere in Rails, this creates a constant (e.g. User). if this name doesn't represent a valid
|
68
|
+
# constant, the reloading in dev will fail with weird messages. i'm not sure if we should just get rid of Rails validations etc.
|
69
|
+
# or if i should look into this?
|
70
|
+
@_name
|
71
|
+
end
|
72
|
+
end
|
145
73
|
end
|
146
74
|
|
147
|
-
def self.deprecate_as!(options) # TODO: remove me in 2.0.
|
148
|
-
return unless as = options.delete(:as)
|
149
|
-
options[:from] = as
|
150
|
-
warn "[Reform] The :as options got renamed to :from. See https://github.com/apotonick/reform/wiki/Migration-Guide and have a nice day."
|
151
|
-
end
|
152
75
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
options[:_writeable] = false
|
76
|
+
# DISCUSS: separate file?
|
77
|
+
module Readonly
|
78
|
+
def readonly?(name)
|
79
|
+
options_for(name)[:writeable] == false
|
158
80
|
end
|
159
|
-
|
160
|
-
|
161
|
-
warn "[Reform] The :empty option has changed! Check https://github.com/apotonick/reform/wiki/Migration-Guide and have a good day."
|
162
|
-
options[:_readable] = false
|
163
|
-
options[:_writeable] = false
|
81
|
+
def options_for(name)
|
82
|
+
self.class.options_for(name)
|
164
83
|
end
|
165
84
|
end
|
85
|
+
def self.options_for(name)
|
86
|
+
representer_class.representable_attrs.get(name)
|
87
|
+
end
|
88
|
+
include Readonly
|
89
|
+
|
166
90
|
|
167
|
-
def self.
|
168
|
-
|
91
|
+
def self.clone # TODO: test. THIS IS ONLY FOR Trailblazer when contract gets cloned in suboperation.
|
92
|
+
Class.new(self)
|
169
93
|
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class Contract_ # DISCUSS: make class?
|
97
|
+
extend Uber::InheritableAttr
|
98
|
+
|
99
|
+
RESERVED_METHODS = [:model] # TODO: refactor that so we don't need that.
|
100
|
+
def handle_reserved_names(name)
|
101
|
+
raise "[Reform] the property name '#{name}' is reserved, please consider something else using :as." if RESERVED_METHODS.include?(name)
|
102
|
+
end
|
103
|
+
|
170
104
|
|
171
105
|
# allows including representers from Representable, Roar or disposable.
|
172
106
|
def self.inherit_module!(representer) # called from Representable::included.
|
@@ -182,48 +116,8 @@ module Reform
|
|
182
116
|
end
|
183
117
|
end
|
184
118
|
|
185
|
-
def self.clone
|
186
|
-
Class.new(self)
|
187
|
-
end
|
188
|
-
|
189
119
|
require 'reform/schema'
|
190
120
|
extend Schema
|
191
|
-
|
192
|
-
alias_method :aliased_model, :model
|
193
|
-
|
194
|
-
module Readonly
|
195
|
-
def readonly?(name)
|
196
|
-
options_for(name)[:writeable] == false
|
197
|
-
end
|
198
|
-
|
199
|
-
def options_for(name)
|
200
|
-
self.class.representer_class.representable_attrs.get(name)
|
201
|
-
end
|
202
|
-
end
|
203
|
-
include Readonly
|
204
|
-
|
205
|
-
# TODO: remove me in 2.0.
|
206
|
-
module Reform20Switch
|
207
|
-
def self.included(base)
|
208
|
-
base.register_feature(Reform20Switch)
|
209
|
-
end
|
210
|
-
end
|
211
|
-
def self.reform_2_0!
|
212
|
-
include Reform20Switch
|
213
|
-
end
|
214
|
-
def self.reform_2_0
|
215
|
-
features[Reform20Switch]
|
216
|
-
end
|
217
|
-
|
218
|
-
|
219
|
-
# Keeps values of the form fields. What's in here is to be displayed in the browser!
|
220
|
-
# we need this intermediate object to display both "original values" and new input from the form after submitting.
|
221
|
-
class Fields < OpenStruct
|
222
|
-
def initialize(properties, values={})
|
223
|
-
fields = properties.inject({}) { |hsh, attr| hsh.merge!(attr => nil) }
|
224
|
-
super(fields.merge!(values)) # TODO: stringify value keys!
|
225
|
-
end
|
226
|
-
end # Fields
|
227
121
|
end
|
228
122
|
end
|
229
123
|
|
@@ -12,8 +12,7 @@ module Reform::Contract::Validate
|
|
12
12
|
def validate!(options)
|
13
13
|
prefix = options[:prefix]
|
14
14
|
|
15
|
-
# call valid? recursively and collect nested errors.
|
16
|
-
valid_representer.new(fields).to_hash(options) # TODO: only include nested forms here.
|
15
|
+
validate_nested!(options) # call valid? recursively and collect nested errors.
|
17
16
|
|
18
17
|
valid? # this validates on <Fields> using AM::Validations, currently.
|
19
18
|
|
@@ -23,17 +22,15 @@ module Reform::Contract::Validate
|
|
23
22
|
private
|
24
23
|
|
25
24
|
# runs form.validate! on all nested forms
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
}
|
36
|
-
)
|
25
|
+
def validate_nested!(options)
|
26
|
+
schema.each(twin: true) do |dfn|
|
27
|
+
property_options = options.dup
|
28
|
+
|
29
|
+
property_options[:prefix] = options[:prefix].dup # TODO: implement Options#dup.
|
30
|
+
property_options[:prefix] << dfn.name
|
31
|
+
|
32
|
+
# recursively call valid? on nested form.
|
33
|
+
Disposable::Twin::PropertyProcessor.new(dfn, self).() { |form| form.validate!(property_options) }
|
37
34
|
end
|
38
35
|
end
|
39
36
|
end
|
data/lib/reform/form.rb
CHANGED
@@ -1,33 +1,88 @@
|
|
1
1
|
module Reform
|
2
2
|
class Form < Contract
|
3
|
-
|
3
|
+
representer_class.instance_eval do
|
4
|
+
def default_inline_class
|
5
|
+
Form
|
6
|
+
end
|
7
|
+
end
|
4
8
|
|
5
9
|
require "reform/form/validate"
|
6
10
|
include Validate # extend Contract#validate with additional behaviour.
|
7
|
-
require "reform/form/sync"
|
8
|
-
include Sync
|
9
|
-
require "reform/form/save"
|
10
|
-
include Save
|
11
|
-
require "reform/form/prepopulate"
|
12
|
-
include Prepopulate
|
13
11
|
|
14
|
-
require "reform/form/
|
15
|
-
|
12
|
+
require "reform/form/populator"
|
13
|
+
|
14
|
+
module Property
|
15
|
+
# add macro logic, e.g. for :populator.
|
16
|
+
def property(name, options={}, &block)
|
17
|
+
if options.delete(:virtual)
|
18
|
+
options[:writeable] = options[:readable] = false # DISCUSS: isn't that like an #option in Twin?
|
19
|
+
end
|
20
|
+
|
21
|
+
if deserializer = options[:deserializer] # this means someone is explicitly specifying :deserializer.
|
22
|
+
options[:deserializer] = Representable::Cloneable::Hash[deserializer]
|
23
|
+
end
|
24
|
+
|
25
|
+
definition = super # let representable sort out inheriting of properties, and so on.
|
26
|
+
definition.merge!(deserializer: Representable::Cloneable::Hash.new) unless definition[:deserializer] # always keep :deserializer per property.
|
27
|
+
|
28
|
+
|
29
|
+
deserializer_options = definition[:deserializer]
|
30
|
+
|
31
|
+
# TODO: make this pluggable.
|
32
|
+
# DISCUSS: Populators should be a representable concept?
|
33
|
+
|
34
|
+
# Populators
|
35
|
+
# * they assign created data, no :setter (hence the name).
|
36
|
+
# * they (ab)use :instance, this is why they need to return a twin form.
|
37
|
+
# * they are only used in the deserializer.
|
38
|
+
|
16
39
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
40
|
+
|
41
|
+
if populator = options.delete(:populate_if_empty)
|
42
|
+
deserializer_options.merge!({instance: Populator::IfEmpty.new(populator)})
|
43
|
+
deserializer_options.merge!({setter: nil})
|
44
|
+
elsif populator = options.delete(:populator)
|
45
|
+
deserializer_options.merge!({instance: Populator.new(populator)})
|
46
|
+
deserializer_options.merge!({setter: nil}) #if options[:collection] # collections don't need to get re-assigned, they don't change.
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
# TODO: shouldn't that go into validate?
|
51
|
+
if proc = options.delete(:skip_if)
|
52
|
+
proc = Reform::Form::Validate::Skip::AllBlank.new if proc == :all_blank
|
53
|
+
|
54
|
+
deserializer_options.merge!(skip_parse: proc)
|
55
|
+
end
|
56
|
+
|
57
|
+
# default:
|
58
|
+
# add Sync populator to nested forms.
|
59
|
+
# FIXME: this is, of course, ridiculous and needs a better structuring.
|
60
|
+
if (deserializer_options == {} || deserializer_options.keys == [:skip_parse]) && block_given? && !options[:inherit] # FIXME: hmm. not a fan of this: only add when no other option given?
|
61
|
+
deserializer_options.merge!(instance: Populator::Sync.new(nil), setter: nil)
|
62
|
+
end
|
63
|
+
|
64
|
+
# per default, everything should be writeable for the deserializer (we're only writing on the form). however, allow turning it off.
|
65
|
+
deserializer_options.merge!(writeable: true) unless deserializer_options.has_key?(:writeable)
|
66
|
+
|
67
|
+
definition
|
68
|
+
end
|
21
69
|
end
|
70
|
+
extend Property
|
71
|
+
|
72
|
+
|
73
|
+
require "reform/form/multi_parameter_attributes"
|
22
74
|
|
75
|
+
require "disposable/twin/changed"
|
76
|
+
feature Disposable::Twin::Changed
|
23
77
|
|
24
|
-
require "
|
25
|
-
|
78
|
+
require "disposable/twin/sync"
|
79
|
+
feature Disposable::Twin::Sync
|
80
|
+
feature Disposable::Twin::Sync::SkipGetter
|
26
81
|
|
82
|
+
require "disposable/twin/save"
|
83
|
+
feature Disposable::Twin::Save
|
27
84
|
|
28
|
-
|
29
|
-
|
30
|
-
register_feature Changed
|
31
|
-
include Changed
|
85
|
+
require "reform/form/prepopulate"
|
86
|
+
include Prepopulate
|
32
87
|
end
|
33
88
|
end
|