reform 2.1.0 → 2.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.travis.yml +4 -12
  4. data/CHANGES.md +8 -0
  5. data/README.md +36 -743
  6. data/Rakefile +1 -31
  7. data/gemfiles/{Gemfile.rails-3.1 → Gemfile.disposable-0.3} +1 -2
  8. data/lib/reform.rb +0 -9
  9. data/lib/reform/contract.rb +5 -1
  10. data/lib/reform/form.rb +1 -4
  11. data/lib/reform/form/composition.rb +1 -2
  12. data/lib/reform/form/dry.rb +29 -16
  13. data/lib/reform/form/module.rb +15 -3
  14. data/lib/reform/form/validate.rb +1 -1
  15. data/lib/reform/validation.rb +3 -3
  16. data/lib/reform/version.rb +1 -1
  17. data/reform.gemspec +3 -10
  18. data/test/coercion_test.rb +7 -7
  19. data/test/composition_test.rb +5 -1
  20. data/test/contract_test.rb +10 -4
  21. data/test/deserialize_test.rb +3 -3
  22. data/test/errors_test.rb +48 -28
  23. data/test/form_option_test.rb +3 -1
  24. data/test/form_test.rb +19 -14
  25. data/test/module_test.rb +51 -11
  26. data/test/populate_test.rb +21 -7
  27. data/test/reform_test.rb +24 -20
  28. data/test/save_test.rb +10 -4
  29. data/test/skip_if_test.rb +5 -3
  30. data/test/test_helper.rb +3 -43
  31. data/test/validate_test.rb +34 -14
  32. data/test/validation/dry_test.rb +60 -0
  33. data/test/validation/dry_validation_test.rb +65 -43
  34. data/test/validation/errors.yml +4 -0
  35. metadata +16 -192
  36. data/database.sqlite3 +0 -0
  37. data/gemfiles/Gemfile.rails-3.2 +0 -7
  38. data/gemfiles/Gemfile.rails-4.0 +0 -8
  39. data/gemfiles/Gemfile.rails-4.1 +0 -8
  40. data/gemfiles/Gemfile.rails-4.2 +0 -8
  41. data/lib/reform/active_record.rb +0 -4
  42. data/lib/reform/form/active_model.rb +0 -87
  43. data/lib/reform/form/active_model/form_builder_methods.rb +0 -48
  44. data/lib/reform/form/active_model/model_reflections.rb +0 -46
  45. data/lib/reform/form/active_model/model_validations.rb +0 -110
  46. data/lib/reform/form/active_model/validations.rb +0 -107
  47. data/lib/reform/form/active_record.rb +0 -30
  48. data/lib/reform/form/lotus.rb +0 -59
  49. data/lib/reform/form/multi_parameter_attributes.rb +0 -48
  50. data/lib/reform/form/validation/unique_validator.rb +0 -54
  51. data/lib/reform/rails.rb +0 -13
  52. data/test/active_model_custom_validation_translations_test.rb +0 -75
  53. data/test/active_model_test.rb +0 -207
  54. data/test/active_model_validation_for_property_named_format_test.rb +0 -18
  55. data/test/active_record_test.rb +0 -273
  56. data/test/builder_test.rb +0 -32
  57. data/test/custom_validation_test.rb +0 -47
  58. data/test/dummy/Rakefile +0 -7
  59. data/test/dummy/app/controllers/albums_controller.rb +0 -18
  60. data/test/dummy/app/controllers/application_controller.rb +0 -4
  61. data/test/dummy/app/controllers/musician_controller.rb +0 -5
  62. data/test/dummy/app/forms/album_form.rb +0 -18
  63. data/test/dummy/app/helpers/application_helper.rb +0 -2
  64. data/test/dummy/app/models/album.rb +0 -4
  65. data/test/dummy/app/models/song.rb +0 -3
  66. data/test/dummy/app/views/albums/new.html.erb +0 -28
  67. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  68. data/test/dummy/config.ru +0 -4
  69. data/test/dummy/config/application.rb +0 -20
  70. data/test/dummy/config/boot.rb +0 -10
  71. data/test/dummy/config/database.yml +0 -22
  72. data/test/dummy/config/environment.rb +0 -5
  73. data/test/dummy/config/environments/development.rb +0 -16
  74. data/test/dummy/config/environments/production.rb +0 -46
  75. data/test/dummy/config/environments/test.rb +0 -33
  76. data/test/dummy/config/locales/en.yml +0 -14
  77. data/test/dummy/config/routes.rb +0 -4
  78. data/test/dummy/db/test.sqlite3 +0 -0
  79. data/test/form_builder_test.rb +0 -138
  80. data/test/lotus/Gemfile +0 -5
  81. data/test/lotus/lotus_test.rb +0 -31
  82. data/test/lotus_test.rb +0 -150
  83. data/test/model_reflections_test.rb +0 -138
  84. data/test/model_validations_test.rb +0 -82
  85. data/test/mongoid_test.rb +0 -313
  86. data/test/multi_parameter_attributes_test.rb +0 -50
  87. data/test/rails/integration_test.rb +0 -54
  88. data/test/unique_test.rb +0 -135
  89. data/test/validation/activemodel_validation_test.rb +0 -252
