dirty_hashy 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc CHANGED
@@ -1,5 +1,12 @@
1
1
  = DirtyHashy CHANGELOG
2
2
 
3
+ == Version 0.2.0 (January 4, 2011)
4
+
5
+ * Extracted DirtyHashy implementation to Dirty::Hash
6
+ * Renamed DirtyAttributes to Dirty::Attributes
7
+ * Inheriting DirtyHashy from Hash instead of HashWithIndifferentAccess on request of @technoweenie
8
+ * Introducing DirtyIndifferentHashy which is the previously known DirtyHashy
9
+
3
10
  == Version 0.1.3 (December 24, 2011)
4
11
 
5
12
  * Fixed incorrect regular expressions within dirty_map!
data/README.textile CHANGED
@@ -26,15 +26,23 @@ Run the following in your console to install with Bundler:
26
26
  bundle install
27
27
  </pre>
28
28
 
29
+ h2. DirtyHashy versus DirtyIndifferentHashy
30
+
31
+ "On request":https://github.com/archan937/dirty_hashy/issues/1 of "@technoweenie":https://twitter.com/technoweenie, I have released DirtyHashy v0.2.0. Previous to this release, the @DirtyHashy@ class inherited from ActiveSupport's @HashWithIndifferentAccess@ *but that has changed*.
32
+
33
+ As of the @0.2.0@ release, the @DirtyHashy@ class is inherited from the @Hash@ class. So I have introduced @DirtyIndifferentHashy@ which resembles the @DirtyHashy@ class of the @0.1.x@ releases. You can check out the different types of behaviour by looking at the "DirtyHashy test":https://github.com/archan937/dirty_hashy/blob/279da72873a91216dbecadb3a6d53ebae2fd8198/test/unit/test_dirty_hashy.rb and "DirtyIndifferentHashy test":https://github.com/archan937/dirty_hashy/blob/279da72873a91216dbecadb3a6d53ebae2fd8198/test/unit/test_dirty_indifferent_hashy.rb.
34
+
35
+ *Note*: This README will continue focusing on the usage of the @DirtyIndifferentHashy@ class and @Dirty::Attributes@ module.
36
+
29
37
  h2. Usage
30
38
 
31
- Using @DirtyHashy@ is pretty straightforward and can be used as follows:
39
+ Using @DirtyIndifferentHashy@ is pretty straightforward and can be used as follows:
32
40
 
33
41
  <pre>
34
42
  require "rubygems"
35
43
  require "dirty_hashy"
36
44
 
37
- h = DirtyHashy.new
45
+ h = DirtyIndifferentHashy.new
38
46
  h.dirty? #=> false
39
47
  h[:name] = "Paul"
40
48
  h.dirty? #=> true
@@ -64,17 +72,17 @@ Using @DirtyHashy@ is pretty straightforward and can be used as follows:
64
72
  h.change :company #=> ["Internetbureau Holder B.V.", nil]
65
73
  </pre>
66
74
 
67
- h3. Method mapping DirtyHashy
75
+ h3. Method mapping DirtyIndifferentHashy
68
76
 
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:
77
+ You can map methods within a DirtyIndifferentHashy 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 DirtyIndifferentHashy:
70
78
 
71
79
  <pre>
72
80
  require "rubygems"
73
81
  require "dirty_hashy"
74
82
 
75
- h = DirtyHashy.new({}, true)
83
+ h = DirtyIndifferentHashy.new({}, true)
76
84
  h.dirty? #=> false
77
- h.name #=> NoMethodError: undefined method `name' for {}:DirtyHashy
85
+ h.name #=> NoMethodError: undefined method `name' for {}:DirtyIndifferentHashy
78
86
  h.name = "Paul"
79
87
  h.dirty? #=> true
80
88
  h.name_changed? #=> true
@@ -92,15 +100,15 @@ You can map methods within a DirtyHashy in order to provide convenience methods
92
100
  h.changes #=> {"name"=>["Paul", "Engel"], "foo"=>[nil, "bar"]}
