dirty_hashy 0.1.3 → 0.2.0

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,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