reform 0.2.7 → 1.0.0
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/CHANGES.md +18 -0
- data/Gemfile +2 -1
- data/README.md +196 -17
- data/TODO.md +14 -3
- data/database.sqlite3 +0 -0
- data/lib/reform.rb +9 -1
- data/lib/reform/composition.rb +41 -34
- data/lib/reform/contract.rb +109 -0
- data/lib/reform/contract/errors.rb +33 -0
- data/lib/reform/contract/setup.rb +44 -0
- data/lib/reform/contract/validate.rb +48 -0
- data/lib/reform/form.rb +13 -309
- data/lib/reform/form/active_model.rb +8 -5
- data/lib/reform/form/active_record.rb +30 -37
- data/lib/reform/form/coercion.rb +10 -11
- data/lib/reform/form/composition.rb +40 -50
- data/lib/reform/form/multi_parameter_attributes.rb +6 -1
- data/lib/reform/form/save.rb +61 -0
- data/lib/reform/form/sync.rb +60 -0
- data/lib/reform/form/validate.rb +104 -0
- data/lib/reform/form/virtual_attributes.rb +3 -5
- data/lib/reform/representer.rb +17 -3
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +2 -1
- data/test/active_model_test.rb +0 -92
- data/test/as_test.rb +75 -0
- data/test/coercion_test.rb +26 -8
- data/test/composition_test.rb +8 -8
- data/test/contract_test.rb +57 -0
- data/test/errors_test.rb +37 -10
- data/test/feature_test.rb +28 -0
- data/test/form_builder_test.rb +105 -0
- data/test/form_composition_test.rb +30 -13
- data/test/nested_form_test.rb +12 -18
- data/test/reform_test.rb +11 -6
- data/test/save_test.rb +81 -0
- data/test/setup_test.rb +38 -0
- data/test/sync_test.rb +39 -0
- data/test/test_helper.rb +36 -2
- data/test/validate_test.rb +191 -0
- metadata +42 -4
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'uber/inheritable_attr'
|
3
|
+
|
4
|
+
require 'reform/representer'
|
5
|
+
|
6
|
+
module Reform
|
7
|
+
# Gives you a DSL for defining the object structure and its validations.
|
8
|
+
class Contract # DISCUSS: make class?
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
extend Uber::InheritableAttr
|
12
|
+
inheritable_attr :representer_class
|
13
|
+
self.representer_class = Reform::Representer.for(:form_class => self)
|
14
|
+
|
15
|
+
inheritable_attr :features
|
16
|
+
self.features = []
|
17
|
+
|
18
|
+
|
19
|
+
module PropertyMethods
|
20
|
+
extend Forwardable
|
21
|
+
|
22
|
+
def property(name, options={}, &block)
|
23
|
+
options[:private_name] = options.delete(:as)
|
24
|
+
|
25
|
+
# at this point, :extend is a Form class.
|
26
|
+
options[:features] = features if block_given?
|
27
|
+
definition = representer_class.property(name, options, &block)
|
28
|
+
setup_form_definition(definition) if block_given? or options[:form]
|
29
|
+
|
30
|
+
create_accessor(name)
|
31
|
+
definition
|
32
|
+
end
|
33
|
+
|
34
|
+
def collection(name, options={}, &block)
|
35
|
+
options[:collection] = true
|
36
|
+
|
37
|
+
property(name, options, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
def properties(names, *args)
|
41
|
+
names.each { |name| property(name, *args) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def setup_form_definition(definition)
|
45
|
+
options = {
|
46
|
+
:form => definition[:form] || definition[:extend].evaluate(nil), # :form is always just a Form class name.
|
47
|
+
:pass_options => true, # new style of passing args
|
48
|
+
:prepare => lambda { |form, args| form }, # always just return the form without decorating.
|
49
|
+
:representable => true, # form: Class must be treated as a typed property.
|
50
|
+
}
|
51
|
+
|
52
|
+
definition.merge!(options)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def create_accessor(name)
|
57
|
+
# Make a module that contains these very accessors, then include it
|
58
|
+
# so they can be overridden but still are callable with super.
|
59
|
+
accessors = Module.new do
|
60
|
+
extend Forwardable # DISCUSS: do we really need Forwardable here?
|
61
|
+
delegate [name, "#{name}="] => :fields
|
62
|
+
end
|
63
|
+
include accessors
|
64
|
+
end
|
65
|
+
end
|
66
|
+
extend PropertyMethods
|
67
|
+
|
68
|
+
|
69
|
+
# FIXME: make AM optional.
|
70
|
+
require 'active_model'
|
71
|
+
include ActiveModel::Validations
|
72
|
+
|
73
|
+
|
74
|
+
attr_accessor :model
|
75
|
+
|
76
|
+
require 'reform/contract/setup'
|
77
|
+
include Setup
|
78
|
+
require 'reform/contract/validate'
|
79
|
+
include Validate
|
80
|
+
|
81
|
+
|
82
|
+
def errors # FIXME: this is needed for Rails 3.0 compatibility.
|
83
|
+
@errors ||= Errors.new(self)
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
private
|
88
|
+
attr_accessor :fields
|
89
|
+
attr_writer :errors # only used in top form. (is that true?)
|
90
|
+
|
91
|
+
def mapper
|
92
|
+
self.class.representer_class
|
93
|
+
end
|
94
|
+
|
95
|
+
alias_method :aliased_model, :model
|
96
|
+
|
97
|
+
|
98
|
+
# Keeps values of the form fields. What's in here is to be displayed in the browser!
|
99
|
+
# we need this intermediate object to display both "original values" and new input from the form after submitting.
|
100
|
+
class Fields < OpenStruct
|
101
|
+
def initialize(properties, values={})
|
102
|
+
fields = properties.inject({}) { |hsh, attr| hsh.merge!(attr => nil) }
|
103
|
+
super(fields.merge!(values)) # TODO: stringify value keys!
|
104
|
+
end
|
105
|
+
end # Fields
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
require 'reform/contract/errors'
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# The Errors class is planned to replace AM::Errors. It provides proper nested error messages.
|
2
|
+
class Reform::Contract::Errors < ActiveModel::Errors
|
3
|
+
def messages
|
4
|
+
return super unless Reform.rails3_0?
|
5
|
+
self
|
6
|
+
end
|
7
|
+
|
8
|
+
# def each
|
9
|
+
# messages.each_key do |attribute|
|
10
|
+
# self[attribute].each { |error| yield attribute, Array.wrap(error) }
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
|
14
|
+
def merge!(errors, prefix)
|
15
|
+
prefixes = prefix.join(".")
|
16
|
+
|
17
|
+
# TODO: merge into AM.
|
18
|
+
errors.messages.each do |field, msgs|
|
19
|
+
field = (prefix+[field]).join(".").to_sym # TODO: why is that a symbol in Rails?
|
20
|
+
|
21
|
+
msgs = [msgs] if Reform.rails3_0? # DISCUSS: fix in #each?
|
22
|
+
|
23
|
+
msgs.each do |msg|
|
24
|
+
next if messages[field] and messages[field].include?(msg)
|
25
|
+
add(field, msg)
|
26
|
+
end # Forms now contains a plain errors hash. the errors for each item are still available in item.errors.
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def valid? # TODO: test me in unit test.
|
31
|
+
blank?
|
32
|
+
end
|
33
|
+
end # Errors
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
module Reform
|
3
|
+
class Contract
|
4
|
+
module Setup
|
5
|
+
def initialize(model)
|
6
|
+
@model = model # we need this for #save.
|
7
|
+
@fields = setup_fields # delegate all methods to Fields instance.
|
8
|
+
end
|
9
|
+
|
10
|
+
def setup_fields
|
11
|
+
representer = mapper.new(aliased_model).extend(Setup::Representer)
|
12
|
+
|
13
|
+
create_fields(representer.fields, representer.to_hash)
|
14
|
+
end
|
15
|
+
|
16
|
+
# DISCUSS: setting up the Validation (populating with values) will soon be handled with Disposable::Twin logic.
|
17
|
+
def create_fields(field_names, fields)
|
18
|
+
Fields.new(field_names, fields)
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# Mechanics for setting up initial Field values.
|
23
|
+
module Representer
|
24
|
+
require 'reform/form/virtual_attributes' # FIXME: that shouldn't be here.
|
25
|
+
|
26
|
+
include Reform::Representer::WithOptions
|
27
|
+
include Reform::Form::EmptyAttributesOptions # FIXME: that shouldn't be here.
|
28
|
+
|
29
|
+
def to_hash(*)
|
30
|
+
nested_forms do |attr|
|
31
|
+
attr.merge!(
|
32
|
+
:representable => false, # don't call #to_hash.
|
33
|
+
:prepare => lambda do |model, args|
|
34
|
+
args.binding[:form].new(model)
|
35
|
+
end
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
super # TODO: allow something like super(:exclude => empty_fields)
|
40
|
+
end
|
41
|
+
end # Representer
|
42
|
+
end
|
43
|
+
end # Validation
|
44
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Reform::Contract::Validate
|
2
|
+
module NestedValid
|
3
|
+
def to_hash(*)
|
4
|
+
nested_forms do |attr|
|
5
|
+
# attr.delete(:prepare)
|
6
|
+
# attr.delete(:extend)
|
7
|
+
|
8
|
+
attr.merge!(
|
9
|
+
:serialize => lambda { |object, args|
|
10
|
+
|
11
|
+
# FIXME: merge with Validate::Writer
|
12
|
+
options = args.user_options.dup
|
13
|
+
options[:prefix] = options[:prefix].dup # TODO: implement Options#dup.
|
14
|
+
options[:prefix] << args.binding.name # FIXME: should be #as.
|
15
|
+
|
16
|
+
# puts "======= user_options: #{args.user_options.inspect}"
|
17
|
+
|
18
|
+
object.validate!(options) # recursively call valid?
|
19
|
+
},
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate
|
28
|
+
options = {:errors => errs = Reform::Contract::Errors.new(self), :prefix => []}
|
29
|
+
|
30
|
+
validate!(options)
|
31
|
+
|
32
|
+
self.errors = errs # if the AM valid? API wouldn't use a "global" variable this would be better.
|
33
|
+
|
34
|
+
errors.valid?
|
35
|
+
end
|
36
|
+
def validate!(options)
|
37
|
+
# puts "validate! in #{self.class.name}: #{true.inspect}"
|
38
|
+
prefix = options[:prefix]
|
39
|
+
|
40
|
+
# call valid? recursively and collect nested errors.
|
41
|
+
mapper.new(self).extend(NestedValid).to_hash(options)
|
42
|
+
|
43
|
+
valid? # this validates on <Fields> using AM::Validations, currently.
|
44
|
+
|
45
|
+
options[:errors].merge!(self.errors, prefix)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/lib/reform/form.rb
CHANGED
@@ -1,323 +1,27 @@
|
|
1
|
-
require 'forwardable'
|
2
1
|
require 'ostruct'
|
3
2
|
|
3
|
+
require 'reform/contract'
|
4
4
|
require 'reform/composition'
|
5
|
-
require 'reform/representer'
|
6
|
-
|
7
|
-
require 'uber/inheritable_attr'
|
8
|
-
|
9
5
|
|
10
6
|
module Reform
|
11
|
-
class Form
|
12
|
-
|
13
|
-
|
14
|
-
extend Uber::InheritableAttr
|
15
|
-
inheritable_attr :representer_class
|
16
|
-
self.representer_class = Class.new(Reform::Representer)
|
17
|
-
|
18
|
-
|
19
|
-
module PropertyMethods
|
20
|
-
extend Forwardable
|
21
|
-
|
22
|
-
def property(name, options={}, &block)
|
23
|
-
process_options(name, options, &block)
|
24
|
-
|
25
|
-
definition = representer_class.property(name, options, &block)
|
26
|
-
setup_form_definition(definition) if block_given? or options[:form]
|
27
|
-
|
28
|
-
create_accessor(name)
|
29
|
-
end
|
30
|
-
|
31
|
-
def collection(name, options={}, &block)
|
32
|
-
options[:form_collection] = true
|
33
|
-
|
34
|
-
property(name, options, &block)
|
35
|
-
end
|
36
|
-
|
37
|
-
def properties(names, *args)
|
38
|
-
names.each { |name| property(name, *args) }
|
39
|
-
end
|
40
|
-
|
41
|
-
def setup_form_definition(definition)
|
42
|
-
# TODO: allow Definition.form?
|
43
|
-
definition.options[:form] ||= definition.options.delete(:extend)
|
7
|
+
class Form < Contract
|
8
|
+
self.representer_class = Reform::Representer.for(:form_class => self)
|
44
9
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
private
|
50
|
-
def create_accessor(name)
|
51
|
-
# Make a module that contains these very accessors, then include it
|
52
|
-
# so they can be overridden but still are callable with super.
|
53
|
-
accessors = Module.new do
|
54
|
-
extend Forwardable # DISCUSS: do we really need Forwardable here?
|
55
|
-
delegate [name, "#{name}="] => :fields
|
56
|
-
end
|
57
|
-
include accessors
|
58
|
-
end
|
59
|
-
|
60
|
-
def process_options(name, options) # DISCUSS: do we need that hook?
|
61
|
-
end
|
62
|
-
end
|
63
|
-
extend PropertyMethods
|
64
|
-
|
65
|
-
|
66
|
-
def initialize(model)
|
67
|
-
@model = model # we need this for #save.
|
68
|
-
@fields = setup_fields(model) # delegate all methods to Fields instance.
|
10
|
+
def aliased_model
|
11
|
+
# TODO: cache the Expose.from class!
|
12
|
+
Reform::Expose.from(mapper).new(:model => model)
|
69
13
|
end
|
70
14
|
|
71
|
-
|
72
|
-
def validate(params)
|
73
|
-
# here it would be cool to have a validator object containing the validation rules representer-like and then pass it the formed model.
|
74
|
-
from_hash(params)
|
75
|
-
|
76
|
-
res = valid? # this validates on <Fields> using AM::Validations, currently.
|
77
|
-
#inject(true) do |res, form| # FIXME: replace that!
|
78
|
-
mapper.new(@fields).nested_forms do |attr, form| #.collect { |attr, form| nested[attr.from] = form }
|
79
|
-
next unless form # FIXME: this happens when the model's reader returns nil (property :song => nil). this shouldn't be considered by nested_forms!
|
80
|
-
res = validate_for(form, res, attr.from)
|
81
|
-
end
|
82
|
-
|
83
|
-
res
|
84
|
-
end
|
15
|
+
require "reform/form/virtual_attributes"
|
85
16
|
|
86
|
-
|
87
|
-
|
88
|
-
|
17
|
+
require 'reform/form/validate'
|
18
|
+
include Validate # extend Contract#validate with additional behaviour.
|
19
|
+
require 'reform/form/sync'
|
20
|
+
include Sync
|
21
|
+
require 'reform/form/save'
|
22
|
+
include Save
|
89
23
|
|
90
|
-
errors.merge!(form.errors, prefix)
|
91
|
-
false
|
92
|
-
end
|
93
|
-
end
|
94
|
-
include ValidateMethods
|
95
24
|
require 'reform/form/multi_parameter_attributes'
|
96
25
|
include MultiParameterAttributes # TODO: make features dynamic.
|
97
|
-
|
98
|
-
def save
|
99
|
-
# DISCUSS: we should never hit @mapper here (which writes to the models) when a block is passed.
|
100
|
-
return yield self, to_nested_hash if block_given?
|
101
|
-
|
102
|
-
save_to_models
|
103
|
-
end
|
104
|
-
|
105
|
-
# Use representer to return current key-value form hash.
|
106
|
-
def to_hash(*args)
|
107
|
-
mapper.new(self).to_hash(*args)
|
108
|
-
end
|
109
|
-
|
110
|
-
require "active_support/hash_with_indifferent_access" # DISCUSS: replace?
|
111
|
-
def to_nested_hash
|
112
|
-
map = mapper.new(self)
|
113
|
-
|
114
|
-
ActiveSupport::HashWithIndifferentAccess.new(map.to_hash)
|
115
|
-
end
|
116
|
-
|
117
|
-
def from_hash(params, *args)
|
118
|
-
mapper.new(self).from_hash(params) # sets form properties found in params on self.
|
119
|
-
end
|
120
|
-
|
121
|
-
def errors
|
122
|
-
@errors ||= Errors.new(self)
|
123
|
-
@errors
|
124
|
-
end
|
125
|
-
|
126
|
-
attr_accessor :model
|
127
|
-
|
128
|
-
private
|
129
|
-
attr_accessor :fields
|
130
|
-
|
131
|
-
def mapper
|
132
|
-
self.class.representer_class
|
133
|
-
end
|
134
|
-
|
135
|
-
def setup_fields(model)
|
136
|
-
representer = mapper.new(model).extend(Setup::Representer)
|
137
|
-
|
138
|
-
create_fields(representer.fields, representer.to_hash)
|
139
|
-
end
|
140
|
-
|
141
|
-
#representer.to_hash override: { write: lambda { |doc, value| } }
|
142
|
-
|
143
|
-
# DISCUSS: this would be cool in representable:
|
144
|
-
# to_hash(hit: lambda { |value| form_class.new(..) })
|
145
|
-
|
146
|
-
# steps:
|
147
|
-
# - bin.get
|
148
|
-
# - map that: Forms.new( orig ) <-- override only this in representable (how?)
|
149
|
-
# - mapped.to_hash
|
150
|
-
|
151
|
-
|
152
|
-
def create_fields(field_names, fields)
|
153
|
-
Fields.new(field_names, fields)
|
154
|
-
end
|
155
|
-
|
156
|
-
|
157
|
-
require "reform/form/virtual_attributes"
|
158
|
-
|
159
|
-
# Mechanics for setting up initial Field values.
|
160
|
-
module Setup
|
161
|
-
module Representer
|
162
|
-
include Reform::Representer::WithOptions
|
163
|
-
include EmptyAttributesOptions
|
164
|
-
|
165
|
-
def to_hash(*)
|
166
|
-
setup_nested_forms
|
167
|
-
|
168
|
-
super # TODO: allow something like super(:exclude => empty_fields)
|
169
|
-
end
|
170
|
-
|
171
|
-
private
|
172
|
-
def setup_nested_forms
|
173
|
-
nested_forms do |attr, model|
|
174
|
-
form_class = attr.options[:form]
|
175
|
-
|
176
|
-
attr.options.merge!(
|
177
|
-
:getter => lambda do |*|
|
178
|
-
# FIXME: this is where i have to fix stuff.
|
179
|
-
nested_model = send(attr.getter) # or next # decorated.hit # TODO: use bin.get # DISCUSS: next moves on if property empty. this should be handled with representable's built-in mechanics.
|
180
|
-
|
181
|
-
if attr.options[:form_collection]
|
182
|
-
nested_model ||= []
|
183
|
-
Forms.new(nested_model.collect { |mdl| form_class.new(mdl)}, attr.options)
|
184
|
-
else
|
185
|
-
next unless nested_model # DISCUSS: do we want that?
|
186
|
-
form_class.new(nested_model)
|
187
|
-
end
|
188
|
-
end,
|
189
|
-
:instance => false, # that's how we make it non-typed?.
|
190
|
-
)
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
# Mechanics for writing input to model.
|
197
|
-
module Sync
|
198
|
-
# Writes input to model.
|
199
|
-
module Representer
|
200
|
-
def from_hash(*)
|
201
|
-
nested_forms do |attr, model|
|
202
|
-
attr.options.merge!(
|
203
|
-
:decorator => attr.options[:form].representer_class
|
204
|
-
)
|
205
|
-
|
206
|
-
if attr.options[:form_collection]
|
207
|
-
attr.options.merge!(
|
208
|
-
:collection => true
|
209
|
-
)
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
super
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
# Transforms form input into what actually gets written to model.
|
218
|
-
module InputRepresenter
|
219
|
-
include Reform::Representer::WithOptions
|
220
|
-
# TODO: make dynamic.
|
221
|
-
include EmptyAttributesOptions
|
222
|
-
include ReadonlyAttributesOptions
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
|
227
|
-
def save_to_models # TODO: rename to #sync_models
|
228
|
-
representer = mapper.new(model)
|
229
|
-
|
230
|
-
representer.extend(Sync::Representer)
|
231
|
-
|
232
|
-
input_representer = mapper.new(self).extend(Sync::InputRepresenter)
|
233
|
-
|
234
|
-
representer.from_hash(input_representer.to_hash)
|
235
|
-
end
|
236
|
-
|
237
|
-
# FIXME: make AM optional.
|
238
|
-
require 'active_model'
|
239
|
-
include ActiveModel::Validations
|
240
|
-
|
241
|
-
# The Errors class is planned to replace AM::Errors. It provides proper nested error messages.
|
242
|
-
class Errors < ActiveModel::Errors
|
243
|
-
def messages
|
244
|
-
return super unless Reform.rails3_0?
|
245
|
-
self
|
246
|
-
end
|
247
|
-
|
248
|
-
# def each
|
249
|
-
# messages.each_key do |attribute|
|
250
|
-
# self[attribute].each { |error| yield attribute, Array.wrap(error) }
|
251
|
-
# end
|
252
|
-
# end
|
253
|
-
|
254
|
-
def merge!(errors, prefix=nil)
|
255
|
-
# TODO: merge into AM.
|
256
|
-
errors.messages.each do |field, msgs|
|
257
|
-
field = "#{prefix}.#{field}" if prefix
|
258
|
-
|
259
|
-
msgs = [msgs] if Reform.rails3_0? # DISCUSS: fix in #each?
|
260
|
-
|
261
|
-
msgs.each do |msg|
|
262
|
-
next if messages[field] and messages[field].include?(msg)
|
263
|
-
add(field, msg)
|
264
|
-
end # Forms now contains a plain errors hash. the errors for each item are still available in item.errors.
|
265
|
-
end
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
require "representable/hash/collection"
|
270
|
-
require 'active_model'
|
271
|
-
class Forms < Array # DISCUSS: this should be a Form subclass.
|
272
|
-
def initialize(ary, options)
|
273
|
-
super(ary)
|
274
|
-
@options = options
|
275
|
-
end
|
276
|
-
|
277
|
-
include Form::ValidateMethods
|
278
|
-
|
279
|
-
# TODO: make valid?(errors) the only public method.
|
280
|
-
def valid?
|
281
|
-
res= validate_cardinality & validate_items
|
282
|
-
end
|
283
|
-
|
284
|
-
def errors
|
285
|
-
@errors ||= Form::Errors.new(self)
|
286
|
-
end
|
287
|
-
|
288
|
-
# this gives us each { to_hash }
|
289
|
-
include Representable::Hash::Collection
|
290
|
-
items :parse_strategy => :sync, :instance => true
|
291
|
-
|
292
|
-
private
|
293
|
-
def validate_items
|
294
|
-
inject(true) do |res, form|
|
295
|
-
res = validate_for(form, res)
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
def validate_cardinality
|
300
|
-
return true unless @options[:cardinality]
|
301
|
-
# TODO: use AM's cardinality validator here.
|
302
|
-
res = size >= @options[:cardinality][:minimum].to_i
|
303
|
-
|
304
|
-
errors.add(:size, "#{@options[:as]} size is 0 but must be #{@options[:cardinality].inspect}") unless res
|
305
|
-
res
|
306
|
-
end
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
|
311
|
-
# Keeps values of the form fields. What's in here is to be displayed in the browser!
|
312
|
-
# we need this intermediate object to display both "original values" and new input from the form after submitting.
|
313
|
-
class Fields < OpenStruct
|
314
|
-
def initialize(properties, values={})
|
315
|
-
fields = properties.inject({}) { |hsh, attr| hsh.merge!(attr => nil) }
|
316
|
-
super(fields.merge!(values)) # TODO: stringify value keys!
|
317
|
-
end
|
318
|
-
end
|
319
|
-
|
320
|
-
def self.rails3_0?
|
321
|
-
::ActiveModel::VERSION::MAJOR == 3 and ::ActiveModel::VERSION::MINOR == 0
|
322
26
|
end
|
323
27
|
end
|