formality 0.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.
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