dirty_hashy 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc CHANGED
@@ -1,5 +1,10 @@
1
1
  = DirtyHashy CHANGELOG
2
2
 
3
+ == Version 0.1.1 (December 24, 2011)
4
+
5
+ * Added convenience methods (with MethodMap) like +name+, +name=+, +name_changed?+, +name_was+ and +name_change+
6
+ * Added DirtyAttributes with which you can implement dirty tracking within objects (models)
7
+
3
8
  == Version 0.1.0 (December 22, 2011)
4
9
 
5
10
  * Initial release
data/README.textile CHANGED
@@ -1,6 +1,6 @@
1
1
  h1. DirtyHashy
2
2
 
3
- Dirty tracking within hashes with indifferent access
3
+ Dirty tracking within hashes with indifferent access or objects as it is expected to be!
4
4
 
5
5
  h2. Introduction
6
6
 
@@ -38,6 +38,7 @@ Using @DirtyHashy@ is pretty straightforward and can be used as follows:
38
38
  h.dirty? #=> false
39
39
  h[:name] = "Paul"
40
40
  h.dirty? #=> true
41
+ h.changed? :name #=> true
41
42
  h.was :name #=> nil
42
43
  h.change :name #=> [nil, "Paul"]
43
44
  h.clean_up!
@@ -63,13 +64,136 @@ Using @DirtyHashy@ is pretty straightforward and can be used as follows:
63
64
  h.change :company #=> ["Internetbureau Holder B.V.", nil]
64
65
  </pre>
65
66
 
67
+ h3. Method mapping DirtyHashy
68
+
69
+ You can map methods within a DirtyHashy in order to provide convenience methods like @name@, @name=@, @name_changed?@, @name_was@ and @name_change@. Just pass @true@ for the @map_methods@ argument when initializing a DirtyHashy:
70
+
71
+ <pre>
72
+ require "rubygems"
73
+ require "dirty_hashy"
74
+
75
+ h = DirtyHashy.new({}, true)
76
+ h.dirty? #=> false
77
+ h.name #=> NoMethodError: undefined method `name' for {}:DirtyHashy
78
+ h.name = "Paul"
79
+ h.dirty? #=> true
80
+ h.name_changed? #=> true
81
+ h.name_was #=> nil
82
+ h.name_change #=> [nil, "Paul"]
83
+ h.clean_up!
84
+ h.dirty? #=> false
85
+ h.name #=> "Paul"
86
+ h.name = "Paul"
87
+ h.dirty? #=> false
88
+ h.name = "Engel"
89
+ h.name_was #=> "Paul"
90
+ h.name_change #=> ["Paul", "Engel"]
91
+ h.foo = "Bar"
92
+ h.changes #=> {"name"=>["Paul", "Engel"], "foo"=>[nil, "Bar"]}
93
+ </pre>
94
+
95
+ h3. Dirty tracking objects (models)
96
+
97
+ Like "ActiveModel::Dirty":http://api.rubyonrails.org/classes/ActiveModel/Dirty.html, you can use "DirtyAttributes":https://github.com/archan937/dirty_hashy/blob/master/lib/dirty_attributes.rb to dirty track your own objects (models). But there are two differences:
98
+
99
+ # Setting up @DirtyAttributes@ is easier to setup than @ActiveModel::Dirty@
100
+ # The implementation of "DirtyAttributes":https://github.com/archan937/dirty_hashy/tree/master/lib is more minimalistic and thus looks a bit cleaner than "ActiveModel::Dirty":https://github.com/rails/rails/blob/master/activemodel/lib/active_model/dirty.rb with "ActiveModel::AttributeMethods":https://github.com/rails/rails/blob/master/activemodel/lib/active_model/attribute_methods.rb
101
+
102
+ The following illustrates the differences between @DirtyAttributes@ and @ActiveModel::Dirty@ when implementing a simple @Person@ model:
103
+
104
+ h4. When using ActiveModel::Dirty
105
+
106
+ <pre>
107
+ class Person
108
+ include ActiveModel::Dirty
109
+ define_attribute_methods = [:name]
110
+
111
+ def name
112
+ @name
113
+ end
114
+
115
+ def name=(val)
116
+ name_will_change! unless val == @name
117
+ @name = val
118
+ end
119
+
120
+ def save
121
+ @previously_changed = changes
122
+ @changed_attributes.clear
123
+ end
124
+ end
125
+ </pre>
126
+
127
+ h4. When using DirtyAttributes
128
+
129
+ <pre>
130
+ class Person
131
+ include DirtyAttributes
132
+ attrs :name
133
+
134
+ def save
135
+ clean_up!
136
+ end
137
+ end
138
+ </pre>
139
+
140
+ You can use @Person@ objects as you would expect:
141
+
142
+ <pre>
143
+ require "rubygems"
144
+ require "dirty_hashy"
145
+
146
+ class Person
147
+ include DirtyAttributes
148
+ attrs :name
149
+ end
150
+
151
+ p = Person.new
152
+ p.dirty? #=> false
153
+ p.name #=> nil
154
+ p.name = "Paul"
155
+ p.dirty? #=> true
156
+ p.name_changed? #=> true
157
+ p.name_was #=> nil
158
+ p.name_change #=> [nil, "Paul"]
159
+ p.clean_up!
160
+ p.dirty? #=> false
161
+ p.name #=> "Paul"
162
+ </pre>
163
+
164
+ And last but not least: don't care about specifying the attributes available? Well don't! ;)
165
+
166
+ <pre>
167
+ require "rubygems"
168
+ require "dirty_hashy"
169
+
170
+ class Person
171
+ include DirtyAttributes
172
+ end
173
+
174
+ p = Person.new
175
+ p.dirty? #=> false
176
+ p.name #=> NoMethodError: undefined method `name' for #<Person:0x00000100d5cd88>
177
+ p.name = "Paul"
178
+ p.dirty? #=> true
179
+ p.name #=> "Paul"
180
+ p.name_changed? #=> true
181
+ p.name_was #=> nil
182
+ p.name_change #=> [nil, "Paul"]
183
+ p.clean_up!
184
+ p.dirty? #=> false
185
+ p.name #=> "Paul"
186
+ p.foo = "bar"
187
+ p.foo #=> "bar"
188
+ </pre>
189
+
66
190
  h2. Last remarks
