reform 1.2.5 → 1.2.6
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 +5 -0
- data/README.md +43 -2
- data/database.sqlite3 +0 -0
- data/lib/reform/form.rb +8 -6
- data/lib/reform/form/multi_parameter_attributes.rb +20 -14
- data/lib/reform/form/prepopulate.rb +54 -0
- data/lib/reform/version.rb +1 -1
- data/test/form_builder_test.rb +27 -4
- data/test/prepopulate_test.rb +85 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ad4504340052787a18f70b1249517dfd7ccc443
|
4
|
+
data.tar.gz: 7bae41c59ff9cc4b242a0eb0855f7fa382c2e4ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b11cfe1184db4d697a87397acaaa2791ff8c8bdffd89682116aa17e6b91d51c0c5b3de44c4e923266fcf8db9cc994c72c49312210c092d8c54e0bf0d62301e2d
|
7
|
+
data.tar.gz: 42823db698bc87293a992987df02305ce8069459e707847cafb686c5477798a79a9f1b61c27825d8e5bb254bb6e58b005830b481ab64855312b5999d60e5d7c9
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
## 1.2.6
|
2
|
+
|
3
|
+
* Added `:prepopulate` to fill out form properties for presentation. Note that you need to call `Form#prepopulate!` to trigger the prepopulation.
|
4
|
+
* Added support for DateTime properties in forms. Until now, we were ignoring the time part. Thanks to @gdott9 for fixing this.
|
5
|
+
|
1
6
|
## 1.2.5
|
2
7
|
|
3
8
|
* Added `Form#options_for` to have access to all property options.
|
data/README.md
CHANGED
@@ -8,7 +8,13 @@ Although reform can be used in any Ruby framework, it comes with [Rails support]
|
|
8
8
|

