reform 1.1.0 → 1.1.1
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 +13 -0
- data/Gemfile +1 -1
- data/README.md +84 -2
- data/database.sqlite3 +0 -0
- data/lib/reform/contract.rb +17 -3
- data/lib/reform/contract/errors.rb +4 -0
- data/lib/reform/form/active_model.rb +3 -2
- data/lib/reform/form/active_record.rb +3 -2
- data/lib/reform/form/module.rb +8 -7
- data/lib/reform/form/save.rb +3 -1
- data/lib/reform/form/validate.rb +8 -0
- data/lib/reform/version.rb +1 -1
- data/test/active_record_test.rb +15 -7
- data/test/builder_test.rb +26 -26
- data/test/deserialize_test.rb +4 -2
- data/test/errors_test.rb +8 -0
- data/test/inherit_test.rb +87 -3
- data/test/reform_test.rb +10 -20
- data/test/save_test.rb +8 -0
- data/test/scalar_test.rb +18 -0
- data/test/validate_test.rb +60 -0
- metadata +28 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4eac1a339ca122c10c68332cbb713fee79ef7670
|
4
|
+
data.tar.gz: fbea1c3d34b9af52108f4d3e508b562b32f79c27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9242a0e45d2392e58af57958d695d37fb0f316f90701ebc383e256a72fa01066097e441752cd1deb112dabe628d91fca7ccfee835ee3deb2e2997fb0ca69a836
|
7
|
+
data.tar.gz: a95af8299b9fc16284ec5c5d583208c1efdc12ed3a6a0bfc71a6cb885409731102d20faba0009c7827523baee402165d9cf331589e41e841d0d449b0b7d2ada5
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## 1.1.2
|
2
|
+
|
3
|
+
* `::validates_uniqueness_of` now accepts options like `:scope`. Thanks to @cveneziani for a failing test and insight.
|
4
|
+
|
5
|
+
## 1.1.1
|
6
|
+
|
7
|
+
* Fix a bug where including a form module would mess up the options has of the validations (under older Rails).
|
8
|
+
* Fix `::properties` which modified the options hash while iterating properties.
|
9
|
+
* `Form#save` now returns the result of the `model.save` invocation.
|
10
|
+
* Fix: When overriding a reader method for a nested form for presentation (e.g. to provide an initial new record), this reader was used in `#update!`. The deserialize/update run now grabs the actual nested form instances directly from `fields`.
|
11
|
+
* `Errors#to_s` is now delegated to `messages.to_s`. This is used in `Trailblazer::Operation`.
|
12
|
+
|
1
13
|
## 1.1.0
|
2
14
|
|
3
15
|
* Deprecate first block argument in save. It's new signature is `save { |hash| }`. You already got the form instance when calling `form.save` so there's no need to pass it into the block.
|
@@ -6,6 +18,7 @@
|
|
6
18
|
* You can now define forms in modules including `Reform::Form::Module` to improve reusability.
|
7
19
|
* Inheriting from forms and then overriding/extending properties with `:inherit` now works properly.
|
8
20
|
* You can now define methods in inline forms.
|
21
|
+
* Added `Form::ActiveModel::ModelValidations` to copy validations from model classes. Thanks to @cameron-martin for this fine addition.
|
9
22
|
* Forms can now also deserialize other formats, e.g. JSON. This allows them to be used as a contract for API endpoints and in Operations in Trailblazer.
|
10
23
|
* Composition forms no longer expose readers to the composition members. The composition is available via `Form#model`, members via `Form#model[:member_name]`.
|
11
24
|
* ActiveRecord support is now included correctly and passed on to nested forms.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -493,6 +493,35 @@ Here's how the block parameters look like.
|
|
493
493
|
end
|
494
494
|
```
|
495
495
|
|
496
|
+
|
497
|
+
## Forms In Modules
|
498
|
+
|
499
|
+
To maximize reusability, you can also define forms in modules and include them in other modules or classes.
|
500
|
+
|
501
|
+
```ruby
|
502
|
+
module SongsForm
|
503
|
+
include Reform::Form::Module
|
504
|
+
|
505
|
+
collection :songs do
|
506
|
+
property :title
|
507
|
+
validates :title, presence: true
|
508
|
+
end
|
509
|
+
end
|
510
|
+
```
|
511
|
+
|
512
|
+
This can now be included into a real form.
|
513
|
+
|
514
|
+
```ruby
|
515
|
+
class AlbumForm < Reform::Form
|
516
|
+
property :title
|
517
|
+
|
518
|
+
include SongsForm
|
519
|
+
end
|
520
|
+
```
|
521
|
+
|
522
|
+
Note that you can also override properties [using inheritance](#inheritance) in Reform.
|
523
|
+
|
524
|
+
|
496
525
|
## Inheritance
|
497
526
|
|
498
527
|
Forms can be derived from other forms and will inherit all properties and validations.
|
@@ -538,6 +567,8 @@ Using `inherit:` here will extend the existing `songs` form with the `band_id` f
|
|
538
567
|
|
539
568
|
Often you want incoming form data to be converted to a type, like timestamps. Reform uses [virtus](https://github.com/solnic/virtus) for coercion, the DSL is seamlessly integrated into Reform with the `:type` option.
|
540
569
|
|
570
|
+
### Virtus Coercion
|
571
|
+
|
541
572
|
Be sure to add `virtus` to your Gemfile.
|
542
573
|
|
543
574
|
```ruby
|
@@ -549,10 +580,32 @@ class SongForm < Reform::Form
|
|
549
580
|
property :written_at, type: DateTime
|
550
581
|
end
|
551
582
|
|
552
|
-
|
553
|
-
|
583
|
+
form.validate("written_at" => "26 September")
|
584
|
+
```
|
585
|
+
|
586
|
+
Coercion only happens in `#validate`.
|
587
|
+
|
588
|
+
```
|
589
|
+
form.written_at #=> <DateTime "2014 September 26 00:00">
|
590
|
+
```
|
591
|
+
|
592
|
+
### Manual Coercing Values
|
593
|
+
|
594
|
+
If you need to filter values manually, you can override the setter in the form.
|
595
|
+
|
596
|
+
```ruby
|
597
|
+
class SongForm < Reform::Form
|
598
|
+
property :title
|
599
|
+
|
600
|
+
def title=(value)
|
601
|
+
super sanitize(value) # value is raw form input.
|
602
|
+
end
|
603
|
+
end
|
554
604
|
```
|
555
605
|
|
606
|
+
As with the built-in coercion, this setter is only called in `#validate`.
|
607
|
+
|
608
|
+
|
556
609
|
## Virtual Attributes
|
557
610
|
|
558
611
|
Virtual fields come in handy when there's no direct mapping to a model attribute or when you plan on displaying but not processing a value.
|
@@ -605,6 +658,35 @@ form.save do |f, nested|
|
|
605
658
|
f.country #=> "Australia"
|
606
659
|
```
|
607
660
|
|
661
|
+
## Validations From Models
|
662
|
+
|
663
|
+
Sometimes when you still keep validations in your models (which you shouldn't) copying them to a form might not feel right. In that case, you can let Reform automatically copy them.
|
664
|
+
|
665
|
+
```ruby
|
666
|
+
class SongForm < Reform::Form
|
667
|
+
property :title
|
668
|
+
|
669
|
+
extend ActiveModel::ModelValidations
|
670
|
+
copy_validations_from Song
|
671
|
+
end
|
672
|
+
```
|
673
|
+
|
674
|
+
Note how `copy_validations_from` copies over the validations allowing you to stay DRY.
|
675
|
+
|
676
|
+
This also works with Composition.
|
677
|
+
|
678
|
+
```ruby
|
679
|
+
class SongForm < Reform::Form
|
680
|
+
include Composition
|
681
|
+
# ...
|
682
|
+
|
683
|
+
extend ActiveModel::ModelValidations
|
684
|
+
copy_validations_from song: Song, band: Band
|
685
|
+
end
|
686
|
+
```
|
687
|
+
|
688
|
+
Be warned that we _do not_ encourage copying validations. You should rather move validation code into forms and not work on your model directly anymore.
|
689
|
+
|
608
690
|
## Agnosticism: Mapping Data
|
609
691
|
|
610
692
|
Reform doesn't really know whether it's working with a PORO, an `ActiveRecord` instance or a `Sequel` row.
|
data/database.sqlite3
CHANGED
Binary file
|
data/lib/reform/contract.rb
CHANGED
@@ -48,14 +48,14 @@ module Reform
|
|
48
48
|
property(name, options, &block)
|
49
49
|
end
|
50
50
|
|
51
|
-
def properties(names,
|
52
|
-
names.each { |name| property(name,
|
51
|
+
def properties(names, options={})
|
52
|
+
names.each { |name| property(name, options.dup) }
|
53
53
|
end
|
54
54
|
|
55
55
|
def setup_form_definition(definition)
|
56
56
|
options = {
|
57
57
|
# TODO: make this a bit nicer. why do we need :form at all?
|
58
|
-
:form => (definition
|
58
|
+
:form => (definition.representer_module) || definition[:form], # :form is always just a Form class name.
|
59
59
|
:pass_options => true, # new style of passing args
|
60
60
|
:prepare => lambda { |form, args| form }, # always just return the form without decorating.
|
61
61
|
:representable => true, # form: Class must be treated as a typed property.
|
@@ -109,6 +109,20 @@ module Reform
|
|
109
109
|
features[mod] = true
|
110
110
|
end
|
111
111
|
|
112
|
+
# allows including representers from Representable, Roar or disposable.
|
113
|
+
def self.inherit_module!(representer) # called from Representable::included.
|
114
|
+
# representer_class.inherit_module!(representer)
|
115
|
+
representer.representable_attrs.each do |dfn|
|
116
|
+
next if dfn.name == "links" # wait a second # FIXME what is that?
|
117
|
+
|
118
|
+
# TODO: remove manifesting and do that in representable, too!
|
119
|
+
args = [dfn.name, dfn.instance_variable_get(:@options)] # TODO: dfn.to_args (inluding &block)
|
120
|
+
|
121
|
+
property(*args) and next unless dfn.representer_module
|
122
|
+
property(*args) { include dfn.representer_module } # nested.
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
112
126
|
alias_method :aliased_model, :model
|
113
127
|
|
114
128
|
|
@@ -25,8 +25,9 @@ module Reform::Form::ActiveModel
|
|
25
25
|
|
26
26
|
# Modify the incoming Rails params hash to be representable compliant.
|
27
27
|
def update!(params)
|
28
|
-
|
29
|
-
#
|
28
|
+
return super unless params.is_a?(Hash)
|
29
|
+
# TODO: run this only for hash deserialization, but generically (#deserialize_hash ?).
|
30
|
+
|
30
31
|
mapper.new(self).nested_forms do |attr, model| # FIXME: make this simpler.
|
31
32
|
rename_nested_param_for!(params, attr)
|
32
33
|
end
|
@@ -8,8 +8,9 @@ module Reform::Form::ActiveRecord
|
|
8
8
|
end
|
9
9
|
|
10
10
|
module ClassMethods
|
11
|
-
def validates_uniqueness_of(attribute)
|
12
|
-
|
11
|
+
def validates_uniqueness_of(attribute, options={})
|
12
|
+
options = options.merge(:attributes => [attribute])
|
13
|
+
validates_with(UniquenessValidator, options)
|
13
14
|
end
|
14
15
|
def i18n_scope
|
15
16
|
:activerecord
|
data/lib/reform/form/module.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Include this in every module that gets further included.
|
2
|
-
module Reform::Form::Module
|
2
|
+
module Reform::Form::Module # Schema
|
3
3
|
def self.included(base)
|
4
4
|
base.extend ClassMethods
|
5
5
|
base.extend Included
|
@@ -8,16 +8,17 @@ module Reform::Form::Module
|
|
8
8
|
module Included # TODO: use representable's inheritance mechanism.
|
9
9
|
def included(base)
|
10
10
|
super
|
11
|
-
|
11
|
+
instructions.each { |cfg|
|
12
|
+
args = cfg[1].dup
|
13
|
+
options = args.extract_options!.dup # we need to duplicate options has as AM::Validations messes it up later.
|
14
|
+
|
15
|
+
base.send(cfg[0], *args, options, &cfg[2]) } # property :name, {} do .. end
|
12
16
|
end
|
13
17
|
end
|
14
18
|
|
15
19
|
module ClassMethods
|
16
|
-
def
|
17
|
-
instructions << [
|
18
|
-
end
|
19
|
-
def validates(*args, &block)
|
20
|
-
instructions << [:validates, args, block]
|
20
|
+
def method_missing(method, *args, &block)
|
21
|
+
instructions << [method, args, block]
|
21
22
|
end
|
22
23
|
|
23
24
|
def instructions
|
data/lib/reform/form/save.rb
CHANGED
@@ -14,6 +14,7 @@ module Reform::Form::Save
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
# Returns the result of that save invocation on the model.
|
17
18
|
def save(&block)
|
18
19
|
# DISCUSS: we should never hit @mapper here (which writes to the models) when a block is passed.
|
19
20
|
return deprecate_first_save_block_arg(&block) if block_given?
|
@@ -23,8 +24,9 @@ module Reform::Form::Save
|
|
23
24
|
end
|
24
25
|
|
25
26
|
def save!
|
26
|
-
save_model
|
27
|
+
result = save_model
|
27
28
|
mapper.new(fields).extend(RecursiveSave).to_hash # save! on all nested forms. # TODO: only include nested forms here.
|
29
|
+
result
|
28
30
|
end
|
29
31
|
|
30
32
|
def save_model
|
data/lib/reform/form/validate.rb
CHANGED
@@ -12,6 +12,8 @@ module Reform::Form::Validate
|
|
12
12
|
:collection => attr[:collection], # TODO: Def#merge! doesn't consider :collection if it's already set in attr YET.
|
13
13
|
:parse_strategy => :sync, # just use nested objects as they are.
|
14
14
|
|
15
|
+
# :getter grabs nested forms directly from fields bypassing the reader method which could possibly be overridden for presentation.
|
16
|
+
:getter => lambda { |options| fields.send(options.binding.name) },
|
15
17
|
:deserialize => lambda { |object, params, args| object.update!(params) },
|
16
18
|
)
|
17
19
|
|
@@ -59,6 +61,10 @@ module Reform::Form::Validate
|
|
59
61
|
form = binding[:form].new(model) # free service: wrap model with Form. this usually happens in #setup.
|
60
62
|
|
61
63
|
if binding.array?
|
64
|
+
# TODO: please extract this into Disposable.
|
65
|
+
fields = fields.send(:fields)
|
66
|
+
|
67
|
+
fields.send("#{binding.setter}", []) unless fields.send("#{binding.getter}") # DISCUSS: why do I have to initialize this here?
|
62
68
|
fields.send("#{binding.getter}")[index] = form
|
63
69
|
else
|
64
70
|
fields.send("#{binding.setter}", form) # :setter is currently overwritten by :parse_strategy.
|
@@ -76,6 +82,8 @@ module Reform::Form::Validate
|
|
76
82
|
super() # run the actual validation on self.
|
77
83
|
end
|
78
84
|
|
85
|
+
# Some users use this method to pre-populate a form. Not saying this is right, but we'll keep
|
86
|
+
# this method here.
|
79
87
|
def update!(params)
|
80
88
|
deserialize!(params)
|
81
89
|
end
|
data/lib/reform/version.rb
CHANGED
data/test/active_record_test.rb
CHANGED
@@ -19,7 +19,6 @@ require 'reform/active_record'
|
|
19
19
|
# end
|
20
20
|
# Artist.new(:name => "Racer X").save
|
21
21
|
|
22
|
-
|
23
22
|
class ActiveRecordTest < MiniTest::Spec
|
24
23
|
class SongForm < Reform::Form
|
25
24
|
include Reform::Form::ActiveRecord
|
@@ -28,7 +27,7 @@ class ActiveRecordTest < MiniTest::Spec
|
|
28
27
|
property :title
|
29
28
|
property :created_at
|
30
29
|
|
31
|
-
validates_uniqueness_of :title
|
30
|
+
validates_uniqueness_of :title, scope: [:album_id, :artist_id]
|
32
31
|
validates :created_at, :presence => true # have another property to test if we mix up.
|
33
32
|
|
34
33
|
property :artist do
|
@@ -37,7 +36,9 @@ class ActiveRecordTest < MiniTest::Spec
|
|
37
36
|
end
|
38
37
|
end
|
39
38
|
|
40
|
-
let
|
39
|
+
let(:album) { Album.create(:title => "Damnation") }
|
40
|
+
let(:artist) { Artist.create(:name => "Opeth") }
|
41
|
+
let(:form) { SongForm.new(Song.new(:artist => Artist.new)) }
|
41
42
|
|
42
43
|
it { form.class.i18n_scope.must_equal :activerecord }
|
43
44
|
|
@@ -45,11 +46,20 @@ class ActiveRecordTest < MiniTest::Spec
|
|
45
46
|
end
|
46
47
|
|
47
48
|
# uniqueness
|
48
|
-
it "is valid when
|
49
|
-
form.validate("
|
49
|
+
it "is valid when title is unique for the same artist and album" do
|
50
|
+
form.validate("title" => "The Gargoyle", "artist_id" => artist.id, "album" => album.id, "created_at" => "November 6, 1966").must_equal true
|
51
|
+
end
|
52
|
+
|
53
|
+
it "is invalid when title is taken for the same artist and album" do
|
54
|
+
Song.create(title: "Windowpane", artist_id: artist.id, album_id: album.id)
|
55
|
+
form.validate("title" => "Windowpane", "artist_id" => artist.id, "album" => album).must_equal false
|
50
56
|
end
|
51
57
|
|
52
58
|
# nested object taken.
|
59
|
+
it "is valid when artist name is unique" do
|
60
|
+
form.validate("artist" => {"name" => "Paul Gilbert"}, "title" => "The Gargoyle", "created_at" => "November 6, 1966").must_equal true
|
61
|
+
end
|
62
|
+
|
53
63
|
it "is invalid and shows error when taken" do
|
54
64
|
Song.delete_all
|
55
65
|
Artist.create(:name => "Racer X")
|
@@ -190,8 +200,6 @@ class PopulateWithActiveRecordTest < MiniTest::Spec
|
|
190
200
|
# a.save
|
191
201
|
# puts a.songs.inspect
|
192
202
|
|
193
|
-
|
194
|
-
|
195
203
|
# b = a.songs.first
|
196
204
|
|
197
205
|
# a.songs = [Song.new(title:"Biomag")]
|
data/test/builder_test.rb
CHANGED
@@ -1,32 +1,32 @@
|
|
1
|
-
require 'test_helper'
|
1
|
+
# require 'test_helper'
|
2
2
|
|
3
|
-
class BuilderTest < MiniTest::Spec
|
4
|
-
|
5
|
-
|
6
|
-
<input name="yo" type="hidden" value="unchecked_value" /><input id="object_name_method" name="yo" type="checkbox" value="checked_value" />}
|
7
|
-
|
8
|
-
end
|
3
|
+
# class BuilderTest < MiniTest::Spec
|
4
|
+
# it do
|
5
|
+
# Builder.new.checkboxes(:settings, :hash => {"play" => true, "released" => false}).must_equal %{<input name="yo" type="hidden" value="unchecked_value" /><input id="object_name_method" name="yo" type="checkbox" value="checked_value" />
|
6
|
+
# <input name="yo" type="hidden" value="unchecked_value" /><input id="object_name_method" name="yo" type="checkbox" value="checked_value" />}
|
7
|
+
# end
|
8
|
+
# end
|
9
9
|
|
10
10
|
|
11
|
-
require 'action_view/helpers/capture_helper'
|
12
|
-
require 'action_view/helpers/tag_helper'
|
13
|
-
require 'action_view/helpers/url_helper'
|
14
|
-
require 'action_view/helpers/sanitize_helper'
|
15
|
-
require 'action_view/helpers/text_helper'
|
16
|
-
require 'action_view/helpers/form_tag_helper'
|
17
|
-
require 'action_view/helpers/form_helper'
|
11
|
+
# require 'action_view/helpers/capture_helper'
|
12
|
+
# require 'action_view/helpers/tag_helper'
|
13
|
+
# require 'action_view/helpers/url_helper'
|
14
|
+
# require 'action_view/helpers/sanitize_helper'
|
15
|
+
# require 'action_view/helpers/text_helper'
|
16
|
+
# require 'action_view/helpers/form_tag_helper'
|
17
|
+
# require 'action_view/helpers/form_helper'
|
18
18
|
|
19
|
-
class Builder
|
20
|
-
|
21
|
-
|
19
|
+
# class Builder
|
20
|
+
# include ActionView::Helpers::CaptureHelper
|
21
|
+
# include ActionView::Helpers::FormHelper
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
# # {name: value}
|
24
|
+
# def checkboxes(name, options)
|
25
|
+
# # get property form.to_a ? to_builder_hash or something like that
|
26
|
+
# options[:hash].collect do |id, value|
|
27
|
+
# ActionView::Helpers::InstanceTag.new(:object_name, :method, self).to_check_box_tag({:name => "yo"}, :checked_value, :unchecked_value)
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
29
|
+
# # check_box_tag(id, value, checked = false, {})
|
30
|
+
# end.join("\n")
|
31
|
+
# end
|
32
|
+
# end
|
data/test/deserialize_test.rb
CHANGED
@@ -3,6 +3,8 @@ require 'representable/json'
|
|
3
3
|
|
4
4
|
class DeserializeTest < BaseTest
|
5
5
|
class AlbumContract < Reform::Form
|
6
|
+
include Reform::Form::ActiveModel::FormBuilderMethods # overrides #update!, too.
|
7
|
+
|
6
8
|
self.representer_class.class_eval do
|
7
9
|
include Representable::JSON
|
8
10
|
end
|
@@ -33,8 +35,8 @@ class DeserializeTest < BaseTest
|
|
33
35
|
|
34
36
|
let (:json) { '{"hit":{"title":"Sacrifice"},"title":"Second Heat","songs":[{"title":"Heart Of A Lion"}],"band":{"label":{"name":"Fat Wreck"}}}' }
|
35
37
|
|
36
|
-
it
|
38
|
+
it do
|
37
39
|
subject.validate(json)
|
38
40
|
subject.band.label.name.must_equal "Fat Wreck"
|
39
|
-
|
41
|
+
end
|
40
42
|
end
|
data/test/errors_test.rb
CHANGED
@@ -119,4 +119,12 @@ class ErrorsTest < MiniTest::Spec
|
|
119
119
|
it { form.title.must_equal "Second Heat" }
|
120
120
|
it { form.songs.first.title.must_equal "Heart Of A Lion" }
|
121
121
|
end
|
122
|
+
|
123
|
+
|
124
|
+
describe "Errors#to_s" do
|
125
|
+
before { form.validate("songs"=>[{"title" => ""}], "band" => {"label" => {}}) }
|
126
|
+
|
127
|
+
# to_s is aliased to messages
|
128
|
+
it { form.errors.to_s.must_equal "{:\"songs.title\"=>[\"can't be blank\"], :\"band.label.name\"=>[\"can't be blank\"]}" }
|
129
|
+
end
|
122
130
|
end
|
data/test/inherit_test.rb
CHANGED
@@ -34,6 +34,7 @@ class InheritTest < BaseTest
|
|
34
34
|
end
|
35
35
|
|
36
36
|
|
37
|
+
require 'reform/form/coercion'
|
37
38
|
class ModuleInclusionTest < MiniTest::Spec
|
38
39
|
module BandPropertyForm
|
39
40
|
include Reform::Form::Module
|
@@ -53,15 +54,35 @@ class ModuleInclusionTest < MiniTest::Spec
|
|
53
54
|
end
|
54
55
|
|
55
56
|
validates :band, :presence => true
|
57
|
+
|
58
|
+
property :cool, type: Virtus::Attribute::Boolean # test coercion.
|
59
|
+
end
|
60
|
+
|
61
|
+
# TODO: test if works, move stuff into inherit_schema!
|
62
|
+
module AirplaysPropertyForm
|
63
|
+
include Reform::Form::Module
|
64
|
+
|
65
|
+
collection :airplays do
|
66
|
+
property :station
|
67
|
+
validates :station, :presence => true
|
68
|
+
end
|
69
|
+
validates :airplays, :presence => true
|
56
70
|
end
|
57
71
|
|
58
72
|
|
73
|
+
# test:
|
74
|
+
# by including BandPropertyForm into multiple classes we assure that options hashes don't get messed up by AM:V.
|
75
|
+
class HitForm < Reform::Form
|
76
|
+
include BandPropertyForm
|
77
|
+
end
|
78
|
+
|
59
79
|
class SongForm < Reform::Form
|
60
80
|
property :title
|
61
81
|
|
62
82
|
include BandPropertyForm
|
63
83
|
end
|
64
84
|
|
85
|
+
|
65
86
|
let (:song) { OpenStruct.new(:band => OpenStruct.new(:title => "Time Again")) }
|
66
87
|
|
67
88
|
# nested form from module is present and creates accessor.
|
@@ -71,9 +92,72 @@ class ModuleInclusionTest < MiniTest::Spec
|
|
71
92
|
it { SongForm.new(song).id.must_equal 1 }
|
72
93
|
it { SongForm.new(song).band.id.must_equal 2 }
|
73
94
|
|
95
|
+
# validators get inherited.
|
96
|
+
it do
|
97
|
+
form = SongForm.new(OpenStruct.new)
|
98
|
+
form.validate({})
|
99
|
+
form.errors.messages.must_equal({:band=>["can't be blank"]})
|
100
|
+
end
|
101
|
+
|
102
|
+
# coercion works
|
103
|
+
it do
|
104
|
+
form = SongForm.new(OpenStruct.new)
|
105
|
+
form.validate({:cool => "1"})
|
106
|
+
form.cool.must_equal true
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
# include a module into a module into a class :)
|
111
|
+
module AlbumFormModule
|
112
|
+
include Reform::Form::Module
|
113
|
+
include BandPropertyForm
|
114
|
+
|
115
|
+
property :name
|
116
|
+
validates :name, :presence => true
|
117
|
+
end
|
118
|
+
|
119
|
+
class AlbumForm < Reform::Form
|
120
|
+
include AlbumFormModule
|
121
|
+
|
122
|
+
property :band, :inherit => true do
|
123
|
+
property :label
|
124
|
+
validates :label, :presence => true
|
125
|
+
end
|
126
|
+
end
|
127
|
+
# puts "......"+ AlbumForm.representer_class.representable_attrs.get(:band).inspect
|
128
|
+
|
129
|
+
it do
|
130
|
+
form = AlbumForm.new(OpenStruct.new(:band => OpenStruct.new))
|
131
|
+
form.validate({"band" => {}})
|
132
|
+
form.errors.messages.must_equal({:"band.title"=>["can't be blank"], :"band.label"=>["can't be blank"], :name=>["can't be blank"]})
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
# including representer into form
|
137
|
+
module GenericRepresenter
|
138
|
+
include Representable
|
139
|
+
|
140
|
+
property :title
|
141
|
+
property :manager do
|
142
|
+
property :title
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class LabelForm < Reform::Form
|
147
|
+
property :location
|
148
|
+
|
149
|
+
include GenericRepresenter
|
150
|
+
validates :title, :presence => true
|
151
|
+
property :manager, :inherit => true do
|
152
|
+
validates :title, :presence => true
|
153
|
+
end
|
154
|
+
end
|
155
|
+
puts "......"+ LabelForm.representer_class.representable_attrs.get(:title).inspect
|
156
|
+
|
157
|
+
|
74
158
|
it do
|
75
|
-
|
76
|
-
|
77
|
-
|
159
|
+
form = LabelForm.new(OpenStruct.new(:manager => OpenStruct.new))
|
160
|
+
form.validate({"manager" => {}, "title"=>""}) # it's important to pass both nested and scalar here!
|
161
|
+
form.errors.messages.must_equal(:title=>["can't be blank"], :"manager.title"=>["can't be blank"], )
|
78
162
|
end
|
79
163
|
end
|
data/test/reform_test.rb
CHANGED
@@ -69,14 +69,21 @@ class ReformTest < ReformSpec
|
|
69
69
|
|
70
70
|
|
71
71
|
describe "::properties" do
|
72
|
+
let (:options) { {:type => String} }
|
73
|
+
|
72
74
|
subject do
|
75
|
+
opts = options
|
73
76
|
Class.new(Reform::Form) do
|
74
|
-
properties [:name, :title]
|
77
|
+
properties [:name, :title], opts
|
78
|
+
properties [:created_at]
|
75
79
|
end.new(comp)
|
76
80
|
end
|
77
81
|
|
78
|
-
it { subject.name.must_equal
|
79
|
-
it { subject.title.must_equal
|
82
|
+
it { subject.name.must_equal "Duran Duran" }
|
83
|
+
it { subject.title.must_equal "Rio" }
|
84
|
+
it { subject.created_at.must_equal nil }
|
85
|
+
# don't overwrite options.
|
86
|
+
it { subject; options.must_equal({:type => String}) }
|
80
87
|
end
|
81
88
|
|
82
89
|
class SongForm < Reform::Form
|
@@ -157,23 +164,6 @@ class ReformTest < ReformSpec
|
|
157
164
|
form.errors.messages.must_equal({:name=>["can't be blank"], :title=>["can't be blank"]})
|
158
165
|
end
|
159
166
|
end
|
160
|
-
|
161
|
-
describe "method validations" do
|
162
|
-
it "allows accessing models" do
|
163
|
-
form = Class.new(Reform::Form) do
|
164
|
-
property :name
|
165
|
-
validate "name_correct?"
|
166
|
-
|
167
|
-
def name_correct?
|
168
|
-
errors.add :name, "Please give me a name" if model.name.nil?
|
169
|
-
end
|
170
|
-
end.new(comp)
|
171
|
-
|
172
|
-
form.validate({}).must_equal false
|
173
|
-
form.errors.messages.must_equal({:name=>["Please give me a name"]})
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
167
|
end
|
178
168
|
|
179
169
|
describe "#errors" do
|
data/test/save_test.rb
CHANGED
@@ -78,4 +78,12 @@ class SaveTest < BaseTest
|
|
78
78
|
it { band.saved?.must_equal true }
|
79
79
|
it { label.saved?.must_equal nil }
|
80
80
|
end
|
81
|
+
|
82
|
+
|
83
|
+
# #save returns result (this goes into disposable soon).
|
84
|
+
it { subject.save.must_equal true }
|
85
|
+
it do
|
86
|
+
album.instance_eval { def save; false; end }
|
87
|
+
subject.save.must_equal false
|
88
|
+
end
|
81
89
|
end
|
data/test/scalar_test.rb
CHANGED
@@ -163,5 +163,23 @@ class SelfNestedTest < BaseTest
|
|
163
163
|
form.validate({}).must_equal true
|
164
164
|
end
|
165
165
|
|
166
|
+
|
167
|
+
it do
|
168
|
+
form = StringForm.new(AlbumCover.new(nil))
|
169
|
+
form.validate({"image"=>""}).must_equal true
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
|
174
|
+
# TODO: move to validate_test.
|
175
|
+
# property :rejection_reason, scalar: true, virtual: true, empty: true do # optional parameter
|
176
|
+
# validates length: {minimum: 5}, if: lambda { model.present? and avatar_moderation == "2" } # IF present, at least 5 characters.
|
177
|
+
# end
|
178
|
+
class BlaForm < Reform::Form
|
179
|
+
property :image# creates "empty" form
|
180
|
+
validates :image, :length => {:minimum => 10}, if: lambda { image and image != "" }
|
181
|
+
end
|
182
|
+
|
183
|
+
it { BlaForm.new(AlbumCover.new(nil)).validate({"image"=>""}).must_equal true }
|
166
184
|
# DISCUSS: when AlbumCover.new("Hello").validate({}), does that fail?
|
167
185
|
end
|
data/test/validate_test.rb
CHANGED
@@ -275,6 +275,66 @@ class ValidateTest < BaseTest
|
|
275
275
|
it { subject.band.label.name.must_equal "Stiff" }
|
276
276
|
it { subject.title.must_equal "House Of Fun" }
|
277
277
|
end
|
278
|
+
|
279
|
+
|
280
|
+
# providing manual validator method allows accessing form's API.
|
281
|
+
describe "with ::validate" do
|
282
|
+
let (:form) {
|
283
|
+
Class.new(Reform::Form) do
|
284
|
+
property :title
|
285
|
+
|
286
|
+
validate :title?
|
287
|
+
|
288
|
+
def title?
|
289
|
+
errors.add :title, "not lowercase" if title == "Fallout"
|
290
|
+
end
|
291
|
+
end
|
292
|
+
}
|
293
|
+
|
294
|
+
let (:params) { {"title" => "Fallout"} }
|
295
|
+
let (:song) { Song.new("Englishman") }
|
296
|
+
|
297
|
+
subject { form.new(song) }
|
298
|
+
|
299
|
+
before { @res = subject.validate(params) }
|
300
|
+
|
301
|
+
it { @res.must_equal false }
|
302
|
+
it { subject.errors.messages.must_equal({:title=>["not lowercase"]}) }
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
# overriding the reader for a nested form should only be considered when rendering.
|
307
|
+
describe "with overridden reader for nested form" do
|
308
|
+
let (:form) {
|
309
|
+
Class.new(Reform::Form) do
|
310
|
+
property :band, :populate_if_empty => lambda { |*| Band.new } do
|
311
|
+
property :label
|
312
|
+
end
|
313
|
+
|
314
|
+
collection :songs, :populate_if_empty => lambda { |*| Song.new } do
|
315
|
+
property :title
|
316
|
+
end
|
317
|
+
|
318
|
+
def band
|
319
|
+
raise "only call me when rendering the form!"
|
320
|
+
end
|
321
|
+
|
322
|
+
def songs
|
323
|
+
raise "only call me when rendering the form!"
|
324
|
+
end
|
325
|
+
end.new(album)
|
326
|
+
}
|
327
|
+
|
328
|
+
let (:album) { Album.new }
|
329
|
+
|
330
|
+
# don't use #artist when validating!
|
331
|
+
it do
|
332
|
+
form.validate("band" => {"label" => "Hellcat"}, "songs" => [{"title" => "Stand Your Ground"}, {"title" => "Otherside"}])
|
333
|
+
form.sync
|
334
|
+
album.band.label.must_equal "Hellcat"
|
335
|
+
album.songs.first.title.must_equal "Stand Your Ground"
|
336
|
+
end
|
337
|
+
end
|
278
338
|
end
|
279
339
|
|
280
340
|
# #validate(params)
|
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.1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
@@ -9,90 +9,90 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-09-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: representable
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
-
- - ~>
|
18
|
+
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
20
|
version: 2.0.3
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
|
-
- - ~>
|
25
|
+
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: 2.0.3
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: disposable
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
|
-
- - ~>
|
32
|
+
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
34
|
version: 0.0.5
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
|
-
- - ~>
|
39
|
+
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: 0.0.5
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: uber
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
|
-
- - ~>
|
46
|
+
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
48
|
version: 0.0.8
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
|
-
- - ~>
|
53
|
+
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: 0.0.8
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: activemodel
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
|
-
- -
|
60
|
+
- - ">="
|
61
61
|
- !ruby/object:Gem::Version
|
62
62
|
version: '0'
|
63
63
|
type: :runtime
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
|
-
- -
|
67
|
+
- - ">="
|
68
68
|
- !ruby/object:Gem::Version
|
69
69
|
version: '0'
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: bundler
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
73
73
|
requirements:
|
74
|
-
- - ~>
|
74
|
+
- - "~>"
|
75
75
|
- !ruby/object:Gem::Version
|
76
76
|
version: '1.3'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
|
-
- - ~>
|
81
|
+
- - "~>"
|
82
82
|
- !ruby/object:Gem::Version
|
83
83
|
version: '1.3'
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
85
|
name: rake
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
87
87
|
requirements:
|
88
|
-
- -
|
88
|
+
- - ">="
|
89
89
|
- !ruby/object:Gem::Version
|
90
90
|
version: '0'
|
91
91
|
type: :development
|
92
92
|
prerelease: false
|
93
93
|
version_requirements: !ruby/object:Gem::Requirement
|
94
94
|
requirements:
|
95
|
-
- -
|
95
|
+
- - ">="
|
96
96
|
- !ruby/object:Gem::Version
|
97
97
|
version: '0'
|
98
98
|
- !ruby/object:Gem::Dependency
|
@@ -113,70 +113,70 @@ dependencies:
|
|
113
113
|
name: activerecord
|
114
114
|
requirement: !ruby/object:Gem::Requirement
|
115
115
|
requirements:
|
116
|
-
- -
|
116
|
+
- - ">="
|
117
117
|
- !ruby/object:Gem::Version
|
118
118
|
version: '0'
|
119
119
|
type: :development
|
120
120
|
prerelease: false
|
121
121
|
version_requirements: !ruby/object:Gem::Requirement
|
122
122
|
requirements:
|
123
|
-
- -
|
123
|
+
- - ">="
|
124
124
|
- !ruby/object:Gem::Version
|
125
125
|
version: '0'
|
126
126
|
- !ruby/object:Gem::Dependency
|
127
127
|
name: sqlite3
|
128
128
|
requirement: !ruby/object:Gem::Requirement
|
129
129
|
requirements:
|
130
|
-
- -
|
130
|
+
- - ">="
|
131
131
|
- !ruby/object:Gem::Version
|
132
132
|
version: '0'
|
133
133
|
type: :development
|
134
134
|
prerelease: false
|
135
135
|
version_requirements: !ruby/object:Gem::Requirement
|
136
136
|
requirements:
|
137
|
-
- -
|
137
|
+
- - ">="
|
138
138
|
- !ruby/object:Gem::Version
|
139
139
|
version: '0'
|
140
140
|
- !ruby/object:Gem::Dependency
|
141
141
|
name: virtus
|
142
142
|
requirement: !ruby/object:Gem::Requirement
|
143
143
|
requirements:
|
144
|
-
- -
|
144
|
+
- - ">="
|
145
145
|
- !ruby/object:Gem::Version
|
146
146
|
version: '0'
|
147
147
|
type: :development
|
148
148
|
prerelease: false
|
149
149
|
version_requirements: !ruby/object:Gem::Requirement
|
150
150
|
requirements:
|
151
|
-
- -
|
151
|
+
- - ">="
|
152
152
|
- !ruby/object:Gem::Version
|
153
153
|
version: '0'
|
154
154
|
- !ruby/object:Gem::Dependency
|
155
155
|
name: rails
|
156
156
|
requirement: !ruby/object:Gem::Requirement
|
157
157
|
requirements:
|
158
|
-
- -
|
158
|
+
- - ">="
|
159
159
|
- !ruby/object:Gem::Version
|
160
160
|
version: '0'
|
161
161
|
type: :development
|
162
162
|
prerelease: false
|
163
163
|
version_requirements: !ruby/object:Gem::Requirement
|
164
164
|
requirements:
|
165
|
-
- -
|
165
|
+
- - ">="
|
166
166
|
- !ruby/object:Gem::Version
|
167
167
|
version: '0'
|
168
168
|
- !ruby/object:Gem::Dependency
|
169
169
|
name: actionpack
|
170
170
|
requirement: !ruby/object:Gem::Requirement
|
171
171
|
requirements:
|
172
|
-
- -
|
172
|
+
- - ">="
|
173
173
|
- !ruby/object:Gem::Version
|
174
174
|
version: '0'
|
175
175
|
type: :development
|
176
176
|
prerelease: false
|
177
177
|
version_requirements: !ruby/object:Gem::Requirement
|
178
178
|
requirements:
|
179
|
-
- -
|
179
|
+
- - ">="
|
180
180
|
- !ruby/object:Gem::Version
|
181
181
|
version: '0'
|
182
182
|
description: Freeing your AR models from form logic.
|
@@ -187,8 +187,8 @@ executables: []
|
|
187
187
|
extensions: []
|
188
188
|
extra_rdoc_files: []
|
189
189
|
files:
|
190
|
-
- .gitignore
|
191
|
-
- .travis.yml
|
190
|
+
- ".gitignore"
|
191
|
+
- ".travis.yml"
|
192
192
|
- CHANGES.md
|
193
193
|
- Gemfile
|
194
194
|
- LICENSE.txt
|
@@ -279,12 +279,12 @@ require_paths:
|
|
279
279
|
- lib
|
280
280
|
required_ruby_version: !ruby/object:Gem::Requirement
|
281
281
|
requirements:
|
282
|
-
- -
|
282
|
+
- - ">="
|
283
283
|
- !ruby/object:Gem::Version
|
284
284
|
version: '0'
|
285
285
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
286
286
|
requirements:
|
287
|
-
- -
|
287
|
+
- - ">="
|
288
288
|
- !ruby/object:Gem::Version
|
289
289
|
version: '0'
|
290
290
|
requirements: []
|