aform 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/aform/builder.rb +2 -1
- data/lib/aform/errors.rb +3 -3
- data/lib/aform/form.rb +28 -27
- data/lib/aform/form_saver.rb +33 -0
- data/lib/aform/model.rb +40 -4
- data/lib/aform/version.rb +1 -1
- data/lib/aform.rb +5 -0
- data/test/errors_test.rb +8 -8
- data/test/form_saver_test.rb +41 -0
- data/test/form_test.rb +61 -51
- data/test/integration/active_record_test.rb +131 -83
- data/test/integration/integration_helper.rb +12 -0
- data/test/model_test.rb +51 -12
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b760e75954d9b87f0d231366049f97cc1fb0efdc
|
4
|
+
data.tar.gz: aa6ff6435aa01dd39298feba9079d8265c4fe029
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fbcf7b499c554d78456c58a5f0a941f9118b00320ffe8bc92df407ca1d74876484c647336579fb334684ca7662333be89a656870935e4a1222e3026e1107bf7a
|
7
|
+
data.tar.gz: 5ac45ca4a8dcfd4c66efc27dea3c336741dd9058c4641ae334cb0ff6d8e3c1fe707769838538d83098c9bc9d9e7cec15a45db2e6f34602db75fd3fb5d8daffcd
|
data/lib/aform/builder.rb
CHANGED
data/lib/aform/errors.rb
CHANGED
@@ -5,7 +5,7 @@ module Aform
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def messages
|
8
|
-
@form.
|
8
|
+
@form.form_model.errors.messages.merge(nested_messages(@form))
|
9
9
|
end
|
10
10
|
|
11
11
|
private
|
@@ -13,8 +13,8 @@ module Aform
|
|
13
13
|
def nested_messages(form)
|
14
14
|
if nf = form.nested_forms
|
15
15
|
nf.inject({}) do |memo, (k,v)|
|
16
|
-
messages = v.
|
17
|
-
e.
|
16
|
+
messages = v.each_with_index.inject({}) do |m, (e, i)|
|
17
|
+
m.merge(i => e.form_model.errors.messages.merge(nested_messages(e)))
|
18
18
|
end
|
19
19
|
memo.merge(k => messages)
|
20
20
|
end
|
data/lib/aform/form.rb
CHANGED
@@ -4,42 +4,36 @@ module Aform
|
|
4
4
|
class_attribute :validations
|
5
5
|
class_attribute :nested_form_klasses
|
6
6
|
|
7
|
-
|
7
|
+
attr_reader :form_model, :attributes, :nested_forms, :model, :nested_models, :parent
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
model_builder = Aform::Builder, errors_klass = Aform::Errors
|
9
|
+
def initialize(model, attributes, parent = nil ,model_klass = Aform::Model,
|
10
|
+
model_builder = Aform::Builder, errors_klass = Aform::Errors,
|
11
|
+
form_saver = Aform::FormSaver, transaction_klass = ActiveRecord::Base)
|
11
12
|
@model_klass, @model_builder, @errors_klass = model_klass, model_builder, errors_klass
|
12
|
-
@
|
13
|
+
@model, @attributes, @transaction_klass = model, attributes, transaction_klass
|
14
|
+
@parent = parent
|
15
|
+
@form_saver = form_saver
|
13
16
|
creator = @model_builder.new(@model_klass)
|
14
|
-
|
15
|
-
self.attributes = attributes
|
17
|
+
@form_model = creator.build_model_klass(self.params, self.validations).new(@model, self, @attributes)
|
16
18
|
initialize_nested
|
17
19
|
end
|
18
20
|
|
19
|
-
#TODO don't save all models if at leas one is fail
|
20
|
-
|
21
21
|
def invalid?
|
22
22
|
!valid?
|
23
23
|
end
|
24
24
|
|
25
25
|
def valid?
|
26
|
-
if
|
27
|
-
main =
|
28
|
-
nested =
|
26
|
+
if @nested_forms
|
27
|
+
main = @form_model.valid?
|
28
|
+
nested = @nested_forms.values.flatten.map(&:valid?).all? #all? don't invoike method on each element
|
29
29
|
main && nested
|
30
30
|
else
|
31
|
-
|
31
|
+
@form_model.valid?
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
35
|
def save
|
36
|
-
|
37
|
-
if self.nested_forms
|
38
|
-
self.model.save && self.nested_forms.values.flatten.all?(&:save)
|
39
|
-
else
|
40
|
-
self.model.save
|
41
|
-
end
|
42
|
-
end
|
36
|
+
self.valid? && @form_saver.new(self, @transaction_klass).save
|
43
37
|
end
|
44
38
|
|
45
39
|
def errors
|
@@ -49,7 +43,12 @@ module Aform
|
|
49
43
|
class << self
|
50
44
|
def param(*args)
|
51
45
|
self.params ||= []
|
52
|
-
|
46
|
+
options = args.extract_options!
|
47
|
+
elements = args.map do |a|
|
48
|
+
field = {field: a}
|
49
|
+
options.present? ? field.merge({options: options}) : field
|
50
|
+
end
|
51
|
+
self.params += elements
|
53
52
|
end
|
54
53
|
|
55
54
|
def method_missing(meth, *args, &block)
|
@@ -84,21 +83,23 @@ module Aform
|
|
84
83
|
nested_form_klasses.each do |k,v|
|
85
84
|
if attributes.has_key? k
|
86
85
|
attributes[k].each do |attrs|
|
87
|
-
|
88
|
-
|
89
|
-
model = nested_ar_model(
|
90
|
-
|
86
|
+
@nested_forms ||= {}
|
87
|
+
@nested_forms[k] ||= []
|
88
|
+
model = nested_ar_model(k, attrs)
|
89
|
+
@nested_forms[k] << v.new(model, attrs, self, @model_klass, @model_builder,
|
90
|
+
@errors_klass, @form_saver, @transaction_klass)
|
91
91
|
end
|
92
92
|
end
|
93
93
|
end
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
-
def nested_ar_model(
|
97
|
+
def nested_ar_model(association, attrs)
|
98
|
+
klass = association.to_s.classify.constantize
|
98
99
|
if attrs.has_key? :id
|
99
|
-
|
100
|
+
klass.find(attrs[:id])
|
100
101
|
else
|
101
|
-
|
102
|
+
klass.new
|
102
103
|
end
|
103
104
|
end
|
104
105
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Aform
|
2
|
+
class FormSaver
|
3
|
+
def initialize(form, transaction_klass = ActiveRecord::Base)
|
4
|
+
@form = form
|
5
|
+
@transaction_klass = transaction_klass
|
6
|
+
end
|
7
|
+
|
8
|
+
def save
|
9
|
+
@transaction_klass.transaction do
|
10
|
+
result =
|
11
|
+
if @form.nested_forms
|
12
|
+
@form.form_model.save && save_nested(@form).all?
|
13
|
+
else
|
14
|
+
@form.form_model.save
|
15
|
+
end
|
16
|
+
raise(ActiveRecord::Rollback) unless result
|
17
|
+
result
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def save_nested(form)
|
24
|
+
form.nested_forms.map do |k, v|
|
25
|
+
v.map do |nf|
|
26
|
+
result = nf.form_model.save(form.model.send(k))
|
27
|
+
save_nested(nf) if nf.nested_forms
|
28
|
+
result
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/aform/model.rb
CHANGED
@@ -2,21 +2,26 @@ require 'active_model'
|
|
2
2
|
class Aform::Model
|
3
3
|
include ActiveModel::Model
|
4
4
|
|
5
|
-
def initialize(object, attributes = {}, destroy_key = :_destroy)
|
6
|
-
@destroy = attributes
|
7
|
-
@attributes = attributes.select{|k,v| params.include? k }
|
5
|
+
def initialize(object, form, attributes = {}, destroy_key = :_destroy)
|
6
|
+
@destroy = attributes.delete(destroy_key)
|
8
7
|
@object = object
|
8
|
+
@form = form
|
9
|
+
sync_with_model
|
10
|
+
@attributes.merge! attributes_for_save(attributes)
|
9
11
|
end
|
10
12
|
|
11
13
|
def self.model_name
|
12
14
|
ActiveModel::Name.new(self, nil, "Aform::Model")
|
13
15
|
end
|
14
16
|
|
15
|
-
|
17
|
+
# AR saves children with parent if it's new object
|
18
|
+
# but dont save children with parent when children is updated
|
19
|
+
def save(association = nil)
|
16
20
|
if @destroy
|
17
21
|
@object.destroy
|
18
22
|
else
|
19
23
|
@object.assign_attributes(@attributes)
|
24
|
+
association << @object if association
|
20
25
|
@object.save
|
21
26
|
end
|
22
27
|
end
|
@@ -24,4 +29,35 @@ class Aform::Model
|
|
24
29
|
def valid?
|
25
30
|
@destroy || super
|
26
31
|
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def sync_with_model
|
36
|
+
attrs = @object.attributes.symbolize_keys
|
37
|
+
@attributes = attributes_for_save(attrs)
|
38
|
+
end
|
39
|
+
|
40
|
+
def attributes_for_save(attributes)
|
41
|
+
attrs = attributes.symbolize_keys
|
42
|
+
params.inject({}) do |memo, p|
|
43
|
+
field_name = get_field_name(p)
|
44
|
+
if @form.respond_to?(p[:field])
|
45
|
+
memo.merge(field_name => @form.public_send(p[:field], attrs))
|
46
|
+
else
|
47
|
+
if attrs[p[:field]]
|
48
|
+
memo.merge(field_name => attrs[p[:field]])
|
49
|
+
else
|
50
|
+
memo
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_field_name(p)
|
57
|
+
if p.has_key?(:options) && p[:options].has_key?(:model_field)
|
58
|
+
p[:options][:model_field]
|
59
|
+
else
|
60
|
+
p[:field]
|
61
|
+
end
|
62
|
+
end
|
27
63
|
end
|
data/lib/aform/version.rb
CHANGED
data/lib/aform.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
1
2
|
require 'active_support/core_ext/class/attribute'
|
3
|
+
require 'active_support/core_ext/array/extract_options'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
|
2
6
|
require 'aform/version'
|
3
7
|
require 'aform/form'
|
4
8
|
require 'aform/model'
|
5
9
|
require 'aform/builder'
|
6
10
|
require 'aform/errors'
|
11
|
+
require 'aform/form_saver'
|
7
12
|
|
8
13
|
module Aform
|
9
14
|
# Your code goes here...
|
data/test/errors_test.rb
CHANGED
@@ -5,14 +5,14 @@ describe Aform::Errors do
|
|
5
5
|
|
6
6
|
before do
|
7
7
|
@mock_form = mock("form")
|
8
|
-
@mock_form.stubs(:
|
8
|
+
@mock_form.stubs(:form_model).returns(stub(errors: stub(messages: {name: ["can't be blank"]})))
|
9
9
|
@mock_form.stubs(:nested_forms).returns({comments: [
|
10
|
-
stub(
|
10
|
+
stub(form_model: stub(errors: stub(messages: {message: ["can't be blank"]})),
|
11
11
|
nested_forms: {
|
12
|
-
authors: [stub(
|
12
|
+
authors: [stub(form_model: stub(errors: stub(messages: {name: ["can't be blank"]})), nested_forms: nil)]
|
13
13
|
},
|
14
14
|
),
|
15
|
-
stub(
|
15
|
+
stub(form_model: stub(errors: stub(messages: {author: ["can't be blank"]})),
|
16
16
|
nested_forms: nil
|
17
17
|
),
|
18
18
|
]})
|
@@ -21,9 +21,9 @@ describe Aform::Errors do
|
|
21
21
|
subject { Aform::Errors.new(@mock_form) }
|
22
22
|
|
23
23
|
it "collects form model errors" do
|
24
|
-
subject.messages.must_equal({name: ["can't be blank"], comments:
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
subject.messages.must_equal({name: ["can't be blank"], comments: {
|
25
|
+
0 => {message: ["can't be blank"], authors: {0 => {name:["can't be blank"]}}},
|
26
|
+
1 => {author: ["can't be blank"]}
|
27
|
+
}})
|
28
28
|
end
|
29
29
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Aform::FormSaver do
|
4
|
+
class TransactionKlass
|
5
|
+
def self.transaction
|
6
|
+
yield
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class TestForm
|
11
|
+
def form_model
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
def nested_forms
|
16
|
+
{comments: }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:form) do
|
21
|
+
OpenStruct
|
22
|
+
form = {
|
23
|
+
form_model: :form_model,
|
24
|
+
nested_forms: [
|
25
|
+
{comments: [
|
26
|
+
nested_form1: {
|
27
|
+
form_model: :form_model,
|
28
|
+
nested_forms: [:nested_form2]
|
29
|
+
}
|
30
|
+
]
|
31
|
+
}
|
32
|
+
]
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
subject { Aform::FormSaver.new(form, TransactionKlass) }
|
37
|
+
|
38
|
+
it "saves form" do
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
data/test/form_test.rb
CHANGED
@@ -12,18 +12,20 @@ describe Aform::Form do
|
|
12
12
|
@mock_builder_instance.stubs(:build_model_klass).returns(@mock_model_klass)
|
13
13
|
@mock_builder_klass = mock("builder_class")
|
14
14
|
@mock_builder_klass.stubs(:new).returns(@mock_builder_instance)
|
15
|
+
@mock_transaction = mock("ar_model")
|
16
|
+
@mock_errors = mock("mock_errors")
|
15
17
|
end
|
16
18
|
|
17
19
|
describe ".param" do
|
18
20
|
subject do
|
19
21
|
Class.new(Aform::Form) do
|
20
22
|
param :name, :count
|
21
|
-
param :size
|
22
|
-
end.new(ar_model, {}, @mock_model_klass, @mock_builder_klass)
|
23
|
+
param :size, model_field: :count
|
24
|
+
end.new(ar_model, {}, @mock_model_klass, @mock_builder_klass, @mock_errors, @mock_transaction)
|
23
25
|
end
|
24
26
|
|
25
27
|
it "stores params" do
|
26
|
-
subject.params.must_equal([:name, :count, :size])
|
28
|
+
subject.params.must_equal([{field: :name}, {field: :count}, {field: :size, options: {model_field: :count}}])
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
@@ -37,7 +39,7 @@ describe Aform::Form do
|
|
37
39
|
validate do
|
38
40
|
errors.add(:base, "Must be foo to be a bar")
|
39
41
|
end
|
40
|
-
end.new(ar_model, {}, @mock_model_klass, @mock_builder_klass)
|
42
|
+
end.new(ar_model, {}, @mock_model_klass, @mock_builder_klass, @mock_errors, @mock_transaction)
|
41
43
|
end
|
42
44
|
|
43
45
|
it "saves validations" do
|
@@ -65,7 +67,7 @@ describe Aform::Form do
|
|
65
67
|
param :name, :count
|
66
68
|
validates_presence_of :name
|
67
69
|
validates :count, presence: true, inclusion: {in: 1..100}
|
68
|
-
end.new(ar_model, {}, @mock_model_klass, @mock_builder_klass)
|
70
|
+
end.new(ar_model, {}, @mock_model_klass, @mock_builder_klass, @mock_errors, @mock_transaction)
|
69
71
|
end
|
70
72
|
|
71
73
|
it "calls valid? on model" do
|
@@ -80,7 +82,7 @@ describe Aform::Form do
|
|
80
82
|
param :name, :count
|
81
83
|
validates_presence_of :name
|
82
84
|
validates :count, presence: true, inclusion: {in: 1..100}
|
83
|
-
end.new(ar_model, {}, @mock_model_klass, @mock_builder_klass)
|
85
|
+
end.new(ar_model, {}, @mock_model_klass, @mock_builder_klass, @mock_errors, @mock_transaction)
|
84
86
|
end
|
85
87
|
|
86
88
|
it "calls model.save" do
|
@@ -118,7 +120,7 @@ describe Aform::Form do
|
|
118
120
|
end
|
119
121
|
|
120
122
|
it "saves params" do
|
121
|
-
subject.comments.params.must_equal([:author, :message])
|
123
|
+
subject.comments.params.must_equal([{field: :author}, {field: :message}])
|
122
124
|
end
|
123
125
|
|
124
126
|
it "saves validations" do
|
@@ -137,7 +139,8 @@ describe Aform::Form do
|
|
137
139
|
model.stubs(comments: relation)
|
138
140
|
subject.new(model, {name: "name", count: 1,
|
139
141
|
comments: [{author: "Joe", message: "Message 1"},
|
140
|
-
{author: "Smith", message: "Message 2"}]}
|
142
|
+
{author: "Smith", message: "Message 2"}]},
|
143
|
+
@mock_model_klass, @mock_builder_klass, @mock_errors, @mock_transaction)
|
141
144
|
end
|
142
145
|
|
143
146
|
context "when `id` is present" do
|
@@ -145,56 +148,63 @@ describe Aform::Form do
|
|
145
148
|
model = mock("ar_model")
|
146
149
|
relation = mock("relation")
|
147
150
|
relation.stubs(:build)
|
148
|
-
|
151
|
+
#TODO: rewirte tests
|
152
|
+
relation.expects(:select).returns([1])
|
149
153
|
model.stubs(comments: relation)
|
150
154
|
subject.new(model, {name: "name", count: 1,
|
151
155
|
comments: [{author: "Joe", message: "Message 1"},
|
152
|
-
{id: 21, author: "Smith", message: "Message 2"}]}
|
156
|
+
{id: 21, author: "Smith", message: "Message 2"}]},
|
157
|
+
@mock_model_klass, @mock_builder_klass, @mock_errors, @mock_transaction)
|
153
158
|
end
|
154
159
|
end
|
155
160
|
end
|
156
161
|
|
157
|
-
describe "#valid?" do
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
162
|
+
#describe "#valid?" do
|
163
|
+
# it "calls valid? on nested forms" do
|
164
|
+
# Aform::Model.any_instance.expects(:valid?).returns(true).times(3)
|
165
|
+
# model = mock("ar_model")
|
166
|
+
# @mock_model_instance.stubs(:valid?).returns(true)
|
167
|
+
# model.stubs(comments: stub(build: mock("ar_comment_model")))
|
168
|
+
# form = subject.new(model, {name: "name", count: 1,
|
169
|
+
# comments: [{author: "Joe", message: "Message 1"},
|
170
|
+
# {author: "Smith", message: "Message 2"}]},
|
171
|
+
# @mock_model_klass, @mock_builder_klass, @mock_errors, @mock_transaction)
|
172
|
+
# form.valid?
|
173
|
+
# end
|
174
|
+
#
|
175
|
+
# #it "calls valid? on nested forms when main form is not valid" do
|
176
|
+
# # Aform::Model.any_instance.expects(:valid?).returns(false).times(3)
|
177
|
+
# # model = mock("ar_model")
|
178
|
+
# # model.stubs(comments: stub(build: mock("ar_comment_model")))
|
179
|
+
# # form = subject.new(model, {name: "name", count: 1,
|
180
|
+
# # comments: [{author: "Joe", message: "Message 1"},
|
181
|
+
# # {author: "Smith", message: "Message 2"}]},
|
182
|
+
# # @mock_model_klass, @mock_builder_klass, @mock_errors, @mock_transaction)
|
183
|
+
# # form.valid?
|
184
|
+
# #end
|
185
|
+
#end
|
186
|
+
|
187
|
+
#describe "#save" do
|
188
|
+
# before do
|
189
|
+
# model = mock("ar_model")
|
190
|
+
# model.stubs(comments: stub(build: mock("ar_comment_model")))
|
191
|
+
# @mock_model_instance.stubs(:valid?).returns(true)
|
192
|
+
# @form = subject.new(model, {name: "name", count: 1,
|
193
|
+
# comments: [{author: "Joe", message: "Message 1"},
|
194
|
+
# {author: "Smith", message: "Message 2"}]},
|
195
|
+
# @mock_model_klass, @mock_builder_klass, @mock_errors, @mock_transaction)
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
# it "calls save on nested forms" do
|
199
|
+
# Aform::Model.any_instance.expects(:save).returns(true).times(3)
|
200
|
+
# @form.save
|
201
|
+
# end
|
202
|
+
#
|
203
|
+
# it "calls valid? on nested forms" do
|
204
|
+
# Aform::Model.any_instance.expects(:valid?).returns(false).times(3)
|
205
|
+
# @form.save
|
206
|
+
# end
|
207
|
+
#end
|
198
208
|
end
|
199
209
|
end
|
200
210
|
end
|
@@ -1,95 +1,143 @@
|
|
1
1
|
require_relative './integration_helper'
|
2
2
|
|
3
|
-
|
4
|
-
param :title, :author
|
5
|
-
validates_presence_of :title, :author
|
3
|
+
describe "saving" do
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
5
|
+
context "basic functionality" do
|
6
|
+
class PostForm < Aform::Form
|
7
|
+
param :title, :author
|
8
|
+
validates_presence_of :title, :author
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
Comment.delete_all
|
17
|
-
Post.delete_all
|
18
|
-
end
|
10
|
+
has_many :comments do
|
11
|
+
param :message, :author
|
12
|
+
validates_presence_of :message, :author
|
19
13
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
]
|
27
|
-
}
|
28
|
-
form = PostForm.new(post, attrs)
|
29
|
-
form.save.must_equal true
|
30
|
-
Post.count.must_equal 1
|
31
|
-
Comment.count.must_equal 2
|
32
|
-
post = Post.first
|
33
|
-
post.title.must_equal("Cool Post")
|
34
|
-
post.author.must_equal("John Doe")
|
35
|
-
comments = post.comments
|
36
|
-
comments.map(&:message).must_equal(["Great post man!", "Really?"])
|
37
|
-
comments.map(&:author).must_equal(["Mr. Smith", "Vasya"])
|
38
|
-
end
|
14
|
+
has_many :likes do
|
15
|
+
param :author
|
16
|
+
validates_presence_of :author
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
39
20
|
|
40
|
-
|
41
|
-
|
42
|
-
|
21
|
+
#TODO: move to helper in some way
|
22
|
+
after do
|
23
|
+
Comment.delete_all
|
24
|
+
Post.delete_all
|
25
|
+
Like.delete_all
|
26
|
+
end
|
43
27
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
28
|
+
it "creates records" do
|
29
|
+
post = Post.new
|
30
|
+
attrs = {title: "Cool Post", author: "John Doe",
|
31
|
+
comments: [
|
32
|
+
{message: "Great post man!", author: "Mr. Smith"},
|
33
|
+
{message: "Really?", author: "Vasya"}
|
34
|
+
]
|
35
|
+
}
|
36
|
+
form = PostForm.new(post, attrs)
|
37
|
+
form.save.must_equal true
|
38
|
+
Post.count.must_equal 1
|
39
|
+
Comment.count.must_equal 2
|
40
|
+
post = Post.first
|
41
|
+
post.title.must_equal("Cool Post")
|
42
|
+
post.author.must_equal("John Doe")
|
43
|
+
comments = post.comments
|
44
|
+
comments.map(&:message).must_equal(["Great post man!", "Really?"])
|
45
|
+
comments.map(&:author).must_equal(["Mr. Smith", "Vasya"])
|
46
|
+
end
|
47
|
+
|
48
|
+
it "updates records" do
|
49
|
+
post = Post.create(title: "Cool Post", author: "John Doe")
|
50
|
+
comment = post.comments.create(message: "Great post man!", author: "Mr. Smith")
|
51
|
+
|
52
|
+
attrs = {title: "Very Cool Post",
|
53
|
+
comments: [
|
54
|
+
{id: comment.id, message: "Great post MAN!", author: "Mr. Smith"},
|
55
|
+
{message: "Really?", author: "Vasya"}
|
56
|
+
]
|
57
|
+
}
|
58
|
+
post.reload
|
59
|
+
form = PostForm.new(post, attrs)
|
60
|
+
form.save.must_equal true
|
61
|
+
Post.count.must_equal 1
|
62
|
+
Comment.count.must_equal 2
|
63
|
+
post = Post.first
|
64
|
+
post.title.must_equal("Very Cool Post")
|
65
|
+
post.author.must_equal("John Doe")
|
66
|
+
comments = post.comments
|
67
|
+
comments.map(&:message).must_equal(["Great post MAN!", "Really?"])
|
68
|
+
comments.map(&:author).must_equal(["Mr. Smith", "Vasya"])
|
69
|
+
end
|
62
70
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
71
|
+
it "delete nested records" do
|
72
|
+
post = Post.create(title: "Cool Post", author: "John Doe")
|
73
|
+
comment = post.comments.create(message: "Great post man!", author: "Mr. Smith")
|
74
|
+
post.comments.create(message: "Really?", author: "Vasya")
|
75
|
+
attrs = {title: "Very Cool Post", author: "John Doe",
|
76
|
+
comments: [
|
77
|
+
{id: comment.id, _destroy: true}
|
78
|
+
]
|
79
|
+
}
|
80
|
+
post.reload
|
81
|
+
form = PostForm.new(post, attrs)
|
82
|
+
form.save.must_equal true
|
83
|
+
Comment.count.must_equal 1
|
84
|
+
post = Post.first
|
85
|
+
comment = post.comments.first
|
86
|
+
comment.message.must_equal("Really?")
|
87
|
+
comment.author.must_equal("Vasya")
|
88
|
+
end
|
89
|
+
|
90
|
+
it "return validation errors" do
|
91
|
+
post = Post.new
|
92
|
+
attrs = {title: "Cool Post",
|
93
|
+
comments: [
|
94
|
+
{message: "Great post man!"},
|
95
|
+
{author: "Vasya"}
|
96
|
+
]
|
97
|
+
}
|
98
|
+
form = PostForm.new(post, attrs)
|
99
|
+
form.wont_be :valid?
|
100
|
+
form.errors.must_equal({author: ["can't be blank"], comments: {0 => {author: ["can't be blank"]},
|
101
|
+
1 => {message: ["can't be blank"]}}})
|
102
|
+
end
|
103
|
+
|
104
|
+
it "creates 3rd nested records" do
|
105
|
+
post = Post.new
|
106
|
+
attrs = {title: "Cool Post", author: "John Doe",
|
107
|
+
comments: [
|
108
|
+
{message: "Great post man!",
|
109
|
+
author: "Mr. Smith",
|
110
|
+
likes: [{author: "Vasya"}]},
|
111
|
+
]
|
112
|
+
}
|
113
|
+
form = PostForm.new(post, attrs)
|
114
|
+
form.save.must_equal true
|
115
|
+
Post.count.must_equal 1
|
116
|
+
Comment.count.must_equal 1
|
117
|
+
Like.count.must_equal 1
|
118
|
+
end
|
80
119
|
end
|
81
120
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
121
|
+
context "attributes from method" do
|
122
|
+
class OtherPostForm < Aform::Form
|
123
|
+
param :title, :author
|
124
|
+
|
125
|
+
def author(attributes)
|
126
|
+
"#{attributes[:first_author]} and #{attributes[:second_author]}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
after do
|
131
|
+
Post.delete_all
|
132
|
+
end
|
133
|
+
|
134
|
+
it "inherits attribute from parent" do
|
135
|
+
post = Post.new
|
136
|
+
attrs = {title: "Cool Post", first_author: "John Doe", second_author: "Mr. Author"}
|
137
|
+
form = OtherPostForm.new(post, attrs)
|
138
|
+
form.save.must_equal true
|
139
|
+
post = Post.first
|
140
|
+
post.author.must_equal("John Doe and Mr. Author")
|
141
|
+
end
|
94
142
|
end
|
95
143
|
end
|
@@ -20,6 +20,11 @@ ActiveRecord::Base.establish_connection(
|
|
20
20
|
# t.column :author, :string
|
21
21
|
# t.belongs_to :post
|
22
22
|
# end
|
23
|
+
#
|
24
|
+
# create_table :likes do |t|
|
25
|
+
# t.column :author, :string
|
26
|
+
# t.belongs_to :comment
|
27
|
+
# end
|
23
28
|
#end
|
24
29
|
|
25
30
|
class Post < ActiveRecord::Base
|
@@ -28,4 +33,11 @@ end
|
|
28
33
|
|
29
34
|
class Comment < ActiveRecord::Base
|
30
35
|
belongs_to :post
|
36
|
+
has_many :likes
|
37
|
+
validates_presence_of :message
|
38
|
+
end
|
39
|
+
|
40
|
+
class Like < ActiveRecord::Base
|
41
|
+
belongs_to :comment
|
42
|
+
validates_presence_of :author
|
31
43
|
end
|
data/test/model_test.rb
CHANGED
@@ -7,7 +7,7 @@ describe Aform::Model do
|
|
7
7
|
|
8
8
|
context "validations" do
|
9
9
|
context "by type" do
|
10
|
-
let(:fields){ [:name, :full_name] }
|
10
|
+
let(:fields){ [{field: :name}, {field: :full_name}] }
|
11
11
|
let(:validations){ [{method: :validates_presence_of, options: [:name]}] }
|
12
12
|
|
13
13
|
it "is not valid" do
|
@@ -20,7 +20,7 @@ describe Aform::Model do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
context "validate" do
|
23
|
-
let(:fields){ [:name, :count] }
|
23
|
+
let(:fields){ [{field: :name}, {field: :count}] }
|
24
24
|
let(:validations){ [{method: :validates, options: [:count, {presence: true, inclusion: {in: 1..100}}]}] }
|
25
25
|
|
26
26
|
it "is not valid" do
|
@@ -33,7 +33,7 @@ describe Aform::Model do
|
|
33
33
|
end
|
34
34
|
|
35
35
|
context "when block is given" do
|
36
|
-
let(:fields){ [:name, :full_name] }
|
36
|
+
let(:fields){ [{field: :name}, {field: :full_name}] }
|
37
37
|
let(:validations) do
|
38
38
|
[{method: :validate, block: ->{errors.add(:base, "must be foo")}}]
|
39
39
|
end
|
@@ -44,7 +44,7 @@ describe Aform::Model do
|
|
44
44
|
end
|
45
45
|
|
46
46
|
context "when marked for destruction" do
|
47
|
-
let(:fields){ [:name, :count] }
|
47
|
+
let(:fields){ [{field: :name}, {field: :count}] }
|
48
48
|
let(:validations){ [{method: :validates_presence_of, options: [:name]}] }
|
49
49
|
|
50
50
|
it "is not valid" do
|
@@ -53,29 +53,68 @@ describe Aform::Model do
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
-
|
57
|
-
let(:fields){ [:name, :count] }
|
56
|
+
describe "#save" do
|
57
|
+
let(:fields){ [{field: :name}, {field: :count}] }
|
58
58
|
let(:validations){ [] }
|
59
59
|
|
60
|
-
let(:
|
60
|
+
let(:form_model) { subject.new(ar_model, name: "name", count: 2, other_attr: "other")}
|
61
61
|
|
62
62
|
it "calls `ar_model.assign_attributes`" do
|
63
63
|
ar_model.expects(:assign_attributes).with(name: "name", count: 2).returns(true)
|
64
64
|
ar_model.stubs(:save)
|
65
|
-
|
65
|
+
form_model.save
|
66
66
|
end
|
67
67
|
|
68
|
-
it "calls `save
|
68
|
+
it "calls `ar_model.save`" do
|
69
69
|
ar_model.stubs(:assign_attributes).returns(true)
|
70
70
|
ar_model.expects(:save).returns(true)
|
71
|
-
|
71
|
+
form_model.save
|
72
|
+
end
|
73
|
+
|
74
|
+
context "when keys are strings" do
|
75
|
+
let(:form_model) { subject.new(ar_model, "name" => "name", "count" => 2, "other_attr" => "other")}
|
76
|
+
|
77
|
+
it "calls `ar_model.assign_attributes`" do
|
78
|
+
ar_model.expects(:assign_attributes).with(name: "name", count: 2).returns(true)
|
79
|
+
ar_model.stubs(:save)
|
80
|
+
form_model.save
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "when fields with model_field option" do
|
85
|
+
let(:fields){ [{field: :name}, {field: :count, options: {model_field: :size}}] }
|
86
|
+
|
87
|
+
it "convert attributes" do
|
88
|
+
ar_model.expects(:assign_attributes).with(name: "name", size: 2).returns(true)
|
89
|
+
ar_model.stubs(:save)
|
90
|
+
form_model.save
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#nested_save" do
|
96
|
+
let(:fields){ [{field: :name}, {field: :count}] }
|
97
|
+
let(:validations){ [] }
|
98
|
+
let(:form_model) { subject.new(ar_model, name: "name", count: 2, other_attr: "other")}
|
99
|
+
|
100
|
+
it "calls `ar_model.assign_attributes`" do
|
101
|
+
ar_model.expects(:assign_attributes).with(name: "name", count: 2).returns(true)
|
102
|
+
ar_model.stubs(:persisted?).returns(false)
|
103
|
+
form_model.nested_save
|
104
|
+
end
|
105
|
+
|
106
|
+
it "calls `ar_model.save` if persisted? is true" do
|
107
|
+
ar_model.stubs(:assign_attributes).returns(true)
|
108
|
+
ar_model.stubs(:persisted?).returns(true)
|
109
|
+
ar_model.expects(:save).returns(true)
|
110
|
+
form_model.nested_save
|
72
111
|
end
|
73
112
|
|
74
113
|
context "when marked for destruction" do
|
75
|
-
let(:
|
114
|
+
let(:form_model) { subject.new(ar_model, _destroy: true)}
|
76
115
|
it "removes element" do
|
77
116
|
ar_model.expects(:destroy).returns(true)
|
78
|
-
|
117
|
+
form_model.nested_save
|
79
118
|
end
|
80
119
|
end
|
81
120
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aform
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anton Versal
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-07-
|
11
|
+
date: 2014-07-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -181,9 +181,11 @@ files:
|
|
181
181
|
- lib/aform/builder.rb
|
182
182
|
- lib/aform/errors.rb
|
183
183
|
- lib/aform/form.rb
|
184
|
+
- lib/aform/form_saver.rb
|
184
185
|
- lib/aform/model.rb
|
185
186
|
- lib/aform/version.rb
|
186
187
|
- test/errors_test.rb
|
188
|
+
- test/form_saver_test.rb
|
187
189
|
- test/form_test.rb
|
188
190
|
- test/integration/active_record_test.rb
|
189
191
|
- test/integration/integration_helper.rb
|
@@ -215,6 +217,7 @@ specification_version: 4
|
|
215
217
|
summary: Filling AR models from complex JSON
|
216
218
|
test_files:
|
217
219
|
- test/errors_test.rb
|
220
|
+
- test/form_saver_test.rb
|
218
221
|
- test/form_test.rb
|
219
222
|
- test/integration/active_record_test.rb
|
220
223
|
- test/integration/integration_helper.rb
|