reform 2.2.3 → 2.3.2

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.
Files changed (97) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +5 -1
  3. data/.travis.yml +11 -6
  4. data/Appraisals +8 -0
  5. data/CHANGES.md +56 -0
  6. data/CONTRIBUTING.md +31 -0
  7. data/Gemfile +2 -16
  8. data/ISSUE_TEMPLATE.md +25 -0
  9. data/LICENSE.txt +1 -1
  10. data/README.md +5 -7
  11. data/Rakefile +16 -9
  12. data/gemfiles/0.13.0.gemfile +8 -0
  13. data/gemfiles/1.5.0.gemfile +9 -0
  14. data/lib/reform.rb +1 -0
  15. data/lib/reform/contract.rb +7 -17
  16. data/lib/reform/contract/custom_error.rb +41 -0
  17. data/lib/reform/contract/validate.rb +53 -23
  18. data/lib/reform/errors.rb +61 -0
  19. data/lib/reform/form.rb +36 -10
  20. data/lib/reform/form/call.rb +1 -1
  21. data/lib/reform/form/composition.rb +2 -2
  22. data/lib/reform/form/dry.rb +10 -58
  23. data/lib/reform/form/dry/input_hash.rb +37 -0
  24. data/lib/reform/form/dry/new_api.rb +47 -0
  25. data/lib/reform/form/dry/old_api.rb +61 -0
  26. data/lib/reform/form/populator.rb +11 -29
  27. data/lib/reform/form/prepopulate.rb +5 -4
  28. data/lib/reform/form/validate.rb +28 -13
  29. data/lib/reform/result.rb +90 -0
  30. data/lib/reform/validation.rb +19 -11
  31. data/lib/reform/validation/groups.rb +12 -27
  32. data/lib/reform/version.rb +1 -1
  33. data/reform.gemspec +13 -13
  34. data/test/benchmarking.rb +39 -6
  35. data/test/call_new_api.rb +23 -0
  36. data/test/{call_test.rb → call_old_api.rb} +4 -4
  37. data/test/changed_test.rb +8 -8
  38. data/test/coercion_test.rb +51 -19
  39. data/test/composition_new_api.rb +186 -0
  40. data/test/{composition_test.rb → composition_old_api.rb} +66 -31
  41. data/test/contract/custom_error_test.rb +55 -0
  42. data/test/contract_new_api.rb +77 -0
  43. data/test/{contract_test.rb → contract_old_api.rb} +13 -13
  44. data/test/default_test.rb +2 -2
  45. data/test/deserialize_test.rb +11 -14
  46. data/test/errors_new_api.rb +225 -0
  47. data/test/errors_old_api.rb +230 -0
  48. data/test/feature_test.rb +8 -10
  49. data/test/fixtures/dry_error_messages.yml +73 -23
  50. data/test/fixtures/dry_new_api_error_messages.yml +104 -0
  51. data/test/form_new_api.rb +57 -0
  52. data/test/{form_test.rb → form_old_api.rb} +5 -5
  53. data/test/form_option_new_api.rb +24 -0
  54. data/test/{form_option_test.rb → form_option_old_api.rb} +4 -4
  55. data/test/from_test.rb +9 -13
  56. data/test/inherit_new_api.rb +105 -0
  57. data/test/inherit_old_api.rb +105 -0
  58. data/test/{module_test.rb → module_new_api.rb} +20 -25
  59. data/test/module_old_api.rb +146 -0
  60. data/test/parse_option_test.rb +40 -0
  61. data/test/parse_pipeline_test.rb +3 -3
  62. data/test/populate_new_api.rb +304 -0
  63. data/test/{populate_test.rb → populate_old_api.rb} +83 -49
  64. data/test/populator_skip_test.rb +9 -9
  65. data/test/prepopulator_test.rb +8 -9
  66. data/test/read_only_test.rb +12 -1
  67. data/test/readable_test.rb +7 -7
  68. data/test/reform_new_api.rb +204 -0
  69. data/test/{reform_test.rb → reform_old_api.rb} +30 -51
  70. data/test/save_new_api.rb +101 -0
  71. data/test/{save_test.rb → save_old_api.rb} +32 -20
  72. data/test/setup_test.rb +8 -8
  73. data/test/{skip_if_test.rb → skip_if_new_api.rb} +23 -12
  74. data/test/skip_if_old_api.rb +92 -0
  75. data/test/skip_setter_and_getter_test.rb +3 -4
  76. data/test/test_helper.rb +25 -14
  77. data/test/validate_new_api.rb +453 -0
  78. data/test/{validate_test.rb → validate_old_api.rb} +59 -69
  79. data/test/validation/dry_validation_new_api.rb +836 -0
  80. data/test/validation/dry_validation_old_api.rb +772 -0
  81. data/test/validation/result_test.rb +77 -0
  82. data/test/validation_library_provided_test.rb +16 -0
  83. data/test/virtual_test.rb +47 -7
  84. data/test/writeable_test.rb +35 -6
  85. metadata +101 -72
  86. data/gemfiles/Gemfile.disposable-0.3 +0 -6
  87. data/lib/reform/contract/errors.rb +0 -43
  88. data/lib/reform/form/mongoid.rb +0 -37
  89. data/lib/reform/form/orm.rb +0 -26
  90. data/lib/reform/mongoid.rb +0 -4
  91. data/test/deprecation_test.rb +0 -27
  92. data/test/errors_test.rb +0 -165
  93. data/test/inherit_test.rb +0 -119
  94. data/test/readonly_test.rb +0 -14
  95. data/test/validation/dry_test.rb +0 -60
  96. data/test/validation/dry_validation_test.rb +0 -352
  97. data/test/validation/errors.yml +0 -4
