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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4b6f50b16163b7e3445a9efe6c8142f6dc9c87f5
4
- data.tar.gz: fc8017f514448b2ea059cc39aa1dccd8a1106f94
3
+ metadata.gz: 6ad4504340052787a18f70b1249517dfd7ccc443
4
+ data.tar.gz: 7bae41c59ff9cc4b242a0eb0855f7fa382c2e4ac
5
5
  SHA512:
6
- metadata.gz: 6144492691a54df88a3f72680480db06021314c8dbe26de2cde0d65ac88ce1c9527f1546dd7ed9f23bc4bb4e134df177acacdde622a740fbb483c161a1108039
7
- data.tar.gz: 9a648e2fcbf4d0708a8dc5966324be103f40233b8c17470585fd44e0e119117555d213045fd71077bfae2eec3353f444bd79522cd4902195ee5cd6298ad21973
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 (and to learn all the cool stuff about Reform).
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 'reform/form/validate'
5
+ require "reform/form/validate"
6
6
  include Validate # extend Contract#validate with additional behaviour.
7
- require 'reform/form/sync'
7
+ require "reform/form/sync"
8
8
  include Sync
9
- require 'reform/form/save'
9
+ require "reform/form/save"
10
10
  include Save
11
+ require "reform/form/prepopulate"
12
+ include Prepopulate
11
13
 
12
- require 'reform/form/multi_parameter_attributes'
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 'reform/form/scalar'
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 'reform/form/changed'
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 DateParamsFilter
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 blank_date_parameter?(year, month, day)
33
+ def params_to_date(year, month, day, hour, minute)
34
+ return nil if [year, month, day].any?(&:blank?)
29
35
 
30
- Date.new(year.to_i, month.to_i, day.to_i) # TODO: test fails.
31
- end
32
-
33
- def blank_date_parameter?(year, month, day)
34
- year.blank? || month.blank? || day.blank?
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
- # TODO: test with nested.
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
@@ -1,3 +1,3 @@
1
1
  module Reform
2
- VERSION = "1.2.5"
2
+ VERSION = "1.2.6"
3
3
  end
@@ -64,19 +64,22 @@ class FormBuilderCompatTest < BaseTest
64
64
  form.must_respond_to("label_attributes=")
65
65
  end
66
66
 
67
- describe "deconstructed date parameters" do
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.5
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-01-09 00:00:00.000000000 Z
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