reform 2.2.4 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +5 -1
  3. data/.rubocop.yml +30 -0
  4. data/.rubocop_todo.yml +460 -0
  5. data/.travis.yml +11 -6
  6. data/Appraisals +8 -0
  7. data/CHANGES.md +54 -4
  8. data/CONTRIBUTING.md +31 -0
  9. data/Gemfile +2 -16
  10. data/ISSUE_TEMPLATE.md +25 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +5 -7
  13. data/Rakefile +18 -9
  14. data/gemfiles/0.13.0.gemfile +8 -0
  15. data/gemfiles/1.5.0.gemfile +9 -0
  16. data/lib/reform.rb +1 -0
  17. data/lib/reform/contract.rb +7 -17
  18. data/lib/reform/contract/custom_error.rb +41 -0
  19. data/lib/reform/contract/validate.rb +53 -23
  20. data/lib/reform/errors.rb +61 -0
  21. data/lib/reform/form.rb +36 -10
  22. data/lib/reform/form/call.rb +1 -1
  23. data/lib/reform/form/composition.rb +2 -2
  24. data/lib/reform/form/dry.rb +10 -58
  25. data/lib/reform/form/dry/input_hash.rb +37 -0
  26. data/lib/reform/form/dry/new_api.rb +46 -0
  27. data/lib/reform/form/dry/old_api.rb +61 -0
  28. data/lib/reform/form/populator.rb +11 -27
  29. data/lib/reform/form/prepopulate.rb +4 -3
  30. data/lib/reform/form/validate.rb +28 -13
  31. data/lib/reform/result.rb +90 -0
  32. data/lib/reform/validation.rb +19 -11
  33. data/lib/reform/validation/groups.rb +12 -27
  34. data/lib/reform/version.rb +1 -1
  35. data/reform.gemspec +15 -13
  36. data/test/benchmarking.rb +39 -6
  37. data/test/call_new_api.rb +23 -0
  38. data/test/{call_test.rb → call_old_api.rb} +4 -4
  39. data/test/changed_test.rb +8 -8
  40. data/test/coercion_test.rb +51 -19
  41. data/test/composition_new_api.rb +186 -0
  42. data/test/{composition_test.rb → composition_old_api.rb} +66 -31
  43. data/test/contract/custom_error_test.rb +55 -0
  44. data/test/contract_new_api.rb +77 -0
  45. data/test/{contract_test.rb → contract_old_api.rb} +13 -13
  46. data/test/default_test.rb +2 -2
  47. data/test/deserialize_test.rb +11 -14
  48. data/test/errors_new_api.rb +225 -0
  49. data/test/errors_old_api.rb +230 -0
  50. data/test/feature_test.rb +8 -10
  51. data/test/fixtures/dry_error_messages.yml +73 -23
  52. data/test/fixtures/dry_new_api_error_messages.yml +104 -0
  53. data/test/form_new_api.rb +57 -0
  54. data/test/{form_test.rb → form_old_api.rb} +5 -5
  55. data/test/form_option_new_api.rb +24 -0
  56. data/test/{form_option_test.rb → form_option_old_api.rb} +4 -4
  57. data/test/from_test.rb +9 -13
  58. data/test/inherit_new_api.rb +105 -0
  59. data/test/inherit_old_api.rb +105 -0
  60. data/test/{module_test.rb → module_new_api.rb} +20 -25
  61. data/test/module_old_api.rb +146 -0
  62. data/test/parse_option_test.rb +40 -0
  63. data/test/parse_pipeline_test.rb +3 -3
  64. data/test/populate_new_api.rb +304 -0
  65. data/test/{populate_test.rb → populate_old_api.rb} +83 -49
  66. data/test/populator_skip_test.rb +9 -9
  67. data/test/prepopulator_test.rb +8 -9
  68. data/test/read_only_test.rb +12 -1
  69. data/test/readable_test.rb +7 -7
  70. data/test/reform_new_api.rb +204 -0
  71. data/test/{reform_test.rb → reform_old_api.rb} +30 -51
  72. data/test/save_new_api.rb +101 -0
  73. data/test/{save_test.rb → save_old_api.rb} +32 -20
  74. data/test/setup_test.rb +8 -8
  75. data/test/{skip_if_test.rb → skip_if_new_api.rb} +23 -12
  76. data/test/skip_if_old_api.rb +92 -0
  77. data/test/skip_setter_and_getter_test.rb +3 -4
  78. data/test/test_helper.rb +25 -14
  79. data/test/validate_new_api.rb +408 -0
  80. data/test/{validate_test.rb → validate_old_api.rb} +59 -69
  81. data/test/validation/dry_validation_new_api.rb +836 -0
  82. data/test/validation/dry_validation_old_api.rb +772 -0
  83. data/test/validation/result_test.rb +77 -0
  84. data/test/validation_library_provided_test.rb +16 -0
  85. data/test/virtual_test.rb +47 -7
  86. data/test/writeable_test.rb +35 -6
  87. metadata +127 -56
  88. data/gemfiles/Gemfile.disposable-0.3 +0 -6
  89. data/lib/reform/contract/errors.rb +0 -43
  90. data/lib/reform/form/mongoid.rb +0 -37
  91. data/lib/reform/form/orm.rb +0 -26
  92. data/lib/reform/mongoid.rb +0 -4
  93. data/test/deprecation_test.rb +0 -27
  94. data/test/errors_test.rb +0 -165
  95. data/test/inherit_test.rb +0 -119
  96. data/test/readonly_test.rb +0 -14
  97. data/test/validation/dry_test.rb +0 -60
  98. data/test/validation/dry_validation_test.rb +0 -352
  99. 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
- required(:title).filled
11
- end
12
- end
13
-
14
- collection :songs do
15
- property :title
16
- validation do
17
- required(:title).filled
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
- required(:name).filled
27
- end
28
- end
29
- # TODO: make band a required object.
30
-
31
- validation do
32
- # required(:name).filled(: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
- required(:title).filled
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