reform 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +3 -1
- data/CHANGES.md +11 -0
- data/README.md +74 -9
- data/TODO.md +1 -0
- data/database.sqlite3 +0 -0
- data/gemfiles/Gemfile.rails-3.1 +7 -0
- data/gemfiles/Gemfile.rails-3.2 +7 -0
- data/gemfiles/Gemfile.rails-4.0 +7 -0
- data/lib/reform/form.rb +93 -49
- data/lib/reform/form/active_record.rb +18 -2
- data/lib/reform/form/virtual_attributes.rb +24 -0
- data/lib/reform/representer.rb +30 -0
- data/lib/reform/version.rb +1 -1
- data/test/active_record_test.rb +67 -0
- data/test/nested_form_test.rb +7 -2
- data/test/reform_test.rb +74 -38
- data/test/test_helper.rb +3 -1
- metadata +27 -41
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b232b1d3bcc6dea9226b28873a60bba7a56772df
|
4
|
+
data.tar.gz: 4b987843362c2cc4ff3349304f0672182437a828
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1c7eefc137c219075f02c5ff89035fadb1868b7b693ac828b100f63621f55158f0dc05b6a497efc935193f670438a42675487ebacdd5dfacf9d0c8a990a53911
|
7
|
+
data.tar.gz: 70f13e49731d80f0645f4467fb5521cce5b34ee9fe7490b23f0b2d6f79f66630ecec4902a2d17d2758a1b861804ad437ce205f3fcba693a084274e89f6662139
|
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
h3. 0.2.1
|
2
|
+
|
3
|
+
* `ActiveRecord::i18n_scope` now returns `activerecord`.
|
4
|
+
* `Form#save` now calls save on the model in `ActiveRecord` context.
|
5
|
+
* `Form#model` is public now.
|
6
|
+
* Introduce `:empty` to have empty fields that are accessible for validation and processing, only.
|
7
|
+
* Introduce `:virtual` for read-only fields the are like `:empty` but initially read from the decorated model.
|
8
|
+
* Fix uniqueness validation with `Composition` form.
|
9
|
+
* Move `setup` and `save` logic into respective representer classes. This might break your code in case you overwrite private reform classes.
|
10
|
+
|
11
|
+
|
1
12
|
h3. 0.2.0
|
2
13
|
|
3
14
|
* Added nested property and collection for `has_one` and `has_many` relationships. . Note that this currently works only 1-level deep.
|
data/README.md
CHANGED
@@ -104,6 +104,11 @@ To push the incoming data to the models directly, call `#save` without the block
|
|
104
104
|
# by calling @form.song.title= and @form.song.length=.
|
105
105
|
```
|
106
106
|
|
107
|
+
Think of `@form.save` as a sync operation where the submitted data is written to your models using public setters.
|
108
|
+
|
109
|
+
Note that this does _not_ call `save` on your models per default: this only happens in an ActiveRecord environment or when `Form::ActiveRecord` is mixed in (learn more [here](https://github.com/apotonick/reform#activerecord-compatibility)).
|
110
|
+
|
111
|
+
|
107
112
|
|
108
113
|
## Nesting Forms: 1-1 Relations
|
109
114
|
|
@@ -165,7 +170,7 @@ Or use something like `#fields_for` in a Rails environment.
|
|
165
170
|
= f.text_field :title
|
166
171
|
= f.text_field :length
|
167
172
|
|
168
|
-
= fields_for :artist do |a|
|
173
|
+
= f.fields_for :artist do |a|
|
169
174
|
= a.text_field :name
|
170
175
|
```
|
171
176
|
|
@@ -233,7 +238,7 @@ However, `#fields_for` works just fine, again.
|
|
233
238
|
= form_for @form |f|
|
234
239
|
= f.text_field :title
|
235
240
|
|
236
|
-
= fields_for :songs do |s|
|
241
|
+
= f.fields_for :songs do |s|
|
237
242
|
= s.text_field :title
|
238
243
|
```
|
239
244
|
|
@@ -247,8 +252,8 @@ The block form of `#save` will expose the data structures already discussed.
|
|
247
252
|
data.songs.first.title #=> "Hungry Like The Wolf"
|
248
253
|
|
249
254
|
nested #=> {title: "Rio"
|
250
|
-
|
251
|
-
|
255
|
+
# songs: [{title: "Hungry Like The Wolf"},
|
256
|
+
# {title: "Last Chance On The Stairways"}]
|
252
257
|
end
|
253
258
|
```
|
254
259
|
|
@@ -304,9 +309,9 @@ Here's how the block parameters look like.
|
|
304
309
|
data.city #=> "London"
|
305
310
|
|
306
311
|
nested #=> {
|
307
|
-
|
308
|
-
|
309
|
-
|
312
|
+
# song: {title: "Rio"}
|
313
|
+
# label: {city: "London"}
|
314
|
+
# }
|
310
315
|
end
|
311
316
|
```
|
312
317
|
|
@@ -329,6 +334,58 @@ end
|
|
329
334
|
data.written_at #=> <DateTime XXX>
|
330
335
|
```
|
331
336
|
|
337
|
+
## Virtual Attributes
|
338
|
+
|
339
|
+
Virtual fields come in handy when there's no direct mapping to a model attribute or when you plan on displaying but not processing a value.
|
340
|
+
|
341
|
+
|
342
|
+
### Empty Fields
|
343
|
+
|
344
|
+
Often, fields like `password_confirmation` shouldn't be retrieved from the model. Reform comes with the `:empty` option for that.
|
345
|
+
|
346
|
+
```ruby
|
347
|
+
class PasswordForm < Reform::Form
|
348
|
+
property :password
|
349
|
+
property :password_confirmation, :empty => true
|
350
|
+
```
|
351
|
+
|
352
|
+
Here, the model won't be queried for a `password_confirmation` field when creating and rendering the form. Likewise, when saving the form, the input value is not written to the decorated model. It is only readable in validations and when saving the form.
|
353
|
+
|
354
|
+
```ruby
|
355
|
+
form.validate("password" => "123", "password_confirmation" => "321")
|
356
|
+
|
357
|
+
form.password_confirmation #=> "321"
|
358
|
+
```
|
359
|
+
|
360
|
+
The nested hash in the block-`#save` provides the same value.
|
361
|
+
|
362
|
+
```ruby
|
363
|
+
form.save do |f, nested|
|
364
|
+
nested[:password_confirmation] #=> "321"
|
365
|
+
```
|
366
|
+
|
367
|
+
### Read-Only Fields
|
368
|
+
|
369
|
+
Almost identical, the `:virtual` option makes fields read-only. Say you wanna show a value, but not process it after submission, this option is your friend.
|
370
|
+
|
371
|
+
```ruby
|
372
|
+
class ProfileForm < Reform::Form
|
373
|
+
property :country, :virtual => true
|
374
|
+
```
|
375
|
+
|
376
|
+
This time reform will query the model for the value by calling `model.country`.
|
377
|
+
|
378
|
+
You want to use this to display an initial value or to further process this field with JavaScript. However, after submission, the field is no longer considered: it won't be written to the model when saving.
|
379
|
+
|
380
|
+
It is still readable in the nested hash and through the form itself.
|
381
|
+
|
382
|
+
```ruby
|
383
|
+
form.save do |f, nested|
|
384
|
+
nested[:country] #=> "Australia"
|
385
|
+
|
386
|
+
f.country #=> "Australia"
|
387
|
+
```
|
388
|
+
|
332
389
|
|
333
390
|
## Agnosticism: Mapping Data
|
334
391
|
|
@@ -348,7 +405,15 @@ Check out [@gogogarret](https://twitter.com/GoGoGarrett/)'s [sample Rails app](h
|
|
348
405
|
Rails and Reform work out-of-the-box. If you're using Rails but for some reason wanna use the pure reform, `require reform/form`, only.
|
349
406
|
|
350
407
|
|
351
|
-
##
|
408
|
+
## ActiveRecord Compatibility
|
409
|
+
|
410
|
+
Reform provides the following `ActiveRecord` specific features. They're mixed in automatically in a Rails/AR setup.
|
411
|
+
|
412
|
+
* Uniqueness validations. Use `validates_uniqueness_of` in your form.
|
413
|
+
* Calling `Form#save` will explicitely call `save` on your model (added in 0.2.1) which will usually trigger a database insertion or update.
|
414
|
+
|
415
|
+
|
416
|
+
## ActiveModel Compliance
|
352
417
|
|
353
418
|
Forms in Reform can easily be made ActiveModel-compliant.
|
354
419
|
|
@@ -366,7 +431,7 @@ If you're not happy with the `model_name` result, configure it manually.
|
|
366
431
|
class CoverSongForm < Reform::Form
|
367
432
|
include Reform::Form::ActiveModel
|
368
433
|
|
369
|
-
model :
|
434
|
+
model :song
|
370
435
|
end
|
371
436
|
```
|
372
437
|
|
data/TODO.md
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
with @model.title == "Little Green Car" and validate({}) the form is still valid (as we "have" a valid title). is that what we want?
|
3
3
|
|
4
4
|
* document Form#to_hash and Form#to_nested_hash (e.g. with OpenStruct composition to make it a very simple form)
|
5
|
+
* document getter: and representer_exec:
|
5
6
|
|
6
7
|
* allow :as to rename nested forms
|
7
8
|
* make #nested_forms easier
|
data/database.sqlite3
CHANGED
Binary file
|
data/lib/reform/form.rb
CHANGED
@@ -17,6 +17,8 @@ module Reform
|
|
17
17
|
extend Forwardable
|
18
18
|
|
19
19
|
def property(name, options={}, &block)
|
20
|
+
process_options(name, options, &block)
|
21
|
+
|
20
22
|
definition = representer_class.property(name, options, &block)
|
21
23
|
setup_form_definition(definition) if block_given?
|
22
24
|
create_accessor(name)
|
@@ -47,6 +49,9 @@ module Reform
|
|
47
49
|
def create_accessor(name)
|
48
50
|
delegate [name, "#{name}="] => :fields
|
49
51
|
end
|
52
|
+
|
53
|
+
def process_options(name, options) # DISCUSS: do we need that hook?
|
54
|
+
end
|
50
55
|
end
|
51
56
|
extend PropertyMethods
|
52
57
|
|
@@ -89,12 +94,15 @@ module Reform
|
|
89
94
|
end
|
90
95
|
|
91
96
|
# Use representer to return current key-value form hash.
|
92
|
-
def to_hash(*)
|
93
|
-
mapper.new(self).to_hash
|
97
|
+
def to_hash(*args)
|
98
|
+
mapper.new(self).to_hash(*args)
|
94
99
|
end
|
95
100
|
|
101
|
+
require "active_support/hash_with_indifferent_access" # DISCUSS: replace?
|
96
102
|
def to_nested_hash
|
97
|
-
|
103
|
+
map = mapper.new(self)
|
104
|
+
|
105
|
+
ActiveSupport::HashWithIndifferentAccess.new(map.to_hash)
|
98
106
|
end
|
99
107
|
|
100
108
|
def from_hash(params, *args)
|
@@ -105,76 +113,112 @@ module Reform
|
|
105
113
|
@errors ||= Errors.new(self)
|
106
114
|
end
|
107
115
|
|
108
|
-
|
109
|
-
attr_accessor :model, :fields
|
116
|
+
attr_accessor :model
|
110
117
|
|
111
|
-
|
112
|
-
|
113
|
-
end
|
118
|
+
private
|
119
|
+
attr_accessor :fields
|
114
120
|
|
115
121
|
def mapper
|
116
122
|
self.class.representer_class
|
117
123
|
end
|
118
124
|
|
119
125
|
def setup_fields(model)
|
120
|
-
representer =
|
121
|
-
|
122
|
-
setup_nested_forms(representer)
|
126
|
+
representer = mapper.new(model).extend(Setup::Representer)
|
123
127
|
|
124
128
|
create_fields(representer.fields, representer.to_hash)
|
125
129
|
end
|
126
130
|
|
127
|
-
|
128
|
-
# TODO: we should simply give a FormBuilder instance to representer.to_hash that does this kind of mapping:
|
129
|
-
# after this, Fields contains scalars and Form instances and Forms with form instances.
|
130
|
-
representer.nested_forms do |attr, model|
|
131
|
-
form_class = attr.options[:form]
|
132
|
-
|
133
|
-
attr.options.merge!(
|
134
|
-
:getter => lambda do |*|
|
135
|
-
nested_model = send(attr.getter) # decorated.hit # TODO: use bin.get
|
131
|
+
#representer.to_hash override: { write: lambda { |doc, value| } }
|
136
132
|
|
137
|
-
|
138
|
-
|
139
|
-
else
|
140
|
-
form_class.new(nested_model)
|
141
|
-
end
|
142
|
-
end,
|
143
|
-
:instance => false, # that's how we make it non-typed?.
|
144
|
-
)
|
145
|
-
end
|
133
|
+
# DISCUSS: this would be cool in representable:
|
134
|
+
# to_hash(hit: lambda { |value| form_class.new(..) })
|
146
135
|
|
147
|
-
|
136
|
+
# steps:
|
137
|
+
# - bin.get
|
138
|
+
# - map that: Forms.new( orig ) <-- override only this in representable (how?)
|
139
|
+
# - mapped.to_hash
|
148
140
|
|
149
|
-
# DISCUSS: this would be cool in representable:
|
150
|
-
# to_hash(hit: lambda { |value| form_class.new(..) })
|
151
|
-
|
152
|
-
# steps:
|
153
|
-
# - bin.get
|
154
|
-
# - map that: Forms.new( orig ) <-- override only this in representable (how?)
|
155
|
-
# - mapped.to_hash
|
156
|
-
end
|
157
141
|
|
158
142
|
def create_fields(field_names, fields)
|
159
143
|
Fields.new(field_names, fields)
|
160
144
|
end
|
161
145
|
|
162
|
-
def save_to_models
|
163
|
-
representer = mapper.new(model)
|
164
146
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
147
|
+
require "reform/form/virtual_attributes"
|
148
|
+
|
149
|
+
# Mechanics for setting up initial Field values.
|
150
|
+
module Setup
|
151
|
+
module Representer
|
152
|
+
include Reform::Representer::WithOptions
|
153
|
+
include EmptyAttributesOptions
|
169
154
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
)
|
155
|
+
def to_hash(*)
|
156
|
+
setup_nested_forms
|
157
|
+
|
158
|
+
super # TODO: allow something like super(:exclude => empty_fields)
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
def setup_nested_forms
|
163
|
+
nested_forms do |attr, model|
|
164
|
+
form_class = attr.options[:form]
|
165
|
+
|
166
|
+
attr.options.merge!(
|
167
|
+
:getter => lambda do |*|
|
168
|
+
nested_model = send(attr.getter) # decorated.hit # TODO: use bin.get
|
169
|
+
|
170
|
+
if attr.options[:form_collection]
|
171
|
+
Forms.new(nested_model.collect { |mdl| form_class.new(mdl)})
|
172
|
+
else
|
173
|
+
form_class.new(nested_model)
|
174
|
+
end
|
175
|
+
end,
|
176
|
+
:instance => false, # that's how we make it non-typed?.
|
177
|
+
)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Mechanics for writing input to model.
|
184
|
+
module Sync
|
185
|
+
# Writes input to model.
|
186
|
+
module Representer
|
187
|
+
def from_hash(*)
|
188
|
+
nested_forms do |attr, model|
|
189
|
+
attr.options.merge!(
|
190
|
+
:decorator => attr.options[:form].representer_class
|
191
|
+
)
|
192
|
+
|
193
|
+
if attr.options[:form_collection]
|
194
|
+
attr.options.merge!(
|
195
|
+
:collection => true
|
196
|
+
)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
super
|
174
201
|
end
|
175
202
|
end
|
176
203
|
|
177
|
-
|
204
|
+
# Transforms form input into what actually gets written to model.
|
205
|
+
module InputRepresenter
|
206
|
+
include Reform::Representer::WithOptions
|
207
|
+
# TODO: make dynamic.
|
208
|
+
include EmptyAttributesOptions
|
209
|
+
include ReadonlyAttributesOptions
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
|
214
|
+
def save_to_models # TODO: rename to #sync_models
|
215
|
+
representer = mapper.new(model)
|
216
|
+
|
217
|
+
representer.extend(Sync::Representer)
|
218
|
+
|
219
|
+
input_representer = mapper.new(self).extend(Sync::InputRepresenter)
|
220
|
+
|
221
|
+
representer.from_hash(input_representer.to_hash)
|
178
222
|
end
|
179
223
|
|
180
224
|
# FIXME: make AM optional.
|
@@ -11,24 +11,40 @@ class Reform::Form
|
|
11
11
|
def validates_uniqueness_of(attribute)
|
12
12
|
validates_with UniquenessValidator, :attributes => [attribute]
|
13
13
|
end
|
14
|
+
def i18n_scope
|
15
|
+
:activerecord
|
16
|
+
end
|
14
17
|
end
|
15
18
|
|
16
19
|
class UniquenessValidator < ::ActiveRecord::Validations::UniquenessValidator
|
17
20
|
# when calling validates it should create the Vali instance already and set @klass there! # TODO: fix this in AM.
|
18
21
|
def validate(form)
|
19
22
|
property = attributes.first
|
20
|
-
#model_name = form.send(:model).class.model_for_property(property)
|
21
23
|
|
22
24
|
# here is the thing: why does AM::UniquenessValidator require a filled-out record to work properly? also, why do we need to set
|
23
25
|
# the class? it would be way easier to pass #validate a hash of attributes and get back an errors hash.
|
24
26
|
# the class for the finder could either be infered from the record or set in the validator instance itself in the call to ::validates.
|
25
|
-
record = form.
|
27
|
+
record = form.model_for_property(property)
|
26
28
|
record.send("#{property}=", form.send(property))
|
29
|
+
|
27
30
|
@klass = record.class # this is usually done in the super-sucky #setup method.
|
28
31
|
super(record).tap do |res|
|
29
32
|
form.errors.add(property, record.errors.first.last) if record.errors.present?
|
30
33
|
end
|
31
34
|
end
|
32
35
|
end
|
36
|
+
|
37
|
+
def save(*)
|
38
|
+
super.tap do
|
39
|
+
model.save # DISCUSS: should we implement nested saving here?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def model_for_property(name)
|
44
|
+
return model unless is_a?(Reform::Form::Composition) # i am too lazy for proper inheritance. there should be a ActiveRecord::Composition that handles this.
|
45
|
+
|
46
|
+
model_name = mapper.representable_attrs[name].options[:on]
|
47
|
+
send(model_name)
|
48
|
+
end
|
33
49
|
end
|
34
50
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Reform
|
2
|
+
class Form
|
3
|
+
# TODO: this should be in Representer namespace.
|
4
|
+
module EmptyAttributesOptions
|
5
|
+
def options
|
6
|
+
empty_fields = representable_attrs.
|
7
|
+
find_all { |d| d.options[:empty] }.
|
8
|
+
collect { |d| d.name.to_sym }
|
9
|
+
|
10
|
+
super.exclude!(empty_fields)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ReadonlyAttributesOptions
|
15
|
+
def options
|
16
|
+
readonly_fields = representable_attrs.
|
17
|
+
find_all { |d| d.options[:virtual] }.
|
18
|
+
collect { |d| d.name.to_sym }
|
19
|
+
|
20
|
+
super.exclude!(readonly_fields)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/reform/representer.rb
CHANGED
@@ -3,6 +3,36 @@ require 'representable/decorator'
|
|
3
3
|
|
4
4
|
module Reform
|
5
5
|
class Representer < Representable::Decorator
|
6
|
+
# Invokes #to_hash and/or #from_hash with #options. This provides a hook for other
|
7
|
+
# modules to add options for the representational process.
|
8
|
+
module WithOptions
|
9
|
+
class Options < Hash
|
10
|
+
def include!(names)
|
11
|
+
self[:include] ||= []
|
12
|
+
self[:include] += names
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def exclude!(names)
|
17
|
+
self[:exclude] ||= []
|
18
|
+
self[:exclude] += names
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def options
|
24
|
+
Options.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash(*)
|
28
|
+
super(options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def from_hash(*)
|
32
|
+
super(options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
6
36
|
include Representable::Hash
|
7
37
|
|
8
38
|
# Returns hash of all property names.
|
data/lib/reform/version.rb
CHANGED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ActiveRecordTest < MiniTest::Spec
|
4
|
+
let (:form) do
|
5
|
+
require 'reform/active_record'
|
6
|
+
Class.new(Reform::Form) do
|
7
|
+
include Reform::Form::ActiveRecord
|
8
|
+
model :artist
|
9
|
+
|
10
|
+
property :name
|
11
|
+
property :created_at
|
12
|
+
|
13
|
+
validates_uniqueness_of :name
|
14
|
+
validates :created_at, :presence => true # have another property to test if we mix up.
|
15
|
+
end.
|
16
|
+
new(Artist.new)
|
17
|
+
end
|
18
|
+
|
19
|
+
it { form.class.i18n_scope.must_equal :activerecord }
|
20
|
+
|
21
|
+
describe "UniquenessValidator" do
|
22
|
+
# ActiveRecord::Schema.define do
|
23
|
+
# create_table :artists do |table|
|
24
|
+
# table.column :name, :string
|
25
|
+
# table.timestamps
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
# Artist.new(:name => "Racer X").save
|
29
|
+
|
30
|
+
it "allows accessing the database" do
|
31
|
+
end
|
32
|
+
|
33
|
+
it "is valid when name is unique" do
|
34
|
+
form.validate({"name" => "Paul Gilbert", "created_at" => "November 6, 1966"}).must_equal true
|
35
|
+
end
|
36
|
+
|
37
|
+
it "is invalid and shows error when taken" do
|
38
|
+
Artist.create(:name => "Racer X")
|
39
|
+
|
40
|
+
form.validate({"name" => "Racer X"}).must_equal false
|
41
|
+
form.errors.messages.must_equal({:name=>["has already been taken"], :created_at => ["can't be blank"]})
|
42
|
+
end
|
43
|
+
|
44
|
+
it "works with Composition" do
|
45
|
+
form = Class.new(Reform::Form) do
|
46
|
+
include Reform::Form::ActiveRecord
|
47
|
+
include Reform::Form::Composition
|
48
|
+
|
49
|
+
property :name, :on => :artist
|
50
|
+
validates_uniqueness_of :name
|
51
|
+
end.new(:artist => Artist.new)
|
52
|
+
|
53
|
+
Artist.create(:name => "Bad Religion")
|
54
|
+
form.validate("name" => "Bad Religion").must_equal false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#save" do
|
59
|
+
# TODO: test 1-n?
|
60
|
+
it "calls AR#save" do
|
61
|
+
Artist.delete_all
|
62
|
+
form.validate("name" => "Bad Religion")
|
63
|
+
form.save
|
64
|
+
Artist.where(:name => "Bad Religion").size.must_equal 1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/test/nested_form_test.rb
CHANGED
@@ -82,14 +82,19 @@ class NestedFormTest < MiniTest::Spec
|
|
82
82
|
frm.songs.first.must_be_kind_of Reform::Form
|
83
83
|
end
|
84
84
|
|
85
|
-
it "returns nested hash with
|
85
|
+
it "returns nested hash with indifferent access" do
|
86
86
|
nested = nil
|
87
87
|
|
88
88
|
form.save do |hash, nested_hash|
|
89
89
|
nested = nested_hash
|
90
90
|
end
|
91
91
|
|
92
|
-
nested.must_equal(
|
92
|
+
nested.must_equal("title"=>"Second Heat", "hit"=>{"title"=>"Sacrifice"}, "songs"=>[{"title"=>"Scarified"}])
|
93
|
+
|
94
|
+
nested[:title].must_equal "Second Heat"
|
95
|
+
nested["title"].must_equal "Second Heat"
|
96
|
+
nested[:hit][:title].must_equal "Sacrifice"
|
97
|
+
nested["hit"]["title"].must_equal "Sacrifice"
|
93
98
|
end
|
94
99
|
|
95
100
|
it "pushes data to models" do
|
data/test/reform_test.rb
CHANGED
@@ -15,6 +15,19 @@ class RepresenterTest < MiniTest::Spec
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
class WithOptionsTest < MiniTest::Spec
|
19
|
+
subject { Reform::Representer::WithOptions::Options.new }
|
20
|
+
|
21
|
+
it { subject.must_equal({}) }
|
22
|
+
it { subject.exclude!([:id, :title]).must_equal(:exclude => [:id, :title]) }
|
23
|
+
it do
|
24
|
+
subject.exclude!([:id, :title])
|
25
|
+
subject.exclude!([:id, :name])
|
26
|
+
subject.must_equal(:exclude => [:id, :title, :id, :name])
|
27
|
+
end
|
28
|
+
it { subject.include!([:id, :title]).must_equal(:include => [:id, :title]) }
|
29
|
+
end
|
30
|
+
|
18
31
|
class FieldsTest < MiniTest::Spec
|
19
32
|
describe "#new" do
|
20
33
|
it "accepts list of properties" do
|
@@ -145,42 +158,6 @@ class ReformTest < ReformSpec
|
|
145
158
|
end
|
146
159
|
end
|
147
160
|
|
148
|
-
describe "UniquenessValidator" do
|
149
|
-
# ActiveRecord::Schema.define do
|
150
|
-
# create_table :artists do |table|
|
151
|
-
# table.column :name, :string
|
152
|
-
# table.timestamps
|
153
|
-
# end
|
154
|
-
# end
|
155
|
-
# Artist.new(:name => "Racer X").save
|
156
|
-
|
157
|
-
let (:form) do
|
158
|
-
require 'reform/active_record'
|
159
|
-
Class.new(Reform::Form) do
|
160
|
-
include Reform::Form::ActiveRecord
|
161
|
-
model :artist
|
162
|
-
|
163
|
-
property :name
|
164
|
-
property :created_at
|
165
|
-
|
166
|
-
validates_uniqueness_of :name
|
167
|
-
validates :created_at, :presence => true # have another property to test if we mix up.
|
168
|
-
end.
|
169
|
-
new(Artist.new)
|
170
|
-
end
|
171
|
-
|
172
|
-
it "allows accessing the database" do
|
173
|
-
end
|
174
|
-
|
175
|
-
it "is valid when name is unique" do
|
176
|
-
form.validate({"name" => "Paul Gilbert", "created_at" => "November 6, 1966"}).must_equal true
|
177
|
-
end
|
178
|
-
|
179
|
-
it "is invalid and shows error when taken" do
|
180
|
-
form.validate({"name" => "Racer X"}).must_equal false
|
181
|
-
form.errors.messages.must_equal({:name=>["has already been taken"], :created_at => ["can't be blank"]})
|
182
|
-
end
|
183
|
-
end
|
184
161
|
end
|
185
162
|
|
186
163
|
describe "#errors" do
|
@@ -227,13 +204,72 @@ class ReformTest < ReformSpec
|
|
227
204
|
hash = map
|
228
205
|
end
|
229
206
|
|
230
|
-
hash.must_equal({
|
207
|
+
hash.must_equal({"name"=>"Diesel Boy"})
|
231
208
|
end
|
232
209
|
end
|
233
210
|
end
|
234
211
|
|
235
212
|
|
236
213
|
describe "#model" do
|
237
|
-
it { form.
|
214
|
+
it { form.model.must_equal comp }
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
class EmptyAttributesTest < MiniTest::Spec
|
219
|
+
Credentials = Struct.new(:password)
|
220
|
+
|
221
|
+
class PasswordForm < Reform::Form
|
222
|
+
property :password
|
223
|
+
property :password_confirmation, :empty => true
|
238
224
|
end
|
225
|
+
|
226
|
+
let (:cred) { Credentials.new }
|
227
|
+
let (:form) { PasswordForm.new(cred) }
|
228
|
+
|
229
|
+
it { form }
|
230
|
+
|
231
|
+
it {
|
232
|
+
|
233
|
+
form.validate("password" => "123", "password_confirmation" => "321")
|
234
|
+
form.password.must_equal "123"
|
235
|
+
form.password_confirmation.must_equal "321"
|
236
|
+
|
237
|
+
form.save
|
238
|
+
cred.password.must_equal "123"
|
239
|
+
|
240
|
+
hash = {}
|
241
|
+
form.save do |f, nested|
|
242
|
+
hash = nested
|
243
|
+
end
|
244
|
+
|
245
|
+
hash.must_equal("password"=> "123", "password_confirmation" => "321")
|
246
|
+
}
|
239
247
|
end
|
248
|
+
|
249
|
+
class ReadonlyAttributesTest < MiniTest::Spec
|
250
|
+
Location = Struct.new(:country)
|
251
|
+
|
252
|
+
class LocationForm < Reform::Form
|
253
|
+
property :country, :virtual => true # read_only: true
|
254
|
+
end
|
255
|
+
|
256
|
+
let (:loc) { Location.new("Australia") }
|
257
|
+
let (:form) { LocationForm.new(loc) }
|
258
|
+
|
259
|
+
it { form.country.must_equal "Australia" }
|
260
|
+
it do
|
261
|
+
form.validate("country" => "Germany") # this usually won't change when submitting.
|
262
|
+
form.country.must_equal "Germany"
|
263
|
+
|
264
|
+
|
265
|
+
form.save
|
266
|
+
loc.country.must_equal "Australia" # the writer wasn't called.
|
267
|
+
|
268
|
+
hash = {}
|
269
|
+
form.save do |f, nested|
|
270
|
+
hash = nested
|
271
|
+
end
|
272
|
+
|
273
|
+
hash.must_equal("country"=> "Germany")
|
274
|
+
end
|
275
|
+
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reform
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Nick Sutterer
|
@@ -10,12 +9,11 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date: 2013-
|
12
|
+
date: 2013-10-30 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: representable
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
18
|
- - ~>
|
21
19
|
- !ruby/object:Gem::Version
|
@@ -23,7 +21,6 @@ dependencies:
|
|
23
21
|
type: :runtime
|
24
22
|
prerelease: false
|
25
23
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
24
|
requirements:
|
28
25
|
- - ~>
|
29
26
|
- !ruby/object:Gem::Version
|
@@ -31,23 +28,20 @@ dependencies:
|
|
31
28
|
- !ruby/object:Gem::Dependency
|
32
29
|
name: activemodel
|
33
30
|
requirement: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
31
|
requirements:
|
36
|
-
- -
|
32
|
+
- - '>='
|
37
33
|
- !ruby/object:Gem::Version
|
38
34
|
version: '0'
|
39
35
|
type: :runtime
|
40
36
|
prerelease: false
|
41
37
|
version_requirements: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
38
|
requirements:
|
44
|
-
- -
|
39
|
+
- - '>='
|
45
40
|
- !ruby/object:Gem::Version
|
46
41
|
version: '0'
|
47
42
|
- !ruby/object:Gem::Dependency
|
48
43
|
name: bundler
|
49
44
|
requirement: !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
45
|
requirements:
|
52
46
|
- - ~>
|
53
47
|
- !ruby/object:Gem::Version
|
@@ -55,7 +49,6 @@ dependencies:
|
|
55
49
|
type: :development
|
56
50
|
prerelease: false
|
57
51
|
version_requirements: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
52
|
requirements:
|
60
53
|
- - ~>
|
61
54
|
- !ruby/object:Gem::Version
|
@@ -63,97 +56,85 @@ dependencies:
|
|
63
56
|
- !ruby/object:Gem::Dependency
|
64
57
|
name: rake
|
65
58
|
requirement: !ruby/object:Gem::Requirement
|
66
|
-
none: false
|
67
59
|
requirements:
|
68
|
-
- -
|
60
|
+
- - '>='
|
69
61
|
- !ruby/object:Gem::Version
|
70
62
|
version: 10.1.0
|
71
63
|
type: :development
|
72
64
|
prerelease: false
|
73
65
|
version_requirements: !ruby/object:Gem::Requirement
|
74
|
-
none: false
|
75
66
|
requirements:
|
76
|
-
- -
|
67
|
+
- - '>='
|
77
68
|
- !ruby/object:Gem::Version
|
78
69
|
version: 10.1.0
|
79
70
|
- !ruby/object:Gem::Dependency
|
80
71
|
name: minitest
|
81
72
|
requirement: !ruby/object:Gem::Requirement
|
82
|
-
none: false
|
83
73
|
requirements:
|
84
|
-
- -
|
74
|
+
- - '>='
|
85
75
|
- !ruby/object:Gem::Version
|
86
76
|
version: '0'
|
87
77
|
type: :development
|
88
78
|
prerelease: false
|
89
79
|
version_requirements: !ruby/object:Gem::Requirement
|
90
|
-
none: false
|
91
80
|
requirements:
|
92
|
-
- -
|
81
|
+
- - '>='
|
93
82
|
- !ruby/object:Gem::Version
|
94
83
|
version: '0'
|
95
84
|
- !ruby/object:Gem::Dependency
|
96
85
|
name: activerecord
|
97
86
|
requirement: !ruby/object:Gem::Requirement
|
98
|
-
none: false
|
99
87
|
requirements:
|
100
|
-
- -
|
88
|
+
- - '>='
|
101
89
|
- !ruby/object:Gem::Version
|
102
90
|
version: '0'
|
103
91
|
type: :development
|
104
92
|
prerelease: false
|
105
93
|
version_requirements: !ruby/object:Gem::Requirement
|
106
|
-
none: false
|
107
94
|
requirements:
|
108
|
-
- -
|
95
|
+
- - '>='
|
109
96
|
- !ruby/object:Gem::Version
|
110
97
|
version: '0'
|
111
98
|
- !ruby/object:Gem::Dependency
|
112
99
|
name: sqlite3
|
113
100
|
requirement: !ruby/object:Gem::Requirement
|
114
|
-
none: false
|
115
101
|
requirements:
|
116
|
-
- -
|
102
|
+
- - '>='
|
117
103
|
- !ruby/object:Gem::Version
|
118
104
|
version: '0'
|
119
105
|
type: :development
|
120
106
|
prerelease: false
|
121
107
|
version_requirements: !ruby/object:Gem::Requirement
|
122
|
-
none: false
|
123
108
|
requirements:
|
124
|
-
- -
|
109
|
+
- - '>='
|
125
110
|
- !ruby/object:Gem::Version
|
126
111
|
version: '0'
|
127
112
|
- !ruby/object:Gem::Dependency
|
128
113
|
name: virtus
|
129
114
|
requirement: !ruby/object:Gem::Requirement
|
130
|
-
none: false
|
131
115
|
requirements:
|
132
|
-
- -
|
116
|
+
- - '>='
|
133
117
|
- !ruby/object:Gem::Version
|
134
118
|
version: '0'
|
135
119
|
type: :development
|
136
120
|
prerelease: false
|
137
121
|
version_requirements: !ruby/object:Gem::Requirement
|
138
|
-
none: false
|
139
122
|
requirements:
|
140
|
-
- -
|
123
|
+
- - '>='
|
141
124
|
- !ruby/object:Gem::Version
|
142
125
|
version: '0'
|
143
126
|
- !ruby/object:Gem::Dependency
|
144
127
|
name: rails
|
145
128
|
requirement: !ruby/object:Gem::Requirement
|
146
|
-
none: false
|
147
129
|
requirements:
|
148
|
-
- -
|
130
|
+
- - '>='
|
149
131
|
- !ruby/object:Gem::Version
|
150
132
|
version: '0'
|
151
133
|
type: :development
|
152
134
|
prerelease: false
|
153
135
|
version_requirements: !ruby/object:Gem::Requirement
|
154
|
-
none: false
|
155
136
|
requirements:
|
156
|
-
- -
|
137
|
+
- - '>='
|
157
138
|
- !ruby/object:Gem::Version
|
158
139
|
version: '0'
|
159
140
|
description: Freeing your AR models from form logic.
|
@@ -174,6 +155,9 @@ files:
|
|
174
155
|
- TODO.md
|
175
156
|
- database.sqlite3
|
176
157
|
- gemfiles/Gemfile.rails-3.0
|
158
|
+
- gemfiles/Gemfile.rails-3.1
|
159
|
+
- gemfiles/Gemfile.rails-3.2
|
160
|
+
- gemfiles/Gemfile.rails-4.0
|
177
161
|
- lib/reform.rb
|
178
162
|
- lib/reform/active_record.rb
|
179
163
|
- lib/reform/composition.rb
|
@@ -182,11 +166,13 @@ files:
|
|
182
166
|
- lib/reform/form/active_record.rb
|
183
167
|
- lib/reform/form/coercion.rb
|
184
168
|
- lib/reform/form/composition.rb
|
169
|
+
- lib/reform/form/virtual_attributes.rb
|
185
170
|
- lib/reform/rails.rb
|
186
171
|
- lib/reform/representer.rb
|
187
172
|
- lib/reform/version.rb
|
188
173
|
- reform.gemspec
|
189
174
|
- test/active_model_test.rb
|
175
|
+
- test/active_record_test.rb
|
190
176
|
- test/coercion_test.rb
|
191
177
|
- test/composition_test.rb
|
192
178
|
- test/dummy/Rakefile
|
@@ -221,31 +207,31 @@ files:
|
|
221
207
|
homepage: ''
|
222
208
|
licenses:
|
223
209
|
- MIT
|
210
|
+
metadata: {}
|
224
211
|
post_install_message:
|
225
212
|
rdoc_options: []
|
226
213
|
require_paths:
|
227
214
|
- lib
|
228
215
|
required_ruby_version: !ruby/object:Gem::Requirement
|
229
|
-
none: false
|
230
216
|
requirements:
|
231
|
-
- -
|
217
|
+
- - '>='
|
232
218
|
- !ruby/object:Gem::Version
|
233
219
|
version: '0'
|
234
220
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
235
|
-
none: false
|
236
221
|
requirements:
|
237
|
-
- -
|
222
|
+
- - '>='
|
238
223
|
- !ruby/object:Gem::Version
|
239
224
|
version: '0'
|
240
225
|
requirements: []
|
241
226
|
rubyforge_project:
|
242
|
-
rubygems_version:
|
227
|
+
rubygems_version: 2.0.3
|
243
228
|
signing_key:
|
244
|
-
specification_version:
|
229
|
+
specification_version: 4
|
245
230
|
summary: Decouples your models from form by giving you form objects with validation,
|
246
231
|
presentation, workflows and security.
|
247
232
|
test_files:
|
248
233
|
- test/active_model_test.rb
|
234
|
+
- test/active_record_test.rb
|
249
235
|
- test/coercion_test.rb
|
250
236
|
- test/composition_test.rb
|
251
237
|
- test/dummy/Rakefile
|