attribute-kit 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "rspec", "~> 2.3.0"
5
+ gem "bundler", "~> 1.0.0"
6
+ gem "jeweler", "~> 1.6.4"
7
+ gem "rcov", ">= 0"
8
+ gem 'yard'
9
+ gem 'rdiscount'
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,32 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.6.4)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rake (0.9.2)
11
+ rcov (0.9.9)
12
+ rdiscount (1.6.8)
13
+ rspec (2.3.0)
14
+ rspec-core (~> 2.3.0)
15
+ rspec-expectations (~> 2.3.0)
16
+ rspec-mocks (~> 2.3.0)
17
+ rspec-core (2.3.1)
18
+ rspec-expectations (2.3.0)
19
+ diff-lcs (~> 1.1.2)
20
+ rspec-mocks (2.3.0)
21
+ yard (0.7.2)
22
+
23
+ PLATFORMS
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ bundler (~> 1.0.0)
28
+ jeweler (~> 1.6.4)
29
+ rcov
30
+ rdiscount
31
+ rspec (~> 2.3.0)
32
+ yard
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Jonathan Mischo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,36 @@
1
+ # attribute-kit
2
+
3
+ Tools for attribute tracking like Hashes with dirty tracking and events, for building hybrid models and generally going beyond what's provided by your local ORM/DRM, while allowing you to expand what you can do with them, live without them, or roll your own
4
+
5
+ ## Contributing to attribute-kit
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ ## Copyright and License
16
+
17
+ Copyright (c) 2011 Jonathan Mischo
18
+
19
+ Permission is hereby granted, free of charge, to any person obtaining
20
+ a copy of this software and associated documentation files (the
21
+ "Software"), to deal in the Software without restriction, including
22
+ without limitation the rights to use, copy, modify, merge, publish,
23
+ distribute, sublicense, and/or sell copies of the Software, and to
24
+ permit persons to whom the Software is furnished to do so, subject to
25
+ the following conditions:
26
+
27
+ The above copyright notice and this permission notice shall be
28
+ included in all copies or substantial portions of the Software.
29
+
30
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
31
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
33
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
34
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
35
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
36
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "attribute-kit"
18
+ gem.homepage = "http://github.com/supertaz/attribute-kit"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Tools for attribute tracking like Hashes with dirty tracking and events, for building hybrid models and generally going beyond what's provided by your local ORM/DRM, while allowing you to expand what you can do with them, live without them, or roll your own}
21
+ gem.description = %Q{Tools for attribute tracking like Hashes with dirty tracking and events, for building hybrid models and generally going beyond what's provided by your local ORM/DRM, while allowing you to expand what you can do with them, live without them, or roll your own}
22
+ gem.email = "jon.mischo@gmail.com"
23
+ gem.authors = ["Jonathan Mischo"]
24
+ gem.version = File.exist?('VERSION') ? File.read('VERSION') : ""
25
+ gem.files = FileList['lib/**/*.rb', '[A-Z]*', 'spec/**/*'].to_a
26
+ # dependencies defined in Gemfile
27
+ end
28
+ Jeweler::RubygemsDotOrgTasks.new
29
+
30
+ require 'rspec/core'
31
+ require 'rspec/core/rake_task'
32
+ RSpec::Core::RakeTask.new(:spec) do |spec|
33
+ spec.pattern = FileList['spec/**/*_spec.rb']
34
+ end
35
+
36
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
37
+ spec.pattern = 'spec/**/*_spec.rb'
38
+ spec.rcov = true
39
+ end
40
+
41
+ task :default => :spec
42
+
43
+ require 'yard'
44
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1 @@
1
+ require 'attribute-kit/attribute-kit.rb'
@@ -0,0 +1 @@
1
+ require 'attribute-kit/attribute_hash'
@@ -0,0 +1,247 @@
1
+ # A group of classes and other tools that are designed to make handling of attributes and dealing with changes to
2
+ # groups of attributes easy and convenient. This is particularly useful if you intend to build, extend, or even
3
+ # simply wish to avoid using someone else's ORM or DRM.
4
+ #
5
+ # The original intent of this library was to make it easier to store and track changes to attributes on a model that
6
+ # was not managed by an ORM, yet needed to be selectively synchronized between a client, server, and data store. From
7
+ # those humble beginnings, this optimized library was created.
8
+ #
9
+ # @author Jonathan Mischo
10
+ # @note Copyright (c) 2011 Jonathan Mischo
11
+ # @note Distributed under the MIT license (see LICENSE.txt, distributed with the original source code)
12
+
13
+ module AttributeKit
14
+
15
+ # AttributeHash inherits from and extends Hash, to provide tracking of attribute status (changed/deleted keys).
16
+ class AttributeHash < Hash
17
+
18
+ # @visibility private
19
+ def self.cond_deletion_method(method_name)
20
+ define_method(method_name) { |&block|
21
+ unless block.nil?
22
+ keys = self.keys
23
+ r = super(&block)
24
+ @deleted_keys += keys - self.keys
25
+ r
26
+ else
27
+ super()
28
+ end
29
+ }
30
+ end
31
+
32
+ private_class_method :cond_deletion_method
33
+
34
+ # Creates a new instance, using identical syntax to Hash
35
+ # @see Hash#new
36
+ def initialize(*args)
37
+ super(*args)
38
+ @dirty_keys = []
39
+ @deleted_keys = []
40
+ end
41
+
42
+ # Assigns a value to a key
43
+ # @param [Object] k key of key-value pair to insert or change in instance
44
+ # @param [Object] v value of key-value pair to insert or change in instance
45
+ # @return [Object] value of key-value pair inserted
46
+ # @see Hash#[]=
47
+ def []=(k,v)
48
+ if self[k].eql? v
49
+ v
50
+ else
51
+ @dirty_keys << k
52
+ super
53
+ end
54
+ end
55
+
56
+ alias_method :store, :[]=
57
+
58
+ # Delete a key-value pair
59
+ # @param [Object] key key of key-value pair to delete from instance
60
+ # @return [Object] value of key-value pair deleted
61
+ # @see Hash#delete
62
+ def delete(k)
63
+ @deleted_keys << k
64
+ @dirty_keys.delete(k)
65
+ super
66
+ end
67
+
68
+ # @method reject!
69
+ # Delete keys matching an expression in the provided block
70
+ # @return [AttributeHash] if changes are made
71
+ # @return [Nil] if no changes are made
72
+ # @yield [key, value] block is executed for every key-value pair stored in the instance
73
+ # @yieldparam [Object] key the key from the key-value pair being evaluated
74
+ # @yieldparam [Object] value the value from the key-value pair being evaluated
75
+ # @yieldreturn [Boolean] whether or not to delete a particular stored key-value pair from the instance
76
+ # @see Hash#reject!
77
+ cond_deletion_method(:reject!)
78
+
79
+ # @method select!
80
+ # Delete keys not matching an expression in the provided block
81
+ # @return [AttributeHash] if changes are made
82
+ # @return [Nil] if no changes are made
83
+ # @yield [key, value] block is executed for every key-value pair stored in the instance
84
+ # @yieldparam [Object] key the key from the key-value pair being evaluated
85
+ # @yieldparam [Object] value the value from the key-value pair being evaluated
86
+ # @yieldreturn [Boolean] whether or not to keep a particular stored key-value pair in the instance
87
+ # @see Hash#select!
88
+ cond_deletion_method(:select!)
89
+
90
+ # @method keep_if
91
+ # Delete keys not matching an expression in the provided block
92
+ # @return [AttributeHash] self with changes applied
93
+ # @yield [key, value] block is executed for every key-value pair stored in the instance
94
+ # @yieldparam [Object] key the key from the key-value pair being evaluated
95
+ # @yieldparam [Object] value the value from the key-value pair being evaluated
96
+ # @yieldreturn [Boolean] whether or not to keep a particular stored key-value pair in the instance
97
+ # @see Hash#keep_if
98
+ cond_deletion_method(:keep_if)
99
+
100
+ # @method delete_if
101
+ # Delete keys matching an expression in the provided block
102
+ # @return [AttributeHash] self with changes applied
103
+ # @yield [key, value] block is executed for every key-value pair stored in the instance
104
+ # @yieldparam [Object] key the key from the key-value pair being evaluated
105
+ # @yieldparam [Object] value the value from the key-value pair being evaluated
106
+ # @yieldreturn [Boolean] whether or not to delete a particular stored key-value pair from the instance
107
+ # @see Hash#delete_if
108
+ cond_deletion_method(:delete_if)
109
+
110
+ # Replace the contents of this object with the contents of the supplied hash
111
+ # @param [Hash] other_hash hash of values to replace instance contents with
112
+ # @return [AttributeHash] self with changes applied
113
+ # @see Hash#replace
114
+ def replace(other_hash)
115
+ old_keys = self.keys
116
+ r = super
117
+ new_keys = self.keys
118
+ @dirty_keys = new_keys
119
+ @deleted_keys += (old_keys - new_keys)
120
+ r
121
+ end
122
+
123
+ # Combine the contents of this object with the contents of the supplied hash, calling an optional supplied block to
124
+ # determine what value is used when there are duplicate keys. Without the block, values from the supplied hash will
125
+ # be used in the case of duplicate keys
126
+ # @param [Hash] other_hash hash of values to merge in to the instance
127
+ # @yield [key, oldval, newval] block is executed for every duplicate key between the instance and other_hash
128
+ # @yieldparam [Object] key the key being evaluated
129
+ # @yieldparam [Object] oldval the value from the value from the instance
130
+ # @yieldparam [Object] newval the value from the value from other_hash
131
+ # @yieldreturn [Object] the value to store for the key in question
132
+ # @return [AttributeHash] self with changes applied
133
+ # @see Hash#merge!
134
+ def merge!(other_hash, &block)
135
+ old_keys = self.keys
136
+ overlapping_keys = old_keys.dup.keep_if {|v| other_hash.keys.include?(v)}
137
+ r = super
138
+ if block.nil?
139
+ @dirty_keys += (self.keys - old_keys) + overlapping_keys
140
+ else
141
+ new_values = other_hash.keep_if {|k,v| overlapping_keys.include?(k)}
142
+ @dirty_keys += (self.keys - old_keys) + (new_values.keep_if {|k,v| !self[k].eql?(v) }).keys
143
+ end
144
+ r
145
+ end
146
+
147
+ alias_method :update, :merge!
148
+
149
+ # Returns a key-value pair from the instance and deletes it.
150
+ # @return [Array] key-value pair
151
+ # @see Hash#shift
152
+ def shift
153
+ (k,v) = super
154
+ @deleted_keys << k
155
+ [k,v]
156
+ end
157
+
158
+ # Clear all contents of the object.
159
+ # @see Hash#clear
160
+ def clear
161
+ @deleted_keys += self.keys
162
+ @dirty_keys.clear
163
+ super
164
+ end
165
+
166
+ # Check whether the contents of the object have changed since the last time clean_attributes was run.
167
+ # @return [Boolean] value indicating whether or not key-value pairs have been added, changed, or deleted
168
+ def dirty?
169
+ !(@dirty_keys.empty? && @deleted_keys.empty?)
170
+ end
171
+
172
+ # Returns the set of keys that have been modified since the AttributeHash was last marked clean.
173
+ # @return [Array] all of the changed keys
174
+ def dirty_keys
175
+ @dirty_keys.uniq!
176
+ @dirty_keys + self.deleted_keys
177
+ end
178
+
179
+ # Returns the set of keys that have been deleted since the AttributeHash was last marked clean.
180
+ # @return [Array] all of the deleted keys
181
+ def deleted_keys
182
+ @deleted_keys.uniq!
183
+ @deleted_keys
184
+ end
185
+
186
+ # Calls a block with a hash of all keys, actions (:changed or :deleted), and current values (if :changed) of keys
187
+ # that have changed since the object was last marked clean. Marks the object as clean when it compiles
188
+ # the list of keys that have been modified.
189
+ # @param [Block] block to execute with hash of modified keys, actions, and values
190
+ def clean_attributes(&block)
191
+ if !@dirty_keys.empty?
192
+ dirty_attrs = {}
193
+ @dirty_keys.uniq!
194
+ dirty = @dirty_keys.dup
195
+ @dirty_keys.clear
196
+ deleted = @deleted_keys.dup
197
+ @deleted_keys.clear
198
+
199
+ while dirty.length > 0 do
200
+ key = dirty.shift
201
+ dirty_attrs[key] = [:changed, self[key]]
202
+ end
203
+
204
+ while deleted.length > 0 do
205
+ key = deleted.shift
206
+ dirty_attrs[key] = [:deleted, nil]
207
+ end
208
+
209
+ block.call(dirty_attrs)
210
+ end
211
+ end
212
+
213
+ # @overload KEY_dirty?()
214
+ # Check whether a particular key is dirty - KEY is a string representation of the key name for the key-value pair being queried
215
+ # @note There can be conflicts if you have multiple keys that are similar, i.e. :blue and 'blue', so only use this
216
+ # when you can guarantee homogenous keys and are using either strings or symbols for the key (the only cases where
217
+ # it will work)
218
+ # @return [Boolean] value indicating key-value pair state
219
+ # @note Uses method_missing to implement the check
220
+ # @overload KEY_deleted?()
221
+ # Check whether a particular key has been deleted - KEY is a string representation of the key name for the key-value pair being queried
222
+ # @note There can be conflicts if you have multiple keys that are similar, i.e. :blue and 'blue', so only use this
223
+ # when you can guarantee homogenous keys and are using either strings or symbols for the key (the only cases where
224
+ # it will work)
225
+ # @return [Boolean] value indicating key-value pair state
226
+ # @note Uses method_missing to implement the check
227
+
228
+ def method_missing(method, *args, &block)
229
+ method_name = method.to_s
230
+ case method_name
231
+ when /(.*)_dirty?/
232
+ return @dirty_keys.include?($1) if self.has_key?($1)
233
+ return @dirty_keys.include?($1.to_sym) if self.has_key?($1.to_sym)
234
+ @deleted_keys.include?($1) || @deleted_keys.include?($1.to_sym)
235
+ when /(.*)_deleted?/
236
+ @deleted_keys.include?($1) || @deleted_keys.include?($1.to_sym)
237
+ else
238
+ if self.class.superclass.instance_methods.include?('method_missing')
239
+ super(method, *args, &block)
240
+ else
241
+ raise NoMethodError.new("undefined method '#{method_name}' for #{self}")
242
+ end
243
+ end
244
+ end
245
+
246
+ end
247
+ end
@@ -0,0 +1,646 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "AttributeKit" do
4
+ it "loads" do
5
+ defined?(AttributeKit).should == 'constant'
6
+ end
7
+
8
+ describe "::AttributeHash" do
9
+ describe '#new' do
10
+ it "should return an empty AttributeHash when initialized without arguments" do
11
+ test_hash = AttributeKit::AttributeHash.new
12
+ test_hash.empty?.should be_true
13
+ end
14
+
15
+ it 'should return a 0 length AttributeHash when initialized' do
16
+ test_hash = AttributeKit::AttributeHash.new
17
+ test_hash.length.should == 0
18
+ end
19
+
20
+ it "should return a default value for invalid keys when initialized with a default value" do
21
+ test_hash = AttributeKit::AttributeHash.new('blue')
22
+ test_hash.empty?.should be_true
23
+ test_hash[:blue].should == 'blue'
24
+ test_hash[:red].should == 'blue'
25
+ end
26
+
27
+ it "should execute and return the result of a block for invalid keys when initialized with a block" do
28
+ test_hash = AttributeKit::AttributeHash.new { |hash, key| [hash, key] }
29
+ test_hash.empty?.should be_true
30
+ test_hash[:blue].eql?([test_hash, :blue]).should be_true
31
+ test_hash[:red].eql?([test_hash, :red]).should be_true
32
+ end
33
+ end
34
+
35
+ describe '#[]=' do
36
+ before(:each) do
37
+ @test_hash = AttributeKit::AttributeHash.new
38
+ @test_hash[:blue] = 'blue'
39
+ end
40
+
41
+ it "should store a value" do
42
+ @test_hash.empty?.should be_false
43
+ @test_hash[:blue].should == 'blue'
44
+ end
45
+
46
+ it "should mark the attribute as dirty" do
47
+ @test_hash.blue_dirty?.should be_true
48
+ end
49
+
50
+ it "should not mark the attribute as deleted" do
51
+ @test_hash.blue_deleted?.should be_false
52
+ end
53
+
54
+ it "should mark the AttributeHash as dirty" do
55
+ @test_hash.dirty?.should be_true
56
+ end
57
+
58
+ it "should return the value set" do
59
+ ret = @test_hash[:red] = 'red'
60
+ ret.should == 'red'
61
+ end
62
+ end
63
+
64
+ describe '#store' do
65
+ before(:each) do
66
+ @test_hash = AttributeKit::AttributeHash.new
67
+ @test_hash.store(:blue, 'blue')
68
+ end
69
+
70
+ it "should store a value" do
71
+ @test_hash.empty?.should be_false
72
+ @test_hash[:blue].should == 'blue'
73
+ end
74
+
75
+ it "should be dirty" do
76
+ @test_hash.blue_dirty?.should be_true
77
+ end
78
+
79
+ it "should not be deleted" do
80
+ @test_hash.blue_deleted?.should be_false
81
+ end
82
+
83
+ it "should return the value set" do
84
+ ret = @test_hash.store(:red, 'red')
85
+ ret.should == 'red'
86
+ end
87
+ end
88
+
89
+ describe '#[]' do
90
+ before(:each) do
91
+ @test_hash = AttributeKit::AttributeHash.new('red')
92
+ @test_hash[:blue] = 'blue'
93
+ @test_hash.clean_attributes {}
94
+ end
95
+
96
+ it "should get a correct value (sanity check)" do
97
+ @test_hash.empty?.should be_false
98
+ @test_hash[:blue].should == 'blue'
99
+ end
100
+
101
+ it "should be clean" do
102
+ @test_hash.dirty?.should be_false
103
+ end
104
+ end
105
+
106
+ describe '#delete' do
107
+ before(:each) do
108
+ @test_hash = AttributeKit::AttributeHash.new
109
+ @test_hash[:blue] = 'blue'
110
+ @test_hash.clean_attributes {}
111
+ @test_hash.delete(:blue)
112
+ end
113
+
114
+ it "should delete a value" do
115
+ @test_hash[:blue].should be_nil
116
+ @test_hash.empty?.should be_true
117
+ end
118
+
119
+ it "should mark the attribute as deleted" do
120
+ @test_hash.blue_deleted?.should be_true
121
+ end
122
+
123
+ it "should mark the AttributeHash as dirty" do
124
+ @test_hash.dirty?.should be_true
125
+ end
126
+
127
+ it "should include the attribute in dirty_keys" do
128
+ @test_hash.dirty_keys.include?(:blue).should be_true
129
+ end
130
+
131
+ it "should include the attribute in deleted_keys" do
132
+ @test_hash.deleted_keys.include?(:blue).should be_true
133
+ end
134
+ end
135
+
136
+ describe '#delete_if' do
137
+ before(:each) do
138
+ @test_hash = AttributeKit::AttributeHash.new
139
+ @test_hash[:blue] = 'blue'
140
+ @test_hash[:red] = 'red'
141
+ @test_hash.clean_attributes {}
142
+ @test_hash.delete_if {|k,v| v == 'blue'}
143
+ end
144
+
145
+ it "should delete only the matching items" do
146
+ @test_hash[:blue].should be_nil
147
+ @test_hash[:red].should == 'red'
148
+ end
149
+
150
+ it "should mark deleted attributes as deleted" do
151
+ @test_hash.blue_deleted?.should be_true
152
+ end
153
+
154
+ it "should not mark untouched attributes as deleted" do
155
+ @test_hash.red_deleted?.should be_false
156
+ end
157
+
158
+ it "should include deleted attributes in dirty_keys" do
159
+ @test_hash.dirty_keys.include?(:blue).should be_true
160
+ end
161
+
162
+ it "should include deleted attributes in deleted_keys" do
163
+ @test_hash.deleted_keys.include?(:blue).should be_true
164
+ end
165
+
166
+ it "should not include undeleted attributes in dirty_keys" do
167
+ @test_hash.dirty_keys.include?(:red).should be_false
168
+ end
169
+
170
+ it "should not include undeleted attributes in deleted_keys" do
171
+ @test_hash.deleted_keys.include?(:red).should be_false
172
+ end
173
+
174
+ it "should mark the AttributeHash as dirty" do
175
+ @test_hash.dirty?.should be_true
176
+ end
177
+ end
178
+
179
+ describe '#keep_if' do
180
+ before(:each) do
181
+ @test_hash = AttributeKit::AttributeHash.new
182
+ @test_hash[:blue] = 'blue'
183
+ @test_hash[:red] = 'red'
184
+ @test_hash.clean_attributes {}
185
+ @test_hash.keep_if {|k,v| v == 'red'}
186
+ end
187
+
188
+ it "should delete only the matching items" do
189
+ @test_hash[:blue].should be_nil
190
+ @test_hash[:red].should == 'red'
191
+ end
192
+
193
+ it "should mark deleted attributes as deleted" do
194
+ @test_hash.blue_deleted?.should be_true
195
+ end
196
+
197
+ it "should not mark untouched attributes as deleted" do
198
+ @test_hash.red_deleted?.should be_false
199
+ end
200
+
201
+ it "should include deleted attributes in dirty_keys" do
202
+ @test_hash.dirty_keys.include?(:blue).should be_true
203
+ end
204
+
205
+ it "should include deleted attributes in deleted_keys" do
206
+ @test_hash.deleted_keys.include?(:blue).should be_true
207
+ end
208
+
209
+ it "should not include undeleted attributes in dirty_keys" do
210
+ @test_hash.dirty_keys.include?(:red).should be_false
211
+ end
212
+
213
+ it "should not include undeleted attributes in deleted_keys" do
214
+ @test_hash.deleted_keys.include?(:red).should be_false
215
+ end
216
+
217
+ it "should mark the AttributeHash as dirty" do
218
+ @test_hash.dirty?.should be_true
219
+ end
220
+ end
221
+
222
+ describe '#reject!' do
223
+ before(:each) do
224
+ @test_hash = AttributeKit::AttributeHash.new
225
+ @test_hash[:blue] = 'blue'
226
+ @test_hash[:red] = 'red'
227
+ @test_hash.clean_attributes {}
228
+ @test_hash.reject! {|k,v| v == 'blue'}
229
+ end
230
+
231
+ it "should delete only the matching items" do
232
+ @test_hash[:blue].should be_nil
233
+ @test_hash[:red].should == 'red'
234
+ end
235
+
236
+ it "should mark deleted attributes as deleted" do
237
+ @test_hash.blue_deleted?.should be_true
238
+ end
239
+
240
+ it "should not mark untouched attributes as deleted" do
241
+ @test_hash.red_deleted?.should be_false
242
+ end
243
+
244
+ it "should include deleted attributes in dirty_keys" do
245
+ @test_hash.dirty_keys.include?(:blue).should be_true
246
+ end
247
+
248
+ it "should include deleted attributes in deleted_keys" do
249
+ @test_hash.deleted_keys.include?(:blue).should be_true
250
+ end
251
+
252
+ it "should not include undeleted attributes in dirty_keys" do
253
+ @test_hash.dirty_keys.include?(:red).should be_false
254
+ end
255
+
256
+ it "should not include undeleted attributes in deleted_keys" do
257
+ @test_hash.deleted_keys.include?(:red).should be_false
258
+ end
259
+
260
+ it "should mark the AttributeHash as dirty" do
261
+ @test_hash.dirty?.should be_true
262
+ end
263
+
264
+ it "should return nil if no changes are made" do
265
+ test_hash = AttributeKit::AttributeHash.new
266
+ test_hash[:blue] = 'blue'
267
+ test_hash.clean_attributes {}
268
+ ret = test_hash.reject! {|k,v| v == 'red'}
269
+ ret.should be_nil
270
+ end
271
+ end
272
+
273
+ describe '#select!' do
274
+ before(:each) do
275
+ @test_hash = AttributeKit::AttributeHash.new
276
+ @test_hash[:blue] = 'blue'
277
+ @test_hash[:red] = 'red'
278
+ @test_hash.clean_attributes {}
279
+ @test_hash.select! {|k,v| v == 'red'}
280
+ end
281
+
282
+ it "should delete only the matching items" do
283
+ @test_hash[:blue].should be_nil
284
+ @test_hash[:red].should == 'red'
285
+ end
286
+
287
+ it "should mark deleted attributes as deleted" do
288
+ @test_hash.blue_deleted?.should be_true
289
+ end
290
+
291
+ it "should not mark untouched attributes as deleted" do
292
+ @test_hash.red_deleted?.should be_false
293
+ end
294
+
295
+ it "should include deleted attributes in dirty_keys" do
296
+ @test_hash.dirty_keys.include?(:blue).should be_true
297
+ end
298
+
299
+ it "should include deleted attributes in deleted_keys" do
300
+ @test_hash.deleted_keys.include?(:blue).should be_true
301
+ end
302
+
303
+ it "should not include undeleted attributes in dirty_keys" do
304
+ @test_hash.dirty_keys.include?(:red).should be_false
305
+ end
306
+
307
+ it "should not include undeleted attributes in deleted_keys" do
308
+ @test_hash.deleted_keys.include?(:red).should be_false
309
+ end
310
+
311
+ it "should mark the AttributeHash as dirty" do
312
+ @test_hash.dirty?.should be_true
313
+ end
314
+
315
+ it "should return nil if no changes are made" do
316
+ test_hash = AttributeKit::AttributeHash.new
317
+ test_hash[:blue] = 'blue'
318
+ test_hash.clean_attributes {}
319
+ ret = test_hash.select! {|k,v| v == 'blue'}
320
+ ret.should be_nil
321
+ end
322
+ end
323
+
324
+ describe "#replace" do
325
+ before(:each) do
326
+ @test_hash = AttributeKit::AttributeHash.new
327
+ @test_hash[:blue] = 'blue'
328
+ @test_hash[:red] = 'red'
329
+ @test_hash.clean_attributes {}
330
+ @ret_val = @test_hash.replace({:yellow => 'yellow', :green => 'green'})
331
+ end
332
+
333
+ it "should remove old contents" do
334
+ @test_hash[:blue].should be_nil
335
+ @test_hash[:red].should be_nil
336
+ end
337
+
338
+ it "should replace contents with supplied hash's contents" do
339
+ @test_hash[:yellow].should == 'yellow'
340
+ @test_hash[:green].should == 'green'
341
+ end
342
+
343
+ it "should mark old attributes as deleted" do
344
+ @test_hash.blue_deleted?.should be_true
345
+ @test_hash.red_deleted?.should be_true
346
+ end
347
+
348
+ it "should mark new attributes as dirty" do
349
+ @test_hash.yellow_dirty?.should be_true
350
+ @test_hash.green_dirty?.should be_true
351
+ end
352
+
353
+ it "should include old keys in deleted_keys" do
354
+ @test_hash.deleted_keys.include?(:blue).should be_true
355
+ @test_hash.deleted_keys.include?(:red).should be_true
356
+ end
357
+
358
+ it "should not include new keys in deleted_keys" do
359
+ @test_hash.deleted_keys.include?(:yellow).should be_false
360
+ @test_hash.deleted_keys.include?(:green).should be_false
361
+ end
362
+
363
+ it "should mark instance as dirty" do
364
+ @test_hash.dirty?.should be_true
365
+ end
366
+
367
+ it "should return an AttributeHash containing the new contents" do
368
+ @ret_val.class.should == AttributeKit::AttributeHash
369
+ @ret_val.eql?({:yellow => 'yellow', :green => 'green'}).should be_true
370
+ end
371
+ end
372
+
373
+ describe "#merge!" do
374
+ before(:each) do
375
+ @test_hash = AttributeKit::AttributeHash.new
376
+ @test_hash[:blue] = 'blue'
377
+ @test_hash[:red] = 'red'
378
+ @test_hash[:green] = 'grn'
379
+ @test_hash.clean_attributes {}
380
+ @ret_val = @test_hash.merge!({:yellow => 'yellow', :green => 'green'})
381
+ end
382
+
383
+ it "should retain unupdated contents" do
384
+ @test_hash.blue_deleted?.should be_false
385
+ @test_hash.red_deleted?.should be_false
386
+ @test_hash[:blue].should == 'blue'
387
+ @test_hash[:red].should == 'red'
388
+ end
389
+
390
+ it "should add supplied hash's contents to object's contents" do
391
+ @test_hash[:yellow].should == 'yellow'
392
+ @test_hash[:green].should == 'green'
393
+ end
394
+
395
+ it "should mark new attributes as dirty" do
396
+ @test_hash.yellow_dirty?.should be_true
397
+ end
398
+
399
+ it "should mark changed attributes as dirty" do
400
+ @test_hash.green_dirty?.should be_true
401
+ end
402
+
403
+ it "should not include unupdated keys in dirty_keys" do
404
+ @test_hash.dirty_keys.include?(:blue).should be_false
405
+ @test_hash.dirty_keys.include?(:red).should be_false
406
+ end
407
+
408
+ it "should include new keys in dirty_keys" do
409
+ @test_hash.dirty_keys.include?(:yellow).should be_true
410
+ end
411
+
412
+ it "should include changed keys in dirty_keys" do
413
+ @test_hash.dirty_keys.include?(:green).should be_true
414
+ end
415
+
416
+ it "should not include unupdated keys in deleted_keys" do
417
+ @test_hash.deleted_keys.include?(:blue).should be_false
418
+ @test_hash.deleted_keys.include?(:red).should be_false
419
+ end
420
+
421
+ it "should not include new keys in deleted_keys" do
422
+ @test_hash.deleted_keys.include?(:yellow).should be_false
423
+ @test_hash.deleted_keys.include?(:green).should be_false
424
+ end
425
+
426
+ it "should mark instance as dirty" do
427
+ @test_hash.dirty?.should be_true
428
+ end
429
+
430
+ it "should return an AttributeHash containing the new contents" do
431
+ @ret_val.class.should == AttributeKit::AttributeHash
432
+ @ret_val.eql?({:blue => 'blue', :red => 'red', :yellow => 'yellow', :green => 'green'}).should be_true
433
+ end
434
+ end
435
+
436
+ describe "#update" do
437
+ before(:each) do
438
+ @test_hash = AttributeKit::AttributeHash.new
439
+ @test_hash[:blue] = 'blue'
440
+ @test_hash[:red] = 'red'
441
+ @test_hash[:green] = 'grn'
442
+ @test_hash.clean_attributes {}
443
+ @ret_val = @test_hash.update({:yellow => 'yellow', :green => 'green'})
444
+ end
445
+
446
+ it "should retain unupdated contents" do
447
+ @test_hash.blue_deleted?.should be_false
448
+ @test_hash.red_deleted?.should be_false
449
+ @test_hash[:blue].should == 'blue'
450
+ @test_hash[:red].should == 'red'
451
+ end
452
+
453
+ it "should add supplied hash's contents to object's contents" do
454
+ @test_hash[:yellow].should == 'yellow'
455
+ @test_hash[:green].should == 'green'
456
+ end
457
+
458
+ it "should mark new attributes as dirty" do
459
+ @test_hash.yellow_dirty?.should be_true
460
+ end
461
+
462
+ it "should mark changed attributes as dirty" do
463
+ @test_hash.green_dirty?.should be_true
464
+ end
465
+
466
+ it "should not include unupdated keys in dirty_keys" do
467
+ @test_hash.dirty_keys.include?(:blue).should be_false
468
+ @test_hash.dirty_keys.include?(:red).should be_false
469
+ end
470
+
471
+ it "should include new keys in dirty_keys" do
472
+ @test_hash.dirty_keys.include?(:yellow).should be_true
473
+ end
474
+
475
+ it "should include changed keys in dirty_keys" do
476
+ @test_hash.dirty_keys.include?(:green).should be_true
477
+ end
478
+
479
+ it "should not include unupdated keys in deleted_keys" do
480
+ @test_hash.deleted_keys.include?(:blue).should be_false
481
+ @test_hash.deleted_keys.include?(:red).should be_false
482
+ end
483
+
484
+ it "should not include new keys in deleted_keys" do
485
+ @test_hash.deleted_keys.include?(:yellow).should be_false
486
+ @test_hash.deleted_keys.include?(:green).should be_false
487
+ end
488
+
489
+ it "should mark instance as dirty" do
490
+ @test_hash.dirty?.should be_true
491
+ end
492
+
493
+ it "should return an AttributeHash containing the new contents" do
494
+ @ret_val.class.should == AttributeKit::AttributeHash
495
+ @ret_val.eql?({:blue => 'blue', :red => 'red', :yellow => 'yellow', :green => 'green'}).should be_true
496
+ end
497
+ end
498
+
499
+ describe "#shift" do
500
+ before(:each) do
501
+ @test_hash = AttributeKit::AttributeHash.new
502
+ @test_hash[:blue] = 'blue'
503
+ @test_hash[:red] = 'red'
504
+ @test_hash.clean_attributes {}
505
+ @ret = @test_hash.shift
506
+ end
507
+
508
+ it "should delete a key-value pair" do
509
+ if @ret[0] == :blue
510
+ @test_hash[:blue].should be_nil
511
+ else
512
+ @test_hash[:red].should be_nil
513
+ end
514
+ end
515
+
516
+ it "should return a key-value pair" do
517
+ @ret.class.should == Array
518
+ if @ret[0] == :blue
519
+ @ret[1].should == 'blue'
520
+ else
521
+ @ret[1].should == 'red'
522
+ end
523
+ end
524
+
525
+ it "should mark the attribute as deleted" do
526
+ if @ret[0] == :blue
527
+ @test_hash.blue_deleted?.should be_true
528
+ else
529
+ @test_hash.red_deleted?.should be_true
530
+ end
531
+ end
532
+
533
+ it "should mark the AttributeHash as dirty" do
534
+ @test_hash.dirty?.should be_true
535
+ end
536
+
537
+ it "should include the attribute in dirty_keys" do
538
+ if @ret[0] == :blue
539
+ @test_hash.dirty_keys.include?(:blue).should be_true
540
+ else
541
+ @test_hash.dirty_keys.include?(:red).should be_true
542
+ end
543
+ end
544
+
545
+ it "should include the attribute in deleted_keys" do
546
+ if @ret[0] == :blue
547
+ @test_hash.deleted_keys.include?(:blue).should be_true
548
+ else
549
+ @test_hash.deleted_keys.include?(:red).should be_true
550
+ end
551
+ end
552
+ end
553
+
554
+ describe "#clear" do
555
+ before(:each) do
556
+ @test_hash = AttributeKit::AttributeHash.new
557
+ @test_hash[:blue] = 'blue'
558
+ @test_hash[:red] = 'red'
559
+ @test_hash[:green] = 'grn'
560
+ @test_hash.clean_attributes {}
561
+ @test_hash[:green] = 'green'
562
+ @old_keys = @test_hash.keys
563
+ @test_hash.clear
564
+ end
565
+
566
+ it "should empty the hash" do
567
+ @test_hash.empty?.should be_true
568
+ end
569
+
570
+ it 'should list all former keys as deleted' do
571
+ @test_hash.deleted_keys.should == @old_keys
572
+ end
573
+
574
+ it 'should list all former keys (and only former keys) as dirty, with no duplicates' do
575
+ @test_hash.dirty_keys.should == @old_keys
576
+ end
577
+
578
+ it 'should respond with true when sent #<key>_deleted?' do
579
+ @test_hash.green_deleted?.should be_true
580
+ end
581
+
582
+ it 'should mark the object as dirty' do
583
+ @test_hash.dirty?.should be_true
584
+ end
585
+ end
586
+
587
+ describe '#clean_attributes' do
588
+ before(:each) do
589
+ @test_hash = AttributeKit::AttributeHash.new
590
+ @test_hash[:blue] = 'blue'
591
+ @test_hash[:red] = 'red'
592
+ @test_hash[:green] = 'grn'
593
+ @test_hash.clean_attributes {}
594
+ @test_hash[:green] = 'green'
595
+ @test_hash[:yellow] = 'yellow'
596
+ @test_hash.delete(:red)
597
+ @test_hash.clean_attributes { |change_hash| @change_hash = change_hash }
598
+ end
599
+
600
+ it "should unmark the hash as dirty" do
601
+ @test_hash.dirty?.should be_false
602
+ end
603
+
604
+ it "should unmark an attribute as dirty" do
605
+ @test_hash.blue_dirty.should be_false
606
+ end
607
+
608
+ it "should unmark an attribute as deleted" do
609
+ @test_hash.red_deleted?.should be_false
610
+ end
611
+
612
+ it 'should pass the block a hash of changed keys' do
613
+ @change_hash.class.should == Hash
614
+ end
615
+
616
+ it 'should contain deleted keys in the passed hash' do
617
+ @change_hash.has_key?(:red).should be_true
618
+ end
619
+
620
+ it 'should contain changed keys in the passed hash' do
621
+ @change_hash.has_key?(:green).should be_true
622
+ end
623
+
624
+ it 'should contain added keys in the passed hash' do
625
+ @change_hash.has_key?(:yellow).should be_true
626
+ end
627
+
628
+ it 'should not contain unchanged keys in the passed hash' do
629
+ @change_hash.has_key?(:bluw).should be_false
630
+ end
631
+
632
+ it 'should contain deleted keys in the passed hash with an Array containing :deleted and nil as the value' do
633
+ @change_hash[:red].eql?([:deleted, nil]).should be_true
634
+ end
635
+
636
+ it 'should contain changed keys in the passed hash with an Array containing :changed and the new value as the value' do
637
+ @change_hash[:green].eql?([:changed, 'green']).should be_true
638
+ end
639
+
640
+ it 'should contain added keys in the passed hash with an Array containing :changed and the new value as the value' do
641
+ @change_hash[:yellow].eql?([:changed, 'yellow']).should be_true
642
+ end
643
+ end
644
+
645
+ end
646
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'attribute-kit'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: attribute-kit
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.1
6
+ platform: ruby
7
+ authors:
8
+ - Jonathan Mischo
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-07-11 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rspec
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 2.3.0
24
+ type: :development
25
+ prerelease: false
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: 1.0.0
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: jeweler
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.6.4
46
+ type: :development
47
+ prerelease: false
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: rcov
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: yard
62
+ requirement: &id005 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: rdiscount
73
+ requirement: &id006 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: *id006
82
+ description: Tools for attribute tracking like Hashes with dirty tracking and events, for building hybrid models and generally going beyond what's provided by your local ORM/DRM, while allowing you to expand what you can do with them, live without them, or roll your own
83
+ email: jon.mischo@gmail.com
84
+ executables: []
85
+
86
+ extensions: []
87
+
88
+ extra_rdoc_files:
89
+ - LICENSE.txt
90
+ - README.markdown
91
+ files:
92
+ - Gemfile
93
+ - Gemfile.lock
94
+ - LICENSE.txt
95
+ - README.markdown
96
+ - Rakefile
97
+ - VERSION
98
+ - lib/attribute-kit.rb
99
+ - lib/attribute-kit/attribute-kit.rb
100
+ - lib/attribute-kit/attribute_hash.rb
101
+ - spec/attribute-kit_spec.rb
102
+ - spec/spec_helper.rb
103
+ has_rdoc: true
104
+ homepage: http://github.com/supertaz/attribute-kit
105
+ licenses:
106
+ - MIT
107
+ post_install_message:
108
+ rdoc_options: []
109
+
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ hash: 3580658089229278680
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: "0"
127
+ requirements: []
128
+
129
+ rubyforge_project:
130
+ rubygems_version: 1.5.3
131
+ signing_key:
132
+ specification_version: 3
133
+ summary: Tools for attribute tracking like Hashes with dirty tracking and events, for building hybrid models and generally going beyond what's provided by your local ORM/DRM, while allowing you to expand what you can do with them, live without them, or roll your own
134
+ test_files: []
135
+