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.
@@ -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=
@@ -0,0 +1,3 @@
1
+ /.bundle
2
+ /vendor/bundle
3
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ end
@@ -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.
@@ -0,0 +1,4 @@
1
+ opatch
2
+ ======
3
+
4
+ Declarative ruby objects patcher
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ module OPatch
2
+ require 'o_patch/patcher'
3
+
4
+ def self.patch(entity, attributes, &block)
5
+ Patcher.patch(entity, attributes, &block)
6
+ end
7
+ end
@@ -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
@@ -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
@@ -0,0 +1,8 @@
1
+ require 'rspec'
2
+ require 'o_patch'
3
+
4
+ RSpec.configure do |config|
5
+ config.expect_with :rspec do |c|
6
+ c.syntax = [:should, :expect]
7
+ end
8
+ end
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