93
101
  </pre>
94
102
 
95
- h3. Method mapping DirtyHashy with key restriction
103
+ h3. Method mapping DirtyIndifferentHashy with key restriction
96
104
 
97
- Along with providing convenience methods, you can also restrict the range of keys you are permitted to read / write / merge / replace of a DirtyHash:
105
+ Along with providing convenience methods, you can also restrict the range of keys you are permitted to read / write / merge / replace of a DirtyIndifferentHashy:
98
106
 
99
107
  <pre>
100
108
  require "rubygems"
101
109
  require "dirty_hashy"
102
110
 
103
- h = DirtyHashy.new({}, true, [:name])
111
+ h = DirtyIndifferentHashy.new({}, true, [:name])
104
112
  h.dirty? #=> false
105
113
  h.name #=> nil
106
114
  h.name = "Paul"
@@ -112,8 +120,8 @@ Along with providing convenience methods, you can also restrict the range of key
112
120
  h.name #=> "Engel"
113
121
  h.name_was #=> nil
114
122
  h.name_change #=> [nil, "Engel"]
115
- h.foo #=> NoMethodError: undefined method `foo' for {"name"=>"Engel"}:DirtyHashy
116
- h.foo = "bar" #=> NoMethodError: undefined method `foo=' for {"name"=>"Engel"}:DirtyHashy
123
+ h.foo #=> NoMethodError: undefined method `foo' for {"name"=>"Engel"}:DirtyIndifferentHashy
124
+ h.foo = "bar" #=> NoMethodError: undefined method `foo=' for {"name"=>"Engel"}:DirtyIndifferentHashy
117
125
  h.clean_up!
118
126
  h.replace :name => "Paul"
119
127
  h.changes #=> {"name"=>["Engel", "Paul"]}
@@ -121,12 +129,12 @@ Along with providing convenience methods, you can also restrict the range of key
121
129
 
122
130
  h3. Dirty tracking objects (models)
123
131
 
124
- 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:
132
+ Like "ActiveModel::Dirty":http://api.rubyonrails.org/classes/ActiveModel/Dirty.html, you can use "Dirty::Attributes":https://github.com/archan937/dirty_hashy/blob/master/lib/dirty_attributes.rb to dirty track your own objects (models). But there are two differences:
125
133
 
126
- # Setting up @DirtyAttributes@ is easier to setup than @ActiveModel::Dirty@
127
- # 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
134
+ # Setting up @Dirty::Attributes@ is easier to setup than @ActiveModel::Dirty@
135
+ # The implementation of "Dirty::Attributes":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
128
136
 
129
- The following illustrates the differences between @DirtyAttributes@ and @ActiveModel::Dirty@ when implementing a simple @Person@ model:
137
+ The following illustrates the differences between @Dirty::Attributes@ and @ActiveModel::Dirty@ when implementing a simple @Person@ model:
130
138
 
131
139
  h4. When using ActiveModel::Dirty
132
140
 
@@ -151,11 +159,11 @@ h4. When using ActiveModel::Dirty
151
159
  end
152
160
  </pre>
153
161
 
154
- h4. When using DirtyAttributes
162
+ h4. When using Dirty::Attributes
155
163
 
156
164
  <pre>
157
165
  class Person
158
- include DirtyAttributes
166
+ include Dirty::Attributes
159
167
  attrs :name
160
168
 
161
169
  def save
@@ -171,7 +179,7 @@ You can use @Person@ objects as you would expect:
171
179
  require "dirty_hashy"
172
180
 
173
181
  class Person
174
- include DirtyAttributes
182
+ include Dirty::Attributes
175
183
  attrs :name
176
184
  end
177
185
 
@@ -196,7 +204,7 @@ And last but not least: don't care about specifying the attributes available? We
196
204
  require "dirty_hashy"
197
205
 
198
206
  class Person
199
- include DirtyAttributes
207
+ include Dirty::Attributes
200
208
  end
201
209
 
202
210
  p = Person.new