|
9
9
|
</a>
|
10
10
|
|
11
|
-
Reform is part of the [Trailblazer project](https://github.com/apotonick/trailblazer). Please [buy my book](https://leanpub.com/trailblazer) to support the development
|
11
|
+
Reform is part of the [Trailblazer project](https://github.com/apotonick/trailblazer). Please [buy my book](https://leanpub.com/trailblazer) to support the development and learn everything about Reform. Currently the book discusses:
|
12
|
+
|
13
|
+
* Form objects, the DSL and basic API (chapter 2 and 3)
|
14
|
+
* Basic validations and rendering forms (chapter 3)
|
15
|
+
* Nested forms, prepopulating and validation populating and pre-selecting values (chapter 5)
|
16
|
+
|
17
|
+
More chapters are coming!
|
12
18
|
|
13
19
|
|
14
20
|
## Installation
|
@@ -114,7 +120,6 @@ class SongForm < Reform::Form
|
|
114
120
|
|
115
121
|
This will still call `song.title` but expose the attribute as `name`.
|
116
122
|
|
117
|
-
|
118
123
|
## Rendering Forms
|
119
124
|
|
120
125
|
Your `@form` is now ready to be rendered, either do it yourself or use something like Rails' `#form_for`, `simple_form` or `formtastic`.
|
@@ -128,6 +133,7 @@ Your `@form` is now ready to be rendered, either do it yourself or use something
|
|
128
133
|
|
129
134
|
Nested forms and collections can be easily rendered with `fields_for`, etc. Just use Reform as if it would be an ActiveModel instance in the view layer.
|
130
135
|
|
136
|
+
Note that you have a mechanism to [prepopulate forms](#prepopulating-forms) for rendering.
|
131
137
|
|
132
138
|
## Validation
|
133
139
|
|
@@ -800,6 +806,17 @@ class SongForm < Reform::Form
|
|
800
806
|
end
|
801
807
|
```
|
802
808
|
|
809
|
+
### Simple Form
|
810
|
+
|
811
|
+
If you want full support for `simple_form` do as follows.
|
812
|
+
|
813
|
+
```ruby
|
814
|
+
class SongForm < Reform::Form
|
815
|
+
include ModelReflections
|
816
|
+
```
|
817
|
+
|
818
|
+
Including this module will add `#column_for_attribute` and other methods need by form builders to automatically guess the type of a property.
|
819
|
+
|
803
820
|
## Validations For File Uploads
|
804
821
|
|
805
822
|
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.
|
@@ -922,6 +939,29 @@ The two features are an excellent way to handle file uploads without ActiveRecor
|
|
922
939
|
_(Please don't read this section!)_
|
923
940
|
|
924
941
|
|
942
|
+
### Prepopulating Forms
|
943
|
+
|
944
|
+
When rendering a new form for an empty object, nested forms won't show up. The [Trailblazer book, chapter 5](https://leanpub.com/trailblazer), discusses this in detail.
|
945
|
+
|
946
|
+
You can use the `:prepopulate` option to configure how to populate a nested form (this also works for scalar properties).
|
947
|
+
|
948
|
+
```ruby
|
949
|
+
property :song, prepopulate: ->(*) { Song.new } do
|
950
|
+
# ..
|
951
|
+
end
|
952
|
+
```
|
953
|
+
|
954
|
+
This option is only executed when being instructed to do so, using the `#prepopulate!` method.
|
955
|
+
|
956
|
+
```ruby
|
957
|
+
form.prepopulate!
|
958
|
+
```
|
959
|
+
|
960
|
+
Only do this for forms that are about to get rendered, though.
|
961
|
+
|
962
|
+
Collections and partial collection population is covered in chapter 5.
|
963
|
+
|
964
|
+
|
925
965
|
### Populator
|
926
966
|
|
927
967
|
You can run your very own populator logic if you're keen (and you know what you're doing).
|
@@ -949,6 +989,7 @@ You can do this using `#options_for`.
|
|
949
989
|
form.options_for(:title) # => {:readable=>true, :coercion_type=>String}
|
950
990
|
```
|
951
991
|
|
992
|
+
Note that Reform renames some options (e.g. `:type` internally becomes `:coercion_type`). Those names are private API and might be changed without deprecation. You better test rendering logic in a unit test to make sure you're forward-compatible.
|
952
993
|
|
953
994
|
## Support
|
954
995
|
|
data/database.sqlite3
CHANGED
Binary file
|
data/lib/reform/form.rb
CHANGED
@@ -2,14 +2,16 @@ module Reform
|
|
2
2
|
class Form < Contract
|
3
3
|
self.representer_class = Reform::Representer.for(:form_class => self)
|
4
4
|
|
5
|
-
require
|
5
|
+
require "reform/form/validate"
|
6
6
|
include Validate # extend Contract#validate with additional behaviour.
|
7
|
-
require
|
7
|
+
require "reform/form/sync"
|
8
8
|
include Sync
|
9
|
-
require
|
9
|
+
require "reform/form/save"
|
10
10
|
include Save
|
11
|
+
require "reform/form/prepopulate"
|
12
|
+
include Prepopulate
|
11
13
|
|
12
|
-
require
|
14
|
+
require "reform/form/multi_parameter_attributes"
|
13
15
|
include MultiParameterAttributes # TODO: make features dynamic.
|
14
16
|
|
15
17
|
private
|
@@ -19,12 +21,12 @@ module Reform
|
|
19
21
|
end
|
20
22
|
|
21
23
|
|
22
|
-
require
|
24
|
+
require "reform/form/scalar"
|
23
25
|
extend Scalar::Property # experimental feature!
|
24
26
|
|
25
27
|
|
26
28
|
# DISCUSS: should that be optional? hooks into #validate, too.
|
27
|
-
require
|
29
|
+
require "reform/form/changed"
|
28
30
|
register_feature Changed
|
29
31
|
include Changed
|
30
32
|
end
|
@@ -1,22 +1,28 @@
|
|
1
1
|
Reform::Form.class_eval do
|
2
|
+
# TODO: this must be implemented with a parse_filter that is only applied to type: Time or Datetime properties.
|
3
|
+
# we then simply add the converted attribute to params.
|
2
4
|
module MultiParameterAttributes
|
5
|
+
# TODO: implement this with parse_filter, so we don't have to manually walk through the hash, etc.
|
3
6
|
def self.included(base)
|
4
7
|
base.send(:register_feature, self)
|
5
8
|
end
|
6
9
|
|
7
|
-
class
|
10
|
+
class DateTimeParamsFilter
|
8
11
|
def call(params)
|
12
|
+
params = params.dup # DISCUSS: not sure if that slows down form processing?
|
9
13
|
date_attributes = {}
|
10
14
|
|
11
15
|
params.each do |attribute, value|
|
12
16
|
if value.is_a?(Hash)
|
13
|
-
call(value) # TODO: #validate should only handle local form params.
|
17
|
+
params[attribute] = call(value) # TODO: #validate should only handle local form params.
|
14
18
|
elsif matches = attribute.match(/^(\w+)\(.i\)$/)
|
15
19
|
date_attribute = matches[1]
|
16
20
|
date_attributes[date_attribute] = params_to_date(
|
17
21
|
params.delete("#{date_attribute}(1i)"),
|
18
22
|
params.delete("#{date_attribute}(2i)"),
|
19
|
-
params.delete("#{date_attribute}(3i)")
|
23
|
+
params.delete("#{date_attribute}(3i)"),
|
24
|
+
params.delete("#{date_attribute}(4i)"),
|
25
|
+
params.delete("#{date_attribute}(5i)")
|
20
26
|
)
|
21
27
|
end
|
22
28
|
end
|
@@ -24,22 +30,22 @@ Reform::Form.class_eval do
|
|
24
30
|
end
|
25
31
|
|
26
32
|
private
|
27
|
-
def params_to_date(year, month, day)
|
28
|
-
return nil if
|
33
|
+
def params_to_date(year, month, day, hour, minute)
|
34
|
+
return nil if [year, month, day].any?(&:blank?)
|
29
35
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
36
|
+
if hour.blank? && minute.blank?
|
37
|
+
Date.new(year.to_i, month.to_i, day.to_i) # TODO: test fails.
|
38
|
+
else
|
39
|
+
args = [year, month, day, hour, minute].map(&:to_i)
|
40
|
+
Time.zone ? Time.zone.local(*args) :
|
41
|
+
Time.new(*args)
|
42
|
+
end
|
35
43
|
end
|
36
44
|
end
|
37
45
|
|
38
46
|
def validate(params)
|
39
47
|
# TODO: make it cleaner to hook into essential reform steps.
|
40
|
-
#
|
41
|
-
DateParamsFilter.new.call(params) if params.is_a?(Hash) # this currently works for hash, only.
|
42
|
-
|
48
|
+
params = DateTimeParamsFilter.new.call(params) if params.is_a?(Hash) # this currently works for hash, only.
|
43
49
|
super
|
44
50
|
end
|
45
51
|
|
@@ -63,4 +69,4 @@ Reform::Form.class_eval do
|
|
63
69
|
# end
|
64
70
|
# end
|
65
71
|
end
|
66
|
-
end
|
72
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# this will soon be handled in Disposable.
|
2
|
+
module Reform::Form::Prepopulate
|
3
|
+
def prepopulate!
|
4
|
+
# TODO: representer.new(fields).from_object(fields)
|
5
|
+
hash = prepopulate_representer.new(fields).to_hash(:parent_form => self)
|
6
|
+
prepopulate_representer.new(fields).from_hash(hash)
|
7
|
+
|
8
|
+
recursive_prepopulate_representer.new(fields).to_hash # not sure if i leave that like this, consider private.
|
9
|
+
self
|
10
|
+
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
|
+
|
32
|
+
:instance => lambda { |obj, *| obj } # needed for from_hash. TODO: make that in one go.
|
33
|
+
)
|
34
|
+
else
|
35
|
+
dfn.merge!(:render_filter => lambda do |v, h, options|
|
36
|
+
parent_form = options.user_options[:parent_form] # TODO: merge with Validate/populate_if_empty.
|
37
|
+
|
38
|
+
# execute in form context, pass user optioins.
|
39
|
+
parent_form.instance_exec(options.user_options, &options.binding[:prepopulate])
|
40
|
+
end)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def recursive_prepopulate_representer
|
46
|
+
self.class.representer(:recursive_prepopulate_representer) do |dfn|
|
47
|
+
dfn.merge!(
|
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
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/lib/reform/version.rb
CHANGED
data/test/form_builder_test.rb
CHANGED
@@ -64,19 +64,22 @@ class FormBuilderCompatTest < BaseTest
|
|
64
64
|
form.must_respond_to("label_attributes=")
|
65
65
|
end
|
66
66
|
|
67
|
-
describe "deconstructed
|
67
|
+
describe "deconstructed datetime parameters" do
|
68
68
|
let(:form_attributes) do
|
69
69
|
{
|
70
70
|
"artist_attributes" => {"name" => "Blink 182"},
|
71
71
|
"songs_attributes" => {"0" => {"title" => "Damnit", "release_date(1i)" => release_year,
|
72
|
-
"release_date(2i)" => release_month, "release_date(3i)" => release_day
|
72
|
+
"release_date(2i)" => release_month, "release_date(3i)" => release_day,
|
73
|
+
"release_date(4i)" => release_hour, "release_date(5i)" => release_minute}}
|
73
74
|
}
|
74
75
|
end
|
75
76
|
let(:release_year) { "1997" }
|
76
77
|
let(:release_month) { "9" }
|
77
78
|
let(:release_day) { "27" }
|
79
|
+
let(:release_hour) { nil }
|
80
|
+
let(:release_minute) { nil }
|
78
81
|
|
79
|
-
describe "with valid parameters" do
|
82
|
+
describe "with valid date parameters" do
|
80
83
|
it "creates a date" do
|
81
84
|
form.validate(form_attributes)
|
82
85
|
|
@@ -84,6 +87,17 @@ class FormBuilderCompatTest < BaseTest
|
|
84
87
|
end
|
85
88
|
end
|
86
89
|
|
90
|
+
describe "with valid datetime parameters" do
|
91
|
+
let(:release_hour) { "10" }
|
92
|
+
let(:release_minute) { "11" }
|
93
|
+
|
94
|
+
it "creates a datetime" do
|
95
|
+
form.validate(form_attributes)
|
96
|
+
|
97
|
+
form.songs.first.release_date.must_equal Time.new(1997, 9, 27, 10, 11)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
87
101
|
%w(year month day).each do |date_attr|
|
88
102
|
describe "when the #{date_attr} is missing" do
|
89
103
|
let(:"release_#{date_attr}") { "" }
|
@@ -95,6 +109,15 @@ class FormBuilderCompatTest < BaseTest
|
|
95
109
|
end
|
96
110
|
end
|
97
111
|
end
|
112
|
+
|
113
|
+
|
114
|
+
# doesn't modify original params.
|
115
|
+
it do
|
116
|
+
original = form_attributes.inspect
|
117
|
+
|
118
|
+
form.validate(form_attributes)
|
119
|
+
form_attributes.inspect.must_equal original
|
120
|
+
end
|
98
121
|
end
|
99
122
|
|
100
123
|
it "returns flat errors hash" do
|
@@ -102,4 +125,4 @@ class FormBuilderCompatTest < BaseTest
|
|
102
125
|
"songs_attributes" => {"0" => {"title" => ""}})
|
103
126
|
form.errors.messages.must_equal(:"artist.name" => ["can't be blank"], :"songs.title" => ["can't be blank"])
|
104
127
|
end
|
105
|
-
end
|
128
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class PrepopulateTest < MiniTest::Spec
|
4
|
+
Song = Struct.new(:title, :band, :length)
|
5
|
+
Band = Struct.new(:name)
|
6
|
+
|
7
|
+
class AlbumForm < Reform::Form
|
8
|
+
property :title, prepopulate: ->(options){ "Another Day At Work" }
|
9
|
+
property :length
|
10
|
+
|
11
|
+
property :hit, prepopulate: ->(options){ Song.new } do
|
12
|
+
property :title
|
13
|
+
|
14
|
+
property :band, prepopulate: ->(options){ Band.new } do
|
15
|
+
property :name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
collection :songs, prepopulate: ->(options){ [Song.new, Song.new] } do
|
20
|
+
property :title
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
subject { AlbumForm.new(OpenStruct.new(length: 1)).prepopulate! }
|
25
|
+
|
26
|
+
it { subject.length.must_equal 1 }
|
27
|
+
it { subject.title.must_equal "Another Day At Work" }
|
28
|
+
it { subject.hit.model.must_equal Song.new }
|
29
|
+
it { subject.songs.size.must_equal 2 }
|
30
|
+
it { subject.songs[0].model.must_equal Song.new }
|
31
|
+
it { subject.songs[1].model.must_equal Song.new }
|
32
|
+
it { subject.hit.band.model.must_equal Band.new }
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
class PrepopulateInFormContextTest < MiniTest::Spec
|
37
|
+
Song = Struct.new(:title, :band, :length)
|
38
|
+
Band = Struct.new(:name)
|
39
|
+
|
40
|
+
class AlbumForm < Reform::Form
|
41
|
+
property :title, prepopulate: ->(options){ "#{my_title} #{options.class}" }
|
42
|
+
|
43
|
+
property :hit, prepopulate: ->(options){ my_hit } do
|
44
|
+
property :title
|
45
|
+
|
46
|
+
property :band, prepopulate: ->(options){ my_band } do
|
47
|
+
property :name
|
48
|
+
end
|
49
|
+
|
50
|
+
def my_band
|
51
|
+
Band.new
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def my_title
|
56
|
+
"Rhode Island Shred"
|
57
|
+
end
|
58
|
+
|
59
|
+
def my_hit
|
60
|
+
Song.new
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
subject { AlbumForm.new(OpenStruct.new).prepopulate! }
|
65
|
+
|
66
|
+
it { subject.title.must_equal "Rhode Island Shred Hash" }
|
67
|
+
it { subject.hit.model.must_equal Song.new }
|
68
|
+
it { subject.hit.band.model.must_equal Band.new }
|
69
|
+
end
|
70
|
+
|
71
|
+
class PrepopulateWithExistingCollectionTest < MiniTest::Spec
|
72
|
+
Song = Struct.new(:title)
|
73
|
+
|
74
|
+
class AlbumForm < Reform::Form
|
75
|
+
collection :songs, prepopulate: ->(*){ songs.map(&:model) + [Song.new] } do
|
76
|
+
property :title
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
subject { AlbumForm.new(OpenStruct.new(songs: [Song.new])).prepopulate! }
|
81
|
+
|
82
|
+
it { subject.songs.size.must_equal 2 }
|
83
|
+
it { subject.songs[0].model.must_equal Song.new }
|
84
|
+
it { subject.songs[1].model.must_equal Song.new }
|
85
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reform
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-02-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: representable
|
@@ -218,6 +218,7 @@ files:
|
|
218
218
|
- lib/reform/form/model_reflections.rb
|
219
219
|
- lib/reform/form/module.rb
|
220
220
|
- lib/reform/form/multi_parameter_attributes.rb
|
221
|
+
- lib/reform/form/prepopulate.rb
|
221
222
|
- lib/reform/form/save.rb
|
222
223
|
- lib/reform/form/scalar.rb
|
223
224
|
- lib/reform/form/sync.rb
|
@@ -273,6 +274,7 @@ files:
|
|
273
274
|
- test/model_reflections_test.rb
|
274
275
|
- test/model_validations_test.rb
|
275
276
|
- test/nested_form_test.rb
|
277
|
+
- test/prepopulate_test.rb
|
276
278
|
- test/rails/integration_test.rb
|
277
279
|
- test/read_only_test.rb
|
278
280
|
- test/readable_test.rb
|
@@ -361,6 +363,7 @@ test_files:
|
|
361
363
|
- test/model_reflections_test.rb
|
362
364
|
- test/model_validations_test.rb
|
363
365
|
- test/nested_form_test.rb
|
366
|
+
- test/prepopulate_test.rb
|
364
367
|
- test/rails/integration_test.rb
|
365
368
|
- test/read_only_test.rb
|
366
369
|
- test/readable_test.rb
|