@@ -1,6 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- # Specify your gem's dependencies in reform.gemspec
4
- gemspec :path => '../'
5
-
6
- gem 'minitest'
@@ -1,43 +0,0 @@
1
- class Reform::Contract::Errors
2
- def initialize(*)
3
- @errors = {}
4
- end
5
-
6
- module Merge
7
- def merge!(errors, prefix)
8
- errors.messages.each do |field, msgs|
9
- unless field.to_sym == :base
10
- field = (prefix+[field]).join(".").to_sym # TODO: why is that a symbol in Rails?
11
- end
12
-
13
- msgs.each do |msg|
14
- next if messages[field] and messages[field].include?(msg)
15
- add(field, msg)
16
- end # Forms now contains a plain errors hash. the errors for each item are still available in item.errors.
17
- end
18
- end
19
-
20
- def to_s
21
- messages.inspect
22
- end
23
- end
24
- include Merge
25
-
26
- def add(field, message)
27
- @errors[field] ||= []
28
- @errors[field] << message
29
- end
30
-
31
- def messages
32
- @errors
33
- end
34
-
35
- def empty?
36
- @errors.empty?
37
- end
38
-
39
- # needed by Rails form builder.
40
- def [](name)
41
- @errors[name] || []
42
- end
43
- end
@@ -1,37 +0,0 @@
1
- module Reform::Form::Mongoid
2
- def self.included(base)
3
- base.class_eval do
4
- register_feature Reform::Form::Mongoid
5
- include Reform::Form::ActiveModel
6
- include Reform::Form::ORM
7
- extend ClassMethods
8
- end
9
- end
10
-
11
- module ClassMethods
12
- def validates_uniqueness_of(attribute, options={})
13
- options = options.merge(:attributes => [attribute])
14
- validates_with(UniquenessValidator, options)
15
- end
16
- def i18n_scope
17
- :mongoid
18
- end
19
- end
20
-
21
-
22
- def self.mongoid_namespace
23
- if mongoid_is_4_or_more?
24
- 'Validatable'
25
- else
26
- 'Validations'
27
- end
28
- end
29
-
30
- def self.mongoid_is_4_or_more?
31
- Mongoid::VERSION.split('.').first.to_i >= 4
32
- end
33
-
34
- UniquenessValidator = Class.new("::Mongoid::#{mongoid_namespace}::UniquenessValidator".constantize) do
35
- include Reform::Form::ORM::UniquenessValidator
36
- end
37
- end
@@ -1,26 +0,0 @@
1
- module Reform::Form::ORM
2
- def model_for_property(name)
3
- return model unless is_a?(Reform::Form::Composition) # i am too lazy for proper inheritance. there should be a ActiveRecord::Composition that handles this.
4
-
5
- model_name = options_for(name)[:on]
6
- model[model_name]
7
- end
8
-
9
- module UniquenessValidator
10
- # when calling validates it should create the Vali instance already and set @klass there! # TODO: fix this in AM.
11
- def validate(form)
12
- property = attributes.first
13
-
14
- # here is the thing: why does AM::UniquenessValidator require a filled-out record to work properly? also, why do we need to set
15
- # the class? it would be way easier to pass #validate a hash of attributes and get back an errors hash.
16
- # the class for the finder could either be infered from the record or set in the validator instance itself in the call to ::validates.
17
- record = form.model_for_property(property)
18
- record.send("#{property}=", form.send(property))
19
-
20
- @klass = record.class # this is usually done in the super-sucky #setup method.
21
- super(record).tap do |res|
22
- form.errors.add(property, record.errors.first.last) if record.errors.present?
23
- end
24
- end
25
- end
26
- end
@@ -1,4 +0,0 @@
1
- require 'reform/form/active_model'
2
- require 'reform/form/orm'
3
- require 'reform/form/mongoid'
4
- require 'reform/form/active_model/model_reflections' # only load this in AR context as simple_form currently is bound to AR.
@@ -1,27 +0,0 @@
1
- require "test_helper"
2
-
3
-
4
- class DeprecationRemoveMePopulatorTest < MiniTest::Spec
5
- Album = Struct.new(:songs)
6
- Song = Struct.new(:title)
7
-
8
-
9
- class AlbumForm < Reform::Form
10
- collection :songs, populator: ->(fragment, collection, index, *) { return Representable::Pipeline::Stop if fragment[:title]=="Good"
11
- songs[index]
12
- } do
13
- property :title
14
- end
15
- end
16
-
17
- it do
18
- form = AlbumForm.new(Album.new([Song.new, Song.new]))
19
- hash = {songs: [{title: "Good"}, {title: "Bad"}]}
20
-
21
- form.validate(hash)
22
-
23
- form.songs.size.must_equal 2
24
- form.songs[0].title.must_equal nil
25
- form.songs[1].title.must_equal "Bad"
26
- end
27
- end
@@ -1,165 +0,0 @@
1
- # require "test_helper"
2
-
3
- # class ErrorsTest < MiniTest::Spec
4
- # class AlbumForm < Reform::Form
5
- # property :title
6
-
7
- # property :hit do
8
- # property :title
9
- # validation do
10
- # key(:title).required
11
- # end
12
- # end
13
-
14
- # collection :songs do
15
- # property :title
16
- # validation do
17
- # key(:title).required
18
- # end
19
- # end
20
-
21
- # property :band do # yepp, people do crazy stuff like that.
22
- # property :name
23
- # property :label do
24
- # property :name
25
- # validation do
26
- # key(:name).required
27
- # end
28
- # end
29
- # # TODO: make band a required object.
30
-
31
- # validation do
32
- # key(:name).required(:music_taste_ok?)
33
-
34
- # configure do
35
- # config.messages_file = "test/validation/errors.yml"
36
-
37
- # def music_taste_ok?(value)
38
- # value != "Nickelback"
39
- # # errors.add(:base, "You are a bad person") if name == "Nickelback"
40
- # end
41
- # end
42
- # end
43
- # # validate :music_taste_ok?
44
-
45
- # # private
46
- # # def music_taste_ok?
47
- # # errors.add(:base, "You are a bad person") if name == "Nickelback"
48
- # # end
49
- # end
50
-
51
- # validation do
52
- # key(:title).required
53
- # end
54
- # end
55
-
56
- # let (:album) do
57
- # OpenStruct.new(
58
- # :title => "Blackhawks Over Los Angeles",
59
- # :hit => song,
60
- # :songs => songs, # TODO: document this requirement,
61
-
62
- # :band => Struct.new(:name, :label).new("Epitaph", OpenStruct.new),
63
- # )
64
- # end
65
- # let (:song) { OpenStruct.new(:title => "Downtown") }
66
- # let (:songs) { [song=OpenStruct.new(:title => "Calling"), song] }
67
- # let (:form) { AlbumForm.new(album) }
68
-
69
-
70
- # describe "incorrect #validate" do
71
- # before { form.validate(
72
- # "hit" =>{"title" => ""},
73
- # "title" => "",
74
- # "songs" => [{"title" => ""}, {"title" => ""}]) } # FIXME: what happens if item is missing?
75
-
76
- # it do
77
- # form.errors.messages.must_equal({
78
- # :title => ["must be filled"],
79
- # :"hit.title"=>["must be filled"],
80
- # :"songs.title"=>["must be filled"],
81
- # :"band.label.name"=>["is missing"]
82
- # })
83
- # end
84
-
85
- # it do
86
- # #form.errors.must_equal({:title => ["must be filled"]})
87
- # # TODO: this should only contain local errors?
88
- # end
89
-
90
- # # nested forms keep their own Errors:
91
- # it { form.hit.errors.messages.must_equal({:title=>["must be filled"]}) }
92
- # it { form.songs[0].errors.messages.must_equal({:title=>["must be filled"]}) }
93
-
94
- # it do
95
- # form.errors.messages.must_equal({
96
- # :title => ["must be filled"],
97
- # :"hit.title" => ["must be filled"],
98
- # :"songs.title"=> ["must be filled"],
99
- # :"band.label.name"=>["is missing"]
100
- # })
101
- # end
102
- # end
103
-
104
-
105
- # describe "#validate with main form invalid" do
106
- # it do
107
- # form.validate("title"=>"", "band"=>{"label"=>{:name => "Fat Wreck"}}).must_equal false
108
- # form.errors.messages.must_equal({:title=>["must be filled"]})
109
- # end
110
- # end
111
-
112
-
113
- # describe "#validate with middle nested form invalid" do
114
- # before { @result = form.validate("hit"=>{"title" => ""}, "band"=>{"label"=>{:name => "Fat Wreck"}}) }
115
-
116
- # it { @result.must_equal false }
117
- # it { form.errors.messages.must_equal({:"hit.title"=>["must be filled"]}) }
118
- # end
119
-
120
-
121
- # describe "#validate with collection form invalid" do
122
- # before { @result = form.validate("songs"=>[{"title" => ""}], "band"=>{"label"=>{:name => "Fat Wreck"}}) }
123
-
124
- # it { @result.must_equal false }
125
- # it { form.errors.messages.must_equal({:"songs.title"=>["must be filled"]}) }
126
- # end
127
-
128
-
129
- # describe "#validate with collection and 2-level-nested invalid" do
130
- # before { @result = form.validate("songs"=>[{"title" => ""}], "band" => {"label" => {}}) }
131
-
132
- # it { @result.must_equal false }
133
- # it { form.errors.messages.must_equal({:"songs.title"=>["must be filled"], :"band.label.name"=>["is missing"]}) }
134
- # end
135
-
136
- # describe "#validate with nested form using :base invalid" do
137
- # it do
138
- # result = form.validate("songs"=>[{"title" => "Someday"}], "band" => {"name" => "Nickelback", "label" => {"name" => "Roadrunner Records"}})
139
- # result.must_equal false
140
- # form.errors.messages.must_equal({:"band.name"=>["You are a bad person"]})
141
- # end
142
- # end
143
-
144
- # describe "correct #validate" do
145
- # before { @result = form.validate(
146
- # "hit" => {"title" => "Sacrifice"},
147
- # "title" => "Second Heat",
148
- # "songs" => [{"title"=>"Heart Of A Lion"}],
149
- # "band" => {"label"=>{:name => "Fat Wreck"}}
150
- # ) }
151
-
152
- # it { @result.must_equal true }
153
- # it { form.hit.title.must_equal "Sacrifice" }
154
- # it { form.title.must_equal "Second Heat" }
155
- # it { form.songs.first.title.must_equal "Heart Of A Lion" }
156
- # end
157
-
158
-
159
- # describe "Errors#to_s" do
160
- # before { form.validate("songs"=>[{"title" => ""}], "band" => {"label" => {}}) }
161
-
162
- # # to_s is aliased to messages
163
- # it { form.errors.to_s.must_equal "{:\"songs.title\"=>[\"must be filled\"], :\"band.label.name\"=>[\"is missing\"]}" }
164
- # end
165
- # end
@@ -1,119 +0,0 @@
1
- require 'test_helper'
2
- require 'representable/json'
3
-
4
- class InheritTest < BaseTest
5
- Populator = Reform::Form::Populator
6
-
7
- class AlbumForm < Reform::Form
8
- property :title, deserializer: {instance: "Instance"}, skip_if: "skip_if in AlbumForm" # allow direct configuration of :deserializer.
9
-
10
- property :hit, populator: "Populator" do
11
- property :title
12
- end
13
-
14
- collection :songs, populate_if_empty: lambda {}, skip_if: :all_blank do
15
- property :title
16
- end
17
-
18
- property :artist, populate_if_empty: lambda {} do
19
-
20
- def artist_id
21
- 1
22
- end
23
- end
24
- end
25
-
26
- puts
27
- puts "inherit"
28
-
29
- class CompilationForm < AlbumForm
30
- property :title, inherit: true, skip_if: "skip_if from CompilationForm"
31
- puts "[#{options_for(:title)[:deserializer].object_id}] COM@@@@@ #{options_for(:title)[:deserializer].inspect}"
32
- # property :hit, :inherit => true do
33
- # property :rating
34
- # validates :title, :rating, :presence => true
35
- # end
36
-
37
- # puts representer_class.representable_attrs.
38
- # get(:hit)[:extend].evaluate(nil).new(OpenStruct.new).rating
39
-
40
- # NO collection here, this is entirely inherited.
41
- # collection :songs, ..
42
-
43
- property :artist, inherit: true do # inherit everything, but explicitely.
44
- end
45
-
46
- # completely override.
47
- property :hit, skip_if: "SkipParse" do
48
- end
49
-
50
- # override partly.
51
- end
52
-
53
- let (:album) { Album.new(nil, OpenStruct.new(:hit => OpenStruct.new()) ) }
54
- subject { CompilationForm.new(album) }
55
-
56
-
57
- # valid.
58
- # it {
59
- # subject.validate("hit" => {"title" => "LA Drone", "rating" => 10})
60
- # subject.hit.title.must_equal "LA Drone"
61
- # subject.hit.rating.must_equal 10
62
- # subject.errors.messages.must_equal({})
63
- # }
64
-
65
- # it do
66
- # subject.validate({})
67
- # subject.hit.title.must_equal nil
68
- # subject.hit.rating.must_equal nil
69
- # subject.errors.messages.must_equal({:"hit.title"=>["can't be blank"], :"hit.rating"=>["can't be blank"]})
70
- # end
71
-
72
- require "pp"
73
-
74
- it "xxx" do
75
- # sub hashes like :deserializer must be properly cloned when inheriting.
76
- AlbumForm.options_for(:title)[:deserializer].object_id.wont_equal CompilationForm.options_for(:title)[:deserializer].object_id
77
-
78
- # don't overwrite direct deserializer: {} configuration.
79
- AlbumForm.options_for(:title)[:internal_populator].must_be_instance_of Reform::Form::Populator::Sync
80
- AlbumForm.options_for(:title)[:deserializer][:skip_parse].must_equal "skip_if in AlbumForm"
81
-
82
- AlbumForm.options_for(:hit)[:internal_populator].inspect.must_match /Reform::Form::Populator:.+ @user_proc="Populator"/
83
- # AlbumForm.options_for(:hit)[:deserializer][:instance].inspect.must_be_instance_with Reform::Form::Populator, user_proc: "Populator"
84
-
85
-
86
- AlbumForm.options_for(:songs)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
87
- AlbumForm.options_for(:songs)[:deserializer][:skip_parse].must_be_instance_of Reform::Form::Validate::Skip::AllBlank
88
-
89
- AlbumForm.options_for(:artist)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
90
-
91
-
92
-
93
- CompilationForm.options_for(:title)[:deserializer][:skip_parse].must_equal "skip_if from CompilationForm"
94
- # pp CompilationForm.options_for(:songs)
95
- CompilationForm.options_for(:songs)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
96
-
97
-
98
- CompilationForm.options_for(:artist)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
99
-
100
- # completely overwrite inherited.
101
- CompilationForm.options_for(:hit)[:internal_populator].must_be_instance_of Reform::Form::Populator::Sync # reset to default.
102
- CompilationForm.options_for(:hit)[:deserializer][:skip_parse].must_equal "SkipParse"
103
-
104
-
105
- # inherit: true with block will still inherit the original class.
106
- AlbumForm.new(OpenStruct.new(artist: OpenStruct.new)).artist.artist_id.must_equal 1
107
- CompilationForm.new(OpenStruct.new(artist: OpenStruct.new)).artist.artist_id.must_equal 1
108
- end
109
-
110
-
111
- class CDForm < AlbumForm
112
- # override :artist's original populate_if_empty but with :inherit.
113
- property :artist, inherit: true, populator: "CD Populator" do
114
-
115
- end
116
- end
117
-
118
- it { CDForm.options_for(:artist)[:internal_populator].instance_variable_get(:@user_proc).must_equal "CD Populator" }
119
- end
@@ -1,14 +0,0 @@
1
- require "test_helper"
2
-
3
- class ReadonlyTest < MiniTest::Spec
4
- class SongForm < Reform::Form
5
- property :artist
6
- property :title, writeable: false
7
- # TODO: what to do with virtual values?
8
- end
9
-
10
- let (:form) { SongForm.new(OpenStruct.new) }
11
-
12
- it { form.readonly?(:artist).must_equal false }
13
- it { form.readonly?(:title).must_equal true }
14
- end