67
191
 
68
- Please check out "test/unit/test_dirty_hashy.rb":https://github.com/archan937/dirty_hashy/blob/master/test/unit/test_dirty_hashy.rb the tests available. You can run the unit tests with @rake@ within the terminal.
192
+ Please check out "test/unit/test_dirty_hashy.rb":https://github.com/archan937/dirty_hashy/blob/master/test/unit/test_dirty_hashy.rb and "test/unit/test_dirty_attributes.rb":https://github.com/archan937/dirty_hashy/blob/master/test/unit/test_dirty_attributes.rb the tests available. You can run the unit tests with @rake@ within the terminal.
69
193
 
70
194
  Also, the DirtyHashy repo is provided with @script/console@ which you can use for testing purposes.
71
195
 
72
- Note: *DirtyHashy is successfully tested using Ruby 1.8.7 and Ruby 1.9.2*
196
+ Note: *DirtyHashy is successfully tested using Ruby 1.8.7, Ruby 1.9.2 and Ruby 1.9.3*
73
197
 
74
198
  h2. Contact me
75
199
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.1
data/dirty_hashy.gemspec CHANGED
@@ -3,8 +3,8 @@
3
3
  Gem::Specification.new do |gem|
4
4
  gem.authors = ["Paul Engel"]
5
5
  gem.email = ["paul.engel@holder.nl"]
6
- gem.description = %q{Dirty tracking within hashes with indifferent access}
7
- gem.summary = %q{Dirty tracking within hashes with indifferent access}
6
+ gem.description = %q{Dirty tracking within hashes with indifferent access or objects as it is expected to be!}
7
+ gem.summary = %q{Dirty tracking within hashes with indifferent access or objects as it is expected to be!}
8
8
  gem.homepage = "https://github.com/archan937/dirty_hashy"
