dirty_hashy 0.1.0 → 0.1.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.
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