reform 1.1.1 → 1.2.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/CHANGES.md +35 -1
- data/Gemfile +1 -1
- data/README.md +83 -21
- data/TODO.md +8 -0
- data/database.sqlite3 +0 -0
- data/gemfiles/Gemfile.rails-4.0 +1 -0
- data/lib/reform.rb +4 -2
- data/lib/reform/active_record.rb +2 -1
- data/lib/reform/composition.rb +2 -2
- data/lib/reform/contract.rb +24 -7
- data/lib/reform/contract/setup.rb +21 -9
- data/lib/reform/contract/validate.rb +0 -6
- data/lib/reform/form.rb +6 -8
- data/lib/reform/form/active_model.rb +3 -2
- data/lib/reform/form/active_model/model_validations.rb +13 -1
- data/lib/reform/form/active_record.rb +1 -7
- data/lib/reform/form/changed.rb +9 -0
- data/lib/reform/form/json.rb +13 -0
- data/lib/reform/form/model_reflections.rb +18 -0
- data/lib/reform/form/save.rb +25 -3
- data/lib/reform/form/scalar.rb +4 -2
- data/lib/reform/form/sync.rb +82 -12
- data/lib/reform/form/validate.rb +38 -0
- data/lib/reform/rails.rb +1 -1
- data/lib/reform/representer.rb +14 -23
- data/lib/reform/schema.rb +23 -0
- data/lib/reform/twin.rb +20 -0
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +2 -2
- data/test/active_model_test.rb +2 -2
- data/test/active_record_test.rb +7 -4
- data/test/changed_test.rb +69 -0
- data/test/custom_validation_test.rb +47 -0
- data/test/deserialize_test.rb +2 -7
- data/test/empty_test.rb +30 -0
- data/test/fields_test.rb +24 -0
- data/test/form_composition_test.rb +24 -2
- data/test/form_test.rb +84 -0
- data/test/inherit_test.rb +12 -0
- data/test/model_reflections_test.rb +65 -0
- data/test/read_only_test.rb +28 -0
- data/test/reform_test.rb +2 -175
- data/test/representer_test.rb +47 -0
- data/test/save_test.rb +51 -1
- data/test/scalar_test.rb +0 -18
- data/test/skip_if_test.rb +62 -0
- data/test/skip_unchanged_test.rb +86 -0
- data/test/sync_option_test.rb +83 -0
- data/test/twin_test.rb +23 -0
- data/test/validate_test.rb +9 -1
- metadata +37 -9
- data/lib/reform/form/virtual_attributes.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 463e240b0183da1d46a70a9252959ead4e45a152
|
4
|
+
data.tar.gz: 07842a7a220f1c72bdc412d49a187bf7ed1b76bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb02ec3ec33906652a1b72aba03481751c0e5b6a5516a1f6b11dd2cf001a681b9b21d16b764b69f4e482875213926ad4d13b4d7a7c5002417419bed3f9653008
|
7
|
+
data.tar.gz: 6a2c816422517921b3b3b93a400deb35b5c989ffa6ea9b21f8d3789aa3d07d52717e36a1b3f313e622344ab3c17bc469d26efe860dc6de120f15f4dad220c779
|
data/CHANGES.md
CHANGED
@@ -1,6 +1,40 @@
|
|
1
|
-
## 1.
|
1
|
+
## 1.2.0
|
2
2
|
|
3
|
+
### Breakage
|
4
|
+
|
5
|
+
* Due to countless bugs we no longer include support for simple_form's type interrogation automatically. This allows using forms with non-AM objects. If you want full support for simple_form do as follows.
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class SongForm < Reform::Form
|
9
|
+
include ModelReflections
|
10
|
+
```
|
11
|
+
|
12
|
+
Including this module will add `#column_for_attribute` and other methods need by form builders to automatically guess the type of a property.
|
13
|
+
|
14
|
+
### Changes
|
15
|
+
|
16
|
+
* `Form#save` with `Composition` now returns true only if all composite models saved.
|
17
|
+
* `Form::copy_validations_from` allows copying custom validators now.
|
3
18
|
* `::validates_uniqueness_of` now accepts options like `:scope`. Thanks to @cveneziani for a failing test and insight.
|
19
|
+
* New call style for `::properties`. Instead of an array, it's now `properties :title, :genre`.
|
20
|
+
* All options are evaluated with `pass_options: true`.
|
21
|
+
|
22
|
+
### New Stuff!!!
|
23
|
+
|
24
|
+
* `:skip_if`, `:skip_if: :all_blank`.
|
25
|
+
* You can now specify validations right in the `::property` call.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
property :title, validates: {presence: true}
|
29
|
+
```
|
30
|
+
|
31
|
+
Thanks to @zubin for this brillant idea!
|
32
|
+
* Wanna parse JSON in `#validate`? Include `Reform::Form::JSON`.
|
33
|
+
* Dirty
|
34
|
+
* :sync
|
35
|
+
* :save
|
36
|
+
* `Sync::SkipUnchanged`.
|
37
|
+
|
4
38
|
|
5
39
|
## 1.1.1
|
6
40
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -29,13 +29,32 @@ class SongForm < Reform::Form
|
|
29
29
|
property :title
|
30
30
|
property :length
|
31
31
|
|
32
|
-
validates :title,
|
32
|
+
validates :title, presence: true
|
33
33
|
validates :length, numericality: true
|
34
34
|
end
|
35
35
|
```
|
36
36
|
|
37
|
-
|
37
|
+
Define your form's fields using `::property`. Validations no longer go into the model, but into the form.
|
38
38
|
|
39
|
+
Luckily, this can be shortened as follows.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class SongForm < Reform::Form
|
43
|
+
property :title, validates: {presence: true}
|
44
|
+
property :length, validates {numericality: true}
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
Use `properties` to bulk-specify fields.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
class SongForm < Reform::Form
|
52
|
+
properties :title, :length, validates: {presence: true} # both required!
|
53
|
+
validates :length, numericality: true
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
After explicitely defining your fields, you're ready to use the form.
|
39
58
|
|
40
59
|
## The API
|
41
60
|
|
@@ -163,12 +182,21 @@ You can always access the form's model. This is helpful when you were using popu
|
|
163
182
|
|
164
183
|
```ruby
|
165
184
|
@form.save do |nested|
|
166
|
-
album = @form.
|
185
|
+
album = @form.model
|
167
186
|
|
168
187
|
album.update_attributes(nested[:album])
|
169
188
|
end
|
170
189
|
```
|
171
190
|
|
191
|
+
If the form wraps multiple models, via [composition](#compositions), you can access them like this:
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
@form.save do |nested|
|
195
|
+
song = @form.model[:song]
|
196
|
+
label = @form.model[:label]
|
197
|
+
end
|
198
|
+
```
|
199
|
+
|
172
200
|
Note that you can call `#sync` and _then_ call `#save { |hsh| }` to save models yourself.
|
173
201
|
|
174
202
|
|
@@ -202,7 +230,7 @@ In future versions and with the upcoming [Trailblazer framework](https://github.
|
|
202
230
|
|
203
231
|
### Using Contracts
|
204
232
|
|
205
|
-
Applying a contract is simple, all you need is a populated object (e.g. an album after `#
|
233
|
+
Applying a contract is simple, all you need is a populated object (e.g. an album after `#assign_attributes`).
|
206
234
|
|
207
235
|
```ruby
|
208
236
|
album.assign_attributes(..)
|
@@ -288,9 +316,7 @@ Or use something like `#fields_for` in a Rails environment.
|
|
288
316
|
The block form of `#save` would give you the following data.
|
289
317
|
|
290
318
|
```ruby
|
291
|
-
@form.save do |
|
292
|
-
data.title #=> "Hungry Like The Wolf"
|
293
|
-
data.artist.name #=> "Duran Duran"
|
319
|
+
@form.save do |nested|
|
294
320
|
|
295
321
|
nested #=> {title: "Hungry Like The Wolf",
|
296
322
|
# artist: {name: "Duran Duran"}}
|
@@ -357,9 +383,7 @@ However, `#fields_for` works just fine, again.
|
|
357
383
|
The block form of `#save` will expose the data structures already discussed.
|
358
384
|
|
359
385
|
```ruby
|
360
|
-
@form.save do |
|
361
|
-
data.title #=> "Rio"
|
362
|
-
data.songs.first.title #=> "Hungry Like The Wolf"
|
386
|
+
@form.save do |nested|
|
363
387
|
|
364
388
|
nested #=> {title: "Rio"
|
365
389
|
# songs: [{title: "Hungry Like The Wolf"},
|
@@ -482,9 +506,7 @@ When using `#save' without a block reform will use writer methods on the differe
|
|
482
506
|
Here's how the block parameters look like.
|
483
507
|
|
484
508
|
```ruby
|
485
|
-
@form.save do |
|
486
|
-
data.title #=> "Rio"
|
487
|
-
data.city #=> "London"
|
509
|
+
@form.save do |nested|
|
488
510
|
|
489
511
|
nested #=> {
|
490
512
|
# song: {title: "Rio"}
|
@@ -632,7 +654,7 @@ form.password_confirmation #=> "321"
|
|
632
654
|
The nested hash in the block-`#save` provides the same value.
|
633
655
|
|
634
656
|
```ruby
|
635
|
-
form.save do |
|
657
|
+
form.save do |nested|
|
636
658
|
nested[:password_confirmation] #=> "321"
|
637
659
|
```
|
638
660
|
|
@@ -652,10 +674,8 @@ You want to use this to display an initial value or to further process this fiel
|
|
652
674
|
It is still readable in the nested hash and through the form itself.
|
653
675
|
|
654
676
|
```ruby
|
655
|
-
form.save do |
|
677
|
+
form.save do |nested|
|
656
678
|
nested[:country] #=> "Australia"
|
657
|
-
|
658
|
-
f.country #=> "Australia"
|
659
679
|
```
|
660
680
|
|
661
681
|
## Validations From Models
|
@@ -765,6 +785,18 @@ class SongForm < Reform::Form
|
|
765
785
|
end
|
766
786
|
```
|
767
787
|
|
788
|
+
## Validations For File Uploads
|
789
|
+
|
790
|
+
In case you're processing uploaded files with your form using CarrierWave, Paperclip, Dragonfly or Paperdragon we recommend using the awesome [file_validators](https://github.com/musaffa/file_validators) gem for file type and size validations.
|
791
|
+
|
792
|
+
```ruby
|
793
|
+
class SongForm < Reform::Form
|
794
|
+
property :image
|
795
|
+
|
796
|
+
validates :image, file_size: {less_than: 2.megabytes},
|
797
|
+
file_content_type: {allow: ['image/jpeg', 'image/png', 'image/gif']}
|
798
|
+
```
|
799
|
+
|
768
800
|
## Multiparameter Dates
|
769
801
|
|
770
802
|
Composed multi-parameter dates as created by the Rails date helper are processed automatically. As soon as Reform detects an incoming `release_date(i1)` or the like it is gonna be converted into a date.
|
@@ -777,9 +809,7 @@ Note that the date will be `nil` when one of the components (year/month/day) is
|
|
777
809
|
By explicitely defining the form layout using `::property` there is no more need for protecting from unwanted input. `strong_parameter` or `attr_accessible` become obsolete. Reform will simply ignore undefined incoming parameters.
|
778
810
|
|
779
811
|
|
780
|
-
##
|
781
|
-
|
782
|
-
### Nesting Without Inline Representers
|
812
|
+
## Nesting Without Inline Representers
|
783
813
|
|
784
814
|
When nesting form, you usually use a so-called inline form doing `property :song do .. end`.
|
785
815
|
|
@@ -792,7 +822,7 @@ property :song, form: SongForm
|
|
792
822
|
The nested `SongForm` is a stand-alone form class you have to provide.
|
793
823
|
|
794
824
|
|
795
|
-
|
825
|
+
## Overriding Setters For Coercion
|
796
826
|
|
797
827
|
When "real" coercion is too much and you simply want to convert incoming data yourself, override the setter.
|
798
828
|
|
@@ -808,6 +838,38 @@ class SongForm < Reform::Form
|
|
808
838
|
This will capitalize the title _after_ calling `form.validate` but _before_ validation happens. Note that you can use `super` to call the original setter.
|
809
839
|
|
810
840
|
|
841
|
+
## Default Values For Presentation
|
842
|
+
|
843
|
+
In case you want to change a value for presentation or provide a default value, override the reader. This is only considered when the form is rendered (e.g. in `form_for`).
|
844
|
+
|
845
|
+
```ruby
|
846
|
+
class SongForm < Reform::Form
|
847
|
+
property :genre
|
848
|
+
|
849
|
+
def genre
|
850
|
+
super || 'Punkrock'
|
851
|
+
end
|
852
|
+
end
|
853
|
+
```
|
854
|
+
|
855
|
+
This will now be used when rendering the view.
|
856
|
+
|
857
|
+
```haml
|
858
|
+
= f.input :genre # calls form.genre which provides default.
|
859
|
+
```
|
860
|
+
|
861
|
+
## Dirty Tracker
|
862
|
+
|
863
|
+
Every form tracks changes in `#validate` and allows to check if a particular property value has changed using `#changed?`.
|
864
|
+
|
865
|
+
```ruby
|
866
|
+
form.title => "Button Up"
|
867
|
+
|
868
|
+
form.validate("title" => "Just Kiddin'")
|
869
|
+
form.changed?(:title) #=> true
|
870
|
+
```
|
871
|
+
|
872
|
+
|
811
873
|
## Undocumented Features
|
812
874
|
|
813
875
|
_(Please don't read this section!)_
|
data/TODO.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
* fields is a Twin and sorts out all the changed? stuff.
|
2
|
+
* virtual: don't read dont write
|
3
|
+
* empty dont read, but write
|
4
|
+
* read_only: read, don't write
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
|
1
9
|
* `validates :title, :presence => true`
|
2
10
|
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
11
|
|
data/database.sqlite3
CHANGED
Binary file
|
data/gemfiles/Gemfile.rails-4.0
CHANGED
data/lib/reform.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
module Reform
|
2
|
-
autoload :Contract, 'reform/contract'
|
3
|
-
|
4
2
|
def self.rails3_0?
|
5
3
|
::ActiveModel::VERSION::MAJOR == 3 and ::ActiveModel::VERSION::MINOR == 0
|
6
4
|
end
|
7
5
|
end
|
8
6
|
|
7
|
+
require 'reform/contract'
|
9
8
|
require 'reform/form'
|
10
9
|
require 'reform/form/composition'
|
11
10
|
require 'reform/form/active_model'
|
11
|
+
require 'reform/form/module'
|
12
|
+
require 'reform/composition'
|
13
|
+
|
12
14
|
|
13
15
|
if defined?(Rails) # DISCUSS: is everyone ok with this?
|
14
16
|
require 'reform/rails'
|
data/lib/reform/active_record.rb
CHANGED
data/lib/reform/composition.rb
CHANGED
data/lib/reform/contract.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'forwardable'
|
2
2
|
require 'uber/inheritable_attr'
|
3
3
|
require 'uber/delegates'
|
4
|
+
require 'ostruct'
|
4
5
|
|
5
6
|
require 'reform/representer'
|
6
7
|
|
@@ -29,11 +30,13 @@ module Reform
|
|
29
30
|
module PropertyMethods
|
30
31
|
def property(name, options={}, &block)
|
31
32
|
options[:private_name] = options.delete(:as)
|
32
|
-
|
33
33
|
options[:coercion_type] = options.delete(:type)
|
34
|
-
|
35
34
|
options[:features] ||= []
|
36
35
|
options[:features] += features.keys if block_given?
|
36
|
+
options[:pass_options] = true
|
37
|
+
options[:virtual] = true if options[:empty] # TODO: check TODO and fix naming!
|
38
|
+
|
39
|
+
validates(name, options.delete(:validates).dup) if options[:validates]
|
37
40
|
|
38
41
|
definition = representer_class.property(name, options, &block)
|
39
42
|
setup_form_definition(definition) if block_given? or options[:form]
|
@@ -42,16 +45,22 @@ module Reform
|
|
42
45
|
definition
|
43
46
|
end
|
44
47
|
|
48
|
+
def properties(*args)
|
49
|
+
options = args.extract_options!
|
50
|
+
|
51
|
+
if args.first.is_a? Array # TODO: REMOVE in 2.0.
|
52
|
+
warn "[Reform] Deprecation: Please pass a list of names instead of array to ::properties, like `properties :title, :id`."
|
53
|
+
args = args.first
|
54
|
+
end
|
55
|
+
args.each { |name| property(name, options.dup) }
|
56
|
+
end
|
57
|
+
|
45
58
|
def collection(name, options={}, &block)
|
46
59
|
options[:collection] = true
|
47
60
|
|
48
61
|
property(name, options, &block)
|
49
62
|
end
|
50
63
|
|
51
|
-
def properties(names, options={})
|
52
|
-
names.each { |name| property(name, options.dup) }
|
53
|
-
end
|
54
|
-
|
55
64
|
def setup_form_definition(definition)
|
56
65
|
options = {
|
57
66
|
# TODO: make this a bit nicer. why do we need :form at all?
|
@@ -65,6 +74,7 @@ module Reform
|
|
65
74
|
end
|
66
75
|
|
67
76
|
private
|
77
|
+
|
68
78
|
def create_accessor(name)
|
69
79
|
handle_reserved_names(name)
|
70
80
|
|
@@ -123,6 +133,13 @@ module Reform
|
|
123
133
|
end
|
124
134
|
end
|
125
135
|
|
136
|
+
def self.clone
|
137
|
+
Class.new(self)
|
138
|
+
end
|
139
|
+
|
140
|
+
require 'reform/schema'
|
141
|
+
extend Schema
|
142
|
+
|
126
143
|
alias_method :aliased_model, :model
|
127
144
|
|
128
145
|
|
@@ -137,4 +154,4 @@ module Reform
|
|
137
154
|
end
|
138
155
|
end
|
139
156
|
|
140
|
-
require 'reform/contract/errors'
|
157
|
+
require 'reform/contract/errors'
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
module Reform
|
3
2
|
class Contract
|
4
3
|
module Setup
|
@@ -9,8 +8,9 @@ module Reform
|
|
9
8
|
|
10
9
|
def setup_fields
|
11
10
|
representer = mapper.new(aliased_model).extend(Setup::Representer)
|
11
|
+
options = setup_options(Reform::Representer::Options[]) # handles :empty.
|
12
12
|
|
13
|
-
create_fields(
|
13
|
+
create_fields(mapper.fields, representer.to_hash(options))
|
14
14
|
end
|
15
15
|
|
16
16
|
# DISCUSS: setting up the Validation (populating with values) will soon be handled with Disposable::Twin logic.
|
@@ -18,14 +18,16 @@ module Reform
|
|
18
18
|
Fields.new(field_names, fields)
|
19
19
|
end
|
20
20
|
|
21
|
+
module SetupOptions
|
22
|
+
def setup_options(options)
|
23
|
+
options
|
24
|
+
end
|
25
|
+
end
|
26
|
+
include SetupOptions
|
27
|
+
|
21
28
|
|
22
29
|
# Mechanics for setting up initial Field values.
|
23
30
|
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
31
|
def to_hash(*)
|
30
32
|
nested_forms do |attr|
|
31
33
|
attr.merge!(
|
@@ -36,9 +38,19 @@ module Reform
|
|
36
38
|
)
|
37
39
|
end
|
38
40
|
|
39
|
-
super
|
41
|
+
super
|
40
42
|
end
|
41
43
|
end # Representer
|
44
|
+
|
45
|
+
|
46
|
+
module Empty
|
47
|
+
def setup_options(options)
|
48
|
+
empty_fields = mapper.representable_attrs.find_all { |d| d[:empty] }.collect { |d| d.name.to_sym }
|
49
|
+
|
50
|
+
options.exclude!(empty_fields)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
include Empty
|
42
54
|
end
|
43
|
-
end #
|
55
|
+
end # Setup
|
44
56
|
end
|