9
9
 
10
10
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -12,7 +12,7 @@ Gem::Specification.new do |gem|
12
12
  gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
13
13
  gem.name = "dirty_hashy"
14
14
  gem.require_paths = ["lib"]
15
- gem.version = "0.1.0"
15
+ gem.version = "0.1.1"
16
16
 
17
17
  gem.add_dependency "activesupport", ">= 3.0.0"
18
18
  end
@@ -0,0 +1,35 @@
1
+ module DirtyAttributes
2
+
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ base.send :include, InstanceMethods
6
+ base.send :include, MethodMap
7
+ end
8
+
9
+ module ClassMethods
10
+ def attrs(*names)
11
+ @attrs = names.collect(&:to_s)
12
+ end
13
+
14
+ def attributes
15
+ @attrs || []
16
+ end
17
+ end
18
+
19
+ module InstanceMethods
20
+ attr_reader :attributes
21
+
22
+ def initialize
23
+ attrs = self.class.attributes.inject({}){|h, a| h.merge({a => nil})}
24
+ @attributes = DirtyHashy.new(attrs).tap do |hashy|
25
+ dirty_map! hashy, attrs.keys
26
+ clean_up!
27
+ end
28
+ end
29
+
30
+ def attributes=(other)
31
+ attributes.clear.merge! other
32
+ end
33
+ end
34
+
35
+ end
data/lib/dirty_hashy.rb CHANGED
@@ -1,8 +1,19 @@
1
1
  require "active_support/hash_with_indifferent_access"
2
2
  require "dirty_hashy/version"
3
+ require "dirty_attributes"
4
+ require "method_map"
3
5
 
4
6
  class DirtyHashy < HashWithIndifferentAccess
5
7
 
8
+ def self.new(constructor = {}, map_methods = false)
9
+ super(constructor).tap do |instance|
10
+ if map_methods
11
+ instance.extend MethodMap
12
+ instance.dirty_map!
13
+ end
14
+ end
15
+ end
16
+
6
17
  alias :_regular_writer :regular_writer
7
18
  def regular_writer(key, value)
8
19
  original_value = changes.key?(key) ? was(key) : self[key]
@@ -1,7 +1,7 @@
1
1
  class DirtyHashy < HashWithIndifferentAccess
2
2
  MAJOR = 0
3
3
  MINOR = 1
4
- TINY = 0
4
+ TINY = 1
5
5
 
6
6
  VERSION = [MAJOR, MINOR, TINY].join(".")
7
7
  end
