reform 1.2.5 → 1.2.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
![](https://raw.githubusercontent.com/apotonick/trailblazer/master/doc/trb.jpg)
|
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
|