@@ -1,30 +0,0 @@
1
- require "reform/form/orm"
2
-
3
- module Reform::Form::ActiveRecord
4
- def self.included(base)
5
- base.class_eval do
6
- register_feature Reform::Form::ActiveRecord
7
- include Reform::Form::ActiveModel
8
- include Reform::Form::ORM
9
- extend ClassMethods
10
- end
11
- end
12
-
13
- module ClassMethods
14
- def validates_uniqueness_of(attribute, options={})
15
- options = options.merge(:attributes => [attribute])
16
- validates_with(UniquenessValidator, options)
17
- end
18
- def i18n_scope
19
- :activerecord
20
- end
21
- end
22
-
23
- def to_nested_hash(*)
24
- super.with_indifferent_access
25
- end
26
-
27
- class UniquenessValidator < ::ActiveRecord::Validations::UniquenessValidator
28
- include Reform::Form::ORM::UniquenessValidator
29
- end
30
- end
@@ -1,59 +0,0 @@
1
- require "lotus/validations"
2
-
3
- # Implements ::validates and friends, and #valid?.
4
- module Reform::Form::Lotus
5
- class Errors < Lotus::Validations::Errors
6
- def merge!(errors, prefix)
7
- new_errors = {}
8
- errors.instance_variable_get(:@errors).each do |name, err|
9
- field = (prefix+[name]).join(".")
10
- new_errors[field] = err
11
- end
12
- # next if messages[field] and messages[field].include?(msg)
13
-
14
- new_errors.each { |field, err| add(field, *err) } # TODO: use namespace feature in Lotus here!
15
- end
16
-
17
- def inspect
18
- @errors.to_s
19
- end
20
-
21
- def messages
22
- self
23
- end
24
- end
25
-
26
-
27
- def self.included(base)
28
- # base.send(:include, Lotus::Validations)
29
- base.extend(ClassMethods)
30
- end
31
-
32
-
33
- module ClassMethods
34
- def validates(name, options)
35
- validations.add(name, options)
36
- end
37
-
38
- def validate(name, *)
39
- # DISCUSS: lotus does not support that?
40
- # validations.add(name, options)
41
- end
42
-
43
- def validations
44
- @validations ||= Lotus::Validations::ValidationSet.new
45
- end
46
- end
47
-
48
- def build_errors
49
- Errors.new
50
- end
51
-
52
- private
53
-
54
- def valid?
55
- # DISCUSS: by using @fields here, we avoid setters being called. win!
56
- validator = Lotus::Validations::Validator.new(self.class.validations, @fields, errors)
57
- validator.validate
58
- end
59
- end
@@ -1,48 +0,0 @@
1
- module Reform::Form::MultiParameterAttributes
2
- # TODO: implement this with parse_filter, so we don't have to manually walk through the hash, etc.
3
- class DateTimeParamsFilter
4
- def call(params)
5
- params = params.dup # DISCUSS: not sure if that slows down form processing?
6
- date_attributes = {}
7
-
8
- params.each do |attribute, value|
9
- if value.is_a?(Hash)
10
- params[attribute] = call(value) # TODO: #validate should only handle local form params.
11
- elsif matches = attribute.match(/^(\w+)\(.i\)$/)
12
- date_attribute = matches[1]
13
- date_attributes[date_attribute] = params_to_date(
14
- params.delete("#{date_attribute}(1i)"),
15
- params.delete("#{date_attribute}(2i)"),
16
- params.delete("#{date_attribute}(3i)"),
17
- params.delete("#{date_attribute}(4i)"),
18
- params.delete("#{date_attribute}(5i)")
19
- )
20
- end
21
- end
22
- params.merge!(date_attributes)
23
- end
24
-
25
- private
26
- def params_to_date(year, month, day, hour, minute)
27
- date_fields = [year, month, day].map!(&:to_i)
28
- time_fields = [hour, minute].map!(&:to_i)
29
-
30
- if date_fields.any?(&:zero?) || !Date.valid_date?(*date_fields)
31
- return nil
32
- end
33
-
34
- if hour.blank? && minute.blank?
35
- Date.new(*date_fields)
36
- else
37
- args = date_fields + time_fields
38
- Time.zone ? Time.zone.local(*args) :
39
- Time.new(*args)
40
- end
41
- end
42
- end
43
-
44
- # this hooks into the format-specific #deserialize! method.
45
- def deserialize!(params)
46
- super DateTimeParamsFilter.new.call(params) # if params.is_a?(Hash) # this currently works for hash, only.
47
- end
48
- end
@@ -1,54 +0,0 @@
1
- # === Unique Validation
2
- # Reform's own implementation for uniqueness which does not write to model
3
- #
4
- # == Usage
5
- # Pass a true boolean value to validate a field against all values available in
6
- # the database:
7
- # validates :title, unique: true
8
- #
9
- # == Options
10
- # = Scope
11
- # A scope can be use to filter the records that need to be compare with the
12
- # current value to validate. A scope array can have one to many fields define.
13
- #
14
- # A scope can be define the following ways:
15
- # validates :title, unique: { scope: :album_id }
16
- # validates :title, unique: { scope: [:album_id] }
17
- # validates :title, unique: { scope: [:album_id, ...] }
18
- #
19
- # All fields included in a scope must be declared as a property like this:
20
- # property :album_id
21
- # validates :title, unique: { scope: :album_id }
22
- #
23
- # Just remove write access to the property if the field must not be change:
24
- # property :album_id, writeable: false
25
- # validates :title, unique: { scope: :album_id }
26
- #
27
- # This use case is useful if album_id is set to a Song this way:
28
- # song = album.songs.new
29
- # album_id is automatically set and can't be change by the operation
30
-
31
- class Reform::Form::UniqueValidator < ActiveModel::EachValidator
32
- def validate_each(form, attribute, value)
33
- model = form.model_for_property(attribute)
34
-
35
- # search for models with attribute equals to form field value
36
- query = model.class.where(attribute => value)
37
-
38
- # apply scope if options has been declared
39
- Array(options[:scope]).each do |field|
40
- # add condition to only check unique value with the same scope
41
- query = query.where(field => form.send(field))
42
- end
43
-
44
- # if model persisted, query may return 0 or 1 rows, else 0
45
- allow_count = model.persisted? ? 1 : 0
46
- form.errors.add(attribute, :taken) if query.count > allow_count
47
- end
48
- end
49
-
50
- # FIXME: ActiveModel loads validators via const_get(#{name}Validator). This magic forces us to
51
- # make the new :unique validator available here.
52
- Reform::Form::ActiveModel::Validations::Validator.class_eval do
53
- UniqueValidator = Reform::Form::UniqueValidator
54
- end
@@ -1,13 +0,0 @@
1
- require "reform/form/active_model"
2
- require "reform/form/active_model/validations"
3
-
4
- require "reform/active_record" if defined?(ActiveRecord)
5
- require "reform/mongoid" if defined?(Mongoid)
6
-
7
- Reform::Form.class_eval do # DISCUSS: i'd prefer having a separate Rails module to be mixed into the Form but this is way more convenient for 99% users.
8
- include Reform::Form::ActiveModel
9
- include Reform::Form::ActiveModel::FormBuilderMethods
10
- include Reform::Form::ActiveRecord if defined?(ActiveRecord)
11
- include Reform::Form::Mongoid if defined?(Mongoid)
12
- include Reform::Form::ActiveModel::Validations
13
- end
@@ -1,75 +0,0 @@
1
- require 'test_helper'
2
-
3
- class ActiveModelCustomValidationTranslationsTest < MiniTest::Spec
4
- module SongForm
5
- class WithBlock < Reform::Form
6
- model :song
7
- property :title
8
-
9
- validate do
10
- errors.add :title, :blank
11
- errors.add :title, :custom_error_message
12
- end
13
- end
14
-
15
- class WithLambda < Reform::Form
16
- model :song
17
- property :title
18
-
19
- validate ->{ errors.add :title, :blank
20
- errors.add :title, :custom_error_message }
21
- end
22
-
23
- class WithMethod < Reform::Form
24
- model :song
25
- property :title
26
-
27
- validate :custom_validation_method
28
- def custom_validation_method
29
- errors.add :title, :blank
30
- errors.add :title, :custom_error_message
31
- end
32
- end
33
- end
34
-
35
-
36
- describe 'when using a default translation' do
37
- it 'translates the error message when custom validation is used with block' do
38
- form = SongForm::WithBlock.new(Song.new)
39
- form.validate({})
40
- form.errors[:title].must_include "can't be blank"
41
- end
42
-
43
- it 'translates the error message when custom validation is used with lambda' do
44
- form = SongForm::WithLambda.new(Song.new)
45
- form.validate({})
46
- form.errors[:title].must_include "can't be blank"
47
- end
48
-
49
- it 'translates the error message when custom validation is used with method' do
50
- form = SongForm::WithMethod.new(Song.new)
51
- form.validate({})
52
- form.errors[:title].must_include "can't be blank"
53
- end
54
- end
55
-
56
- describe 'when using a custom translation' do
57
- it 'translates the error message when custom validation is used with block' do
58
- form = SongForm::WithBlock.new(Song.new)
59
- form.validate({})
60
- form.errors[:title].must_include "Custom Error Message"
61
- end
62
-
63
- it 'translates the error message when custom validation is used with lambda' do
64
- form = SongForm::WithLambda.new(Song.new)
65
- form.validate({})
66
- form.errors[:title].must_include "Custom Error Message"
67
- end
68
-
69
- it 'translates the error message when custom validation is used with method' do
70
- form = SongForm::WithMethod.new(Song.new)
71
- form.validate({})
72
- form.errors[:title].must_include "Custom Error Message"
73
- end
74
- end
75
- end
@@ -1,207 +0,0 @@
1
- require 'test_helper'
2
-
3
- module IsolatedRailsEngine
4
- def self.use_relative_model_naming?
5
- true
6
- end
7
-
8
- class Lyric < ActiveRecord::Base
9
- end
10
- end
11
-
12
- module NormalRailsEngine
13
- class Lyric < ActiveRecord::Base
14
- end
15
- end
16
-
17
-
18
- class NewActiveModelTest < MiniTest::Spec # TODO: move to test/rails/
19
- class SongForm < Reform::Form
20
- include Reform::Form::ActiveModel
21
-
22
- property :name
23
- end
24
-
25
- let (:artist) { Artist.create(:name => "Frank Zappa") }
26
- let (:form) { SongForm.new(artist) }
27
-
28
- it do
29
- form.persisted?.must_equal true
30
- form.to_key.must_equal [artist.id]
31
- form.to_param.must_equal "#{artist.id}"
32
- form.to_model.must_equal form
33
- form.id.must_equal artist.id
34
- form.model_name.must_equal form.class.model_name
35
- end
36
-
37
- describe "::model_name" do
38
- it { form.class.model_name.must_be_kind_of ActiveModel::Name }
39
- it { form.class.model_name.to_s.must_equal "NewActiveModelTest::Song" }
40
-
41
- let (:class_with_model) {
42
- Class.new(Reform::Form) do
43
- include Reform::Form::ActiveModel
44
-
45
- model :album
46
- end
47
- }
48
-
49
- it { class_with_model.model_name.must_be_kind_of ActiveModel::Name }
50
- it { class_with_model.model_name.to_s.must_equal "Album" }
51
-
52
- let (:class_with_isolated_model) {
53
- Class.new(Reform::Form) do
54
- include Reform::Form::ActiveModel
55
-
56
- model "isolated_rails_engine/lyric", namespace: "isolated_rails_engine"
57
- end
58
- }
59
-
60
- it { class_with_isolated_model.model_name.must_be_kind_of ActiveModel::Name }
61
- it { class_with_isolated_model.model_name.to_s.must_equal "IsolatedRailsEngine::Lyric" }
62
-
63
- let (:class_with_namespace_model) {
64
- Class.new(Reform::Form) do
65
- include Reform::Form::ActiveModel
66
-
67
- model "normal_rails_engine/lyric"
68
- end
69
- }
70
-
71
- it { class_with_namespace_model.model_name.must_be_kind_of ActiveModel::Name }
72
- it { class_with_namespace_model.model_name.to_s.must_equal "NormalRailsEngine::Lyric" }
73
-
74
- let (:subclass_of_class_with_model) {
75
- Class.new(class_with_model)
76
- }
77
-
78
- it { subclass_of_class_with_model.model_name.must_be_kind_of ActiveModel::Name }
79
- it { subclass_of_class_with_model.model_name.to_s.must_equal 'Album' }
80
-
81
- it { form.class.model_name.route_key.must_equal "new_active_model_test_songs" }
82
- it { class_with_model.model_name.route_key.must_equal "albums" }
83
- it { class_with_isolated_model.model_name.route_key.must_equal "lyrics" }
84
- it { class_with_namespace_model.model_name.route_key.must_equal "normal_rails_engine_lyrics" }
85
- it { subclass_of_class_with_model.model_name.route_key.must_equal 'albums' }
86
-
87
- describe "class named Song::Form" do
88
- it do
89
- class Form < Reform::Form
90
- include Reform::Form::ActiveModel
91
- self
92
- end.model_name.to_s.must_equal "NewActiveModelTest"
93
- end
94
- end
95
-
96
-
97
- describe "inline with model" do
98
- let (:form_class) {
99
- Class.new(Reform::Form) do
100
- include Reform::Form::ActiveModel
101
-
102
- property :song do
103
- include Reform::Form::ActiveModel
104
- model :hit
105
- end
106
- end
107
- }
108
-
109
- let (:inline) { form_class.new(OpenStruct.new(:song => Object.new)).song }
110
-
111
- it { inline.class.model_name.must_be_kind_of ActiveModel::Name }
112
- it { inline.class.model_name.to_s.must_equal "Hit" }
113
- end
114
-
115
- describe "inline without model" do
116
- let (:form_class) {
117
- Class.new(Reform::Form) do
118
- include Reform::Form::ActiveModel
119
-
120
- property :song do
121
- include Reform::Form::ActiveModel
122
- end
123
-
124
- collection :hits do
125
- include Reform::Form::ActiveModel
126
- end
127
- end
128
- }
129
-
130
- let (:form) { form_class.new(OpenStruct.new(:hits=>[OpenStruct.new], :song => OpenStruct.new)) }
131
-
132
- it { form.song.class.model_name.must_be_kind_of ActiveModel::Name }
133
- it { form.song.class.model_name.to_s.must_equal "Song" }
134
- it "singularizes collection name" do
135
- form.hits.first.class.model_name.to_s.must_equal "Hit"
136
- end
137
- end
138
- end
139
- end
140
-
141
-
142
- class ActiveModelWithCompositionTest < MiniTest::Spec
143
- class HitForm < Reform::Form
144
- include Composition
145
- include Reform::Form::ActiveModel
146
-
147
- property :title, :on => :song
148
- properties :name, :genre, :on => :artist # we need to check both ::property and ::properties here!
149
-
150
- model :hit, :on => :song
151
- end
152
-
153
- let (:rio) { OpenStruct.new(:title => "Rio") }
154
- let (:duran) { OpenStruct.new }
155
- let (:form) { HitForm.new(:song => rio, :artist => duran) }
156
-
157
- describe "model accessors a la model#[:hit]" do
158
- it { form.model[:song].must_equal rio }
159
- it { form.model[:artist].must_equal duran }
160
-
161
- it "doesn't delegate when :on missing" do
162
- class SongOnlyForm < Reform::Form
163
- include Composition
164
- include Reform::Form::ActiveModel
165
-
166
- property :title, :on => :song
167
-
168
- model :song
169
- end.new(:song => rio, :artist => duran).model[:song].must_equal rio
170
- end
171
- end
172
-
173
-
174
- it "provides ::model_name" do
175
- form.class.model_name.must_equal "Hit"
176
- end
177
-
178
- it "provides #persisted?" do
179
- HitForm.new(:song => OpenStruct.new.instance_eval { def persisted?; "yo!"; end; self }, :artist => OpenStruct.new).persisted?.must_equal "yo!"
180
- end
181
-
182
- it "provides #to_key" do
183
- HitForm.new(:song => OpenStruct.new.instance_eval { def to_key; "yo!"; end; self }, :artist => OpenStruct.new).to_key.must_equal "yo!"
184
- end
185
-
186
- it "provides #to_param" do
187
- HitForm.new(:song => OpenStruct.new.instance_eval { def to_param; "yo!"; end; self }, :artist => OpenStruct.new).to_param.must_equal "yo!"
188
- end
189
-
190
- it "provides #to_model" do
191
- form = HitForm.new(:song => OpenStruct.new, :artist => OpenStruct.new)
192
- form.to_model.must_equal form
193
- end
194
-
195
- it "works with any order of ::model and ::property" do
196
- class AnotherForm < Reform::Form
197
- include Composition
198
- include Reform::Form::ActiveModel
199
-
200
- model :song, :on => :song
201
- property :title, :on => :song
202
- end
203
-
204
-
205
- AnotherForm.new(:song => rio).model[:song].must_equal rio
206
- end
207
- end