@@ -217,7 +225,8 @@ And last but not least: don't care about specifying the attributes available? We
217
225
 
218
226
  h2. Last remarks
219
227
 
220
- 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.
228
+ Please check out "test/unit/test_dirty_hashy.rb":https://github.com/archan937/dirty_hashy/blob/master/test/unit/test_dirty_hashy.rb, "test/unit/test_dirty_indifferent_hashy.rb":https://github.com/archan937/dirty_hashy/blob/master/test/unit/test_dirty_indifferent_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.
229
+ You can run the unit tests with @rake@ within the terminal.
221
230
 
222
231
  Also, the DirtyHashy repo is provided with @script/console@ which you can use for testing purposes.
223
232
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3
1
+ 0.2.0
data/dirty_hashy.gemspec CHANGED
@@ -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.3"
15
+ gem.version = "0.2.0"
16
16
 
17
17
  gem.add_dependency "activesupport", ">= 3.0.0"
18
18
  end
data/lib/dirty.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "dirty/hash"
2
+ require "dirty/attributes"
@@ -0,0 +1,39 @@
1
+ module Dirty
2
+ module Attributes
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ base.send :include, InstanceMethods
7
+ base.send :include, MethodMap
8
+ end
9
+
10
+ module ClassMethods
11
+ def attrs(*names)
12
+ @attrs = names.collect(&:to_s)
13
+ end
14
+
15
+ def attributes
16
+ @attrs
17
+ end
18
+ end
19
+
20
+ module InstanceMethods
21
+ attr_reader :attributes
22
+
23
+ def initialize
24
+ @attributes = DirtyIndifferentHashy.new({}, true, self.class.attributes).tap do |hashy|
25
+ dirty_map! hashy
26
+ clean_up!
27
+ end
28
+ end
29
+
30
+ def attributes=(other)
31
+ attributes.replace other
32
+ rescue IndexError => e
33
+ e.message.match /"(.*)"/
34
+ raise NoMethodError, "undefined method `#{$1}=' for #{self.inspect}"
35
+ end
36
+ end
37
+
38
+ end
39
+ end
data/lib/dirty/hash.rb ADDED
@@ -0,0 +1,108 @@
1
+ module Dirty
2
+ module Hash
3
+
4
+ def self.included(base)
5
+ base.method_defined?(:regular_writer).tap do |defined|
6
+ base.send :include, InstanceMethods
7
+ unless defined
8
+ base.send :alias_method, :_store, :store
9
+ base.send :alias_method, :store, :regular_writer
10
+ base.send :alias_method, :[]=, :store
11
+ base.send :define_method, :update do |other|
12
+ other.each{|key, value| store key, value}
13
+ end
14
+ base.send :alias_method, :merge!, :update
15
+ end
16
+ end
17
+ end
18
+
19
+ module InstanceMethods
20
+ def initialize(constructor = {}, map_methods = false, restricted_keys = nil)
21
+ constructor.each do |key, value|
22
+ self[key] = value
23
+ end
24
+ if map_methods
25
+ extend MethodMap
26
+ dirty_map!
27
+ end
28
+ if restricted_keys
29
+ restricted_keys.each{|key| self[correct_key(key, map_methods)] ||= nil}
30
+ @restricted_keys = keys
31
+ end
32
+ end
33
+
34
+ def replace(other)
35
+ clear
36
+ merge! other
37
+ end
38
+
39
+ def clear
40
+ keys.each{|key| delete key}
41
+ end
42
+
43
+ def [](key, mapped = false)
44
+ validate_read!(key) if mapped || restricted_keys?
45
+ super(key)
46
+ end
47
+
48
+ def regular_writer(key, value)
49
+ validate_write!(key)
50
+ original_value = changes.key?(key) ? was(key) : fetch(key, nil)
51
+ if original_value == value
52
+ changes.delete key
53
+ else
54
+ changes[key] = [original_value, value]
55
+ end
56
+ defined?(_store) ? _store(key, value) : super(key, value)
57
+ end
58
+
59
+ def delete(key)
60
+ self[key] = nil
61
+ super
62
+ end
63
+
64
+ def changes
65
+ @changes ||= self.class.superclass.new
66
+ end
67
+
68
+ def changed?(key = nil, mapped = false)
69
+ validate_read!(correct_key(key, mapped)) if !key.nil? && (mapped || restricted_keys?)
70
+ key.nil? ? !changes.empty? : changes.key?(key)
71
+ end
72
+ alias :dirty? :changed?
73
+
74
+ def change(key, mapped = false)
75
+ changes[key] if changed?(key, mapped)
76
+ end
77
+
78
+ def was(key, mapped = false)
79
+ change(key).first if changed?(key, mapped)
80
+ end
81
+
82
+ def clean_up!
83
+ changes.clear
84
+ nil
85
+ end
86
+
87
+ private
88
+
89
+ def correct_key(key, mapped = false)
90
+ mapped || is_a?(HashWithIndifferentAccess) ? key.to_s : key
91
+ end
92
+
93
+ def restricted_keys?
94
+ !(@restricted_keys || []).empty?
95
+ end
96
+
97
+ def validate_read!(key)
98
+ raise IndexError, "Invalid key: \"#{key}\"" unless (keys + changes.keys).include?(key)
99
+ end
100
+
101
+ def validate_write!(key)
102
+ raise IndexError, "Invalid key: \"#{key}\"" unless @restricted_keys.nil? || @restricted_keys.empty? || @restricted_keys.include?(key)
103
+ end
104
+
105
+ end
106
+
107
+ end
108
+ end
data/lib/dirty_hashy.rb CHANGED
@@ -1,88 +1,8 @@
1
- require "active_support/hash_with_indifferent_access"
1
+ require "dirty"
2
+ require "dirty_indifferent_hashy"
2
3
  require "dirty_hashy/version"
