reform 0.1.1 → 0.1.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.
- data/.gitignore +1 -0
- data/.travis.yml +3 -2
- data/CHANGES.md +5 -0
- data/Gemfile +3 -0
- data/README.md +37 -1
- data/TODO.md +3 -1
- data/gemfiles/Gemfile.rails-3.0 +7 -0
- data/lib/reform/form.rb +22 -11
- data/lib/reform/form/active_model.rb +10 -7
- data/lib/reform/form/coercion.rb +18 -0
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +2 -1
- data/test/active_model_test.rb +20 -2
- data/test/dsl_test.rb +25 -0
- data/test/reform_test.rb +10 -5
- data/test/test_helper.rb +2 -4
- metadata +22 -4
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -100,6 +100,26 @@ To push the incoming data to the models directly, call `#save` without the block
|
|
100
100
|
# by calling @form.song.name= and @form.artist.title=.
|
101
101
|
```
|
102
102
|
|
103
|
+
## Coercion
|
104
|
+
|
105
|
+
Often you want incoming form data to be converted to a type, like timestamps. Reform uses [virtus](https://github.com/solnic/virtus) for coercion, the DSL is seamlessly integrated into Reform with the `:type` option.
|
106
|
+
|
107
|
+
Be sure to add `virtus` to your Gemfile.
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
require 'reform/form/coercion'
|
111
|
+
|
112
|
+
class SongRequestForm < Reform::Form
|
113
|
+
include DSL
|
114
|
+
include Reform::Form::Coercion
|
115
|
+
|
116
|
+
property :written_at, on: :song, type: DateTime
|
117
|
+
end
|
118
|
+
|
119
|
+
@form.save do |data, nested|
|
120
|
+
data.written_at #=> <DateTime XXX>
|
121
|
+
```
|
122
|
+
|
103
123
|
## Rails Integration
|
104
124
|
|
105
125
|
[A sample Rails app using Reform.](https://github.com/gogogarrett/reform_example)
|
@@ -121,7 +141,7 @@ class UserProfileForm < Reform::Form
|
|
121
141
|
property :email, on: :user
|
122
142
|
properties [:gender, :age], on: :profile
|
123
143
|
|
124
|
-
model :user
|
144
|
+
model :user
|
125
145
|
|
126
146
|
validates :email, :gender, presence: true
|
127
147
|
validates :age, numericality: true
|
@@ -129,6 +149,12 @@ class UserProfileForm < Reform::Form
|
|
129
149
|
end
|
130
150
|
```
|
131
151
|
|
152
|
+
Basically, `model :user` tells Reform to use the `:user` object in the composition as the form main object while using `"user"` as the form name (needed for URL computation). If you want to change the form name let Reform know.
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
model :singer, :on => :user # form name is "singer" whereas main object is `:user` in composition.
|
156
|
+
```
|
157
|
+
|
132
158
|
|
133
159
|
#### View Form
|
134
160
|
|
@@ -190,3 +216,13 @@ end
|
|
190
216
|
## Security
|
191
217
|
|
192
218
|
By explicitely defining the form layout using `::property` there is no more need for protecting from unwanted input. `strong_parameter` or `attr_accessible` become obsolete. Reform will simply ignore undefined incoming parameters.
|
219
|
+
|
220
|
+
## Maintainers
|
221
|
+
|
222
|
+
[Nick Sutterer](https://github.com/apotonick)
|
223
|
+
|
224
|
+
[Garrett Heinlen](https://github.com/gogogarrett)
|
225
|
+
|
226
|
+
### Attributions!!!
|
227
|
+
|
228
|
+
Great thanks to [Blake Education](https://github.com/blake-education) for giving us the freedom and time to develop this project while working on their project.
|
data/TODO.md
CHANGED
@@ -1,2 +1,4 @@
|
|
1
1
|
* `validates :title, :presence => true`
|
2
|
-
with @model.title == "Little Green Car" and validate({}) the form is still valid (as we "have" a valid title). is that what we want?
|
2
|
+
with @model.title == "Little Green Car" and validate({}) the form is still valid (as we "have" a valid title). is that what we want?
|
3
|
+
|
4
|
+
* document Form#to_hash and Form#to_nested_hash (e.g. with OpenStruct composition to make it a very simple form)
|
data/lib/reform/form.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'delegate'
|
2
|
+
require 'ostruct'
|
2
3
|
|
3
4
|
module Reform
|
4
5
|
class Form < SimpleDelegator
|
@@ -8,12 +9,11 @@ module Reform
|
|
8
9
|
# validation: this object also contains the validation rules itself, should be separated.
|
9
10
|
# TODO: figure out #to_key issues.
|
10
11
|
|
11
|
-
def initialize(
|
12
|
-
@mapper =
|
12
|
+
def initialize(mapper_class, composition)
|
13
|
+
@mapper = mapper_class
|
13
14
|
@model = composition
|
14
|
-
representer = @mapper.new(composition)
|
15
15
|
|
16
|
-
super
|
16
|
+
super(setup_fields(mapper_class, composition)) # delegate all methods to Fields instance.
|
17
17
|
end
|
18
18
|
|
19
19
|
def validate(params)
|
@@ -30,13 +30,6 @@ module Reform
|
|
30
30
|
@mapper.new(model).from_hash(to_hash) # DISCUSS: move to Composition?
|
31
31
|
end
|
32
32
|
|
33
|
-
private
|
34
|
-
attr_accessor :mapper, :model
|
35
|
-
|
36
|
-
def update_with(params)
|
37
|
-
mapper.new(self).from_hash(params) # sets form properties found in params on self.
|
38
|
-
end
|
39
|
-
|
40
33
|
# Use representer to return current key-value form hash.
|
41
34
|
def to_hash
|
42
35
|
mapper.new(self).to_hash
|
@@ -46,6 +39,24 @@ module Reform
|
|
46
39
|
model.nested_hash_for(to_hash) # use composition to compute nested hash.
|
47
40
|
end
|
48
41
|
|
42
|
+
private
|
43
|
+
attr_accessor :mapper, :model
|
44
|
+
|
45
|
+
def setup_fields(mapper_class, composition)
|
46
|
+
# decorate composition and transform to hash.
|
47
|
+
representer = mapper_class.new(composition)
|
48
|
+
|
49
|
+
create_fields(representer.fields, representer.to_hash)
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_fields(field_names, fields)
|
53
|
+
Fields.new(field_names, fields)
|
54
|
+
end
|
55
|
+
|
56
|
+
def update_with(params)
|
57
|
+
mapper.new(self).from_hash(params) # sets form properties found in params on self.
|
58
|
+
end
|
59
|
+
|
49
60
|
# FIXME: make AM optional.
|
50
61
|
require 'active_model'
|
51
62
|
include ActiveModel::Validations
|
@@ -6,14 +6,14 @@ module Reform::Form::ActiveModel
|
|
6
6
|
end
|
7
7
|
|
8
8
|
module ClassMethods
|
9
|
-
def model(
|
10
|
-
@model_options
|
11
|
-
|
9
|
+
def model(main_model, options={})
|
10
|
+
@model_options = [main_model, options] # FIXME: make inheritable!
|
11
|
+
composition_model = options[:on] || main_model
|
12
12
|
|
13
|
-
delegate
|
14
|
-
delegate :persisted?, :to_key, :to_param, :to =>
|
13
|
+
delegate composition_model, :to => :model # #song => model.song
|
14
|
+
delegate :persisted?, :to_key, :to_param, :to_model, :to => composition_model # #to_key => song.to_key
|
15
15
|
|
16
|
-
alias_method
|
16
|
+
alias_method main_model, composition_model # #hit => model.song.
|
17
17
|
end
|
18
18
|
|
19
19
|
def property(name, options={})
|
@@ -22,7 +22,10 @@ module Reform::Form::ActiveModel
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def model_name
|
25
|
-
|
25
|
+
name = @model_options.first.to_s.camelize
|
26
|
+
|
27
|
+
return ::ActiveModel::Name.new(OpenStruct.new(:name => name)) if ::ActiveModel::VERSION::MAJOR == 3 and ::ActiveModel::VERSION::MINOR == 0
|
28
|
+
::ActiveModel::Name.new(self, nil, name)
|
26
29
|
end
|
27
30
|
end
|
28
31
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'representable/decorator/coercion'
|
2
|
+
|
3
|
+
class Reform::Form
|
4
|
+
module Coercion
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def representer_class
|
11
|
+
super.class_eval do
|
12
|
+
include Representable::Decorator::Coercion unless self < Representable::Decorator::Coercion # DISCUSS: include it once. why do we have to check this?
|
13
|
+
self
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/reform/version.rb
CHANGED
data/reform.gemspec
CHANGED
@@ -18,11 +18,12 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "representable"
|
21
|
+
spec.add_dependency "representable", ">= 1.5.3"
|
22
22
|
spec.add_dependency "activemodel"
|
23
23
|
spec.add_development_dependency "bundler", "~> 1.3"
|
24
24
|
spec.add_development_dependency "rake"
|
25
25
|
spec.add_development_dependency "minitest"
|
26
26
|
spec.add_development_dependency "activerecord"
|
27
27
|
spec.add_development_dependency "sqlite3"
|
28
|
+
spec.add_development_dependency "virtus"
|
28
29
|
end
|
data/test/active_model_test.rb
CHANGED
@@ -15,10 +15,24 @@ class ActiveModelTest < MiniTest::Spec
|
|
15
15
|
let (:duran) { OpenStruct.new }
|
16
16
|
let (:form) { HitForm.new(:song => rio, :artist => duran) }
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
describe "main form reader #hit" do
|
19
|
+
it "delegates to :on model" do
|
20
|
+
form.hit.must_equal rio
|
21
|
+
end
|
22
|
+
|
23
|
+
it "doesn't delegate when :on missing" do
|
24
|
+
class HitForm < Reform::Form
|
25
|
+
include DSL
|
26
|
+
include Reform::Form::ActiveModel
|
27
|
+
|
28
|
+
property :title, :on => :song
|
29
|
+
|
30
|
+
model :song
|
31
|
+
end.new(:song => rio, :artist => duran).song.must_equal rio
|
32
|
+
end
|
20
33
|
end
|
21
34
|
|
35
|
+
|
22
36
|
it "creates composition readers" do
|
23
37
|
form.song.must_equal rio
|
24
38
|
form.artist.must_equal duran
|
@@ -40,6 +54,10 @@ class ActiveModelTest < MiniTest::Spec
|
|
40
54
|
HitForm.new(:song => OpenStruct.new.instance_eval { def to_param; "yo!"; end; self }, :artist => OpenStruct.new).to_param.must_equal "yo!"
|
41
55
|
end
|
42
56
|
|
57
|
+
it "provides #to_model" do
|
58
|
+
HitForm.new(:song => OpenStruct.new.instance_eval { def to_model; "yo!"; end; self }, :artist => OpenStruct.new).to_model.must_equal "yo!"
|
59
|
+
end
|
60
|
+
|
43
61
|
it "works with any order of ::model and ::property" do
|
44
62
|
class AnotherForm < Reform::Form
|
45
63
|
include DSL
|
data/test/dsl_test.rb
CHANGED
@@ -15,4 +15,29 @@ class DslTest < MiniTest::Spec
|
|
15
15
|
it "works by creating Representer and Composition for you" do
|
16
16
|
form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb").must_equal false
|
17
17
|
end
|
18
|
+
|
19
|
+
require 'reform/form/coercion'
|
20
|
+
it "allows coercion" do
|
21
|
+
form = Class.new(Reform::Form) do
|
22
|
+
include Reform::Form::DSL
|
23
|
+
include Reform::Form::Coercion
|
24
|
+
|
25
|
+
property :written_at, :type => DateTime, :on => :song
|
26
|
+
end.new(:song => OpenStruct.new(:written_at => "31/03/1981"))
|
27
|
+
|
28
|
+
form.written_at.must_be_kind_of DateTime
|
29
|
+
form.written_at.must_equal DateTime.parse("Tue, 31 Mar 1981 00:00:00 +0000")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "allows coercion in validate" do
|
33
|
+
form = Class.new(Reform::Form) do
|
34
|
+
include Reform::Form::DSL
|
35
|
+
include Reform::Form::Coercion
|
36
|
+
|
37
|
+
property :id, :type => Integer, :on => :song
|
38
|
+
end.new(:song => OpenStruct.new())
|
39
|
+
|
40
|
+
form.validate("id" => "1")
|
41
|
+
form.to_hash.must_equal("id" => 1)
|
42
|
+
end
|
18
43
|
end
|
data/test/reform_test.rb
CHANGED
@@ -45,6 +45,7 @@ class FieldsTest < MiniTest::Spec
|
|
45
45
|
end
|
46
46
|
|
47
47
|
it "processes value syms" do
|
48
|
+
skip "we don't need to test this as representer.to_hash always returns strings"
|
48
49
|
fields = Reform::Fields.new(["name", "title"], :title => "The Body")
|
49
50
|
fields.name.must_equal nil
|
50
51
|
fields.title.must_equal "The Body"
|
@@ -53,6 +54,12 @@ class FieldsTest < MiniTest::Spec
|
|
53
54
|
end
|
54
55
|
|
55
56
|
class ReformTest < MiniTest::Spec
|
57
|
+
def errors_for(form)
|
58
|
+
errors = form.errors
|
59
|
+
errors = errors.messages unless ::ActiveModel::VERSION::MAJOR == 3 and ::ActiveModel::VERSION::MINOR == 0
|
60
|
+
errors
|
61
|
+
end
|
62
|
+
|
56
63
|
let (:duran) { OpenStruct.new(:name => "Duran Duran") }
|
57
64
|
let (:rio) { OpenStruct.new(:title => "Rio") }
|
58
65
|
|
@@ -174,7 +181,7 @@ class ReformTest < MiniTest::Spec
|
|
174
181
|
|
175
182
|
it "populates errors" do
|
176
183
|
form.validate({})
|
177
|
-
form.
|
184
|
+
errors_for(form).must_equal({:name=>["can't be blank"], :title=>["can't be blank"]})
|
178
185
|
end
|
179
186
|
end
|
180
187
|
|
@@ -189,7 +196,7 @@ class ReformTest < MiniTest::Spec
|
|
189
196
|
end.new(SongAndArtistMap, comp)
|
190
197
|
|
191
198
|
form.validate({}).must_equal false
|
192
|
-
form.
|
199
|
+
errors_for(form).must_equal({:name=>["Please give me a name"]})
|
193
200
|
end
|
194
201
|
end
|
195
202
|
|
@@ -212,7 +219,7 @@ class ReformTest < MiniTest::Spec
|
|
212
219
|
it "is invalid and shows error when taken" do
|
213
220
|
form = ActiveRecordForm.new(SongAndArtistMap, comp)
|
214
221
|
form.validate({"name" => "Racer X"}).must_equal false
|
215
|
-
form.
|
222
|
+
errors_for(form).must_equal({:name=>["has already been taken"], :title => ["can't be blank"]})
|
216
223
|
end
|
217
224
|
|
218
225
|
require 'reform/rails'
|
@@ -264,5 +271,3 @@ class ReformTest < MiniTest::Spec
|
|
264
271
|
end
|
265
272
|
end
|
266
273
|
end
|
267
|
-
|
268
|
-
# TODO: test errors
|
data/test/test_helper.rb
CHANGED
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: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-
|
13
|
+
date: 2013-06-14 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: representable
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version:
|
22
|
+
version: 1.5.3
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -27,7 +27,7 @@ dependencies:
|
|
27
27
|
requirements:
|
28
28
|
- - ! '>='
|
29
29
|
- !ruby/object:Gem::Version
|
30
|
-
version:
|
30
|
+
version: 1.5.3
|
31
31
|
- !ruby/object:Gem::Dependency
|
32
32
|
name: activemodel
|
33
33
|
requirement: !ruby/object:Gem::Requirement
|
@@ -124,6 +124,22 @@ dependencies:
|
|
124
124
|
- - ! '>='
|
125
125
|
- !ruby/object:Gem::Version
|
126
126
|
version: '0'
|
127
|
+
- !ruby/object:Gem::Dependency
|
128
|
+
name: virtus
|
129
|
+
requirement: !ruby/object:Gem::Requirement
|
130
|
+
none: false
|
131
|
+
requirements:
|
132
|
+
- - ! '>='
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
type: :development
|
136
|
+
prerelease: false
|
137
|
+
version_requirements: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
127
143
|
description: Freeing your AR models from form logic.
|
128
144
|
email:
|
129
145
|
- apotonick@gmail.com
|
@@ -141,10 +157,12 @@ files:
|
|
141
157
|
- Rakefile
|
142
158
|
- TODO.md
|
143
159
|
- database.sqlite3
|
160
|
+
- gemfiles/Gemfile.rails-3.0
|
144
161
|
- lib/reform.rb
|
145
162
|
- lib/reform/form.rb
|
146
163
|
- lib/reform/form/active_model.rb
|
147
164
|
- lib/reform/form/active_record.rb
|
165
|
+
- lib/reform/form/coercion.rb
|
148
166
|
- lib/reform/form/dsl.rb
|
149
167
|
- lib/reform/rails.rb
|
150
168
|
- lib/reform/version.rb
|