rails-patterns 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +4 -3
- data/Gemfile.lock +38 -0
- data/README.md +101 -2
- data/VERSION +1 -1
- data/lib/patterns/form.rb +100 -0
- data/lib/rails-patterns.rb +1 -0
- data/rails-patterns.gemspec +11 -3
- data/spec/patterns/form_spec.rb +432 -0
- metadata +32 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af39ea7538e043642610c2ea2ff35a9fa5c8a0e7
|
4
|
+
data.tar.gz: ff3231a21b94a92280acf664497a40b3039f7140
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c3653e8ce337a6225ad14162600f8b2a462321fcca8595f8f1463920935749e17b48d3f7359530dff528bc7ff3f7175ddaa23b7ac1203903ff42b296bfb8c51d
|
7
|
+
data.tar.gz: 37f4e6cb0fc581140dfee9372386b9d37b77a092d63d798fb98d8617475ac249d2e8f49aa9d8092ae5630e3de5e48a0aa6677ed7d62e89429a788c9fb51e44f2
|
data/Gemfile
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
source "https://rubygems.org"
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
|
3
|
+
gem "activerecord", ">= 4.2.6"
|
4
|
+
gem "actionpack", ">= 4.2.6"
|
5
|
+
gem "virtus"
|
5
6
|
|
6
7
|
# Add dependencies to develop your gem here.
|
7
8
|
# Include everything needed to run rake, tests, features, etc.
|
data/Gemfile.lock
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
+
actionpack (5.0.2)
|
5
|
+
actionview (= 5.0.2)
|
6
|
+
activesupport (= 5.0.2)
|
7
|
+
rack (~> 2.0)
|
8
|
+
rack-test (~> 0.6.3)
|
9
|
+
rails-dom-testing (~> 2.0)
|
10
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
11
|
+
actionview (5.0.2)
|
12
|
+
activesupport (= 5.0.2)
|
13
|
+
builder (~> 3.1)
|
14
|
+
erubis (~> 2.7.0)
|
15
|
+
rails-dom-testing (~> 2.0)
|
16
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
4
17
|
activemodel (5.0.2)
|
5
18
|
activesupport (= 5.0.2)
|
6
19
|
activerecord (5.0.2)
|
@@ -14,12 +27,20 @@ GEM
|
|
14
27
|
tzinfo (~> 1.1)
|
15
28
|
addressable (2.4.0)
|
16
29
|
arel (7.1.4)
|
30
|
+
axiom-types (0.1.1)
|
31
|
+
descendants_tracker (~> 0.0.4)
|
32
|
+
ice_nine (~> 0.11.0)
|
33
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
17
34
|
builder (3.2.3)
|
18
35
|
coderay (1.1.1)
|
36
|
+
coercible (1.0.0)
|
37
|
+
descendants_tracker (~> 0.0.1)
|
19
38
|
concurrent-ruby (1.0.5)
|
20
39
|
descendants_tracker (0.0.4)
|
21
40
|
thread_safe (~> 0.3, >= 0.3.1)
|
22
41
|
diff-lcs (1.3)
|
42
|
+
equalizer (0.0.11)
|
43
|
+
erubis (2.7.0)
|
23
44
|
faraday (0.9.2)
|
24
45
|
multipart-post (>= 1.2, < 3)
|
25
46
|
git (1.3.0)
|
@@ -33,6 +54,7 @@ GEM
|
|
33
54
|
hashie (3.5.5)
|
34
55
|
highline (1.7.8)
|
35
56
|
i18n (0.8.1)
|
57
|
+
ice_nine (0.11.2)
|
36
58
|
juwelier (2.1.3)
|
37
59
|
builder
|
38
60
|
bundler (>= 1.13)
|
@@ -44,6 +66,8 @@ GEM
|
|
44
66
|
rdoc
|
45
67
|
semver
|
46
68
|
jwt (1.5.6)
|
69
|
+
loofah (2.0.3)
|
70
|
+
nokogiri (>= 1.5.9)
|
47
71
|
method_source (0.8.2)
|
48
72
|
mime-types (2.99.3)
|
49
73
|
mini_portile2 (2.1.0)
|
@@ -66,6 +90,13 @@ GEM
|
|
66
90
|
pry-rails (0.3.6)
|
67
91
|
pry (>= 0.10.4)
|
68
92
|
rack (2.0.1)
|
93
|
+
rack-test (0.6.3)
|
94
|
+
rack (>= 1.0)
|
95
|
+
rails-dom-testing (2.0.2)
|
96
|
+
activesupport (>= 4.2.0, < 6.0)
|
97
|
+
nokogiri (~> 1.6)
|
98
|
+
rails-html-sanitizer (1.0.3)
|
99
|
+
loofah (~> 2.0)
|
69
100
|
rake (12.0.0)
|
70
101
|
rdoc (5.1.0)
|
71
102
|
rspec (3.5.0)
|
@@ -86,16 +117,23 @@ GEM
|
|
86
117
|
thread_safe (0.3.6)
|
87
118
|
tzinfo (1.2.3)
|
88
119
|
thread_safe (~> 0.1)
|
120
|
+
virtus (1.0.5)
|
121
|
+
axiom-types (~> 0.1)
|
122
|
+
coercible (~> 1.0)
|
123
|
+
descendants_tracker (~> 0.0, >= 0.0.3)
|
124
|
+
equalizer (~> 0.0, >= 0.0.9)
|
89
125
|
|
90
126
|
PLATFORMS
|
91
127
|
ruby
|
92
128
|
|
93
129
|
DEPENDENCIES
|
130
|
+
actionpack (>= 4.2.6)
|
94
131
|
activerecord (>= 4.2.6)
|
95
132
|
bundler (~> 1.0)
|
96
133
|
juwelier (~> 2.1.0)
|
97
134
|
pry-rails
|
98
135
|
rspec
|
136
|
+
virtus
|
99
137
|
|
100
138
|
BUNDLED WITH
|
101
139
|
1.14.6
|
data/README.md
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
A collection of lightweight, standardized, rails-oriented patterns.
|
4
4
|
|
5
|
+
- [Query - complex querying on active record relation](#query)
|
6
|
+
- [Service - useful for handling processes involving multiple steps](#service)
|
7
|
+
- [Collection - when in need to add a method that relates to the collection a whole](#collection)
|
8
|
+
- [Form - when you need a place for callbacks, want to replace strong parameters or handle virtual/composite resources](#form)
|
9
|
+
|
5
10
|
## Installation
|
6
11
|
|
7
12
|
```ruby
|
@@ -166,8 +171,102 @@ end
|
|
166
171
|
|
167
172
|
```ruby
|
168
173
|
ColorsCollection.new
|
169
|
-
|
170
|
-
|
174
|
+
CustomerEventsByTypeCollection.for(customer)
|
175
|
+
CustomerEventsByTypeCollection.for(customer, label_method: "name")
|
176
|
+
```
|
177
|
+
|
178
|
+
## Form
|
179
|
+
|
180
|
+
### When to use it
|
181
|
+
|
182
|
+
Form objects, just like service objects, are commonly used to mitigate problems with model callbacks that interact with external classes ([read more...](http://samuelmullen.com/2013/05/the-problem-with-rails-callbacks/)).
|
183
|
+
Form objects can also be used as replacement for `ActionController::StrongParameters` strategy, as all writable attributes are re-defined within each form.
|
184
|
+
Finally form objects can be used as wrappers for virtual (with no model representation) or composite (saving multiple models at once) resources.
|
185
|
+
In the latter case this may act as replacement for `ActiveRecord::NestedAttributes`.
|
186
|
+
|
187
|
+
### Assumptions and rules
|
188
|
+
|
189
|
+
* Forms include `ActiveModel::Validations` to support validation.
|
190
|
+
* Forms include `Virtus.model` to support `attribute` static method with all [corresponding capabilities](https://github.com/solnic/virtus).
|
191
|
+
* Forms can be initialized using `.new`.
|
192
|
+
* Forms accept optional resource object as first constructor argument.
|
193
|
+
* Forms accept optional attributes hash as latter constructor argument.
|
194
|
+
* forms have to implement `#persist` method that returns falsey (if failed) or truthy (if succeeded) value.
|
195
|
+
* Forms provide access to first constructor argument using `#resource`.
|
196
|
+
* Forms are saved using their `#save` or `#save!` methods.
|
197
|
+
* Forms will attempt to pre-populate their fields using `resource#attributes` and public getters for `resource`
|
198
|
+
* Form's fields are populated with passed-in attributes hash reverse-merged with pre-populated attributes if possible.
|
199
|
+
* Forms provide `#as` builder method that populates internal `@form_owner` variable (can be used to store current user).
|
200
|
+
* Forms allow defining/overriding their `#param_key` method result by using `.param_key` static method. This defaults to `#resource#model_name#param_key`.
|
201
|
+
* Forms delegate `#persisted?` method to `#resource` if possible.
|
202
|
+
* Forms do handle `ActionController::Parameters` as attributes hash (using `to_unsafe_h`)
|
203
|
+
* It is recommended to wrap `#persist` method in transaction if possible and if multiple model are affected.
|
204
|
+
|
205
|
+
### Examples
|
206
|
+
|
207
|
+
#### Declaration
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
class UserForm < Patterns::Form
|
211
|
+
param_key "person"
|
212
|
+
|
213
|
+
attribute :first_name, String
|
214
|
+
attribute :last_name, String
|
215
|
+
attribute :age, Integer
|
216
|
+
attribute :full_address, String
|
217
|
+
attribute :skip_notification, Boolean
|
218
|
+
|
219
|
+
validate :first_name, :last_name, presence: true
|
220
|
+
|
221
|
+
private
|
222
|
+
|
223
|
+
def persist
|
224
|
+
update_user and
|
225
|
+
update_address and
|
226
|
+
deliver_notification
|
227
|
+
end
|
228
|
+
|
229
|
+
def update_user
|
230
|
+
resource.update_attributes(attributes.except(:full_address, :skip_notification))
|
231
|
+
end
|
232
|
+
|
233
|
+
def update_address
|
234
|
+
resource.address.update_attributes(full_address: full_address)
|
235
|
+
end
|
236
|
+
|
237
|
+
def deliver_notification
|
238
|
+
skip_notification || UserNotifier.user_update_notification(user, form_owner).deliver
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
class ReportConfigurationForm < Patterns::Form
|
243
|
+
param_key "report"
|
244
|
+
|
245
|
+
attribute :include_extra_data, Boolean
|
246
|
+
attribute :dump_as_csv, Boolean
|
247
|
+
attribute :comma_separated_column_names, String
|
248
|
+
attribute :date_start, Date
|
249
|
+
attribute :date_end, Date
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
def persist
|
254
|
+
SendReport.call(attributes)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
```
|
258
|
+
|
259
|
+
#### Usage
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
form = UserForm.new(User.find(1), params[:person])
|
263
|
+
form.save
|
264
|
+
|
265
|
+
form = UserForm.new(User.new, params[:person]).as(current_user)
|
266
|
+
form.save!
|
267
|
+
|
268
|
+
ReportConfigurationForm.new
|
269
|
+
ReportConfigurationForm.new({ include_extra_data: true, dump_as_csv: true })
|
171
270
|
```
|
172
271
|
|
173
272
|
## Further reading
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require "virtus"
|
2
|
+
require "action_controller/metal/strong_parameters"
|
3
|
+
|
4
|
+
module Patterns
|
5
|
+
class Form
|
6
|
+
include Virtus.model
|
7
|
+
include ActiveModel::Validations
|
8
|
+
|
9
|
+
Error = Class.new(StandardError)
|
10
|
+
Invalid = Class.new(Error)
|
11
|
+
NoParamKey = Class.new(Error)
|
12
|
+
|
13
|
+
def initialize(*args)
|
14
|
+
attributes = args.extract_options!
|
15
|
+
|
16
|
+
if attributes.blank? && args.last.is_a?(ActionController::Parameters)
|
17
|
+
attributes = args.pop.to_unsafe_h
|
18
|
+
end
|
19
|
+
|
20
|
+
@resource = args.first
|
21
|
+
|
22
|
+
super(build_original_attributes.merge(attributes))
|
23
|
+
end
|
24
|
+
|
25
|
+
def save
|
26
|
+
valid? ? persist : false
|
27
|
+
end
|
28
|
+
|
29
|
+
def save!
|
30
|
+
save.tap do |saved|
|
31
|
+
raise Invalid unless saved
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def as(form_owner)
|
36
|
+
@form_owner = form_owner
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_key
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_partial_path
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_model
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def persisted?
|
53
|
+
if resource&.respond_to?(:persisted?)
|
54
|
+
resource.persisted?
|
55
|
+
else
|
56
|
+
false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def model_name
|
61
|
+
@model_name ||= Struct.
|
62
|
+
new(:param_key).
|
63
|
+
new(param_key)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.param_key(key = nil)
|
67
|
+
if key.nil?
|
68
|
+
@param_key
|
69
|
+
else
|
70
|
+
@param_key = key
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
attr_reader :resource, :form_owner
|
77
|
+
|
78
|
+
def param_key
|
79
|
+
param_key = self.class.param_key
|
80
|
+
param_key ||= resource&.respond_to?(:model_name) && resource.model_name.param_key
|
81
|
+
raise NoParamKey if param_key.blank?
|
82
|
+
param_key
|
83
|
+
end
|
84
|
+
|
85
|
+
def build_original_attributes
|
86
|
+
return {} if resource.nil?
|
87
|
+
base_attributes = resource.respond_to?(:attributes) && resource.attributes.symbolize_keys
|
88
|
+
|
89
|
+
self.class.attribute_set.each_with_object(base_attributes || {}) do |attribute, result|
|
90
|
+
if result[attribute.name].blank? && resource.respond_to?(attribute.name)
|
91
|
+
result[attribute.name] = resource.public_send(attribute.name)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def persist
|
97
|
+
raise NotImplementedError, "#persist has to be implemented"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/rails-patterns.rb
CHANGED
data/rails-patterns.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: rails-patterns 0.
|
5
|
+
# stub: rails-patterns 0.4.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "rails-patterns".freeze
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "0.4.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["Stevo".freeze]
|
14
|
-
s.date = "2017-04-
|
14
|
+
s.date = "2017-04-21"
|
15
15
|
s.description = "A collection of lightweight, standardized, rails-oriented patterns.".freeze
|
16
16
|
s.email = "b.kosmowski@selleo.com".freeze
|
17
17
|
s.extra_rdoc_files = [
|
@@ -29,11 +29,13 @@ Gem::Specification.new do |s|
|
|
29
29
|
"VERSION",
|
30
30
|
"lib/patterns.rb",
|
31
31
|
"lib/patterns/collection.rb",
|
32
|
+
"lib/patterns/form.rb",
|
32
33
|
"lib/patterns/query.rb",
|
33
34
|
"lib/patterns/service.rb",
|
34
35
|
"lib/rails-patterns.rb",
|
35
36
|
"rails-patterns.gemspec",
|
36
37
|
"spec/patterns/collection_spec.rb",
|
38
|
+
"spec/patterns/form_spec.rb",
|
37
39
|
"spec/patterns/query_spec.rb",
|
38
40
|
"spec/patterns/service_spec.rb",
|
39
41
|
"spec/spec_helper.rb"
|
@@ -48,17 +50,23 @@ Gem::Specification.new do |s|
|
|
48
50
|
|
49
51
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
50
52
|
s.add_runtime_dependency(%q<activerecord>.freeze, [">= 4.2.6"])
|
53
|
+
s.add_runtime_dependency(%q<actionpack>.freeze, [">= 4.2.6"])
|
54
|
+
s.add_runtime_dependency(%q<virtus>.freeze, [">= 0"])
|
51
55
|
s.add_development_dependency(%q<rspec>.freeze, [">= 0"])
|
52
56
|
s.add_development_dependency(%q<bundler>.freeze, ["~> 1.0"])
|
53
57
|
s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
54
58
|
else
|
55
59
|
s.add_dependency(%q<activerecord>.freeze, [">= 4.2.6"])
|
60
|
+
s.add_dependency(%q<actionpack>.freeze, [">= 4.2.6"])
|
61
|
+
s.add_dependency(%q<virtus>.freeze, [">= 0"])
|
56
62
|
s.add_dependency(%q<rspec>.freeze, [">= 0"])
|
57
63
|
s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
|
58
64
|
s.add_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
59
65
|
end
|
60
66
|
else
|
61
67
|
s.add_dependency(%q<activerecord>.freeze, [">= 4.2.6"])
|
68
|
+
s.add_dependency(%q<actionpack>.freeze, [">= 4.2.6"])
|
69
|
+
s.add_dependency(%q<virtus>.freeze, [">= 0"])
|
62
70
|
s.add_dependency(%q<rspec>.freeze, [">= 0"])
|
63
71
|
s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
|
64
72
|
s.add_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
@@ -0,0 +1,432 @@
|
|
1
|
+
RSpec.describe Patterns::Form do
|
2
|
+
after { Object.send(:remove_const, :CustomForm) if defined?(CustomForm) }
|
3
|
+
|
4
|
+
it "includes Virtus.model" do
|
5
|
+
CustomForm = Class.new(Patterns::Form)
|
6
|
+
|
7
|
+
expect(CustomForm.attribute_set).to be_kind_of(Virtus::AttributeSet)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "includes ActiveModel::Validations" do
|
11
|
+
CustomForm = Class.new(Patterns::Form)
|
12
|
+
|
13
|
+
expect(CustomForm).to be < ActiveModel::Validations
|
14
|
+
end
|
15
|
+
|
16
|
+
describe ".new" do
|
17
|
+
it "returns form instance" do
|
18
|
+
CustomForm = Class.new(Patterns::Form)
|
19
|
+
|
20
|
+
form = CustomForm.new(double)
|
21
|
+
|
22
|
+
expect(form).to be_a_kind_of(CustomForm)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "assigns form attributes with values passed as second argument" do
|
26
|
+
CustomForm = Class.new(Patterns::Form) do
|
27
|
+
attribute :first_name, String
|
28
|
+
attribute :last_name, String
|
29
|
+
end
|
30
|
+
|
31
|
+
form = CustomForm.new({ first_name: "Tony", last_name: "Stark" })
|
32
|
+
|
33
|
+
expect(form.first_name).to eq "Tony"
|
34
|
+
expect(form.last_name).to eq "Stark"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "handles both symbols and strings as attribute keys" do
|
38
|
+
CustomForm = Class.new(Patterns::Form) do
|
39
|
+
attribute :first_name, String
|
40
|
+
attribute :last_name, String
|
41
|
+
attribute :email, String
|
42
|
+
attribute :age, Integer
|
43
|
+
end
|
44
|
+
resource = double(
|
45
|
+
attributes: {
|
46
|
+
"first_name" => "Bat",
|
47
|
+
last_name: "Man",
|
48
|
+
"email" => "bat@man.dev"
|
49
|
+
}
|
50
|
+
)
|
51
|
+
|
52
|
+
form = CustomForm.new(
|
53
|
+
resource, { first_name: "Christian", "last_name" => "Bale", "age" => 40 }
|
54
|
+
)
|
55
|
+
|
56
|
+
expect(form.first_name).to eq "Christian"
|
57
|
+
expect(form.last_name).to eq "Bale"
|
58
|
+
expect(form.email).to eq "bat@man.dev"
|
59
|
+
expect(form.age).to eq 40
|
60
|
+
end
|
61
|
+
|
62
|
+
context "if second parameter is ActionController::Parameters object" do
|
63
|
+
it "treats ActionController::Parameters as regular hash" do
|
64
|
+
CustomForm = Class.new(Patterns::Form) do
|
65
|
+
attribute :first_name, String
|
66
|
+
attribute :last_name, String
|
67
|
+
end
|
68
|
+
|
69
|
+
strong_parameters = ActionController::Parameters.new(
|
70
|
+
{ "first_name" => "Kobe", "last_name" => "Bryant" }
|
71
|
+
)
|
72
|
+
|
73
|
+
form = CustomForm.new(double, strong_parameters)
|
74
|
+
|
75
|
+
expect(form.first_name).to eq "Kobe"
|
76
|
+
expect(form.last_name).to eq "Bryant"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "if only parameter is ActionController::Parameters object" do
|
81
|
+
it "treats ActionController::Parameters as regular hash" do
|
82
|
+
CustomForm = Class.new(Patterns::Form) do
|
83
|
+
attribute :first_name, String
|
84
|
+
attribute :last_name, String
|
85
|
+
end
|
86
|
+
|
87
|
+
strong_parameters = ActionController::Parameters.new(
|
88
|
+
{ "first_name" => "Saul", "last_name" => "Goodman" }
|
89
|
+
)
|
90
|
+
|
91
|
+
form = CustomForm.new(strong_parameters)
|
92
|
+
|
93
|
+
expect(form.first_name).to eq "Saul"
|
94
|
+
expect(form.last_name).to eq "Goodman"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it "can be initialized without providing resource" do
|
99
|
+
CustomForm = Class.new(Patterns::Form)
|
100
|
+
|
101
|
+
form = CustomForm.new
|
102
|
+
|
103
|
+
expect(form).to be_a_kind_of(CustomForm)
|
104
|
+
end
|
105
|
+
|
106
|
+
context "when resource exists" do
|
107
|
+
context "when resource responds to #attributes" do
|
108
|
+
it "assigns merged attributes from resource and passed as argument" do
|
109
|
+
CustomForm = Class.new(Patterns::Form) do
|
110
|
+
attribute :first_name, String
|
111
|
+
attribute :last_name, String
|
112
|
+
end
|
113
|
+
resource = double(attributes: { first_name: "Jack", last_name: "Black" })
|
114
|
+
|
115
|
+
form = CustomForm.new(resource, { first_name: "Tony" })
|
116
|
+
|
117
|
+
expect(form.first_name).to eq "Tony"
|
118
|
+
expect(form.last_name).to eq "Black"
|
119
|
+
end
|
120
|
+
|
121
|
+
it "attempts to use public getters to populate missing attributes" do
|
122
|
+
CustomForm = Class.new(Patterns::Form) do
|
123
|
+
attribute :first_name, String
|
124
|
+
attribute :last_name, String
|
125
|
+
attribute :age, Integer
|
126
|
+
attribute :email, String
|
127
|
+
end
|
128
|
+
resource = double(attributes: { first_name: "Jack", last_name: "Black" }, age: 27)
|
129
|
+
|
130
|
+
form = CustomForm.new(resource, { first_name: "Tony" })
|
131
|
+
|
132
|
+
expect(form.first_name).to eq "Tony"
|
133
|
+
expect(form.last_name).to eq "Black"
|
134
|
+
expect(form.age).to eq 27
|
135
|
+
expect(form.email).to eq nil
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context "when resource does not respond to #attributes" do
|
140
|
+
it "assigns attributes passed as arguments" do
|
141
|
+
CustomForm = Class.new(Patterns::Form) do
|
142
|
+
attribute :first_name, String
|
143
|
+
attribute :last_name, String
|
144
|
+
end
|
145
|
+
|
146
|
+
form = CustomForm.new(double, { first_name: "Tony" })
|
147
|
+
|
148
|
+
expect(form.first_name).to eq "Tony"
|
149
|
+
expect(form.last_name).to eq nil
|
150
|
+
end
|
151
|
+
|
152
|
+
it "attempts to use public getters to populate missing attributes" do
|
153
|
+
CustomForm = Class.new(Patterns::Form) do
|
154
|
+
attribute :first_name, String
|
155
|
+
attribute :last_name, String
|
156
|
+
attribute :age, Integer
|
157
|
+
attribute :email, String
|
158
|
+
end
|
159
|
+
resource = double(last_name: "Black", age: 27)
|
160
|
+
|
161
|
+
form = CustomForm.new(resource, { first_name: "Tony" })
|
162
|
+
|
163
|
+
expect(form.first_name).to eq "Tony"
|
164
|
+
expect(form.last_name).to eq "Black"
|
165
|
+
expect(form.age).to eq 27
|
166
|
+
expect(form.email).to eq nil
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "#save" do
|
173
|
+
context "when form is valid" do
|
174
|
+
it "requires #persist method to be implemented" do
|
175
|
+
CustomForm = Class.new(Patterns::Form)
|
176
|
+
|
177
|
+
form = CustomForm.new(double)
|
178
|
+
|
179
|
+
expect { form.save }.to raise_error NotImplementedError, "#persist has to be implemented"
|
180
|
+
end
|
181
|
+
|
182
|
+
it "returns result of #persist method" do
|
183
|
+
CustomForm = Class.new(Patterns::Form) do
|
184
|
+
private
|
185
|
+
|
186
|
+
def persist
|
187
|
+
10
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
form = CustomForm.new(double)
|
192
|
+
result = form.save
|
193
|
+
|
194
|
+
expect(result).to eq 10
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context "when form is invalid" do
|
199
|
+
it "does not call #persist method" do
|
200
|
+
CustomForm = Class.new(Patterns::Form) do
|
201
|
+
private
|
202
|
+
|
203
|
+
def persist
|
204
|
+
raise StandardError, "Should not be raised!"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
form = CustomForm.new(double)
|
208
|
+
allow(form).to receive(:valid?) { false }
|
209
|
+
|
210
|
+
expect { form.save }.to_not raise_error
|
211
|
+
end
|
212
|
+
|
213
|
+
it "returns false" do
|
214
|
+
CustomForm = Class.new(Patterns::Form) do
|
215
|
+
private
|
216
|
+
|
217
|
+
def persist
|
218
|
+
10
|
219
|
+
end
|
220
|
+
end
|
221
|
+
form = CustomForm.new(double)
|
222
|
+
allow(form).to receive(:valid?) { false }
|
223
|
+
|
224
|
+
expect(form.save).to eq false
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe "#save!" do
|
230
|
+
context "#save returned falsey value" do
|
231
|
+
it "returns Pattern::Form::Invalid exception" do
|
232
|
+
CustomForm = Class.new(Patterns::Form) do
|
233
|
+
private
|
234
|
+
|
235
|
+
def persist
|
236
|
+
10
|
237
|
+
end
|
238
|
+
end
|
239
|
+
form = CustomForm.new(double)
|
240
|
+
allow(form).to receive(:save) { false }
|
241
|
+
|
242
|
+
expect { form.save! }.to raise_error Patterns::Form::Invalid
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
context "#save returned truthy value" do
|
247
|
+
it "returns value returned from #save" do
|
248
|
+
CustomForm = Class.new(Patterns::Form) do
|
249
|
+
private
|
250
|
+
|
251
|
+
def persist
|
252
|
+
10
|
253
|
+
end
|
254
|
+
end
|
255
|
+
form = CustomForm.new(double)
|
256
|
+
|
257
|
+
expect(form.save!).to eq 10
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
describe "#as" do
|
263
|
+
it "saves argument in @form_owner" do
|
264
|
+
CustomForm = Class.new(Patterns::Form)
|
265
|
+
form_owner = double("Form owner")
|
266
|
+
|
267
|
+
form = CustomForm.new(double).as(form_owner)
|
268
|
+
|
269
|
+
expect(form.instance_variable_get("@form_owner")).to eq form_owner
|
270
|
+
end
|
271
|
+
|
272
|
+
it "returns itself" do
|
273
|
+
CustomForm = Class.new(Patterns::Form)
|
274
|
+
|
275
|
+
form = CustomForm.new(double)
|
276
|
+
result = form.as(double)
|
277
|
+
|
278
|
+
expect(result).to eq form
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
describe "#persisted?" do
|
283
|
+
context "when resource is nil" do
|
284
|
+
it "returns false" do
|
285
|
+
CustomForm = Class.new(Patterns::Form)
|
286
|
+
|
287
|
+
form = CustomForm.new
|
288
|
+
|
289
|
+
expect(form.persisted?).to eq false
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
context "when resource is not nil" do
|
294
|
+
context "when resource responds to #persisted?" do
|
295
|
+
it "returns resource#persisted?" do
|
296
|
+
CustomForm = Class.new(Patterns::Form)
|
297
|
+
|
298
|
+
form_1 = CustomForm.new(double(persisted?: true))
|
299
|
+
form_2 = CustomForm.new(double(persisted?: false))
|
300
|
+
|
301
|
+
expect(form_1.persisted?).to eq true
|
302
|
+
expect(form_2.persisted?).to eq false
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
context "when resource does not respond to #persisted?" do
|
307
|
+
it "returns false" do
|
308
|
+
CustomForm = Class.new(Patterns::Form)
|
309
|
+
|
310
|
+
form = CustomForm.new(double)
|
311
|
+
|
312
|
+
expect(form.persisted?).to eq false
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
describe "#to_model" do
|
319
|
+
it "retruns itself" do
|
320
|
+
CustomForm = Class.new(Patterns::Form)
|
321
|
+
|
322
|
+
form = CustomForm.new(double)
|
323
|
+
|
324
|
+
expect(form.to_model).to eq form
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
describe "#to_partial_path" do
|
329
|
+
it "returns nil" do
|
330
|
+
CustomForm = Class.new(Patterns::Form)
|
331
|
+
|
332
|
+
form = CustomForm.new(double)
|
333
|
+
|
334
|
+
expect(form.to_partial_path).to eq nil
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
describe "#to_key" do
|
339
|
+
it "returns nil" do
|
340
|
+
CustomForm = Class.new(Patterns::Form)
|
341
|
+
|
342
|
+
form = CustomForm.new(double)
|
343
|
+
|
344
|
+
expect(form.to_key).to eq nil
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
describe "#model_name" do
|
349
|
+
describe "#param_key" do
|
350
|
+
context "resource exists" do
|
351
|
+
context "resource responds to #model_name" do
|
352
|
+
context "param_key is not defined" do
|
353
|
+
it "returns object responding to #param_key returning resource#param_key" do
|
354
|
+
CustomForm = Class.new(Patterns::Form)
|
355
|
+
resource = double(model_name: double(param_key: "resource_key"))
|
356
|
+
|
357
|
+
form = CustomForm.new(resource)
|
358
|
+
result = form.model_name
|
359
|
+
|
360
|
+
expect(result).to respond_to(:param_key)
|
361
|
+
expect(result.param_key).to eq "resource_key"
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
context "param_key is defined" do
|
366
|
+
it "returns param_key" do
|
367
|
+
CustomForm = Class.new(Patterns::Form) do
|
368
|
+
param_key "test_key"
|
369
|
+
end
|
370
|
+
resource = double(model_name: double(param_key: "resource_key"))
|
371
|
+
|
372
|
+
form = CustomForm.new(resource)
|
373
|
+
result = form.model_name
|
374
|
+
|
375
|
+
expect(result.param_key).to eq "test_key"
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
context "resource does not respond to #model_name" do
|
381
|
+
context "param_key is not defined" do
|
382
|
+
it "raises NoParamKey" do
|
383
|
+
CustomForm = Class.new(Patterns::Form)
|
384
|
+
|
385
|
+
form = CustomForm.new(double)
|
386
|
+
|
387
|
+
expect { form.model_name }.to raise_error(Patterns::Form::NoParamKey)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
context "param_key is defined" do
|
392
|
+
it "returns param_key" do
|
393
|
+
CustomForm = Class.new(Patterns::Form) do
|
394
|
+
param_key "test_key"
|
395
|
+
end
|
396
|
+
|
397
|
+
form = CustomForm.new(double)
|
398
|
+
result = form.model_name
|
399
|
+
|
400
|
+
expect(result.param_key).to eq "test_key"
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
context "resource does not exist" do
|
407
|
+
context "param_key is not defined" do
|
408
|
+
it "raises NoParamKey" do
|
409
|
+
CustomForm = Class.new(Patterns::Form)
|
410
|
+
|
411
|
+
form = CustomForm.new
|
412
|
+
|
413
|
+
expect { form.model_name }.to raise_error(Patterns::Form::NoParamKey)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
context "param_key is defined" do
|
418
|
+
it "returns param_key" do
|
419
|
+
CustomForm = Class.new(Patterns::Form) do
|
420
|
+
param_key "test_key"
|
421
|
+
end
|
422
|
+
|
423
|
+
form = CustomForm.new
|
424
|
+
result = form.model_name
|
425
|
+
|
426
|
+
expect(result.param_key).to eq "test_key"
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-patterns
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stevo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-04-
|
11
|
+
date: 2017-04-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 4.2.6
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: actionpack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 4.2.6
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 4.2.6
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: virtus
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: rspec
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -84,11 +112,13 @@ files:
|
|
84
112
|
- VERSION
|
85
113
|
- lib/patterns.rb
|
86
114
|
- lib/patterns/collection.rb
|
115
|
+
- lib/patterns/form.rb
|
87
116
|
- lib/patterns/query.rb
|
88
117
|
- lib/patterns/service.rb
|
89
118
|
- lib/rails-patterns.rb
|
90
119
|
- rails-patterns.gemspec
|
91
120
|
- spec/patterns/collection_spec.rb
|
121
|
+
- spec/patterns/form_spec.rb
|
92
122
|
- spec/patterns/query_spec.rb
|
93
123
|
- spec/patterns/service_spec.rb
|
94
124
|
- spec/spec_helper.rb
|