data/lib/method_map.rb ADDED
@@ -0,0 +1,54 @@
1
+ module MethodMap
2
+
3
+ def dirty_map!(mapped = nil, restricted_keys = nil)
4
+ @mapped = mapped || self
5
+ @restricted_keys = (restricted_keys || []).collect(&:to_s)
6
+ map_method(:changes)
7
+ map_method(:dirty?)
8
+ map_method(:changed?)
9
+ map_method(:clean_up!)
10
+ map_method(/^([\w_]+)_changed\?$/, Proc.new{ |match| :changed? if map_key? match })
11
+ map_method(/^([\w_]+)_change$/ , Proc.new{ |match| :change if map_key? match })
12
+ map_method(/^([\w_]+)_was$/ , Proc.new{ |match| :was if map_key? match })
13
+ map_method(/(^[\w_]+)=$/ , Proc.new{ |match| :[]= if accept_key? match })
14
+ map_method(/(^[\w_]+)$/ , Proc.new{ |match| :[] if map_key? match })
15
+ end
16
+
17
+ def map_method(pattern, method_or_proc = nil)
18
+ regex = pattern.is_a?(Regexp) ? pattern : Regexp.new("^#{Regexp.escape(pattern.to_s)}$")
19
+ method_map[regex] = method_or_proc || pattern
20
+ end
21
+
22
+ def method_missing(method, *args)
23
+ if m = match_method(method)
24
+ @mapped.send *(m + args)
25
+ else
26
+ super
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def map_key?(key)
33
+ (@mapped.keys + @mapped.changes.keys).include?(key.to_s)
34
+ end
35
+
36
+ def accept_key?(key)
37
+ @restricted_keys.empty? || @restricted_keys.include?(key.to_s)
38
+ end
39
+
40
+ def method_map
41
+ @method_map ||= {}
42
+ end
43
+
44
+ def match_method(method)
45
+ method_map.each do |pattern, method_or_proc|
46
+ if method.to_s.match pattern
47
+ m = method_or_proc.is_a?(Proc) ? method_or_proc.call($1 || method) : method_or_proc
48
+ return [m, $1].compact if m
49
+ end
50
+ end
51
+ nil
52
+ end
53
+
54
+ end
@@ -0,0 +1,126 @@
1
+ require File.expand_path("../../test_helper", __FILE__)
2
+
3
+ module Unit
4
+ class TestDirtyAttributes < MiniTest::Unit::TestCase
5
+
6
+ describe DirtyAttributes do
7
+ it "should behave as expected without key restriction" do
8
+ class Person
9
+ include DirtyAttributes
10
+ end
11
+
12
+ person = Person.new
13
+
14
+ assert_equal({}, person.attributes)
15
+ assert_equal false, person.dirty?
16
+ assert_equal false, person.changed?
17
+ assert_equal({}, person.changes)
18
+
19
+ assert_raises(NoMethodError) do
20
+ person.name
21
+ end
22
+
23
+ person.name = "Paul"
24
+
25
+ assert_equal "Paul", person.name
26
+ assert_equal true, person.dirty?
27
+ assert_equal true, person.changed?
28
+ assert_equal true, person.name_changed?
29
+ assert_equal [nil, "Paul"], person.name_change
30
+ assert_equal({"name" => [nil, "Paul"]}, person.changes)
31
+
32
+ person.name = nil
33
+
34
+ assert_equal false, person.dirty?
35
+ assert_equal false, person.changed?
36
+ assert_equal false, person.name_changed?
37
+ assert_equal nil, person.name_change
38
+ assert_equal({}, person.changes)
39
+
40
+ person.name = "Stephan"
41
+
42
+ assert_equal true, person.dirty?
43
+ assert_equal true, person.changed?
44
+ assert_equal true, person.name_changed?
45
+ assert_equal [nil, "Stephan"], person.name_change
46
+ assert_equal({"name" => [nil, "Stephan"]}, person.changes)
47
+
48
+ person.clean_up!
49
+
50
+ assert_equal false, person.dirty?
51
+ assert_equal false, person.changed?
52
+ assert_equal false, person.name_changed?
53
+ assert_equal nil, person.name_change
54
+ assert_equal({}, person.changes)
55
+
56
+ person.foo = "Bar"
57
+
58
+ assert_equal "Bar", person.foo
59
+ assert_equal true, person.dirty?
60
+ assert_equal true, person.changed?
61
+ assert_equal false, person.name_changed?
62
+ assert_equal true, person.foo_changed?
63
+ assert_equal nil, person.name_change
64
+ assert_equal [nil, "Bar"], person.foo_change
65
+ assert_equal({"foo" => [nil, "Bar"]}, person.changes)
66
+ end
67
+
68
+ it "should behave as expected with key restriction" do
69
+ class User
70
+ include DirtyAttributes
71
+ attrs :name
72
+ end
73
+
74
+ user = User.new
75
+
76
+ assert_equal({"name" => nil}, user.attributes)
77
+ assert_equal nil, user.name
78
+ assert_equal false, user.dirty?
79
+ assert_equal false, user.changed?
80
+ assert_equal({}, user.changes)
81
+
82
+ assert_raises(NoMethodError) do
83
+ user.foo
84
+ end
85
+
86
+ assert_raises(NoMethodError) do
87
+ user.foo = "Bar"
88
+ end
89
+
90
+ user.name = "Paul"
91
+
92
+ assert_equal "Paul", user.name
93
+ assert_equal true, user.dirty?
94
+ assert_equal true, user.changed?
95
+ assert_equal true, user.name_changed?
96
+ assert_equal [nil, "Paul"], user.name_change
97
+ assert_equal({"name" => [nil, "Paul"]}, user.changes)
98
+
99
+ user.name = nil
100
+
101
+ assert_equal false, user.dirty?
102
+ assert_equal false, user.changed?
103
+ assert_equal false, user.name_changed?
104
+ assert_equal nil, user.name_change
105
+ assert_equal({}, user.changes)
106
+
107
+ user.name = "Stephan"
108
+
109
+ assert_equal true, user.dirty?
110
+ assert_equal true, user.changed?
111
+ assert_equal true, user.name_changed?
112
+ assert_equal [nil, "Stephan"], user.name_change
113
+ assert_equal({"name" => [nil, "Stephan"]}, user.changes)
114
+
115
+ user.clean_up!
116
+
117
+ assert_equal false, user.dirty?
118
+ assert_equal false, user.changed?
119
+ assert_equal false, user.name_changed?
120
+ assert_equal nil, user.name_change
121
+ assert_equal({}, user.changes)
122
+ end
123
+ end
124
+
125
+ end
126
+ end
@@ -4,13 +4,17 @@ module Unit
4
4
  class TestDirtyHashy < MiniTest::Unit::TestCase
