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 ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ - 1.8.7
6
+ - rbx-19mode
7
+ - jruby-19mode
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
- ## ActiveModel - Rails Integration
103
+ ## Rails Integration
104
104
 
105
- Reform offers ActiveModel support to easily make this accessible in Rails based projects. You simply `include Reform::Form::ActiveModel` in your form object and the Rails specific code will be handled for you.
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 specifiy which is the main object of the form.
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::ActiveModel
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
@@ -0,0 +1,2 @@
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?
data/database.sqlite3 ADDED
Binary file
data/lib/reform.rb CHANGED
@@ -1,9 +1,4 @@
1
- require "reform/version"
2
-
3
- module Reform
4
- # Your code goes here...
5
- end
6
-
1
+ require 'reform/version'
7
2
  require 'reform/form'
8
3
  require 'reform/form/dsl'
9
4
  require 'reform/form/active_model'
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, to: "@#{model}"
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 "persisted?", :to_key, :to_param, :to => main_model # #to_key => song.to_key
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
@@ -0,0 +1,2 @@
1
+ require 'reform/form/active_model'
2
+ require 'reform/form/active_record'
@@ -1,3 +1,3 @@
1
1
  module Reform
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
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{Freeing your AR models from form logic.}
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 "activemodel"
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: true
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: :artist
54
- property :title, on: :song
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
- it "returns false when invalid" do
164
+ describe "invalid input" do
156
165
  class ValidatingForm < Reform::Form
157
- validates :name, :presence => true
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
- ValidatingForm.new(SongAndArtistMap, comp).validate({}).must_equal false
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.0
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-09 00:00:00.000000000 Z
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: activemodel
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: Freeing your AR models from form logic.
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