o_patch 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 +15 -0
- data/.gitignore +3 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +31 -0
- data/LICENSE +21 -0
- data/README.md +4 -0
- data/Rakefile +1 -0
- data/lib/o_patch.rb +7 -0
- data/lib/o_patch/patcher.rb +88 -0
- data/o_patch.gemspec +18 -0
- data/spec/o_patch/o_patch_spec.rb +226 -0
- data/spec/spec_helper.rb +8 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
N2EzZDY2NDZjYWM5NTFmMzA1OWVjNmE0MmU4YTE3NzQzZmZiNzA2YQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MTBmOTIzNmQ0NjhlMzcyY2I4ZGI2NWM1MjE4YThkYTM4ZTc1NTk2MA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZDFmM2Q2OTJkZTgzNTFiNTM1ZmMzZTFlMTQ3ZGNhYTFkZWIxOGQ1NDQ1YmIy
|
10
|
+
NTRhMDY0NjliYTI1MzgxZDk4NjNhMzQ1MzI3NzFiYjRiMzU5ZDk4MTc5MDU0
|
11
|
+
ODFkMjI5MmVjMWMxZGZlODMxODVjOWU0ZTE4OWQyN2VjNDE0NmE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NGRmYTFiZDEyYWVkN2E0ZDlkYTEwMTdkZmNlYTkxYWM2ZDZkZWExZTVmODVk
|
14
|
+
YjllZTI3YzY5NTYxMzU5Nzk4ZTBjNzQ3MmNiMjcyYzAxZDkyZTBlY2JhMWQ5
|
15
|
+
NTIxYmU2NjBkNzQ1MWMzZjQ5ZmZlNWY4ZTFkMDg1NjYxMzI2MTk=
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
o_patch (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.2.5)
|
10
|
+
rake (10.3.2)
|
11
|
+
rspec (3.0.0)
|
12
|
+
rspec-core (~> 3.0.0)
|
13
|
+
rspec-expectations (~> 3.0.0)
|
14
|
+
rspec-mocks (~> 3.0.0)
|
15
|
+
rspec-core (3.0.2)
|
16
|
+
rspec-support (~> 3.0.0)
|
17
|
+
rspec-expectations (3.0.2)
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
19
|
+
rspec-support (~> 3.0.0)
|
20
|
+
rspec-mocks (3.0.2)
|
21
|
+
rspec-support (~> 3.0.0)
|
22
|
+
rspec-support (3.0.2)
|
23
|
+
|
24
|
+
PLATFORMS
|
25
|
+
ruby
|
26
|
+
|
27
|
+
DEPENDENCIES
|
28
|
+
bundler (~> 1.3)
|
29
|
+
o_patch!
|
30
|
+
rake
|
31
|
+
rspec
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Albert Gazizov
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/o_patch.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
class OPatch::Patcher
|
2
|
+
attr_reader :entity, :attributes
|
3
|
+
|
4
|
+
def initialize(entity, attributes, &block)
|
5
|
+
@entity = entity
|
6
|
+
@attributes = attributes
|
7
|
+
end
|
8
|
+
|
9
|
+
private :initialize
|
10
|
+
|
11
|
+
def self.patch(entity, attributes, &block)
|
12
|
+
new(entity, attributes).instance_exec(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def field(field_name, block = nil)
|
18
|
+
raise ArgumentError, "field name should be a symbol" unless field_name.is_a?(Symbol)
|
19
|
+
if attributes.has_key?(field_name)
|
20
|
+
value = attributes[field_name]
|
21
|
+
if block
|
22
|
+
block.call(entity, value)
|
23
|
+
else
|
24
|
+
entity.send("#{field_name}=", value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def object(field_name, options = {}, &block)
|
30
|
+
if attributes.has_key?(field_name)
|
31
|
+
if attributes[field_name].nil?
|
32
|
+
entity.send("#{field_name}=", nil)
|
33
|
+
else
|
34
|
+
child_entity = entity.send(field_name)
|
35
|
+
if child_entity
|
36
|
+
self.class.patch(entity.send(field_name), attributes[field_name], &block)
|
37
|
+
else
|
38
|
+
build_block = options[:build]
|
39
|
+
# Todo: ensure block is proc
|
40
|
+
raise ArgumentError, "#{field_name} build block should be specified" unless build_block
|
41
|
+
build_block.call(entity, attributes[field_name])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def collection(collection_name, options = {}, &block)
|
48
|
+
raise ArgumentError, "#{collection_name} collection key should be specified" unless options[:key]
|
49
|
+
raise ArgumentError, "#{collection_name} build block should be specified" unless options[:build]
|
50
|
+
# Todo: ensure key is a symbol or array of symbols, block is proc
|
51
|
+
key = options[:key]
|
52
|
+
build_block = options[:build]
|
53
|
+
if attributes.has_key?(collection_name)
|
54
|
+
collection = entity.send(collection_name)
|
55
|
+
collection_hash = collection.group_by { |item| object_key_value(item, key) }
|
56
|
+
attributes[collection_name].each do |child_attributes|
|
57
|
+
key_value = attributes_key_value(child_attributes, key)
|
58
|
+
if key_value
|
59
|
+
child_object = collection_hash[key_value].first
|
60
|
+
raise "#{collection_name} don't have an object with key: #{key_value}" unless child_object
|
61
|
+
if child_attributes[:_destroy]
|
62
|
+
collection.delete(child_object)
|
63
|
+
else
|
64
|
+
self.class.patch(child_object, child_attributes, &block)
|
65
|
+
end
|
66
|
+
else
|
67
|
+
build_block.call(entity, child_attributes)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def object_key_value(object, key)
|
74
|
+
if key.is_a?(Array)
|
75
|
+
key.map { |k| object.send(k) }
|
76
|
+
else
|
77
|
+
object.send(key)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def attributes_key_value(attributes, key)
|
82
|
+
if key.is_a?(Array)
|
83
|
+
key.map { |k| attributes[:k] }
|
84
|
+
else
|
85
|
+
attributes[key]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/o_patch.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = "o_patch"
|
6
|
+
spec.version = "0.0.1"
|
7
|
+
spec.authors = ["Albert Gazizov"]
|
8
|
+
spec.description = %q{Declarative ruby objects patcher}
|
9
|
+
spec.summary = %q{Declarative ruby objects patcher}
|
10
|
+
|
11
|
+
spec.files = `git ls-files`.split($/)
|
12
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
13
|
+
spec.test_files = spec.files.grep(%r{^(spec)/})
|
14
|
+
spec.require_paths = ["lib"]
|
15
|
+
|
16
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
17
|
+
spec.add_development_dependency "rake"
|
18
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OPatch do
|
4
|
+
|
5
|
+
context "simple fields" do
|
6
|
+
before do
|
7
|
+
Person = Struct.new(:name)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should patch simple field" do
|
11
|
+
person = Person.new('Ivan')
|
12
|
+
|
13
|
+
OPatch.patch(person, name: 'Vasya') do
|
14
|
+
field :name
|
15
|
+
end
|
16
|
+
|
17
|
+
person.name.should == 'Vasya'
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should patch field using given block" do
|
21
|
+
person = Person.new('Ivan')
|
22
|
+
|
23
|
+
OPatch.patch(person, name: 'Vasya') do
|
24
|
+
field :name, proc { |person, new_name| person.name = new_name }
|
25
|
+
end
|
26
|
+
|
27
|
+
person.name.should == 'Vasya'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "nested object" do
|
32
|
+
before do
|
33
|
+
Address = Struct.new(:country, :city)
|
34
|
+
Person = Struct.new(:name, :address)
|
35
|
+
class Person
|
36
|
+
def build_address(attrs)
|
37
|
+
self.address = Address.new(attrs[:country], attrs[:city])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should patch nested object fields" do
|
43
|
+
person = Person.new('Ivan', Address.new('Russia', 'Kazan'))
|
44
|
+
|
45
|
+
OPatch.patch(person, name: 'Vasya', address: { country: 'USA', city: 'New York'}) do
|
46
|
+
field :name
|
47
|
+
object :address do
|
48
|
+
field :country
|
49
|
+
field :city
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
person.address.country.should == 'USA'
|
54
|
+
person.address.city.should == 'New York'
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should nullify nested object if nil was given" do
|
58
|
+
person = Person.new('Ivan', Address.new('Russia', 'Kazan'))
|
59
|
+
|
60
|
+
OPatch.patch(person, name: 'Vasya', address: nil) do
|
61
|
+
field :name
|
62
|
+
object :address do
|
63
|
+
field :country
|
64
|
+
field :city
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
person.address.should be_nil
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should build nested object if it was nil before" do
|
72
|
+
person = Person.new('Ivan')
|
73
|
+
|
74
|
+
OPatch.patch(person, name: 'Vasya', address: { country: 'Russia', city: 'Kazan' }) do
|
75
|
+
field :name
|
76
|
+
object :address, build: proc { |person, attributes| person.build_address(attributes) } do
|
77
|
+
field :country
|
78
|
+
field :city
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
person.address.should_not be_nil
|
83
|
+
person.address.country.should == 'Russia'
|
84
|
+
person.address.city.should == 'Kazan'
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should raise error if build block wasn't specified but attributes were given" do
|
88
|
+
person = Person.new('Ivan')
|
89
|
+
|
90
|
+
expect do
|
91
|
+
OPatch.patch(person, name: 'Vasya', address: { country: 'Russia', city: 'Kazan' }) do
|
92
|
+
field :name
|
93
|
+
object :address do
|
94
|
+
field :country
|
95
|
+
field :city
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end.to raise_error(ArgumentError, "address build block should be specified")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "nested collection" do
|
103
|
+
before do
|
104
|
+
Person = Struct.new(:name, :emails)
|
105
|
+
Email = Struct.new(:id, :address, :type)
|
106
|
+
class Person
|
107
|
+
def build_email(attrs)
|
108
|
+
self.emails << Email.new(nil, attrs[:address], attrs[:type])
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should patch collection objects" do
|
114
|
+
person = Person.new(
|
115
|
+
'Ivan',
|
116
|
+
[
|
117
|
+
Email.new(1, 'work@example.com', :work),
|
118
|
+
Email.new(2, 'work2@example.com', :work)
|
119
|
+
]
|
120
|
+
)
|
121
|
+
|
122
|
+
attributes = {
|
123
|
+
emails: [
|
124
|
+
{ id: 1, address: 'home@example.com', type: :home },
|
125
|
+
{ id: 2, address: 'home2@example.com', type: :home }
|
126
|
+
]
|
127
|
+
}
|
128
|
+
OPatch.patch(person, attributes) do
|
129
|
+
collection :emails, key: :id, build: proc { |person, attrs| person.build_email(attrs) } do
|
130
|
+
field :address
|
131
|
+
field :type
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
person.emails.count.should == 2
|
136
|
+
|
137
|
+
email = person.emails[0]
|
138
|
+
email.address.should == 'home@example.com'
|
139
|
+
email.type.should == :home
|
140
|
+
|
141
|
+
email = person.emails[1]
|
142
|
+
email.address.should == 'home2@example.com'
|
143
|
+
email.type.should == :home
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should add new object to collection" do
|
147
|
+
person = Person.new(
|
148
|
+
'Ivan',
|
149
|
+
[
|
150
|
+
Email.new(1, 'work@example.com', :work),
|
151
|
+
]
|
152
|
+
)
|
153
|
+
|
154
|
+
attributes = {
|
155
|
+
emails: [
|
156
|
+
{ address: 'home@example.com', type: :home }
|
157
|
+
]
|
158
|
+
}
|
159
|
+
OPatch.patch(person, attributes) do
|
160
|
+
collection :emails, key: :id, build: proc { |person, attrs| person.build_email(attrs) } do
|
161
|
+
field :address
|
162
|
+
field :type
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
person.emails.count.should == 2
|
167
|
+
|
168
|
+
email = person.emails[0]
|
169
|
+
email.address.should == 'work@example.com'
|
170
|
+
email.type.should == :work
|
171
|
+
|
172
|
+
email = person.emails[1]
|
173
|
+
email.address.should == 'home@example.com'
|
174
|
+
email.type.should == :home
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should remove collection object if _destroy: true was specified" do
|
178
|
+
person = Person.new(
|
179
|
+
'Ivan',
|
180
|
+
[
|
181
|
+
Email.new(1, 'work@example.com', :work),
|
182
|
+
Email.new(2, 'work2@example.com', :work)
|
183
|
+
]
|
184
|
+
)
|
185
|
+
|
186
|
+
attributes = {
|
187
|
+
emails: [
|
188
|
+
{ id: 2, _destroy: true },
|
189
|
+
]
|
190
|
+
}
|
191
|
+
OPatch.patch(person, attributes) do
|
192
|
+
collection :emails, key: :id, build: proc { |person, attrs| person.build_email(attrs) } do
|
193
|
+
field :address
|
194
|
+
field :type
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
person.emails.count.should == 1
|
199
|
+
person.emails.first.id.should == 1
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should raise error if non existing key was specified" do
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should build new object with key if assign_key is true" do
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should raise error if undeclared attributes were specified" do
|
212
|
+
end
|
213
|
+
|
214
|
+
context "errors" do
|
215
|
+
it "should raise error if non symbol argument was given to the field method" do
|
216
|
+
Person = Struct.new(:name)
|
217
|
+
person = Person.new('Ivan')
|
218
|
+
|
219
|
+
expect do
|
220
|
+
OPatch.patch(person, name: 'Vasya') do
|
221
|
+
field 'name'
|
222
|
+
end
|
223
|
+
end.to raise_error(ArgumentError, "field name should be a symbol")
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: o_patch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Albert Gazizov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
type: :development
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
name: bundler
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ~>
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '1.3'
|
26
|
+
prerelease: false
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
type: :development
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
name: rake
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
prerelease: false
|
41
|
+
description: Declarative ruby objects patcher
|
42
|
+
email:
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- .gitignore
|
48
|
+
- Gemfile
|
49
|
+
- Gemfile.lock
|
50
|
+
- LICENSE
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- lib/o_patch.rb
|
54
|
+
- lib/o_patch/patcher.rb
|
55
|
+
- o_patch.gemspec
|
56
|
+
- spec/o_patch/o_patch_spec.rb
|
57
|
+
- spec/spec_helper.rb
|
58
|
+
homepage:
|
59
|
+
licenses: []
|
60
|
+
metadata: {}
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 2.4.1
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: Declarative ruby objects patcher
|
81
|
+
test_files:
|
82
|
+
- spec/o_patch/o_patch_spec.rb
|
83
|
+
- spec/spec_helper.rb
|