5
5
 
6
6
  describe DirtyHashy do
7
- it "should behave as expected" do
7
+ it "should behave as expected without method mapping" do
8
8
  hashy = DirtyHashy.new
9
9
 
10
10
  assert_equal false, hashy.dirty?
11
11
  assert_equal false, hashy.changed?
12
12
  assert_equal({}, hashy.changes)
13
13
 
14
+ assert_raises(NoMethodError) do
15
+ hashy.name = "Paul"
16
+ end
17
+
14
18
  hashy["name"] = "Paul"
15
19
 
16
20
  assert_equal true, hashy.dirty?
@@ -134,6 +138,98 @@ module Unit
134
138
  assert_equal ["Holder", nil], hashy.change(:company)
135
139
  assert_equal ["Holder", nil], hashy.change("company")
136
140
  end
141
+
142
+ it "should behave as expected with method mapping" do
143
+ hashy = DirtyHashy.new({}, true)
144
+
145
+ assert_equal false, hashy.dirty?
146
+ assert_equal false, hashy.changed?
147
+ assert_equal({}, hashy.changes)
148
+
149
+ assert_raises(NoMethodError) do
150
+ hashy.name
151
+ end
152
+
153
+ hashy.name = "Paul"
154
+
155
+ assert_equal "Paul", hashy.name
156
+ assert_equal true, hashy.dirty?
157
+ assert_equal true, hashy.changed?
158
+ assert_equal true, hashy.name_changed?
159
+ assert_equal true, hashy.changed?(:name)
160
+ assert_equal [nil, "Paul"], hashy.name_change
161
+ assert_equal [nil, "Paul"], hashy.change(:name)
162
+ assert_equal({"name" => [nil, "Paul"]}, hashy.changes)
163
+
164
+ hashy.name = nil
165
+
166
+ assert_equal false, hashy.dirty?
167
+ assert_equal false, hashy.changed?
168
+ assert_equal false, hashy.name_changed?
169
+ assert_equal false, hashy.changed?(:name)
170
+ assert_equal nil, hashy.name_change
171
+ assert_equal nil, hashy.change(:name)
172
+ assert_equal({}, hashy.changes)
173
+
174
+ hashy.name = "Stephan"
175
+
176
+ assert_equal true, hashy.dirty?
177
+ assert_equal true, hashy.changed?
178
+ assert_equal true, hashy.name_changed?
179
+ assert_equal true, hashy.changed?(:name)
180
+ assert_equal [nil, "Stephan"], hashy.name_change
181
+ assert_equal [nil, "Stephan"], hashy.change(:name)
182
+ assert_equal({"name" => [nil, "Stephan"]}, hashy.changes)
183
+
184
+ hashy.clean_up!
185
+
186
+ assert_equal false, hashy.dirty?
187
+ assert_equal false, hashy.changed?
188
+ assert_equal false, hashy.name_changed?
189
+ assert_equal false, hashy.changed?(:name)
190
+ assert_equal nil, hashy.name_change
191
+ assert_equal nil, hashy.change(:name)
192
+ assert_equal({}, hashy.changes)
193
+
194
+ hashy.name = "Chris"
195
+
196
+ assert_equal true, hashy.dirty?
197
+ assert_equal true, hashy.changed?
198
+ assert_equal true, hashy.name_changed?
199
+ assert_equal true, hashy.changed?(:name)
200
+ assert_equal "Stephan", hashy.name_was
201
+ assert_equal "Stephan", hashy.was(:name)
202
+ assert_equal ["Stephan", "Chris"], hashy.name_change
203
+ assert_equal ["Stephan", "Chris"], hashy.change(:name)
204
+ assert_equal({"name" => ["Stephan", "Chris"]}, hashy.changes)
205
+
206
+ hashy.name = "Stephan"
207
+
208
+ assert_equal false, hashy.dirty?
209
+ assert_equal false, hashy.changed?
210
+ assert_equal false, hashy.name_changed?
211
+ assert_equal false, hashy.changed?(:name)
212
+ assert_equal nil, hashy.name_change
213
+ assert_equal nil, hashy.change(:name)
214
+ assert_equal({}, hashy.changes)
215
+
216
+ hashy.name = "Tim"
217
+ hashy.city = "Amsterdam"
218
+
219
+ assert_equal true, hashy.city_changed?
220
+ assert_equal true, hashy.changed?(:city)
221
+ assert_equal nil, hashy.city_was
222
+ assert_equal [nil, "Amsterdam"], hashy.city_change
223
+ assert_equal({"name" => ["Stephan", "Tim"], "city" => [nil, "Amsterdam"]}, hashy.changes)
224
+
225
+ hashy.delete :city
226
+ assert_equal({"name" => ["Stephan", "Tim"]}, hashy.changes)
227
+
228
+ hashy.clean_up!
229
+
230
+ assert_equal false, hashy.dirty?
231
+ assert_equal false, hashy.changed?
232
+ end
137
233
  end
