aform 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.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +14 -0
- data/aform.gemspec +32 -0
- data/lib/aform/builder.rb +22 -0
- data/lib/aform/errors.rb +26 -0
- data/lib/aform/form.rb +105 -0
- data/lib/aform/model.rb +27 -0
- data/lib/aform/version.rb +3 -0
- data/lib/aform.rb +10 -0
- data/test/errors_test.rb +29 -0
- data/test/form_test.rb +201 -0
- data/test/integration/active_record_test.rb +95 -0
- data/test/integration/integration_helper.rb +31 -0
- data/test/model_test.rb +82 -0
- data/test/test_helper.rb +17 -0
- metadata +222 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6e0b098dd9b0e5a9923997ab226a3112303bc544
|
4
|
+
data.tar.gz: c95259178fad5768b17a20c10c68bc5503513552
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e775499f47737d02c36c1b18def8cbb3bb439a3d0bd671d46a7ca403b4860604e0b94c4740fa586a1bd0deb41c623e0688baaacc3e117163c4c41bb6a50d384f
|
7
|
+
data.tar.gz: d742de23c84c233ee75113892d00dbc7c97218bedc9d44e2af32bcf1404d32d762de74e19538af24a9e4dded4a11df55a3c3c21b646714f6fdfcc9179338333e
|
data/.gitignore
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
.idea
|
24
|
+
database.sqlite3
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Anton Versal
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Aform
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'aform'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install aform
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it ( https://github.com/[my-github-username]/aform/fork )
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.libs << 'test'
|
7
|
+
t.pattern = 'test/*_test.rb'
|
8
|
+
end
|
9
|
+
|
10
|
+
Rake::TestTask.new do |t|
|
11
|
+
t.libs << 'test'
|
12
|
+
t.name = "test:integration"
|
13
|
+
t.pattern = 'test/integration/*_test.rb'
|
14
|
+
end
|
data/aform.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'aform/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "aform"
|
8
|
+
spec.version = Aform::VERSION
|
9
|
+
spec.authors = ["Anton Versal"]
|
10
|
+
spec.email = ["ant.ver@gmail.com"]
|
11
|
+
spec.summary = %q{Filling AR models from complex JSON}
|
12
|
+
spec.description = %q{Form Object implementation for filling models from JSON with nested arrays}
|
13
|
+
spec.homepage = "https://github.com/antonversal/aform"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "activesupport"
|
22
|
+
spec.add_dependency "activemodel"
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "minitest"
|
26
|
+
spec.add_development_dependency "pry"
|
27
|
+
spec.add_development_dependency "pry-byebug"
|
28
|
+
spec.add_development_dependency "minitest-emoji"
|
29
|
+
spec.add_development_dependency "mocha"
|
30
|
+
spec.add_development_dependency "activerecord"
|
31
|
+
spec.add_development_dependency "sqlite3"
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
class Aform::Builder
|
3
|
+
def initialize(model_klass)
|
4
|
+
@model_klass = model_klass
|
5
|
+
end
|
6
|
+
|
7
|
+
def build_model_klass(params, validations)
|
8
|
+
Class.new(@model_klass) do
|
9
|
+
class_attribute :params
|
10
|
+
|
11
|
+
self.params = params
|
12
|
+
|
13
|
+
validations.each do |v|
|
14
|
+
send(v[:method], *v[:options])
|
15
|
+
end if validations
|
16
|
+
|
17
|
+
params.each do |p|
|
18
|
+
self.send(:define_method, p) { @attributes[p] }
|
19
|
+
end if params
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/aform/errors.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Aform
|
2
|
+
class Errors
|
3
|
+
def initialize(form)
|
4
|
+
@form = form
|
5
|
+
end
|
6
|
+
|
7
|
+
def messages
|
8
|
+
@form.model.errors.messages.merge(nested_messages(@form))
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def nested_messages(form)
|
14
|
+
if nf = form.nested_forms
|
15
|
+
nf.inject({}) do |memo, (k,v)|
|
16
|
+
messages = v.map do |e|
|
17
|
+
e.model.errors.messages.merge(nested_messages(e))
|
18
|
+
end
|
19
|
+
memo.merge(k => messages)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
{}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/aform/form.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
module Aform
|
2
|
+
class Form
|
3
|
+
class_attribute :params
|
4
|
+
class_attribute :validations
|
5
|
+
class_attribute :nested_form_klasses
|
6
|
+
|
7
|
+
attr_accessor :model, :attributes, :nested_forms
|
8
|
+
|
9
|
+
def initialize(ar_model, attributes, model_klass = Aform::Model,
|
10
|
+
model_builder = Aform::Builder, errors_klass = Aform::Errors)
|
11
|
+
@model_klass, @model_builder, @errors_klass = model_klass, model_builder, errors_klass
|
12
|
+
@ar_model = ar_model
|
13
|
+
creator = @model_builder.new(@model_klass)
|
14
|
+
self.model = creator.build_model_klass(self.params, self.validations).new(@ar_model, attributes)
|
15
|
+
self.attributes = attributes
|
16
|
+
initialize_nested
|
17
|
+
end
|
18
|
+
|
19
|
+
#TODO don't save all models if at leas one is fail
|
20
|
+
|
21
|
+
def invalid?
|
22
|
+
!valid?
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid?
|
26
|
+
if self.nested_forms
|
27
|
+
main = self.model.valid?
|
28
|
+
nested = self.nested_forms.values.flatten.map(&:valid?).all? #all? don't invoike method on each element
|
29
|
+
main && nested
|
30
|
+
else
|
31
|
+
self.model.valid?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def save
|
36
|
+
if self.valid?
|
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
|
43
|
+
end
|
44
|
+
|
45
|
+
def errors
|
46
|
+
@errors_klass.new(self).messages
|
47
|
+
end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
def param(*args)
|
51
|
+
self.params ||= []
|
52
|
+
self.params += args
|
53
|
+
end
|
54
|
+
|
55
|
+
def method_missing(meth, *args, &block)
|
56
|
+
if meth.to_s.start_with?("validate")
|
57
|
+
options = {method: meth, options: args}
|
58
|
+
options.merge!(block: block) if block_given?
|
59
|
+
self.validations ||= []
|
60
|
+
self.validations << options
|
61
|
+
elsif meth == :has_many
|
62
|
+
define_nested_form(args, &block)
|
63
|
+
else
|
64
|
+
super
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def self.define_nested_form(args, &block)
|
72
|
+
name = args.shift
|
73
|
+
self.nested_form_klasses ||= {}
|
74
|
+
class_attribute name
|
75
|
+
klass = Class.new(Aform::Form, &block)
|
76
|
+
self.send("#{name}=", klass)
|
77
|
+
self.nested_form_klasses.merge! name => klass
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def initialize_nested
|
83
|
+
if nested_form_klasses
|
84
|
+
nested_form_klasses.each do |k,v|
|
85
|
+
if attributes.has_key? k
|
86
|
+
attributes[k].each do |attrs|
|
87
|
+
self.nested_forms ||= {}
|
88
|
+
self.nested_forms[k] ||= []
|
89
|
+
model = nested_ar_model(@ar_model, k, attrs)
|
90
|
+
self.nested_forms[k] << v.new(model, attrs, @model_klass, @model_builder, @errors_klass)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def nested_ar_model(ar_model, association, attrs)
|
98
|
+
if attrs.has_key? :id
|
99
|
+
ar_model.public_send(association).find(attrs[:id])
|
100
|
+
else
|
101
|
+
ar_model.public_send(association).build
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/aform/model.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
class Aform::Model
|
3
|
+
include ActiveModel::Model
|
4
|
+
|
5
|
+
def initialize(object, attributes = {}, destroy_key = :_destroy)
|
6
|
+
@destroy = attributes[destroy_key]
|
7
|
+
@attributes = attributes.select{|k,v| params.include? k }
|
8
|
+
@object = object
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.model_name
|
12
|
+
ActiveModel::Name.new(self, nil, "Aform::Model")
|
13
|
+
end
|
14
|
+
|
15
|
+
def save
|
16
|
+
if @destroy
|
17
|
+
@object.destroy
|
18
|
+
else
|
19
|
+
@object.assign_attributes(@attributes)
|
20
|
+
@object.save
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid?
|
25
|
+
@destroy || super
|
26
|
+
end
|
27
|
+
end
|
data/lib/aform.rb
ADDED
data/test/errors_test.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Aform::Errors do
|
4
|
+
#TODO other way for mocking ...
|
5
|
+
|
6
|
+
before do
|
7
|
+
@mock_form = mock("form")
|
8
|
+
@mock_form.stubs(:model).returns(stub(errors: stub(messages: {name: ["can't be blank"]})))
|
9
|
+
@mock_form.stubs(:nested_forms).returns({comments: [
|
10
|
+
stub(model: stub(errors: stub(messages: {message: ["can't be blank"]})),
|
11
|
+
nested_forms: {
|
12
|
+
authors: [stub(model: stub(errors: stub(messages: {name: ["can't be blank"]})), nested_forms: nil)]
|
13
|
+
},
|
14
|
+
),
|
15
|
+
stub(model: stub(errors: stub(messages: {author: ["can't be blank"]})),
|
16
|
+
nested_forms: nil
|
17
|
+
),
|
18
|
+
]})
|
19
|
+
end
|
20
|
+
|
21
|
+
subject { Aform::Errors.new(@mock_form) }
|
22
|
+
|
23
|
+
it "collects form model errors" do
|
24
|
+
subject.messages.must_equal({name: ["can't be blank"], comments: [
|
25
|
+
{message: ["can't be blank"], authors: [{name:["can't be blank"]}]},
|
26
|
+
{author: ["can't be blank"]}
|
27
|
+
]})
|
28
|
+
end
|
29
|
+
end
|
data/test/form_test.rb
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Aform::Form do
|
4
|
+
|
5
|
+
let(:ar_model) { mock("ar_model") }
|
6
|
+
|
7
|
+
before do
|
8
|
+
@mock_model_instance = mock("model_instance")
|
9
|
+
@mock_model_klass = mock("model_class")
|
10
|
+
@mock_model_klass.stubs(:new).returns(@mock_model_instance)
|
11
|
+
@mock_builder_instance = mock("builder_instance")
|
12
|
+
@mock_builder_instance.stubs(:build_model_klass).returns(@mock_model_klass)
|
13
|
+
@mock_builder_klass = mock("builder_class")
|
14
|
+
@mock_builder_klass.stubs(:new).returns(@mock_builder_instance)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe ".param" do
|
18
|
+
subject do
|
19
|
+
Class.new(Aform::Form) do
|
20
|
+
param :name, :count
|
21
|
+
param :size
|
22
|
+
end.new(ar_model, {}, @mock_model_klass, @mock_builder_klass)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "stores params" do
|
26
|
+
subject.params.must_equal([:name, :count, :size])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "validation remembering" do
|
31
|
+
subject do
|
32
|
+
Class.new(Aform::Form) do
|
33
|
+
param :name, :count
|
34
|
+
validates_presence_of :name
|
35
|
+
validates :count, presence: true, inclusion: [1..100]
|
36
|
+
validate :custom_validation
|
37
|
+
validate do
|
38
|
+
errors.add(:base, "Must be foo to be a bar")
|
39
|
+
end
|
40
|
+
end.new(ar_model, {}, @mock_model_klass, @mock_builder_klass)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "saves validations" do
|
44
|
+
subject.validations.size.must_be_same_as 4
|
45
|
+
end
|
46
|
+
|
47
|
+
it "saves `validates_presence_of` validation" do
|
48
|
+
subject.validations.must_include({method: :validates_presence_of, options: [:name]})
|
49
|
+
end
|
50
|
+
|
51
|
+
it "saves `validates` validation" do
|
52
|
+
subject.validations.must_include({method: :validates,
|
53
|
+
options: [:count, {presence: true, inclusion: [1..100]}]})
|
54
|
+
end
|
55
|
+
|
56
|
+
it "saves `validate` validation" do
|
57
|
+
subject.validations.last[:method].must_be_same_as(:validate)
|
58
|
+
subject.validations.last[:block].wont_be_nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#valid?" do
|
63
|
+
subject do
|
64
|
+
Class.new(Aform::Form) do
|
65
|
+
param :name, :count
|
66
|
+
validates_presence_of :name
|
67
|
+
validates :count, presence: true, inclusion: {in: 1..100}
|
68
|
+
end.new(ar_model, {}, @mock_model_klass, @mock_builder_klass)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "calls valid? on model" do
|
72
|
+
@mock_model_instance.expects(:valid?)
|
73
|
+
subject.valid?
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "#save" do
|
78
|
+
subject do
|
79
|
+
Class.new(Aform::Form) do
|
80
|
+
param :name, :count
|
81
|
+
validates_presence_of :name
|
82
|
+
validates :count, presence: true, inclusion: {in: 1..100}
|
83
|
+
end.new(ar_model, {}, @mock_model_klass, @mock_builder_klass)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "calls model.save" do
|
87
|
+
subject.stubs(:valid?).returns(true)
|
88
|
+
@mock_model_instance.expects(:save)
|
89
|
+
subject.save
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#errors" do
|
94
|
+
#subject do
|
95
|
+
# Class.new(Aform::Form) do
|
96
|
+
# param :name, :count
|
97
|
+
# validates_presence_of :name
|
98
|
+
# validates :count, presence: true, inclusion: {in: 1..100}
|
99
|
+
# end.new(ar_model, {}, @mock_model_klass, @mock_builder_klass)
|
100
|
+
#end
|
101
|
+
#
|
102
|
+
#it "calls model.errors" do
|
103
|
+
# @mock_model_instance.expects(:errors)
|
104
|
+
# subject.errors
|
105
|
+
#end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "nested objects" do
|
109
|
+
describe "has_many" do
|
110
|
+
subject do
|
111
|
+
Class.new(Aform::Form) do
|
112
|
+
param :name, :count
|
113
|
+
has_many :comments do
|
114
|
+
param :author, :message
|
115
|
+
validates_presence_of :message, :author
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it "saves params" do
|
121
|
+
subject.comments.params.must_equal([:author, :message])
|
122
|
+
end
|
123
|
+
|
124
|
+
it "saves validations" do
|
125
|
+
subject.comments.validations.must_equal([{method: :validates_presence_of, options: [:message, :author]}])
|
126
|
+
end
|
127
|
+
|
128
|
+
it "defines `nested_forms`" do
|
129
|
+
subject.nested_form_klasses.must_equal({comments: subject.comments})
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "initialization" do
|
133
|
+
it "initializes nested froms" do
|
134
|
+
model = mock("ar_model")
|
135
|
+
relation = mock("relation")
|
136
|
+
relation.expects(:build).times(2)
|
137
|
+
model.stubs(comments: relation)
|
138
|
+
subject.new(model, {name: "name", count: 1,
|
139
|
+
comments: [{author: "Joe", message: "Message 1"},
|
140
|
+
{author: "Smith", message: "Message 2"}]})
|
141
|
+
end
|
142
|
+
|
143
|
+
context "when `id` is present" do
|
144
|
+
it "finds model for nested form" do
|
145
|
+
model = mock("ar_model")
|
146
|
+
relation = mock("relation")
|
147
|
+
relation.stubs(:build)
|
148
|
+
relation.expects(:find).with(21).times(1)
|
149
|
+
model.stubs(comments: relation)
|
150
|
+
subject.new(model, {name: "name", count: 1,
|
151
|
+
comments: [{author: "Joe", message: "Message 1"},
|
152
|
+
{id: 21, author: "Smith", message: "Message 2"}]})
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "#valid?" do
|
158
|
+
it "calls valid? on nested forms" do
|
159
|
+
Aform::Model.any_instance.expects(:valid?).returns(true).times(3)
|
160
|
+
model = mock("ar_model")
|
161
|
+
model.stubs(comments: stub(build: mock("ar_comment_model")))
|
162
|
+
form = subject.new(model, {name: "name", count: 1,
|
163
|
+
comments: [{author: "Joe", message: "Message 1"},
|
164
|
+
{author: "Smith", message: "Message 2"}]})
|
165
|
+
form.valid?
|
166
|
+
end
|
167
|
+
|
168
|
+
it "calls valid? on nested forms when main form is not valid" do
|
169
|
+
Aform::Model.any_instance.expects(:valid?).returns(false).times(3)
|
170
|
+
model = mock("ar_model")
|
171
|
+
model.stubs(comments: stub(build: mock("ar_comment_model")))
|
172
|
+
form = subject.new(model, {name: "name", count: 1,
|
173
|
+
comments: [{author: "Joe", message: "Message 1"},
|
174
|
+
{author: "Smith", message: "Message 2"}]})
|
175
|
+
form.valid?
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "#save" do
|
180
|
+
before do
|
181
|
+
model = mock("ar_model")
|
182
|
+
model.stubs(comments: stub(build: mock("ar_comment_model")))
|
183
|
+
@form = subject.new(model, {name: "name", count: 1,
|
184
|
+
comments: [{author: "Joe", message: "Message 1"},
|
185
|
+
{author: "Smith", message: "Message 2"}]})
|
186
|
+
end
|
187
|
+
|
188
|
+
it "calls save on nested forms" do
|
189
|
+
Aform::Model.any_instance.expects(:save).returns(true).times(3)
|
190
|
+
@form.save
|
191
|
+
end
|
192
|
+
|
193
|
+
it "calls valid? on nested forms" do
|
194
|
+
Aform::Model.any_instance.expects(:valid?).returns(false).times(3)
|
195
|
+
@form.save
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require_relative './integration_helper'
|
2
|
+
|
3
|
+
class PostForm < Aform::Form
|
4
|
+
param :title, :author
|
5
|
+
validates_presence_of :title, :author
|
6
|
+
|
7
|
+
has_many :comments do
|
8
|
+
param :message, :author
|
9
|
+
validates_presence_of :message, :author
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "ActiveRecord" do
|
14
|
+
#TODO: move to helper in some way
|
15
|
+
after do
|
16
|
+
Comment.delete_all
|
17
|
+
Post.delete_all
|
18
|
+
end
|
19
|
+
|
20
|
+
it "creates records" do
|
21
|
+
post = Post.new
|
22
|
+
attrs = {title: "Cool Post", author: "John Doe",
|
23
|
+
comments: [
|
24
|
+
{message: "Great post man!", author: "Mr. Smith"},
|
25
|
+
{message: "Really?", author: "Vasya"}
|
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
|
39
|
+
|
40
|
+
it "updates records" do
|
41
|
+
post = Post.create(title: "Cool Post", author: "John Doe")
|
42
|
+
comment = post.comments.create(message: "Great post man!", author: "Mr. Smith")
|
43
|
+
|
44
|
+
attrs = {title: "Very Cool Post", author: "John Doe",
|
45
|
+
comments: [
|
46
|
+
{id: comment.id, message: "Great post MAN!", author: "Mr. Smith"},
|
47
|
+
{message: "Really?", author: "Vasya"}
|
48
|
+
]
|
49
|
+
}
|
50
|
+
post.reload
|
51
|
+
form = PostForm.new(post, attrs)
|
52
|
+
form.save.must_equal true
|
53
|
+
Post.count.must_equal 1
|
54
|
+
Comment.count.must_equal 2
|
55
|
+
post = Post.first
|
56
|
+
post.title.must_equal("Very Cool Post")
|
57
|
+
post.author.must_equal("John Doe")
|
58
|
+
comments = post.comments
|
59
|
+
comments.map(&:message).must_equal(["Great post MAN!", "Really?"])
|
60
|
+
comments.map(&:author).must_equal(["Mr. Smith", "Vasya"])
|
61
|
+
end
|
62
|
+
|
63
|
+
it "delete nested records" do
|
64
|
+
post = Post.create(title: "Cool Post", author: "John Doe")
|
65
|
+
comment = post.comments.create(message: "Great post man!", author: "Mr. Smith")
|
66
|
+
post.comments.create(message: "Really?", author: "Vasya")
|
67
|
+
attrs = {title: "Very Cool Post", author: "John Doe",
|
68
|
+
comments: [
|
69
|
+
{id: comment.id, _destroy: true}
|
70
|
+
]
|
71
|
+
}
|
72
|
+
post.reload
|
73
|
+
form = PostForm.new(post, attrs)
|
74
|
+
form.save.must_equal true
|
75
|
+
Comment.count.must_equal 1
|
76
|
+
post = Post.first
|
77
|
+
comment = post.comments.first
|
78
|
+
comment.message.must_equal("Really?")
|
79
|
+
comment.author.must_equal("Vasya")
|
80
|
+
end
|
81
|
+
|
82
|
+
it "return validation errors" do
|
83
|
+
post = Post.new
|
84
|
+
attrs = {title: "Cool Post",
|
85
|
+
comments: [
|
86
|
+
{message: "Great post man!"},
|
87
|
+
{author: "Vasya"}
|
88
|
+
]
|
89
|
+
}
|
90
|
+
form = PostForm.new(post, attrs)
|
91
|
+
form.wont_be :valid?
|
92
|
+
form.errors.must_equal({author: ["can't be blank"], comments: [{author: ["can't be blank"]},
|
93
|
+
{message: ["can't be blank"]}]})
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative "../test_helper"
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
ActiveRecord::Base.establish_connection(
|
6
|
+
:adapter => "sqlite3",
|
7
|
+
:database => "#{Dir.pwd}/database.sqlite3"
|
8
|
+
)
|
9
|
+
|
10
|
+
#TODO: create task
|
11
|
+
#ActiveRecord::Schema.define do
|
12
|
+
# create_table :posts do |t|
|
13
|
+
# t.column :title, :string
|
14
|
+
# t.column :author, :string
|
15
|
+
# t.timestamps
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# create_table :comments do |t|
|
19
|
+
# t.column :message, :string
|
20
|
+
# t.column :author, :string
|
21
|
+
# t.belongs_to :post
|
22
|
+
# end
|
23
|
+
#end
|
24
|
+
|
25
|
+
class Post < ActiveRecord::Base
|
26
|
+
has_many :comments
|
27
|
+
end
|
28
|
+
|
29
|
+
class Comment < ActiveRecord::Base
|
30
|
+
belongs_to :post
|
31
|
+
end
|
data/test/model_test.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Aform::Model do
|
4
|
+
subject { Aform::Builder.new(Aform::Model).build_model_klass(fields, validations) }
|
5
|
+
|
6
|
+
let(:ar_model) { mock("ar_model") }
|
7
|
+
|
8
|
+
context "validations" do
|
9
|
+
context "by type" do
|
10
|
+
let(:fields){ [:name, :full_name] }
|
11
|
+
let(:validations){ [{method: :validates_presence_of, options: [:name]}] }
|
12
|
+
|
13
|
+
it "is not valid" do
|
14
|
+
subject.new(ar_model).wont_be :valid?
|
15
|
+
end
|
16
|
+
|
17
|
+
it "is valid" do
|
18
|
+
subject.new(ar_model, name: "the name").must_be :valid?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "validate" do
|
23
|
+
let(:fields){ [:name, :count] }
|
24
|
+
let(:validations){ [{method: :validates, options: [:count, {presence: true, inclusion: {in: 1..100}}]}] }
|
25
|
+
|
26
|
+
it "is not valid" do
|
27
|
+
subject.new(ar_model, count: -1).wont_be :valid?
|
28
|
+
end
|
29
|
+
|
30
|
+
it "is valid" do
|
31
|
+
subject.new(ar_model, name: "the name", count: 3).must_be :valid?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when block is given" do
|
36
|
+
let(:fields){ [:name, :full_name] }
|
37
|
+
let(:validations) do
|
38
|
+
[{method: :validate, block: ->{errors.add(:base, "must be foo")}}]
|
39
|
+
end
|
40
|
+
|
41
|
+
it "is not valid" do
|
42
|
+
skip("not implemented")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when marked for destruction" do
|
47
|
+
let(:fields){ [:name, :count] }
|
48
|
+
let(:validations){ [{method: :validates_presence_of, options: [:name]}] }
|
49
|
+
|
50
|
+
it "is not valid" do
|
51
|
+
subject.new(ar_model, _destroy: true).must_be :valid?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "#save" do
|
57
|
+
let(:fields){ [:name, :count] }
|
58
|
+
let(:validations){ [] }
|
59
|
+
|
60
|
+
let(:model) { subject.new(ar_model, name: "name", count: 2, other_attr: "other")}
|
61
|
+
|
62
|
+
it "calls `ar_model.assign_attributes`" do
|
63
|
+
ar_model.expects(:assign_attributes).with(name: "name", count: 2).returns(true)
|
64
|
+
ar_model.stubs(:save)
|
65
|
+
model.save
|
66
|
+
end
|
67
|
+
|
68
|
+
it "calls `save.ar_model`" do
|
69
|
+
ar_model.stubs(:assign_attributes).returns(true)
|
70
|
+
ar_model.expects(:save).returns(true)
|
71
|
+
model.save
|
72
|
+
end
|
73
|
+
|
74
|
+
context "when marked for destruction" do
|
75
|
+
let(:model) { subject.new(ar_model, _destroy: true)}
|
76
|
+
it "removes element" do
|
77
|
+
ar_model.expects(:destroy).returns(true)
|
78
|
+
model.save
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'aform'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'minitest/spec'
|
4
|
+
require 'minitest/pride'
|
5
|
+
require 'minitest/emoji'
|
6
|
+
require 'mocha/mini_test'
|
7
|
+
require 'pry'
|
8
|
+
|
9
|
+
I18n.enforce_available_locales = false
|
10
|
+
|
11
|
+
module MiniTest
|
12
|
+
class Spec
|
13
|
+
class << self
|
14
|
+
alias_method :context, :describe
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aform
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Anton Versal
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activemodel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.6'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.6'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry-byebug
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: minitest-emoji
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: mocha
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: activerecord
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: sqlite3
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
description: Form Object implementation for filling models from JSON with nested arrays
|
168
|
+
email:
|
169
|
+
- ant.ver@gmail.com
|
170
|
+
executables: []
|
171
|
+
extensions: []
|
172
|
+
extra_rdoc_files: []
|
173
|
+
files:
|
174
|
+
- ".gitignore"
|
175
|
+
- Gemfile
|
176
|
+
- LICENSE.txt
|
177
|
+
- README.md
|
178
|
+
- Rakefile
|
179
|
+
- aform.gemspec
|
180
|
+
- lib/aform.rb
|
181
|
+
- lib/aform/builder.rb
|
182
|
+
- lib/aform/errors.rb
|
183
|
+
- lib/aform/form.rb
|
184
|
+
- lib/aform/model.rb
|
185
|
+
- lib/aform/version.rb
|
186
|
+
- test/errors_test.rb
|
187
|
+
- test/form_test.rb
|
188
|
+
- test/integration/active_record_test.rb
|
189
|
+
- test/integration/integration_helper.rb
|
190
|
+
- test/model_test.rb
|
191
|
+
- test/test_helper.rb
|
192
|
+
homepage: https://github.com/antonversal/aform
|
193
|
+
licenses:
|
194
|
+
- MIT
|
195
|
+
metadata: {}
|
196
|
+
post_install_message:
|
197
|
+
rdoc_options: []
|
198
|
+
require_paths:
|
199
|
+
- lib
|
200
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
201
|
+
requirements:
|
202
|
+
- - ">="
|
203
|
+
- !ruby/object:Gem::Version
|
204
|
+
version: '0'
|
205
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
206
|
+
requirements:
|
207
|
+
- - ">="
|
208
|
+
- !ruby/object:Gem::Version
|
209
|
+
version: '0'
|
210
|
+
requirements: []
|
211
|
+
rubyforge_project:
|
212
|
+
rubygems_version: 2.2.2
|
213
|
+
signing_key:
|
214
|
+
specification_version: 4
|
215
|
+
summary: Filling AR models from complex JSON
|
216
|
+
test_files:
|
217
|
+
- test/errors_test.rb
|
218
|
+
- test/form_test.rb
|
219
|
+
- test/integration/active_record_test.rb
|
220
|
+
- test/integration/integration_helper.rb
|
221
|
+
- test/model_test.rb
|
222
|
+
- test/test_helper.rb
|