formality 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Arun Srinivasan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,253 @@
1
+ # Formality
2
+
3
+ Form objects. For rails.
4
+
5
+ gem install formality
6
+
7
+ Forms have both data and behavior. Sounds suspiciously like they should be
8
+ objects. Let's make them so.
9
+
10
+ Additionally, let's get all that validation logic out of the ORM. Enforcing
11
+ data integrity is one thing; making sure that you get either a phone number or
12
+ an email from a user is another.
13
+
14
+ ## Quick Example
15
+
16
+ Let's make a signup form.
17
+
18
+ ```ruby
19
+ class SignupForm
20
+ include Formality
21
+
22
+ attribute :email
23
+ attribute :password
24
+ attribute :password_confirmation
25
+
26
+ validates_presence_of :email, :password, :password_confirmation
27
+ validates_confirmation_of :password
28
+ end
29
+ ```
30
+
31
+ In, say, a UsersController:
32
+
33
+ ```ruby
34
+ class UsersController < ApplicaationController
35
+ def new
36
+ @form = SignupForm.new
37
+ end
38
+
39
+ def create
40
+ @form = SignupForm.assign params[:signup_form]
41
+
42
+ @form.valid do
43
+ User.create! @form.attributes
44
+ end
45
+
46
+ @form.invalid do
47
+ render :new
48
+ end
49
+ end
50
+ end
51
+ ```
52
+
53
+ In the view:
54
+
55
+ ```haml
56
+ = form_for @form do |f|
57
+ = f.text_field :email, :placeholder => "Email"
58
+ = f.password_field :password, :placeholder => "Password"
59
+ = f.password_field :password_confirmation, :placeholder => "Confirm Password"
60
+ = f.submit "Sign up!"
61
+ ```
62
+
63
+ ## More docs
64
+
65
+ #### Attributes
66
+
67
+ Use the `attribute` class method to declare attributes. It accepts a `:default`
68
+ option.
69
+
70
+ ```ruby
71
+ class TodoForm
72
+ include Formality
73
+
74
+ attribute :description
75
+ attribute :done, :default => false
76
+ end
77
+
78
+ default = TodoForm.new
79
+ default.description #=> nil
80
+ default.done #=> false
81
+ ```
82
+
83
+ `assign` is available as a class or instance method, and will:
84
+
85
+ 1. Assign any declared attributes from the hash it receives.
86
+ 2. Assign an `:id` if one is present, whether declared or not (this allows us
87
+ to get some nice behavior when working with models).
88
+ 3. Return the form object.
89
+
90
+ ```ruby
91
+ # Continuing using the TodoForm
92
+
93
+ form = TodoForm.assign :id => 1, :description => "Buy some milk"
94
+ # or
95
+ form = TodoForm.new.assign :id => 1, :description => "Buy some milk"
96
+
97
+ form.id #=> 1
98
+ form.description #=> "Buy some milk"
99
+ form.done #=> false
100
+ ```
101
+
102
+ `attributes` gets you a Hash of the attributes:
103
+
104
+ ```ruby
105
+ form = TodoForm.assign :id => 1, :description => "Buy some milk"
106
+
107
+ form.attributes #=> {"description" => "Buy some milk", "done" => false}
108
+
109
+ # It's an ActiveSupport::HashWithIndifferentAccess, so:
110
+ attrs = form.attributes
111
+ attrs[:description] == attrs["description"] == "Buy some milk"
112
+ ```
113
+
114
+ `attribute_names` returns an Array of attribute names:
115
+
116
+ ```ruby
117
+ form = TodoForm.new
118
+ form.attribute_names #=> ["description", "done"]
119
+ ```
120
+
121
+ #### Validations
122
+
123
+ This one's easy to write docs for, as they are [already
124
+ written][validddocs].
125
+
126
+ You get all your standard ActiveModel validations to play with:
127
+
128
+ ```ruby
129
+ class SignupForm
130
+ include Formality
131
+
132
+ attribute :email
133
+ attribute :password
134
+ attribute :password_confirmation
135
+
136
+ validates_presence_of :email, :password, :password_confirmation,
137
+ :message => "can't be blank"
138
+ validates_confirmation_of :password,
139
+ :message => "confirmation mismatch"
140
+ end
141
+
142
+ form = SignupForm.assign :password => "123", :password_confirmation => "456"
143
+ form.valid? #=> false
144
+ form.invalid? #=> true
145
+ form.errors[:email] #=> ["can't be blank"]
146
+ form.errors[:password] #=> ["confirmation mismatch"]
147
+ ```
148
+
149
+ You can, of course, use `:valid?` and `:invalid?` directly, but Formality also
150
+ provides with a little bit of sugar:
151
+
152
+ ```ruby
153
+ form = SignupForm.assign(some_attrs_from_somewhere)
154
+
155
+ form.valid do
156
+ # this block will run only if the form is valid
157
+ end
158
+
159
+ form.invalid do
160
+ # this block will run only if the form is NOT valid
161
+ end
162
+ ```
163
+
164
+ It's a couple of lines longer than `if form.valid?...else...end`, but I feel it
165
+ reads a little more clearly. Use it if you like it, don't if you don't.
166
+
167
+ #### Working With Models
168
+
169
+ The `from_model` class method builds a from object from an associated model.
170
+ The model object must have an `attributes` method (in case you're looking at
171
+ using this with something that's not ActiveRecord).
172
+
173
+ The `model` class method lets you specify which model the form object
174
+ represents. This lets `form_for` generate the right resourceful routes for your
175
+ object from the form object itself.
176
+
177
+ ```ruby
178
+ # config.routes.rb
179
+ resources :todos
180
+
181
+ # models/todo.rb
182
+ class Todo < ActiveRecord::Base; end
183
+
184
+ # models/forms/todo_form.rb (or wherever you want to put it)
185
+ class TodoForm
186
+ include Formality
187
+
188
+ model :todo
189
+
190
+ attribute :description
191
+ attribute :done, :default => false
192
+ end
193
+
194
+ todo = Todo.create!(:description => "Feed the dog")
195
+ todo.id #=> 42 (or whatever)
196
+
197
+ form = TodoForm.from_model(todo)
198
+ form.id #=> 42
199
+ form.description #=> "Feed the dog"
200
+ form.done #=> false
201
+
202
+ # Now, when you pass your form into `form_for`, it will
203
+ # automatically create a form that points to "/todos/42"
204
+ # with the method set to put.
205
+ ```
206
+
207
+ #### Nesting
208
+
209
+ Use `nest_many` or `nest_one` to declare nested form objects. This will define
210
+ reader and writer methods such that the form object will work cleanly with
211
+ a `fields_for` call.
212
+
213
+ If you also add a `:from_model_attribute` option, nested form objects will get
214
+ correctly populated when you use `from_model`.
215
+
216
+ ```ruby
217
+ Question = Struct.new(:id, :text, :answers) do
218
+ def attributes; Hash[members.zip(values)] end
219
+ end
220
+
221
+ Answer = Struct.new(:id, :text) do
222
+ def attributes; Hash[members.zip(values)] end
223
+ end
224
+
225
+ class QuestionForm
226
+ include Formality
227
+ attribute :text
228
+ nest_many :answer_forms, :from_model_attribute => :answers
229
+ validates_presence_of :text, :message => "can't be empty"
230
+ end
231
+
232
+ class AnswerForm
233
+ include Formality
234
+ attribute :text
235
+ validates_presence_of :text, :message => "can't be empty"
236
+ end
237
+
238
+ question = Question.new(1, "Question 1")
239
+ question.answers = [Answer.new(1, "Answer 1"), Answer.new(2, "Answer 2")]
240
+
241
+ form = QuestionForm.from_model(question)
242
+ form.attributes #=> {"text" => "Question 1",
243
+ "answer_forms_attributes" => [{"text"=>"Answer 1"},
244
+ {"text"=>"Answer 2"}]}
245
+
246
+ form.answer_forms[0].id #=> 1
247
+ form.answer_forms[1].id #=> 2
248
+ ```
249
+
250
+ I think there are still maybe some API kinks to work out with the nested forms,
251
+ but this gets it most of the way there.
252
+
253
+ [validdocs]: http://guides.rubyonrails.org/active_record_validations_callbacks.html
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ $:.push("lib")
2
+ require "formality"
3
+
4
+ desc "Run the tests"
5
+ task :test do
6
+ require "tst"
7
+ Tst.run "test/*.rb",
8
+ :load_paths => ['.', 'lib'],
9
+ :reporter => Tst::Reporters::Pretty.new
10
+ end
11
+
12
+ NAME = "formality"
13
+ VERSION = Formality::VERSION
14
+
15
+ namespace :gem do
16
+ desc 'Clean up generated files'
17
+ task :clean do
18
+ sh 'rm -rf pkg'
19
+ end
20
+
21
+ desc "Build the gem"
22
+ task :build => :clean do
23
+ sh "mkdir pkg"
24
+ sh "gem build #{NAME}.gemspec"
25
+ sh "mv #{NAME}-#{VERSION}.gem pkg/"
26
+ end
27
+
28
+ desc "Release v#{VERSION}"
29
+ task :release => :build do
30
+ sh "git commit --allow-empty -a -m 'Release #{VERSION}'"
31
+ sh "git tag v#{VERSION}"
32
+ sh "git push origin master"
33
+ sh "git push origin v#{VERSION}"
34
+ sh "gem push pkg/#{NAME}-#{VERSION}.gem"
35
+ end
36
+ end
37
+
38
+ task :default => :test
@@ -0,0 +1,89 @@
1
+ require 'test/lint'
2
+ require 'formality'
3
+
4
+ class ::Form
5
+ include Formality
6
+ attribute :no_default
7
+ attribute :answer, :default => 42
8
+ end
9
+
10
+ lint(Form.new)
11
+
12
+ tst ".attribute defines reader and writer" do
13
+ assert Form.new.respond_to? :answer
14
+ assert Form.new.respond_to? :answer=
15
+ end
16
+
17
+ tst "an attribute without a :default defaults to nil" do
18
+ assert_equal nil, Form.new.no_default
19
+ end
20
+
21
+ tst ":default option sets a default value" do
22
+ assert_equal 42, Form.new.answer
23
+ end
24
+
25
+ tst "#attribute_names returns an Array of attribute names" do
26
+ assert_equal ["no_default", "answer"], Form.new.attribute_names
27
+ end
28
+
29
+ tst "#attributes returns a HashWithIndifferentAccess" do
30
+ form = Form.new
31
+ assert ActiveSupport::HashWithIndifferentAccess === form.attributes
32
+ end
33
+
34
+ tst "#attributes hash contains all the attribute values" do
35
+ form = Form.new
36
+ assert_equal nil, form.attributes[:no_default]
37
+ assert_equal 42, form.attributes[:answer]
38
+ end
39
+
40
+ tst "#attribute? returns true if the attribute was declared" do
41
+ form = Form.new
42
+ assert form.attribute?(:answer)
43
+ assert form.attribute?(:no_default)
44
+ assert form.attribute?("answer")
45
+ assert form.attribute?("no_default")
46
+ end
47
+
48
+ tst "#assign assigns attribute values" do
49
+ form = Form.new
50
+ form.assign :answer => "hello", :no_default => "some value"
51
+
52
+ assert_equal "some value", form.attributes[:no_default]
53
+ assert_equal "hello", form.attributes[:answer]
54
+ end
55
+
56
+ tst "#assign returns the form instance for chaining/assignment convenience" do
57
+ form = Form.new
58
+ the_same = form.assign :answer => "hello", :no_default => "some value"
59
+
60
+ assert_equal form, the_same
61
+ end
62
+
63
+
64
+ tst "#assign does not assign undeclared attributes" do
65
+ form = Form.new
66
+ def form.undeclared=(*args) raise("shouldn't get here") end
67
+ form.assign(:undeclared => 123,
68
+ :answer => "hello",
69
+ :no_default => "some value")
70
+
71
+ assert_equal "some value", form.attributes[:no_default]
72
+ assert_equal "hello", form.attributes[:answer]
73
+ end
74
+
75
+ tst "#assign will assign to @id if an :id key comes in" do
76
+ form = Form.new
77
+ form.assign :id => "123", :answer => "hello", :no_default => "some value"
78
+ assert_equal "123", form.id
79
+ end
80
+
81
+ tst ".assign: just a thin wrapper to not have to call new" do
82
+ form = Form.assign :answer => "hello", :no_default => "some value"
83
+
84
+ assert form.kind_of?(Form)
85
+ assert_equal "hello", form.answer
86
+ assert_equal "some value", form.no_default
87
+ end
88
+
89
+ Object.send(:remove_const, :Form)
@@ -0,0 +1,13 @@
1
+ require 'test/lint'
2
+ require 'formality'
3
+
4
+ #
5
+ # Run the ActiveModel::Lint tests on a fresh class
6
+ # that includes Formality.
7
+ #
8
+
9
+ class Form
10
+ include Formality
11
+ end
12
+
13
+ lint(Form.new)
data/test/lint.rb ADDED
@@ -0,0 +1,77 @@
1
+ #
2
+ # Checks `model` for ActiveModel compliance.
3
+ #
4
+ # The tests are ported directly from ActiveModel::Lint
5
+ #
6
+
7
+ def lint(model)
8
+ tst "responds to :to_key" do
9
+ assert model.respond_to? :to_key
10
+ end
11
+
12
+ tst ":to_key returns nil when :persisted? returns false" do
13
+ form = model
14
+ def form.persisted?; false end
15
+ assert_equal nil, form.to_key
16
+ end
17
+
18
+ tst "responds to :to_param" do
19
+ assert model.respond_to? :to_param
20
+ end
21
+
22
+ tst ":to_param returns nil when :persisted? returns false" do
23
+ form = model
24
+ def form.persisted?; false end
25
+ assert_equal nil, form.to_param
26
+ end
27
+
28
+ tst "responds to :to_partial_path" do
29
+ assert model.respond_to? :to_partial_path
30
+ end
31
+
32
+ tst ":to_partial_path returns a String" do
33
+ assert String === model.to_partial_path
34
+ end
35
+
36
+ tst "responds to :valid?" do
37
+ assert model.respond_to? :valid?
38
+ end
39
+
40
+ tst ":valid? returns a Boolean" do
41
+ valid = model.valid?
42
+ assert valid === true || valid === false
43
+ end
44
+
45
+ tst "responds to :persisted?" do
46
+ assert model.respond_to? :persisted?
47
+ end
48
+
49
+ tst ":persisted? returns a Boolean" do
50
+ persisted = model.persisted?
51
+ assert persisted === true || persisted === false
52
+ end
53
+
54
+ tst "responds to :model_name" do
55
+ assert model.class.respond_to? :model_name
56
+ end
57
+
58
+ tst ":model_name returns a string with convenience methods" do
59
+ name = model.class.model_name
60
+ assert String === name
61
+ assert String === name.human
62
+ assert String === name.singular
63
+ assert String === name.plural
64
+ end
65
+
66
+ tst "responds to :errors" do
67
+ assert model.respond_to? :errors
68
+ end
69
+
70
+ tst "error#[] returns an Array" do
71
+ assert Array === model.errors[:hello]
72
+ end
73
+
74
+ tst "errors#full_messages returns an Array" do
75
+ assert Array === model.errors.full_messages
76
+ end
77
+ end
data/test/nest_many.rb ADDED
@@ -0,0 +1,116 @@
1
+ require 'test/lint'
2
+ require 'formality'
3
+
4
+ ::Question = Struct.new(:id, :text, :answers) do
5
+ def attributes; Hash[members.zip(values)] end
6
+ end
7
+
8
+ ::Answer = Struct.new(:id, :text) do
9
+ def attributes; Hash[members.zip(values)] end
10
+ end
11
+
12
+ class ::QuestionForm
13
+ include Formality
14
+ attribute :text
15
+ nest_many :answer_forms, :from_model_attribute => :answers
16
+ validates_presence_of :text, :message => "can't be empty"
17
+ end
18
+
19
+ class ::AnswerForm
20
+ include Formality
21
+ attribute :text
22
+ validates_presence_of :text, :message => "can't be empty"
23
+ end
24
+
25
+ lint(AnswerForm.new)
26
+ lint(QuestionForm.new)
27
+
28
+ tst ".nest_many: defines an '<assoc>' reader" do
29
+ form = QuestionForm.new
30
+ assert form.respond_to?(:answer_forms)
31
+ end
32
+
33
+ tst ".nest_many: defines an '<assoc>_attributes' reader" do
34
+ form = QuestionForm.new
35
+ assert form.respond_to?(:answer_forms_attributes)
36
+ end
37
+
38
+ tst ".nest_many: reader defaults to an empty array" do
39
+ form = QuestionForm.new
40
+ assert_equal [], form.answer_forms
41
+ end
42
+
43
+ tst ".nest_many: defines an '<assoc>_attributes=' writer" do
44
+ form = QuestionForm.new
45
+ assert form.respond_to?(:answer_forms_attributes=)
46
+ end
47
+
48
+ tst ".nest_many: reader returns an Array of child form objects" do
49
+ form = QuestionForm.new
50
+ form.answer_forms_attributes = [{:text => "first"}]
51
+ answer_form = form.answer_forms.first
52
+
53
+ assert answer_form.kind_of?(AnswerForm)
54
+ assert_equal "first", answer_form.text
55
+ end
56
+
57
+ tst "#attributes: includes the nested form's attributes" do
58
+ form = QuestionForm.new
59
+ form.assign :answer_forms_attributes => [{:text => "first"}]
60
+
61
+
62
+ expected = {"text" => nil,
63
+ "answer_forms_attributes" => [{"text" => "first"}]}
64
+
65
+ assert_equal expected, form.attributes
66
+ end
67
+
68
+ tst "#valid?: returns false if any nested forms are invalid" do
69
+ form = QuestionForm.new.assign :text => "question text"
70
+ form.answer_forms_attributes = [{:text => ""}]
71
+
72
+ assert_equal false, form.valid?
73
+ end
74
+
75
+ tst "valid?: sets errors on the nested forms" do
76
+ form = QuestionForm.new.assign :text => "question text"
77
+ form.answer_forms_attributes = [{:text => ""}]
78
+ form.valid?
79
+ answer_form = form.answer_forms.first
80
+
81
+ assert_equal ["can't be empty"], answer_form.errors[:text]
82
+ end
83
+
84
+ tst "#invalid?: returns true if any nested forms are invalid" do
85
+ form = QuestionForm.new.assign :text => "question text"
86
+ form.answer_forms_attributes = [{:text => ""}]
87
+
88
+ assert_equal true, form.invalid?
89
+ end
90
+
91
+ tst "#invalid?: sets errors on the nested forms" do
92
+ form = QuestionForm.new.assign :text => "question text"
93
+ form.answer_forms_attributes = [{:text => ""}]
94
+ form.invalid?
95
+ answer_form = form.answer_forms.first
96
+
97
+ assert_equal ["can't be empty"], answer_form.errors[:text]
98
+ end
99
+
100
+ tst "#from_model: assigns to nested form from nested models" do
101
+ question = Question.new(1, "Question 1")
102
+ question.answers = [Answer.new(1, "Answer 1"), Answer.new(2, "Answer 2")]
103
+ form = QuestionForm.from_model(question)
104
+
105
+ answer_form_1, answer_form_2 = form.answer_forms
106
+
107
+ assert_equal 1, answer_form_1.id
108
+ assert_equal "Answer 1", answer_form_1.text
109
+ assert_equal 2, answer_form_2.id
110
+ assert_equal "Answer 2", answer_form_2.text
111
+ end
112
+
113
+ Object.send(:remove_const, :QuestionForm)
114
+ Object.send(:remove_const, :AnswerForm)
115
+ Object.send(:remove_const, :Question)
116
+ Object.send(:remove_const, :Answer)
data/test/nest_one.rb ADDED
@@ -0,0 +1,106 @@
1
+ require 'test/lint'
2
+ require 'formality'
3
+
4
+ ::Question = Struct.new(:id, :text, :answer) do
5
+ def attributes; Hash[members.zip(values)] end
6
+ end
7
+
8
+ ::Answer = Struct.new(:id, :text) do
9
+ def attributes; Hash[members.zip(values)] end
10
+ end
11
+
12
+
13
+ class ::QuestionForm
14
+ include Formality
15
+ attribute :text
16
+ nest_one :answer_form, :from_model_attribute => :answer
17
+ validates_presence_of :text, :message => "can't be empty"
18
+ end
19
+
20
+ class ::AnswerForm
21
+ include Formality
22
+ attribute :text
23
+ validates_presence_of :text, :message => "can't be empty"
24
+ end
25
+
26
+ tst ".nest_one: defines an '<assoc>' reader" do
27
+ form = QuestionForm.new
28
+ assert form.respond_to?(:answer_form)
29
+ end
30
+
31
+ tst ".nest_one: defines an '<assoc>_attributes' reader" do
32
+ form = QuestionForm.new
33
+ assert form.respond_to?(:answer_form_attributes)
34
+ end
35
+
36
+ tst ".nest_one: reader defaults to nil" do
37
+ form = QuestionForm.new
38
+ assert_equal nil, form.answer_form
39
+ end
40
+
41
+ tst ".nest_one: defines an '<assoc>_attributes=' writer" do
42
+ form = QuestionForm.new
43
+ assert form.respond_to?(:answer_form_attributes=)
44
+ end
45
+
46
+ tst ".nest_many: reader returns a child form object" do
47
+ form = QuestionForm.new
48
+ form.answer_form_attributes = {:text => "first"}
49
+
50
+ assert form.answer_form.kind_of?(AnswerForm)
51
+ assert_equal "first", form.answer_form.text
52
+ end
53
+
54
+ tst "#attributes: includes the nested form's attributes" do
55
+ form = QuestionForm.new
56
+ form.answer_form_attributes = {:text => "first"}
57
+
58
+ expected = {"text" => nil,
59
+ "answer_form_attributes" => {"text" => "first"}}
60
+
61
+ assert_equal expected, form.attributes
62
+ end
63
+
64
+ tst "#valid?: returns false if the nested form is invalid" do
65
+ form = QuestionForm.new.assign :text => "question text"
66
+ form.answer_form_attributes = {:text => ""}
67
+
68
+ assert_equal false, form.valid?
69
+ end
70
+
71
+ tst "valid?: sets errors on the nested form" do
72
+ form = QuestionForm.new.assign :text => "question text"
73
+ form.answer_form_attributes = {:text => ""}
74
+ form.valid?
75
+
76
+ assert_equal ["can't be empty"], form.answer_form.errors[:text]
77
+ end
78
+
79
+ tst "#invalid?: returns true if the nested form is invalid" do
80
+ form = QuestionForm.new.assign :text => "question text"
81
+ form.answer_form_attributes = {:text => ""}
82
+
83
+ assert_equal true, form.invalid?
84
+ end
85
+
86
+ tst "invalid?: sets errors on the nested form" do
87
+ form = QuestionForm.new.assign :text => "question text"
88
+ form.answer_form_attributes = {:text => ""}
89
+ form.invalid?
90
+
91
+ assert_equal ["can't be empty"], form.answer_form.errors[:text]
92
+ end
93
+
94
+ tst "#from_model: assigns to nested form from nested model" do
95
+ question = Question.new(1, "Question 1")
96
+ question.answer = Answer.new(1, "Answer 1")
97
+ form = QuestionForm.from_model(question)
98
+
99
+ assert_equal 1, form.answer_form.id
100
+ assert_equal "Answer 1", form.answer_form.text
101
+ end
102
+
103
+ Object.send(:remove_const, :QuestionForm)
104
+ Object.send(:remove_const, :AnswerForm)
105
+ Object.send(:remove_const, :Question)
106
+ Object.send(:remove_const, :Answer)
data/test/rails.rb ADDED
@@ -0,0 +1,88 @@
1
+ require 'test/lint'
2
+ require 'formality'
3
+ require 'action_dispatch/routing'
4
+
5
+ #
6
+ # Some tests to make sure that Formality forms work
7
+ # well with the Rails view helpers (:form_for, et al.)
8
+ #
9
+
10
+ # A little fake AR-like model
11
+ ::Person = Struct.new(:id, :first, :last) do
12
+ def attributes; Hash[members.zip(values)] end
13
+ end
14
+
15
+ # Our intrepid hero
16
+ class ::PersonForm
17
+ include Formality
18
+ attribute :first
19
+ attribute :last
20
+ end
21
+
22
+ lint(PersonForm.new)
23
+
24
+ # For testing correct route generation delegation
25
+ class Route
26
+ include ActionDispatch::Routing::UrlFor
27
+
28
+ def self.url_for(*args)
29
+ new.url_for(*args)
30
+ end
31
+
32
+ def person_url(person) "person/#{person.id}" end
33
+ def people_url; "people" end
34
+ end
35
+
36
+ tst "#from_model assigns form's attributes" do
37
+ person = Person.new(1, "Arthur", "Dent")
38
+ form = PersonForm.from_model(person)
39
+
40
+ assert_equal "Arthur", form.first
41
+ assert_equal "Dent", form.last
42
+ end
43
+
44
+ tst "#from_model assigns form's id" do
45
+ person = Person.new(1, "Arthur", "Dent")
46
+ form = PersonForm.from_model(person)
47
+
48
+ assert_equal 1, form.id
49
+ end
50
+
51
+ tst "just to confirm, :id doesn't show up in #attributes" do
52
+ person = Person.new(1, "Arthur", "Dent")
53
+ form = PersonForm.from_model(person)
54
+
55
+ assert !form.attributes.has_key?(:id)
56
+ assert !form.attributes.has_key?("id")
57
+ end
58
+
59
+ tst "#persisted? is true if there's an id present" do
60
+ person = Person.new(1, "Arthur", "Dent")
61
+ form = PersonForm.from_model(person)
62
+
63
+ assert form.persisted?
64
+ end
65
+
66
+ tst ".model method lets you set the model for the form" do
67
+ klass = PersonForm.dup
68
+ klass.model :person
69
+
70
+ assert_equal "Person", klass.model_name
71
+ assert_equal "people", klass.model_name.route_key
72
+ assert_equal "person", klass.model_name.singular_route_key
73
+ end
74
+
75
+ tst ".model lets rails generate routes for underlying model" do
76
+ klass = PersonForm.dup
77
+ klass.model :person
78
+
79
+ dent = Person.new(1, "Arthur", "Dent")
80
+ dent_form = klass.from_model(dent)
81
+ anon_form = klass.new
82
+
83
+ assert_equal "person/1", Route.url_for(dent_form)
84
+ assert_equal "people", Route.url_for(anon_form)
85
+ end
86
+
87
+ Object.send(:remove_const, :Person)
88
+ Object.send(:remove_const, :PersonForm)
data/test/valid.rb ADDED
@@ -0,0 +1,91 @@
1
+ require 'test/lint'
2
+ require 'formality'
3
+
4
+ # A form class with validations
5
+ class ::LoginForm
6
+ include Formality
7
+
8
+ attribute :email
9
+ attribute :password
10
+ attribute :password_confirmation
11
+
12
+ validates_presence_of :email, :password, :password_confirmation,
13
+ :message => "can't be empty"
14
+
15
+ validates_confirmation_of :password,
16
+ :message => "don't match"
17
+ end
18
+
19
+ # Check that we still pass ActiveModel::Lint
20
+ lint(LoginForm.new)
21
+
22
+ # A little helper to generate valid attributes
23
+ def valid_attributes
24
+ { :email => 'test@example.com',
25
+ :password => '123',
26
+ :password_confirmation => '123' }
27
+ end
28
+
29
+ tst "the builtin ActiveModel validations exist" do
30
+ [ :validates, :validates!, :validate, :validates_each,
31
+ :validates_confirmation_of, :validates_exclusion_of,
32
+ :validates_inclusion_of, :validates_format_of, :validates_length_of,
33
+ :validates_numericality_of, :validates_presence_of, :validates_with
34
+ ].each do |validator|
35
+ assert LoginForm.respond_to?(validator)
36
+ end
37
+ end
38
+
39
+ tst "#valid? and #invalid? do the right thing when conditions ARE NOT met" do
40
+ form = LoginForm.new
41
+ assert_equal false, form.valid?
42
+ assert_equal true, form.invalid?
43
+ end
44
+
45
+ tst "#valid? and #invalid? do the right thing when conditions ARE met" do
46
+ form = LoginForm.new.assign(valid_attributes)
47
+ assert_equal true, form.valid?
48
+ assert_equal false, form.invalid?
49
+ end
50
+
51
+ tst "#errors contains the error messages" do
52
+ form = LoginForm.new
53
+
54
+ assert_equal false, form.valid?
55
+ assert_equal ["can't be empty"], form.errors[:email]
56
+ assert_equal ["can't be empty"], form.errors[:password]
57
+ assert_equal ["can't be empty"], form.errors[:password_confirmation]
58
+
59
+ form.email = "test@example.com"
60
+ form.password = "123"
61
+ form.password_confirmation = "1234"
62
+
63
+ assert_equal false, form.valid?
64
+ assert_equal ["don't match"], form.errors[:password]
65
+ end
66
+
67
+ tst "#valid runs its block if the form is valid" do
68
+ form = LoginForm.new.assign(valid_attributes)
69
+ called = 0
70
+ form.valid { called += 1 }
71
+ assert_equal 1, called
72
+ end
73
+
74
+ tst "#valid does NOT run its block if the form is invalid" do
75
+ form = LoginForm.new
76
+ form.valid { raise("it's an error if you get here") }
77
+ end
78
+
79
+ tst "#invalid runs its block if the form is invalid" do
80
+ form = LoginForm.new
81
+ called = 0
82
+ form.invalid { called += 1 }
83
+ assert_equal 1, called
84
+ end
85
+
86
+ tst "#invalid does NOT run its block if the form is valid" do
87
+ form = LoginForm.new.assign(valid_attributes)
88
+ form.invalid { raise("it's an error if you get here") }
89
+ end
90
+
91
+ Object.send(:remove_const, :LoginForm)
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: formality
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Arun Srinivasan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activemodel
16
+ requirement: &70175687157900 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70175687157900
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ requirement: &70175687157400 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 3.0.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70175687157400
36
+ - !ruby/object:Gem::Dependency
37
+ name: actionpack
38
+ requirement: &70175687156940 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 3.0.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70175687156940
47
+ - !ruby/object:Gem::Dependency
48
+ name: tst
49
+ requirement: &70175687156560 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70175687156560
58
+ description: ! 'ActiveModel-compliant form objects for rails app.
59
+
60
+
61
+ Forms have data and behavior. Let them be the
62
+
63
+ objects they want to be. Plus, get presentation-
64
+
65
+ specific validation logic out of your models.
66
+
67
+ '
68
+ email: satchmorun@gmail.com
69
+ executables: []
70
+ extensions: []
71
+ extra_rdoc_files: []
72
+ files:
73
+ - Gemfile
74
+ - LICENSE
75
+ - Rakefile
76
+ - README.md
77
+ - test/attributes.rb
78
+ - test/compliance.rb
79
+ - test/lint.rb
80
+ - test/nest_many.rb
81
+ - test/nest_one.rb
82
+ - test/rails.rb
83
+ - test/valid.rb
84
+ homepage: https://github.com/satchmorun/formality
85
+ licenses:
86
+ - MIT
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 1.8.10
106
+ signing_key:
107
+ specification_version: 3
108
+ summary: Form objects for your rails app.
109
+ test_files:
110
+ - test/attributes.rb
111
+ - test/compliance.rb
112
+ - test/lint.rb
113
+ - test/nest_many.rb
114
+ - test/nest_one.rb
115
+ - test/rails.rb
116
+ - test/valid.rb