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 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