reform 2.1.0 → 2.2.0.rc1

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