aform 0.0.1 → 0.0.2
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.
- 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
|