3
- require "dirty_attributes"
4
4
  require "method_map"
5
5
 
6
- class DirtyHashy < HashWithIndifferentAccess
7
-
8
- def initialize(constructor = {}, map_methods = false, restricted_keys = nil)
9
- super constructor
10
- if map_methods
11
- extend MethodMap
12
- dirty_map!
13
- end
14
- if restricted_keys
15
- restricted_keys.each{|key| self[key] ||= nil}
16
- @restricted_keys = keys
17
- end
18
- end
19
-
20
- def replace(other)
21
- clear
22
- merge! other
23
- end
24
-
25
- def clear
26
- keys.each{|key| delete key}
27
- end
28
-
29
- def [](key, mapped = false)
30
- validate_read!(key) if mapped || restricted_keys?
31
- super(key)
32
- end
33
-
34
- alias :_regular_writer :regular_writer
35
- def regular_writer(key, value)
36
- validate_write!(key)
37
- original_value = changes.key?(key) ? was(key) : fetch(key, nil)
38
- if original_value == value
39
- changes.delete key
40
- else
41
- changes[key] = [original_value, value]
42
- end
43
- _regular_writer key, value
44
- end
45
-
46
- def delete(key)
47
- self[key] = nil
48
- super
49
- end
50
-
51
- def changes
52
- @changes ||= HashWithIndifferentAccess.new
53
- end
54
-
55
- def changed?(key = nil, mapped = false)
56
- validate_read!(key) if !key.nil? && (mapped || restricted_keys?)
57
- key.nil? ? !changes.empty? : changes.key?(key)
58
- end
59
- alias :dirty? :changed?
60
-
61
- def change(key, mapped = false)
62
- changes[key] if changed?(key, mapped)
63
- end
64
-
65
- def was(key, mapped = false)
66
- change(key).first if changed?(key, mapped)
67
- end
68
-
69
- def clean_up!
70
- changes.clear
71
- nil
72
- end
73
-
74
- private
75
-
76
- def restricted_keys?
77
- !(@restricted_keys || []).empty?
78
- end
79
-
80
- def validate_read!(key)
81
- raise IndexError, "Invalid key: \"#{key}\"" unless (keys + changes.keys).include?(key.to_s)
82
- end
83
-
84
- def validate_write!(key)
85
- raise IndexError, "Invalid key: \"#{key}\"" unless @restricted_keys.nil? || @restricted_keys.empty? || @restricted_keys.include?(key.to_s)
86
- end
87
-
6
+ class DirtyHashy < Hash
7
+ include Dirty::Hash
88
8
  end
