o_patch 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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