reform 1.0.0 → 1.0.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 +28 -0
- data/README.md +12 -12
- data/database.sqlite3 +0 -0
- data/lib/reform/form/active_record.rb +1 -1
- data/lib/reform/form/composition.rb +17 -4
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +2 -2
- data/test/active_model_test.rb +3 -3
- data/test/composition_test.rb +0 -6
- data/test/form_composition_test.rb +18 -7
- data/test/reform_test.rb +1 -3
- data/test/setup_test.rb +31 -1
- data/test/test_helper.rb +5 -4
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dbf6193ca27f1a4d6694b198771ab0bc3fec6459
|
4
|
+
data.tar.gz: 797ddc8942d2fe5c7947833b4eb52d6d5dd7fc24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba03b87c0652c7daff1b3c7a5cbb3d2807f2a70f0333bda323e6da7319be22907a9d11614d3c2d6c5064f02d0c93e2957bdf194e342cb5937f1667b85093ca26
|
7
|
+
data.tar.gz: 259e07b615f263a630891ae34400cf9d2aacc966fa50c0f666b49b3047cd4cd82d11563f2ea8376e560ea70534d58973d21bcc61d90487bda3570e83d47e1c9b
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,31 @@
|
|
1
|
+
## 1.0.1
|
2
|
+
|
3
|
+
* Deprecated model readers for `Composition` and `ActiveModel`. Consider the following setup.
|
4
|
+
```ruby
|
5
|
+
class RecordingForm < Reform::Form
|
6
|
+
include Composition
|
7
|
+
|
8
|
+
property :title, on: :song
|
9
|
+
end
|
10
|
+
```
|
11
|
+
|
12
|
+
Before, Reform would allow you to do `form.song` which returned the song model. You can still do this (but you shouldn't) with `form.model[:song]`.
|
13
|
+
|
14
|
+
This allows having composed models and properties with the same name. Until 1.1, you have to use `skip_accessors: true` to advise Reform _not_ to create the deprecated accessor.
|
15
|
+
|
16
|
+
Also deprecated is the alias accessor as found with `ActiveModel`.
|
17
|
+
```ruby
|
18
|
+
class RecordingForm < Reform::Form
|
19
|
+
include Composition
|
20
|
+
include ActiveModel
|
21
|
+
|
22
|
+
model :hit, on: :song
|
23
|
+
end
|
24
|
+
```
|
25
|
+
Here, an automatic reader `Form#hit` was created. This is deprecated as
|
26
|
+
|
27
|
+
This is gonna be **removed in 1.1**.
|
28
|
+
|
1
29
|
## 1.0.0
|
2
30
|
|
3
31
|
* Removed `Form::DSL` in favour of `Form::Composition`.
|
data/README.md
CHANGED
@@ -42,7 +42,7 @@ To add fields to the form use the `::property` method. Also, validations no long
|
|
42
42
|
Forms have a ridiculously simple API with only a handful of public methods.
|
43
43
|
|
44
44
|
1. `#initialize` always requires a model that the form represents.
|
45
|
-
2. `#validate(params)`
|
45
|
+
2. `#validate(params)` updates the form's fields with the input data (only the form, _not_ the model) and then runs all validations. The return value is the boolean result of the validations.
|
46
46
|
3. `#errors` returns validation messages in a classy ActiveModel style.
|
47
47
|
4. `#sync` writes form data back to the model. This will only use setter methods on the model(s).
|
48
48
|
5. `#save` (optional) will call `#save` on the model and nested models. Note that this implies a `#sync` call.
|
@@ -52,7 +52,7 @@ In addition to the main API, forms expose accessors to the defined properties. T
|
|
52
52
|
|
53
53
|
## Setup
|
54
54
|
|
55
|
-
In your controller you'd create a form instance and pass in the models you
|
55
|
+
In your controller you'd create a form instance and pass in the models you want to work on.
|
56
56
|
|
57
57
|
```ruby
|
58
58
|
class SongsController
|
@@ -100,12 +100,12 @@ Your `@form` is now ready to be rendered, either do it yourself or use something
|
|
100
100
|
= f.input :title
|
101
101
|
```
|
102
102
|
|
103
|
-
Nested forms and collections can easily rendered with `fields_for`, etc. Just use Reform as if it would be an ActiveModel instance in the view layer.
|
103
|
+
Nested forms and collections can be easily rendered with `fields_for`, etc. Just use Reform as if it would be an ActiveModel instance in the view layer.
|
104
104
|
|
105
105
|
|
106
106
|
## Validation
|
107
107
|
|
108
|
-
After a form submission, you
|
108
|
+
After a form submission, you want to validate the input.
|
109
109
|
|
110
110
|
```ruby
|
111
111
|
class SongsController
|
@@ -117,9 +117,9 @@ class SongsController
|
|
117
117
|
if @form.validate(params[:song])
|
118
118
|
```
|
119
119
|
|
120
|
-
|
120
|
+
The `#validate` method first updates the values of the form - the underlying model is still treated as immutuable and *remains unchanged*. It then runs all validations you provided in the form.
|
121
121
|
|
122
|
-
|
122
|
+
It's the only entry point for updating the form. This is per design, as separating writing and validation doesn't make sense for a form.
|
123
123
|
|
124
124
|
This allows rendering the form after `validate` with the data that has been submitted. However, don't get confused, the model's values are still the old, original values and are only changed after a `#save` or `#sync` operation.
|
125
125
|
|
@@ -191,7 +191,7 @@ class AlbumContract < Reform::Contract
|
|
191
191
|
end
|
192
192
|
```
|
193
193
|
|
194
|
-
It defines the validations and the object graph to be
|
194
|
+
It defines the validations and the object graph to be inspected.
|
195
195
|
|
196
196
|
In future versions and with the upcoming [Trailblazer framework](https://github.com/apotonick/trailblazer), contracts can be inherited from forms, representers, and cells, and vice-versa. Actually this already works with representer inheritance - let me know if you need help.
|
197
197
|
|
@@ -412,7 +412,7 @@ class AlbumForm < Reform::Form
|
|
412
412
|
|
413
413
|
This works for both `property` and `collection` and instantiates `Song` objects where they're missing when calling `#validate`.
|
414
414
|
|
415
|
-
If you
|
415
|
+
If you want to create the objects yourself, because you're smarter than Reform, do it with a lambda.
|
416
416
|
|
417
417
|
```ruby
|
418
418
|
class AlbumForm < Reform::Form
|
@@ -537,7 +537,7 @@ form.save do |f, nested|
|
|
537
537
|
|
538
538
|
### Read-Only Fields
|
539
539
|
|
540
|
-
Almost identical, the `:virtual` option makes fields read-only. Say you
|
540
|
+
Almost identical, the `:virtual` option makes fields read-only. Say you want to show a value, but not process it after submission, this option is your friend.
|
541
541
|
|
542
542
|
```ruby
|
543
543
|
class ProfileForm < Reform::Form
|
@@ -619,7 +619,7 @@ class CoverSongForm < Reform::Form
|
|
619
619
|
end
|
620
620
|
```
|
621
621
|
|
622
|
-
This is especially helpful when your framework tries to render `cover_song_path` although you
|
622
|
+
This is especially helpful when your framework tries to render `cover_song_path` although you want to go with `song_path`.
|
623
623
|
|
624
624
|
|
625
625
|
## FormBuilder Support
|
@@ -653,10 +653,10 @@ By explicitely defining the form layout using `::property` there is no more need
|
|
653
653
|
|
654
654
|
When nesting form, you usually use a so-called inline form doing `property :song do .. end`.
|
655
655
|
|
656
|
-
Sometimes you
|
656
|
+
Sometimes you want to specify an explicit form rather than using an inline form. Use the `form:` option here.
|
657
657
|
|
658
658
|
```ruby
|
659
|
-
property :song, form: SongForm
|
659
|
+
property :song, form: SongForm
|
660
660
|
```
|
661
661
|
|
662
662
|
The nested `SongForm` is a stand-alone form class you have to provide.
|
data/database.sqlite3
CHANGED
Binary file
|
@@ -38,6 +38,6 @@ module Reform::Form::ActiveRecord
|
|
38
38
|
return model unless is_a?(Reform::Form::Composition) # i am too lazy for proper inheritance. there should be a ActiveRecord::Composition that handles this.
|
39
39
|
|
40
40
|
model_name = mapper.representable_attrs[name][:on]
|
41
|
-
|
41
|
+
model[model_name]
|
42
42
|
end
|
43
43
|
end
|
@@ -18,7 +18,7 @@ module Reform::Form::Composition
|
|
18
18
|
|
19
19
|
def property(name, options={})
|
20
20
|
super.tap do |definition|
|
21
|
-
|
21
|
+
handle_deprecated_model_accessor(options[:on]) unless options[:skip_accessors] # TODO: remove in 1.2.
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -32,12 +32,25 @@ module Reform::Form::Composition
|
|
32
32
|
|
33
33
|
composition_model = options[:on] || main_model
|
34
34
|
|
35
|
-
|
35
|
+
handle_deprecated_model_accessor(composition_model) unless options[:skip_accessors] # TODO: remove in 1.2.
|
36
36
|
|
37
37
|
# FIXME: this should just delegate to :model as in FB, and the comp would take care of it internally.
|
38
|
-
|
38
|
+
[:persisted?, :to_key, :to_param].each do |method|
|
39
|
+
define_method method do
|
40
|
+
model[composition_model].send(method)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
alias_method main_model, composition_model # #hit => model.song. # TODO: remove in 1.2.
|
45
|
+
end
|
39
46
|
|
40
|
-
|
47
|
+
private
|
48
|
+
def handle_deprecated_model_accessor(name, aliased=name)
|
49
|
+
define_method name do # form.band -> composition.band
|
50
|
+
warn %{[Reform] Deprecation WARNING: When using Composition, you may not call Form##{name} anymore to access the contained model. Please use Form#model[:#{name}] and have a lovely day!}
|
51
|
+
|
52
|
+
@model[name]
|
53
|
+
end
|
41
54
|
end
|
42
55
|
end
|
43
56
|
|
data/lib/reform/version.rb
CHANGED
data/reform.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["apotonick@gmail.com", "heinleng@gmail.com"]
|
11
11
|
spec.description = %q{Freeing your AR models from form logic.}
|
12
12
|
spec.summary = %q{Decouples your models from form by giving you form objects with validation, presentation, workflows and security.}
|
13
|
-
spec.homepage = ""
|
13
|
+
spec.homepage = "https://github.com/apotonick/reform"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency "representable", "~> 1.8.1"
|
22
|
-
spec.add_dependency "disposable", "~> 0.0.
|
22
|
+
spec.add_dependency "disposable", "~> 0.0.4"
|
23
23
|
spec.add_dependency "uber", "~> 0.0.4"
|
24
24
|
spec.add_dependency "activemodel"
|
25
25
|
spec.add_development_dependency "bundler", "~> 1.3"
|
data/test/active_model_test.rb
CHANGED
@@ -110,9 +110,9 @@ class ActiveModelWithCompositionTest < MiniTest::Spec
|
|
110
110
|
let (:duran) { OpenStruct.new }
|
111
111
|
let (:form) { HitForm.new(:song => rio, :artist => duran) }
|
112
112
|
|
113
|
-
describe "main form reader #hit" do
|
113
|
+
describe "main form reader #hit" do# TODO: remove in 1.2. we don't support this reader #hit anymore.
|
114
114
|
it "delegates to :on model" do
|
115
|
-
form.hit.must_equal rio
|
115
|
+
form.hit.must_equal rio # TODO: remove in 1.2.
|
116
116
|
end
|
117
117
|
|
118
118
|
it "doesn't delegate when :on missing" do
|
@@ -177,6 +177,6 @@ class ActiveModelWithCompositionTest < MiniTest::Spec
|
|
177
177
|
end
|
178
178
|
|
179
179
|
|
180
|
-
AnotherForm.new(:song => rio).song.must_equal rio
|
180
|
+
AnotherForm.new(:song => rio).model[:song].must_equal rio
|
181
181
|
end
|
182
182
|
end
|
data/test/composition_test.rb
CHANGED
@@ -16,12 +16,6 @@ class CompositionTest < ReformSpec
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
it "creates readers to models" do
|
20
|
-
comp.song.object_id.must_equal rio.object_id
|
21
|
-
comp.artist.object_id.must_equal @artist.object_id
|
22
|
-
end
|
23
|
-
|
24
|
-
|
25
19
|
describe "::from" do
|
26
20
|
it "creates the same mapping" do
|
27
21
|
comp =
|
@@ -2,23 +2,25 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class FormCompositionTest < MiniTest::Spec
|
4
4
|
Song = Struct.new(:id, :title)
|
5
|
-
Requester = Struct.new(:id, :name)
|
5
|
+
Requester = Struct.new(:id, :name, :requester)
|
6
6
|
|
7
7
|
class RequestForm < Reform::Form
|
8
8
|
include Composition
|
9
9
|
|
10
|
-
|
11
|
-
property :
|
10
|
+
# TODO: remove skip_accessors in 1.1.
|
11
|
+
property :name, :on => :requester, :skip_accessors => true
|
12
|
+
property :requester_id, :on => :requester, :as => :id, :skip_accessors => true
|
12
13
|
properties [:title, :id], :on => :song
|
13
14
|
# property :channel # FIXME: what about the "main model"?
|
14
15
|
property :channel, :empty => true, :on => :song
|
16
|
+
property :requester, :on => :requester, :skip_accessors => true
|
15
17
|
|
16
18
|
validates :name, :title, :channel, :presence => true
|
17
19
|
end
|
18
20
|
|
19
21
|
let (:form) { RequestForm.new(:song => song, :requester => requester) }
|
20
22
|
let (:song) { Song.new(1, "Rio") }
|
21
|
-
let (:requester) { Requester.new(2, "Duran Duran") }
|
23
|
+
let (:requester) { Requester.new(2, "Duran Duran", "MCP") }
|
22
24
|
|
23
25
|
|
24
26
|
# delegation form -> composition works
|
@@ -27,10 +29,19 @@ class FormCompositionTest < MiniTest::Spec
|
|
27
29
|
it { form.name.must_equal "Duran Duran" }
|
28
30
|
it { form.requester_id.must_equal 2 }
|
29
31
|
it { form.channel.must_equal nil }
|
32
|
+
it { form.requester.must_equal "MCP" } # same name as composed model.
|
30
33
|
|
34
|
+
# [DEPRECATED] # TODO: remove in 1.2.
|
31
35
|
# delegation form -> composed models (e.g. when saving this can be handy)
|
32
|
-
it { form.song.must_equal
|
33
|
-
|
36
|
+
it { form.song.must_equal song }
|
37
|
+
|
38
|
+
|
39
|
+
# #model just returns <Composition>.
|
40
|
+
it { form.model.must_be_kind_of Reform::Composition }
|
41
|
+
|
42
|
+
# #model[] -> composed models
|
43
|
+
it { form.model[:requester].must_equal requester }
|
44
|
+
it { form.model[:song].must_equal song }
|
34
45
|
|
35
46
|
|
36
47
|
it "creates Composition for you" do
|
@@ -58,7 +69,7 @@ class FormCompositionTest < MiniTest::Spec
|
|
58
69
|
hash = map
|
59
70
|
end
|
60
71
|
|
61
|
-
hash.must_equal({:song=>{:title=>"Greyhound", :id=>1, :channel => "JJJ"}, :requester=>{:name=>"Frenzal Rhomb", :id=>2}})
|
72
|
+
hash.must_equal({:song=>{:title=>"Greyhound", :id=>1, :channel => "JJJ"}, :requester=>{:name=>"Frenzal Rhomb", :id=>2, :requester => "MCP"}})
|
62
73
|
end
|
63
74
|
|
64
75
|
it "pushes data to models and calls #save when no block passed" do
|
data/test/reform_test.rb
CHANGED
@@ -249,11 +249,9 @@ class EmptyAttributesTest < MiniTest::Spec
|
|
249
249
|
let (:cred) { Credentials.new }
|
250
250
|
let (:form) { PasswordForm.new(cred) }
|
251
251
|
|
252
|
-
|
252
|
+
before { form.validate("password" => "123", "password_confirmation" => "321") }
|
253
253
|
|
254
254
|
it {
|
255
|
-
|
256
|
-
form.validate("password" => "123", "password_confirmation" => "321")
|
257
255
|
form.password.must_equal "123"
|
258
256
|
form.password_confirmation.must_equal "321"
|
259
257
|
|
data/test/setup_test.rb
CHANGED
@@ -1,8 +1,37 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
class SetupTest < BaseTest
|
4
|
+
class AlbumForm < Reform::Form
|
5
|
+
property :title
|
6
|
+
|
7
|
+
property :hit do
|
8
|
+
property :title
|
9
|
+
validates :title, :presence => true
|
10
|
+
end
|
11
|
+
|
12
|
+
collection :songs do
|
13
|
+
property :title
|
14
|
+
validates :title, :presence => true
|
15
|
+
|
16
|
+
property :length do
|
17
|
+
property :minutes
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
property :band do # yepp, people do crazy stuff like that.
|
22
|
+
property :label do
|
23
|
+
property :name
|
24
|
+
validates :name, :presence => true
|
25
|
+
end
|
26
|
+
# TODO: make band a required object.
|
27
|
+
end
|
28
|
+
|
29
|
+
validates :title, :presence => true
|
30
|
+
end
|
31
|
+
|
32
|
+
|
4
33
|
describe "populated" do
|
5
|
-
subject { AlbumForm.new(Album.new("Best Of", hit, [Song.new("Fallout"), Song.new("Roxanne")])) }
|
34
|
+
subject { AlbumForm.new(Album.new("Best Of", hit, [Song.new("Fallout", Length.new(2,3)), Song.new("Roxanne")])) }
|
6
35
|
|
7
36
|
it { subject.title.must_equal "Best Of" }
|
8
37
|
|
@@ -15,6 +44,7 @@ class SetupTest < BaseTest
|
|
15
44
|
|
16
45
|
it { subject.songs[0].must_be_kind_of Reform::Form }
|
17
46
|
it { subject.songs[0].title.must_equal "Fallout" }
|
47
|
+
it { subject.songs[0].length.minutes.must_equal 2 }
|
18
48
|
|
19
49
|
it { subject.songs[1].must_be_kind_of Reform::Form }
|
20
50
|
it { subject.songs[1].title.must_equal "Roxanne" }
|
data/test/test_helper.rb
CHANGED
@@ -29,10 +29,11 @@ class BaseTest < MiniTest::Spec
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
Song
|
33
|
-
Album
|
34
|
-
Band
|
35
|
-
Label
|
32
|
+
Song = Struct.new(:title, :length)
|
33
|
+
Album = Struct.new(:title, :hit, :songs, :band)
|
34
|
+
Band = Struct.new(:label)
|
35
|
+
Label = Struct.new(:name)
|
36
|
+
Length = Struct.new(:minutes, :seconds)
|
36
37
|
|
37
38
|
|
38
39
|
let (:hit) { Song.new("Roxanne") }
|
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.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-05-
|
12
|
+
date: 2014-05-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: representable
|
@@ -31,14 +31,14 @@ dependencies:
|
|
31
31
|
requirements:
|
32
32
|
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: 0.0.
|
34
|
+
version: 0.0.4
|
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
|
-
version: 0.0.
|
41
|
+
version: 0.0.4
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: uber
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -248,7 +248,7 @@ files:
|
|
248
248
|
- test/sync_test.rb
|
249
249
|
- test/test_helper.rb
|
250
250
|
- test/validate_test.rb
|
251
|
-
homepage:
|
251
|
+
homepage: https://github.com/apotonick/reform
|
252
252
|
licenses:
|
253
253
|
- MIT
|
254
254
|
metadata: {}
|