138
234
 
139
235
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dirty_hashy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-22 00:00:00.000000000 Z
12
+ date: 2011-12-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
16
- requirement: &2156836380 !ruby/object:Gem::Requirement
16
+ requirement: &2152647000 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,8 +21,9 @@ dependencies:
21
21
  version: 3.0.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2156836380
25
- description: Dirty tracking within hashes with indifferent access
24
+ version_requirements: *2152647000
25
+ description: Dirty tracking within hashes with indifferent access or objects as it
26
+ is expected to be!
26
27
  email:
27
28
  - paul.engel@holder.nl
28
29
  executables: []
@@ -37,10 +38,13 @@ files:
37
38
  - Rakefile
38
39
  - VERSION
39
40
  - dirty_hashy.gemspec
41
+ - lib/dirty_attributes.rb
40
42
  - lib/dirty_hashy.rb
41
43
  - lib/dirty_hashy/version.rb
44
+ - lib/method_map.rb
42
45
  - script/console
43
46
  - test/test_helper.rb
47
+ - test/unit/test_dirty_attributes.rb
44
48
  - test/unit/test_dirty_hashy.rb
45
49
  homepage: https://github.com/archan937/dirty_hashy
46
50
  licenses: []
@@ -65,7 +69,9 @@ rubyforge_project:
65
69
  rubygems_version: 1.8.10
66
70
  signing_key:
67
71
  specification_version: 3
68
- summary: Dirty tracking within hashes with indifferent access
72
+ summary: Dirty tracking within hashes with indifferent access or objects as it is
73
+ expected to be!
69
74
  test_files:
70
75
  - test/test_helper.rb
76
+ - test/unit/test_dirty_attributes.rb
71
77
  - test/unit/test_dirty_hashy.rb