@@ -1,7 +1,7 @@
1
- class DirtyHashy < HashWithIndifferentAccess
1
+ class DirtyHashy < Hash
2
2
  MAJOR = 0
3
- MINOR = 1
4
- TINY = 3
3
+ MINOR = 2
4
+ TINY = 0
5
5
 
6
6
  VERSION = [MAJOR, MINOR, TINY].join(".")
7
7
  end
@@ -0,0 +1,5 @@
1
+ require "active_support/hash_with_indifferent_access"
2
+
3
+ class DirtyIndifferentHashy < HashWithIndifferentAccess
4
+ include Dirty::Hash
5
+ end
@@ -0,0 +1,176 @@
1
+ require File.expand_path("../../../test_helper", __FILE__)
2
+
3
+ module Unit
4
+ module Dirty
5
+ class TestAttributes < MiniTest::Unit::TestCase
6
+
7
+ describe ::Dirty::Attributes do
8
+ it "should behave as expected without key restriction" do
9
+ class Person
10
+ include ::Dirty::Attributes
11
+ end
12
+
13
+ person = Person.new
14
+
15
+ assert_equal({}, person.attributes)
16
+ assert_equal false, person.dirty?
17
+ assert_equal false, person.changed?
18
+ assert_equal({}, person.changes)
19
+
20
+ assert_raises(NoMethodError) do
21
+ person.name
22
+ end
23
+
24
+ person.name = "Paul"
25
+
26
+ assert_equal "Paul", person.name
27
+ assert_equal true, person.dirty?
28
+ assert_equal true, person.changed?
29
+ assert_equal true, person.name_changed?
30
+ assert_equal [nil, "Paul"], person.name_change
31
+ assert_equal({"name" => [nil, "Paul"]}, person.changes)
32
+
33
+ person.name = nil
34
+
35
+ assert_equal false, person.dirty?
36
+ assert_equal false, person.changed?
37
+ assert_equal false, person.name_changed?
38
+ assert_equal nil, person.name_change
39
+ assert_equal({}, person.changes)
40
+
41
+ person.name = "Stephan"
42
+
43
+ assert_equal true, person.dirty?
44
+ assert_equal true, person.changed?
45
+ assert_equal true, person.name_changed?
46
+ assert_equal [nil, "Stephan"], person.name_change
47
+ assert_equal({"name" => [nil, "Stephan"]}, person.changes)
48
+
49
+ person.clean_up!
50
+
51
+ assert_equal false, person.dirty?
52
+ assert_equal false, person.changed?
53
+ assert_equal false, person.name_changed?
54
+ assert_equal nil, person.name_change
55
+ assert_equal({}, person.changes)
56
+
57
+ person.foo = "Bar"
58
+
59
+ assert_equal "Bar", person.foo
60
+ assert_equal true, person.dirty?
61
+ assert_equal true, person.changed?
62
+ assert_equal false, person.name_changed?
63
+ assert_equal true, person.foo_changed?
64
+ assert_equal nil, person.name_change
65
+ assert_equal [nil, "Bar"], person.foo_change
66
+ assert_equal({"foo" => [nil, "Bar"]}, person.changes)
67
+
68
+ person.attributes.merge! :company => "Internetbureau Holder B.V."
69
+
70
+ assert_equal true, person.dirty?
71
+ assert_equal true, person.changed?
72
+ assert_equal false, person.name_changed?
73
+ assert_equal true, person.foo_changed?
74
+ assert_equal true, person.company_changed?
75
+ assert_equal nil, person.name_change
76
+ assert_equal [nil, "Bar"], person.foo_change
77
+ assert_equal [nil, "Internetbureau Holder B.V."], person.company_change
78
+ assert_equal({"foo" => [nil, "Bar"], "company" => [nil, "Internetbureau Holder B.V."]}, person.changes)
79
+
80
+ person.attributes.delete :foo
81
+ person.clean_up!
82
+
83
+ assert_equal false, person.dirty?
84
+ assert_equal false, person.changed?
85
+ assert_equal({"name" => "Stephan", "company" => "Internetbureau Holder B.V."}, person.attributes)
86
+ assert_equal({}, person.changes)
87
+
88
+ person.attributes = {"name" => "Paul", "city" => "Amsterdam"}
89
+
90
+ assert_equal true, person.dirty?
91
+ assert_equal true, person.changed?
92
+ assert_equal true, person.name_changed?
93
+ assert_equal true, person.company_changed?
94
+ assert_equal true, person.city_changed?
95
+ assert_equal ["Stephan", "Paul"], person.name_change
96
+ assert_equal ["Internetbureau Holder B.V.", nil], person.company_change
97
+ assert_equal [nil, "Amsterdam"], person.city_change
98
+ assert_equal({"name" => ["Stephan", "Paul"], "company" => ["Internetbureau Holder B.V.", nil], "city" => [nil, "Amsterdam"]}, person.changes)
99
+ end
100
+
101
+ it "should behave as expected with key restriction" do
102
+ class User
103
+ include ::Dirty::Attributes
104
+ attrs :name
105
+ end
106
+
107
+ user = User.new
108
+
109
+ assert_equal({"name" => nil}, user.attributes)
110
+ assert_equal nil, user.name
111
+ assert_equal false, user.dirty?
112
+ assert_equal false, user.changed?
113
+ assert_equal({}, user.changes)
114
+
115
+ assert_raises(NoMethodError) do
116
+ user.foo
117
+ end
118
+
119
+ assert_raises(NoMethodError) do
120
+ user.foo = "Bar"
121
+ end
122
+
123
+ user.name = "Paul"
124
+
125
+ assert_equal "Paul", user.name
126
+ assert_equal true, user.dirty?
127
+ assert_equal true, user.changed?
128
+ assert_equal true, user.name_changed?
129
+ assert_equal [nil, "Paul"], user.name_change
130
+ assert_equal({"name" => [nil, "Paul"]}, user.changes)
131
+
132
+ user.name = nil
133
+
134
+ assert_equal false, user.dirty?
135
+ assert_equal false, user.changed?
136
+ assert_equal false, user.name_changed?
137
+ assert_equal nil, user.name_change
138
+ assert_equal({}, user.changes)
139
+
140
+ user.name = "Stephan"
141
+
142
+ assert_equal true, user.dirty?
143
+ assert_equal true, user.changed?
144
+ assert_equal true, user.name_changed?
145
+ assert_equal [nil, "Stephan"], user.name_change
146
+ assert_equal({"name" => [nil, "Stephan"]}, user.changes)
147
+
148
+ user.clean_up!
149
+
150
+ assert_equal false, user.dirty?
151
+ assert_equal false, user.changed?
152
+ assert_equal false, user.name_changed?
153
+ assert_equal nil, user.name_change
154
+ assert_equal({}, user.changes)
155
+
156
+ user.attributes = {"name" => "Paul"}
157
+
158
+ assert_equal true, user.dirty?
159
+ assert_equal true, user.changed?
160
+ assert_equal true, user.name_changed?
161
+ assert_equal ["Stephan", "Paul"], user.name_change
162
+ assert_equal({"name" => ["Stephan", "Paul"]}, user.changes)
163
+
164
+ assert_raises(NoMethodError) do
165
+ user.attributes = {"company" => "Internetbureau Holder B.V."}
166
+ end
167
+
168
+ assert_raises(IndexError) do
169
+ user.attributes.merge! "company" => "Internetbureau Holder B.V."
170
+ end
171
+ end
172
+ end
173
+
174
+ end
175
+ end
176
+ end