reform 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +7 -0
- data/CHANGES.md +6 -0
- data/README.md +26 -4
- data/TODO.md +2 -0
- data/database.sqlite3 +0 -0
- data/lib/reform.rb +1 -6
- data/lib/reform/form.rb +2 -2
- data/lib/reform/form/active_model.rb +3 -3
- data/lib/reform/form/active_record.rb +34 -0
- data/lib/reform/rails.rb +2 -0
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +4 -2
- data/test/active_model_test.rb +55 -0
- data/test/dsl_test.rb +1 -55
- data/test/reform_test.rb +70 -5
- metadata +44 -4
data/.travis.yml
ADDED
data/CHANGES.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
h3. 0.1.1
|
2
|
+
|
3
|
+
* Added `reform/rails` that requires everything you need (even in other frameworks :).
|
4
|
+
* Added `Form::ActiveRecord` that gives you `validates_uniqueness_with`. Note that this is strongly coupled to your database, thou.
|
5
|
+
* Merged a lot of cleanups from sweet @parndt <3.
|
6
|
+
|
1
7
|
h3. 0.1.0
|
2
8
|
|
3
9
|
* Oh yeah.
|
data/README.md
CHANGED
@@ -100,19 +100,23 @@ 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
|
-
##
|
103
|
+
## Rails Integration
|
104
104
|
|
105
|
-
|
105
|
+
[A sample Rails app using Reform.](https://github.com/gogogarrett/reform_example)
|
106
|
+
|
107
|
+
Reform offers ActiveRecord support to easily make this accessible in Rails based projects. You simply `include Reform::Form::ActiveRecord` in your form object and the Rails specific code will be handled for you. This happens by adding behaviour to make the form ActiveModel-compliant. Note that this module will also work with other ORMs like Datamapper.
|
106
108
|
|
107
109
|
### Simple Integration
|
108
110
|
#### Form Class
|
109
111
|
|
110
|
-
You have to include a call to `model` to
|
112
|
+
You have to include a call to `model` to specify which is the main object of the form.
|
111
113
|
|
112
114
|
```ruby
|
115
|
+
require 'reform/rails'
|
116
|
+
|
113
117
|
class UserProfileForm < Reform::Form
|
114
118
|
include DSL
|
115
|
-
include Reform::Form::
|
119
|
+
include Reform::Form::ActiveRecord
|
116
120
|
|
117
121
|
property :email, on: :user
|
118
122
|
properties [:gender, :age], on: :profile
|
@@ -121,9 +125,11 @@ class UserProfileForm < Reform::Form
|
|
121
125
|
|
122
126
|
validates :email, :gender, presence: true
|
123
127
|
validates :age, numericality: true
|
128
|
+
validates_uniqueness_of :email
|
124
129
|
end
|
125
130
|
```
|
126
131
|
|
132
|
+
|
127
133
|
#### View Form
|
128
134
|
|
129
135
|
The form becomes __very__ dumb as it knows nothing about the backend assocations or data binding to the database layer. This simply takes input and passes it along to the controller as it should.
|
@@ -165,6 +171,22 @@ end
|
|
165
171
|
|
166
172
|
__Note__: this can also be used for the update action as well.
|
167
173
|
|
174
|
+
## Using Your Models In Validations
|
175
|
+
|
176
|
+
Sometimes you want to access your database in a validation. You can access the models using the `#model` accessor in the form.
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
class ArtistForm < Reform::Form
|
180
|
+
property :name
|
181
|
+
|
182
|
+
validate "name_correct?"
|
183
|
+
|
184
|
+
def name_correct?
|
185
|
+
errors.add :name, "#{name} is stupid!" if model.artist.stupid_name?(name)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
168
190
|
## Security
|
169
191
|
|
170
192
|
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.
|
data/TODO.md
ADDED
data/database.sqlite3
ADDED
Binary file
|
data/lib/reform.rb
CHANGED
data/lib/reform/form.rb
CHANGED
@@ -92,7 +92,7 @@ module Reform
|
|
92
92
|
private
|
93
93
|
def create_accessors(model, methods)
|
94
94
|
accessors = methods.collect { |m| [m, "#{m}="] }.flatten
|
95
|
-
delegate *accessors
|
95
|
+
delegate *accessors << {:to => :"#{model}"}
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
@@ -129,4 +129,4 @@ module Reform
|
|
129
129
|
representable_attrs.collect { |cfg| cfg.name }
|
130
130
|
end
|
131
131
|
end
|
132
|
-
end
|
132
|
+
end
|
@@ -11,7 +11,7 @@ module Reform::Form::ActiveModel
|
|
11
11
|
main_model = args.last[:on]
|
12
12
|
|
13
13
|
delegate main_model, :to => :model # #song => model.song
|
14
|
-
delegate
|
14
|
+
delegate :persisted?, :to_key, :to_param, :to => main_model # #to_key => song.to_key
|
15
15
|
|
16
16
|
alias_method args.first, main_model # #hit => model.song.
|
17
17
|
end
|
@@ -22,7 +22,7 @@ module Reform::Form::ActiveModel
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def model_name
|
25
|
-
ActiveModel::Name.new(self, nil, @model_options.first.to_s.camelize)
|
25
|
+
::ActiveModel::Name.new(self, nil, @model_options.first.to_s.camelize)
|
26
26
|
end
|
27
27
|
end
|
28
|
-
end
|
28
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Reform::Form
|
2
|
+
module ActiveRecord
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
include ActiveModel
|
6
|
+
extend ClassMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def validates_uniqueness_of(attribute)
|
12
|
+
validates_with UniquenessValidator, :attributes => [attribute]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class UniquenessValidator < ::ActiveRecord::Validations::UniquenessValidator
|
17
|
+
# when calling validates it should create the Vali instance already and set @klass there! # TODO: fix this in AM.
|
18
|
+
def validate(form)
|
19
|
+
property = attributes.first
|
20
|
+
model_name = form.send(:model).class.model_for_property(property)
|
21
|
+
|
22
|
+
# here is the thing: why does AM::UniquenessValidator require a filled-out record to work properly? also, why do we need to set
|
23
|
+
# the class? it would be way easier to pass #validate a hash of attributes and get back an errors hash.
|
24
|
+
# the class for the finder could either be infered from the record or set in the validator instance itself in the call to ::validates.
|
25
|
+
record = form.send(model_name)
|
26
|
+
record.send("#{property}=", form.send(property))
|
27
|
+
@klass = record.class # this is usually done in the super-sucky #setup method.
|
28
|
+
super(record).tap do |res|
|
29
|
+
form.errors.add(property, record.errors.first.last) if record.errors.present?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/reform/rails.rb
ADDED
data/lib/reform/version.rb
CHANGED
data/reform.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Nick Sutterer", "Garrett Heinlen"]
|
10
10
|
spec.email = ["apotonick@gmail.com", "heinleng@gmail.com"]
|
11
11
|
spec.description = %q{Freeing your AR models from form logic.}
|
12
|
-
spec.summary = %q{
|
12
|
+
spec.summary = %q{Decouples your models from form by giving you form objects with validation, presentation, workflows and security.}
|
13
13
|
spec.homepage = ""
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -19,8 +19,10 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency "representable"
|
22
|
+
spec.add_dependency "activemodel"
|
22
23
|
spec.add_development_dependency "bundler", "~> 1.3"
|
23
24
|
spec.add_development_dependency "rake"
|
24
25
|
spec.add_development_dependency "minitest"
|
25
|
-
spec.add_development_dependency "
|
26
|
+
spec.add_development_dependency "activerecord"
|
27
|
+
spec.add_development_dependency "sqlite3"
|
26
28
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ActiveModelTest < MiniTest::Spec
|
4
|
+
class HitForm < Reform::Form
|
5
|
+
include DSL
|
6
|
+
include Reform::Form::ActiveModel
|
7
|
+
|
8
|
+
property :title, :on => :song
|
9
|
+
properties [:name, :genre], :on => :artist
|
10
|
+
|
11
|
+
model :hit, :on => :song
|
12
|
+
end
|
13
|
+
|
14
|
+
let (:rio) { OpenStruct.new(:title => "Rio") }
|
15
|
+
let (:duran) { OpenStruct.new }
|
16
|
+
let (:form) { HitForm.new(:song => rio, :artist => duran) }
|
17
|
+
|
18
|
+
it "creates model readers" do
|
19
|
+
form.hit.must_equal rio
|
20
|
+
end
|
21
|
+
|
22
|
+
it "creates composition readers" do
|
23
|
+
form.song.must_equal rio
|
24
|
+
form.artist.must_equal duran
|
25
|
+
end
|
26
|
+
|
27
|
+
it "provides ::model_name" do
|
28
|
+
form.class.model_name.must_equal "Hit"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "provides #persisted?" do
|
32
|
+
HitForm.new(:song => OpenStruct.new.instance_eval { def persisted?; "yo!"; end; self }, :artist => OpenStruct.new).persisted?.must_equal "yo!"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "provides #to_key" do
|
36
|
+
HitForm.new(:song => OpenStruct.new.instance_eval { def to_key; "yo!"; end; self }, :artist => OpenStruct.new).to_key.must_equal "yo!"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "provides #to_param" do
|
40
|
+
HitForm.new(:song => OpenStruct.new.instance_eval { def to_param; "yo!"; end; self }, :artist => OpenStruct.new).to_param.must_equal "yo!"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "works with any order of ::model and ::property" do
|
44
|
+
class AnotherForm < Reform::Form
|
45
|
+
include DSL
|
46
|
+
include Reform::Form::ActiveModel
|
47
|
+
|
48
|
+
model :song, :on => :song
|
49
|
+
property :title, :on => :song
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
AnotherForm.new(:song => rio).song.must_equal rio
|
54
|
+
end
|
55
|
+
end
|
data/test/dsl_test.rb
CHANGED
@@ -7,7 +7,7 @@ class DslTest < MiniTest::Spec
|
|
7
7
|
property :title, :on => :song
|
8
8
|
properties [:name, :genre], :on => :artist
|
9
9
|
|
10
|
-
validates :name, :title, :genre, presence
|
10
|
+
validates :name, :title, :genre, :presence => true
|
11
11
|
end
|
12
12
|
|
13
13
|
let (:form) { SongForm.new(:song => OpenStruct.new(:title => "Rio"), :artist => OpenStruct.new()) }
|
@@ -16,57 +16,3 @@ class DslTest < MiniTest::Spec
|
|
16
16
|
form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb").must_equal false
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
20
|
-
class ActiveModelTest < MiniTest::Spec
|
21
|
-
class HitForm < Reform::Form
|
22
|
-
include DSL
|
23
|
-
include Reform::Form::ActiveModel
|
24
|
-
|
25
|
-
property :title, :on => :song
|
26
|
-
properties [:name, :genre], :on => :artist
|
27
|
-
|
28
|
-
model :hit, :on => :song
|
29
|
-
end
|
30
|
-
|
31
|
-
let (:rio) { OpenStruct.new(:title => "Rio") }
|
32
|
-
let (:duran) { OpenStruct.new }
|
33
|
-
let (:form) { HitForm.new(:song => rio, :artist => duran) }
|
34
|
-
|
35
|
-
it "creates model readers" do
|
36
|
-
form.hit.must_equal rio
|
37
|
-
end
|
38
|
-
|
39
|
-
it "creates composition readers" do
|
40
|
-
form.song.must_equal rio
|
41
|
-
form.artist.must_equal duran
|
42
|
-
end
|
43
|
-
|
44
|
-
it "provides ::model_name" do
|
45
|
-
form.class.model_name.must_equal "Hit"
|
46
|
-
end
|
47
|
-
|
48
|
-
it "provides #persisted?" do
|
49
|
-
HitForm.new(:song => OpenStruct.new.instance_eval { def persisted?; "yo!"; end; self }, :artist => OpenStruct.new).persisted?.must_equal "yo!"
|
50
|
-
end
|
51
|
-
|
52
|
-
it "provides #to_key" do
|
53
|
-
HitForm.new(:song => OpenStruct.new.instance_eval { def to_key; "yo!"; end; self }, :artist => OpenStruct.new).to_key.must_equal "yo!"
|
54
|
-
end
|
55
|
-
|
56
|
-
it "provides #to_param" do
|
57
|
-
HitForm.new(:song => OpenStruct.new.instance_eval { def to_param; "yo!"; end; self }, :artist => OpenStruct.new).to_param.must_equal "yo!"
|
58
|
-
end
|
59
|
-
|
60
|
-
it "works with any order of ::model and ::property" do
|
61
|
-
class AnotherForm < Reform::Form
|
62
|
-
include DSL
|
63
|
-
include Reform::Form::ActiveModel
|
64
|
-
|
65
|
-
model :song, :on => :song
|
66
|
-
property :title, :on => :song
|
67
|
-
end
|
68
|
-
|
69
|
-
|
70
|
-
AnotherForm.new(:song => rio).song.must_equal rio
|
71
|
-
end
|
72
|
-
end
|
data/test/reform_test.rb
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
+
require 'active_record'
|
4
|
+
class Artist < ActiveRecord::Base
|
5
|
+
end
|
6
|
+
ActiveRecord::Base.establish_connection(
|
7
|
+
:adapter => "sqlite3",
|
8
|
+
:database => "#{Dir.pwd}/database.sqlite3"
|
9
|
+
)
|
10
|
+
|
11
|
+
|
3
12
|
class RepresenterTest < MiniTest::Spec
|
4
13
|
class SongRepresenter < Reform::Representer
|
5
14
|
properties [:title, :year]
|
@@ -50,8 +59,8 @@ class ReformTest < MiniTest::Spec
|
|
50
59
|
let (:form) { SongForm.new(SongAndArtistMap, comp) }
|
51
60
|
|
52
61
|
class SongAndArtistMap < Reform::Representer
|
53
|
-
property :name, on
|
54
|
-
property :title, on
|
62
|
+
property :name, :on => :artist
|
63
|
+
property :title, :on => :song
|
55
64
|
end
|
56
65
|
|
57
66
|
class SongForm < Reform::Form
|
@@ -152,12 +161,68 @@ class ReformTest < MiniTest::Spec
|
|
152
161
|
end
|
153
162
|
|
154
163
|
# TODO: test errors. test valid.
|
155
|
-
|
164
|
+
describe "invalid input" do
|
156
165
|
class ValidatingForm < Reform::Form
|
157
|
-
validates :name,
|
166
|
+
validates :name, :presence => true
|
167
|
+
validates :title, :presence => true
|
168
|
+
end
|
169
|
+
let (:form) { ValidatingForm.new(SongAndArtistMap, comp) }
|
170
|
+
|
171
|
+
it "returns false when invalid" do
|
172
|
+
form.validate({}).must_equal false
|
173
|
+
end
|
174
|
+
|
175
|
+
it "populates errors" do
|
176
|
+
form.validate({})
|
177
|
+
form.errors.messages.must_equal({:name=>["can't be blank"], :title=>["can't be blank"]})
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe "method validations" do
|
182
|
+
it "allows accessing models" do
|
183
|
+
form = Class.new(Reform::Form) do
|
184
|
+
validate "name_correct?"
|
185
|
+
|
186
|
+
def name_correct?
|
187
|
+
errors.add :name, "Please give me a name" if model.artist.name.nil?
|
188
|
+
end
|
189
|
+
end.new(SongAndArtistMap, comp)
|
190
|
+
|
191
|
+
form.validate({}).must_equal false
|
192
|
+
form.errors.messages.must_equal({:name=>["Please give me a name"]})
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
describe "UniquenessValidator" do
|
197
|
+
# ActiveRecord::Schema.define do
|
198
|
+
# create_table :artists do |table|
|
199
|
+
# table.column :name, :string
|
200
|
+
# end
|
201
|
+
# end
|
202
|
+
#Artist.new(:name => "Racer X").save
|
203
|
+
let (:comp) { SongAndArtist.new(:artist => Artist.new, :song => OpenStruct.new) }
|
204
|
+
|
205
|
+
it "allows accessing the database" do
|
158
206
|
end
|
159
207
|
|
160
|
-
|
208
|
+
it "is valid when name is unique" do
|
209
|
+
ActiveRecordForm.new(SongAndArtistMap, comp).validate({"name" => "Paul Gilbert", "title" => "Godzilla"}).must_equal true
|
210
|
+
end
|
211
|
+
|
212
|
+
it "is invalid and shows error when taken" do
|
213
|
+
form = ActiveRecordForm.new(SongAndArtistMap, comp)
|
214
|
+
form.validate({"name" => "Racer X"}).must_equal false
|
215
|
+
form.errors.messages.must_equal({:name=>["has already been taken"], :title => ["can't be blank"]})
|
216
|
+
end
|
217
|
+
|
218
|
+
require 'reform/rails'
|
219
|
+
class ActiveRecordForm < Reform::Form
|
220
|
+
include Reform::Form::ActiveRecord
|
221
|
+
model :artist, :on => :artist # FIXME: i want form.artist, so move this out of ActiveModel into ModelDelegations.
|
222
|
+
|
223
|
+
validates_uniqueness_of :name
|
224
|
+
validates :title, :presence => true # have another property to test if we mix up.
|
225
|
+
end
|
161
226
|
end
|
162
227
|
end
|
163
228
|
|
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.1
|
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-05-
|
13
|
+
date: 2013-05-13 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: representable
|
@@ -28,6 +28,22 @@ dependencies:
|
|
28
28
|
- - ! '>='
|
29
29
|
- !ruby/object:Gem::Version
|
30
30
|
version: '0'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: activemodel
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
31
47
|
- !ruby/object:Gem::Dependency
|
32
48
|
name: bundler
|
33
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -77,7 +93,23 @@ dependencies:
|
|
77
93
|
- !ruby/object:Gem::Version
|
78
94
|
version: '0'
|
79
95
|
- !ruby/object:Gem::Dependency
|
80
|
-
name:
|
96
|
+
name: activerecord
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sqlite3
|
81
113
|
requirement: !ruby/object:Gem::Requirement
|
82
114
|
none: false
|
83
115
|
requirements:
|
@@ -101,17 +133,23 @@ extensions: []
|
|
101
133
|
extra_rdoc_files: []
|
102
134
|
files:
|
103
135
|
- .gitignore
|
136
|
+
- .travis.yml
|
104
137
|
- CHANGES.md
|
105
138
|
- Gemfile
|
106
139
|
- LICENSE.txt
|
107
140
|
- README.md
|
108
141
|
- Rakefile
|
142
|
+
- TODO.md
|
143
|
+
- database.sqlite3
|
109
144
|
- lib/reform.rb
|
110
145
|
- lib/reform/form.rb
|
111
146
|
- lib/reform/form/active_model.rb
|
147
|
+
- lib/reform/form/active_record.rb
|
112
148
|
- lib/reform/form/dsl.rb
|
149
|
+
- lib/reform/rails.rb
|
113
150
|
- lib/reform/version.rb
|
114
151
|
- reform.gemspec
|
152
|
+
- test/active_model_test.rb
|
115
153
|
- test/dsl_test.rb
|
116
154
|
- test/reform_test.rb
|
117
155
|
- test/test_helper.rb
|
@@ -139,8 +177,10 @@ rubyforge_project:
|
|
139
177
|
rubygems_version: 1.8.25
|
140
178
|
signing_key:
|
141
179
|
specification_version: 3
|
142
|
-
summary:
|
180
|
+
summary: Decouples your models from form by giving you form objects with validation,
|
181
|
+
presentation, workflows and security.
|
143
182
|
test_files:
|
183
|
+
- test/active_model_test.rb
|
144
184
|
- test/dsl_test.rb
|
145
185
|
- test/reform_test.rb
|
146
186
|
- test/test_helper.rb
|