ricordami 0.0.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.
Files changed (62) hide show
  1. data/CHANGELOG.md +13 -0
  2. data/ISSUES.md +0 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.md +454 -0
  5. data/TODO.md +21 -0
  6. data/examples/calls.rb +64 -0
  7. data/examples/singers.rb +42 -0
  8. data/lib/ricordami/attribute.rb +52 -0
  9. data/lib/ricordami/can_be_queried.rb +133 -0
  10. data/lib/ricordami/can_be_validated.rb +27 -0
  11. data/lib/ricordami/can_have_relationships.rb +152 -0
  12. data/lib/ricordami/configuration.rb +39 -0
  13. data/lib/ricordami/connection.rb +22 -0
  14. data/lib/ricordami/exceptions.rb +14 -0
  15. data/lib/ricordami/has_attributes.rb +159 -0
  16. data/lib/ricordami/has_indices.rb +105 -0
  17. data/lib/ricordami/is_lockable.rb +52 -0
  18. data/lib/ricordami/is_persisted.rb +123 -0
  19. data/lib/ricordami/is_retrievable.rb +35 -0
  20. data/lib/ricordami/key_namer.rb +63 -0
  21. data/lib/ricordami/model.rb +29 -0
  22. data/lib/ricordami/query.rb +68 -0
  23. data/lib/ricordami/relationship.rb +40 -0
  24. data/lib/ricordami/unique_index.rb +59 -0
  25. data/lib/ricordami/unique_validator.rb +21 -0
  26. data/lib/ricordami/value_index.rb +26 -0
  27. data/lib/ricordami/version.rb +3 -0
  28. data/lib/ricordami.rb +26 -0
  29. data/spec/acceptance/manage_relationships_spec.rb +42 -0
  30. data/spec/acceptance/model_with_validation_spec.rb +78 -0
  31. data/spec/acceptance/query_model_spec.rb +93 -0
  32. data/spec/acceptance_helper.rb +2 -0
  33. data/spec/ricordami/attribute_spec.rb +113 -0
  34. data/spec/ricordami/can_be_queried_spec.rb +254 -0
  35. data/spec/ricordami/can_be_validated_spec.rb +115 -0
  36. data/spec/ricordami/can_have_relationships_spec.rb +255 -0
  37. data/spec/ricordami/configuration_spec.rb +45 -0
  38. data/spec/ricordami/connection_spec.rb +25 -0
  39. data/spec/ricordami/exceptions_spec.rb +43 -0
  40. data/spec/ricordami/has_attributes_spec.rb +266 -0
  41. data/spec/ricordami/has_indices_spec.rb +73 -0
  42. data/spec/ricordami/is_lockable_spec.rb +45 -0
  43. data/spec/ricordami/is_persisted_spec.rb +186 -0
  44. data/spec/ricordami/is_retrievable_spec.rb +55 -0
  45. data/spec/ricordami/key_namer_spec.rb +56 -0
  46. data/spec/ricordami/model_spec.rb +65 -0
  47. data/spec/ricordami/query_spec.rb +156 -0
  48. data/spec/ricordami/relationship_spec.rb +123 -0
  49. data/spec/ricordami/unique_index_spec.rb +87 -0
  50. data/spec/ricordami/unique_validator_spec.rb +41 -0
  51. data/spec/ricordami/value_index_spec.rb +40 -0
  52. data/spec/spec_helper.rb +29 -0
  53. data/spec/support/constants.rb +43 -0
  54. data/spec/support/db_manager.rb +18 -0
  55. data/test/bin/data_loader.rb +107 -0
  56. data/test/data/domains.txt +462 -0
  57. data/test/data/first_names.txt +1220 -0
  58. data/test/data/last_names.txt +1028 -0
  59. data/test/data/people_100_000.csv.bz2 +0 -0
  60. data/test/data/people_10_000.csv.bz2 +0 -0
  61. data/test/data/people_1_000_000.csv.bz2 +0 -0
  62. metadata +258 -0
@@ -0,0 +1,255 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require "spec_helper"
3
+ require "ricordami/can_have_relationships"
4
+
5
+ describe Ricordami::CanHaveRelationships do
6
+ uses_constants("Computer", "Software", "Editor")
7
+
8
+ before(:each) do
9
+ Computer.send(:include, Ricordami::CanHaveRelationships)
10
+ Computer.attribute :model
11
+ Computer.index :unique => :model, :get_by => true
12
+ Computer.references_many :softwares
13
+ Software.send(:include, Ricordami::CanHaveRelationships)
14
+ Software.attribute :name
15
+ Software.index :unique => :name, :get_by => true
16
+ Software.referenced_in :computer
17
+ Software.referenced_in :editor
18
+ Editor.send(:include, Ricordami::CanHaveRelationships)
19
+ Editor.attribute :corp_name
20
+ Editor.index :unique => :corp_name, :get_by => true
21
+ Editor.references_one :software
22
+ end
23
+
24
+ describe "class" do
25
+ it "can declare a :referenced_in relationship with #referenced_in" do
26
+ Software.referenced_in :computer
27
+ Software.relationships[:computer].should be_a(Ricordami::Relationship)
28
+ Software.relationships[:computer].type.should == :referenced_in
29
+ Software.relationships[:computer].name.should == :computer
30
+ Software.relationships[:computer].object_kind.should == :computer
31
+ end
32
+
33
+ it "can declare a :references_many relationship with #references_many" do
34
+ Computer.references_many :softwares, :dependent => :nullify, :as => :softs
35
+ Computer.relationships[:softs].should be_a(Ricordami::Relationship)
36
+ Computer.relationships[:softs].type.should == :references_many
37
+ Computer.relationships[:softs].name.should == :softs
38
+ Computer.relationships[:softs].object_kind.should == :software
39
+ Computer.relationships[:softs].dependent.should == :nullify
40
+ end
41
+
42
+ it "can declare a :references_one relationship with #references_one" do
43
+ Editor.references_one :software, :dependent => :nullify
44
+ Editor.relationships[:software].should be_a(Ricordami::Relationship)
45
+ Editor.relationships[:software].type.should == :references_one
46
+ Editor.relationships[:software].name.should == :software
47
+ Editor.relationships[:software].object_kind.should == :software
48
+ Editor.relationships[:software].dependent.should == :nullify
49
+ end
50
+ end
51
+
52
+ describe "instance that is referenced..." do
53
+ it "creates an attribute for the referrer id" do
54
+ game = Software.create(:name => "Masquerade")
55
+ game.computer_id.should be_nil
56
+ end
57
+
58
+ it "indexes the referrer id" do
59
+ iic = Computer.create(:model => "IIc")
60
+ mac = Computer.create(:model => "Macintosh")
61
+ Software.create(:name => "Masquerade", :computer_id => iic.id)
62
+ Software.create(:name => "Transylvania", :computer_id => iic.id)
63
+ Software.create(:name => "Dungeon Master", :computer_id => mac.id)
64
+ Software.where(:computer_id => iic.id).map(&:name).should =~ ["Masquerade", "Transylvania"]
65
+ end
66
+
67
+ it "can access the referrer with a method of the name of the reference" do
68
+ game = Software.create(:name => "Masquerade")
69
+ game.computer.should be_nil
70
+ computer = Computer.create(:model => "IIc")
71
+ game.computer_id = computer.id
72
+ game.computer.should be_a(Computer)
73
+ game.computer.model.should == "IIc"
74
+ end
75
+
76
+ describe "handling caching" do
77
+ before(:each) do
78
+ %w(IIc Macintosh).each { |model| Computer.create(:model => model).should be_true }
79
+ end
80
+
81
+ it "caches the referrer isntance after it was cached" do
82
+ computer = Computer.get_by_model("IIc")
83
+ game = Software.create(:name => "Masquerade", :computer_id => computer.id)
84
+ game.computer.should == game.computer
85
+ end
86
+
87
+ it "updates the referrer when changing the referrer id" do
88
+ game = Software.create(:name => "Sorcery")
89
+ game.computer.should be_nil
90
+ game.computer_id = Computer.get_by_model("IIc").id
91
+ game.computer.model.should == "IIc"
92
+ game.computer_id = Computer.get_by_model("Macintosh").id
93
+ game.computer.model.should == "Macintosh"
94
+ game.computer_id = nil
95
+ game.computer.should be_nil
96
+ end
97
+
98
+ it "sweeps the referrer instance from the cache after it is reloaded" do
99
+ game = Software.create(:name => "Swashbuckler",
100
+ :computer_id => Computer.get_by_model("IIc").id)
101
+ game.computer.model.should == "IIc"
102
+ game.update_attributes(:computer_id => Computer.get_by_model("Macintosh").id)
103
+ game.computer.model.should == "Macintosh"
104
+ end
105
+ end
106
+ end
107
+
108
+ describe "instance that references many..." do
109
+ before(:each) do
110
+ @iic = Computer.create(:model => "IIc")
111
+ @mac = Computer.create(:model => "MacBook Air")
112
+ [
113
+ [@iic, "Masquerade"], [@iic, "Transylvania"], [@iic, "Bruce Lee"],
114
+ [@iic, "Karateka"], [@mac, "Half-Life"], [@mac, "Chopper 2"]
115
+ ].each do |computer, soft_name|
116
+ Software.create(:name => soft_name, :computer_id => computer.id)
117
+ end
118
+ end
119
+
120
+ it "has a method to access its referenced objects" do
121
+ @iic.should respond_to(:softwares)
122
+ end
123
+
124
+ it "can list all the objects it references with the reference method" do
125
+ @iic.softwares.map(&:name).should =~ ["Masquerade", "Transylvania",
126
+ "Bruce Lee", "Karateka"]
127
+ @mac.softwares.map(&:name).should =~ ["Half-Life", "Chopper 2"]
128
+ end
129
+
130
+ it "can return there is no reference objects when non persisted" do
131
+ Computer.new.softwares.map(&:name).should == []
132
+ Computer.new.softwares.should be_empty
133
+ Computer.new.softwares.count.should == 0
134
+ end
135
+
136
+ it "can build a new reference object through the reference method" do
137
+ soft = @mac.softwares.build
138
+ soft.should_not be_persisted
139
+ soft.computer_id.should == @mac.id
140
+ end
141
+
142
+ it "can create a new reference object through the reference method" do
143
+ soft = @mac.softwares.create(:name => "Call of Duty 4")
144
+ soft.should be_persisted
145
+ soft.computer_id.should == @mac.id
146
+ soft.name.should == "Call of Duty 4"
147
+ end
148
+
149
+ it "can have more than 1 reference for the same type of object" do
150
+ Computer.references_many :softwares, :as => :softs, :alias => :host, :dependent => :delete
151
+ Software.referenced_in :computer, :as => :host, :alias => :softs
152
+ iie = Computer.create(:model => "IIe")
153
+ iie.softs.create(:name => "Masquerade")
154
+ iie.softwares.create(:name => "Conan")
155
+ iie.softs.map(&:name).should == ["Masquerade"]
156
+ iie.softwares.map(&:name).should == ["Conan"]
157
+ end
158
+
159
+ it "deletes dependents when delete is set to :dependent" do
160
+ Computer.references_many :softwares, :as => :softs, :dependent => :delete
161
+ iie = Computer.create(:model => "IIe")
162
+ iie.softs.create(:name => "Castle Wolfenstein")
163
+ Software.get_by_name("Castle Wolfenstein").should_not be_nil
164
+ iie.delete
165
+ lambda {
166
+ Software.get_by_name("Castle Wolfenstein")
167
+ }.should raise_error(Ricordami::NotFound)
168
+ end
169
+
170
+ it "sets to nil reference's referrer_id when delete is set to :nullify" do
171
+ Computer.references_many :softwares, :as => :softs, :alias => :host, :dependent => :nullify
172
+ Software.referenced_in :computer, :as => :host, :alias => :softs
173
+ iie = Computer.create(:model => "IIe")
174
+ iie.reload
175
+ iie.softs.create(:name => "Castle Wolfenstein")
176
+ Software.get_by_name("Castle Wolfenstein").host_id.should == iie.id
177
+ iie.delete
178
+ Software.get_by_name("Castle Wolfenstein").host_id.should be_empty
179
+ end
180
+
181
+ it "can build a new referrer object through a build referrer method" do
182
+ software = Software.create(:name => "Karateka")
183
+ editor = software.build_editor(:corp_name => "Brøderbund")
184
+ editor.should_not be_persisted
185
+ editor.id.should == software.editor_id
186
+ end
187
+ end
188
+
189
+ describe "instance that references one..." do
190
+ before(:each) do
191
+ @aes = Editor.create(:corp_name => "American Eagle Software")
192
+ Software.create(:name => "Masquerade", :editor_id => @aes.id)
193
+ @datasoft = Editor.create(:corp_name => "Datasoft")
194
+ Software.create(:name => "Bruce Lee", :editor_id => @datasoft.id)
195
+ end
196
+
197
+ it "has a method to access its referenced object" do
198
+ @aes.should respond_to(:software)
199
+ end
200
+
201
+ it "can fetch the object it references with the reference method" do
202
+ @aes.software.name.should == "Masquerade"
203
+ @datasoft.software.name.should == "Bruce Lee"
204
+ end
205
+
206
+ it "can return there is no reference objects when non persisted" do
207
+ Editor.new.software.should be_nil
208
+ end
209
+
210
+ it "can build a new reference object through a build reference method" do
211
+ soft = @aes.build_software
212
+ soft.should_not be_persisted
213
+ soft.editor_id.should == @aes.id
214
+ end
215
+
216
+ it "can create a new reference object through the reference method" do
217
+ soft = @datasoft.create_software(:name => "Alternate Reality: The City")
218
+ soft.should be_persisted
219
+ soft.editor_id.should == @datasoft.id
220
+ soft.name.should == "Alternate Reality: The City"
221
+ end
222
+
223
+ it "can have more than 1 reference for the same type of object" do
224
+ Editor.references_one :software, :as => :soft, :alias => :second_editor, :dependent => :delete
225
+ Software.referenced_in :editor, :as => :second_editor, :alias => :soft
226
+ broderbund = Editor.create(:corp_name => "Brøderbund")
227
+ broderbund.create_software(:name => "Karateka")
228
+ broderbund.create_soft(:name => "Lode Runner")
229
+ broderbund.software.name.should == "Karateka"
230
+ broderbund.soft.name.should == "Lode Runner"
231
+ end
232
+
233
+ it "deletes dependents when delete is set to :dependent" do
234
+ Editor.references_one :software, :as => :soft, :dependent => :delete
235
+ broderbund = Editor.create(:corp_name => "Brøderbund")
236
+ broderbund.create_soft(:name => "Karateka")
237
+ Software.get_by_name("Karateka").should_not be_nil
238
+ broderbund.delete
239
+ lambda {
240
+ Software.get_by_name("Karateka")
241
+ }.should raise_error(Ricordami::NotFound)
242
+ end
243
+
244
+ it "sets to nil reference's referrer_id when delete is set to :nullify" do
245
+ Editor.references_one :software, :as => :soft, :alias => :developper, :dependent => :nullify
246
+ Software.referenced_in :editor, :as => :developper, :alias => :soft
247
+ broderbund = Editor.create(:corp_name => "Brøderbund")
248
+ broderbund.reload
249
+ broderbund.create_soft(:name => "Choplifter")
250
+ Software.get_by_name("Choplifter").developper_id.should == broderbund.id
251
+ broderbund.delete
252
+ Software.get_by_name("Choplifter").developper_id.should be_empty
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,45 @@
1
+ require "spec_helper"
2
+
3
+ describe Ricordami::Configuration, " using #configure" do
4
+ it "raises an error of no block is given" do
5
+ lambda { Ricordami.configure }.should raise_error(ArgumentError)
6
+ end
7
+
8
+ it "raises an error when a config attribute doesn't exist" do
9
+ lambda {
10
+ Ricordami.configuration.does_not_exist
11
+ }.should raise_error(Ricordami::AttributeNotSupported)
12
+ end
13
+
14
+ it "can configure redis host with #redis_host" do
15
+ Ricordami.configure { |config| config.redis_host = "my_host" }
16
+ Ricordami.configuration.redis_host.should == "my_host"
17
+ end
18
+
19
+ it "can configure redis port with #redis_port" do
20
+ Ricordami.configure { |config| config.redis_port = 6379 }
21
+ Ricordami.configuration.redis_port.should == 6379
22
+ end
23
+
24
+ it "can configure redis db (0, 1, 2, ..., 8) with #redis_db" do
25
+ Ricordami.configure { |config| config.redis_db = 1 }
26
+ Ricordami.configuration.redis_db.should == 1
27
+ end
28
+
29
+ it "can configure redis to use as thread-safe with #thread_safe" do
30
+ Ricordami.configure { |config| config.thread_safe = true }
31
+ Ricordami.configuration.thread_safe.should be_true
32
+ end
33
+
34
+ it "can configure all attributes at once using #from_hash" do
35
+ Ricordami.configure do |config|
36
+ config.from_hash(:redis_host => "serge",
37
+ :redis_port => 6380,
38
+ :redis_db => 2)
39
+ end
40
+ Ricordami.configuration.redis_host.should == "serge"
41
+ Ricordami.configuration.redis_port.should == 6380
42
+ Ricordami.configuration.redis_db.should == 2
43
+ Ricordami.configuration.thread_safe.should be_false
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+ require "rspec/mocks"
3
+
4
+ describe Ricordami::Connection do
5
+ describe "the module" do
6
+ before(:all) do
7
+ module Mixer
8
+ extend self
9
+ include Ricordami::Connection
10
+ end
11
+ RSpec::Mocks::setup(self)
12
+ @config = mock(:config)
13
+ [:redis_host, :redis_port, :redis_db, :thread_safe].each do |name|
14
+ @config.stub!(name)
15
+ end
16
+ Mixer.stub!(:configuration).and_return(@config)
17
+ end
18
+
19
+ it "has a connection to Redis" do
20
+ Mixer.driver.should be_a(Redis)
21
+ Mixer.driver.get("not_exist")
22
+ Mixer.driver.client.connection.should be_connected
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ require "spec_helper"
2
+
3
+ describe Ricordami::Error do
4
+ it "has an error to notify when data was not found on the server" do
5
+ Ricordami::NotFound.new.should be_a_kind_of(Ricordami::Error)
6
+ end
7
+
8
+ it "has an error to notify when an attribute is not supported" do
9
+ Ricordami::AttributeNotSupported.new.should be_a_kind_of(Ricordami::Error)
10
+ end
11
+
12
+ it "has an error to notify an attempty to change a read-only attribute" do
13
+ Ricordami::ReadOnlyAttribute.new.should be_a_kind_of(Ricordami::Error)
14
+ end
15
+
16
+ it "has an error to notify when an index declaration is invalid" do
17
+ Ricordami::InvalidIndexDefinition.new.should be_a_kind_of(Ricordami::Error)
18
+ end
19
+
20
+ it "has an error to notify when a model is deleted" do
21
+ Ricordami::ModelHasBeenDeleted.new.should be_a_kind_of(Ricordami::Error)
22
+ end
23
+
24
+ it "has an error to notify when a type is not supported" do
25
+ Ricordami::TypeNotSupported.new.should be_a_kind_of(Ricordami::Error)
26
+ end
27
+
28
+ it "has an error to notify when an index is missing" do
29
+ Ricordami::MissingIndex.new.should be_a_kind_of(Ricordami::Error)
30
+ end
31
+
32
+ it "has an error to notify when an event is not supported" do
33
+ Ricordami::EventNotSupported.new.should be_a_kind_of(Ricordami::Error)
34
+ end
35
+
36
+ it "has an error to notify when an option value is invalid" do
37
+ Ricordami::OptionValueInvalid.new.should be_a_kind_of(Ricordami::Error)
38
+ end
39
+
40
+ it "has an error to notify when mandatory arguments are missing" do
41
+ Ricordami::MissingMandatoryArgs.new.should be_a_kind_of(Ricordami::Error)
42
+ end
43
+ end
@@ -0,0 +1,266 @@
1
+ require "spec_helper"
2
+
3
+ describe Ricordami::HasAttributes do
4
+ describe "the class" do
5
+ uses_constants("Boat")
6
+
7
+ it "can declare attributes using #attribute" do
8
+ Boat.attribute :sail
9
+ Boat.attributes[:sail].should be_a(Ricordami::Attribute)
10
+ Boat.attributes[:sail].name.should == :sail
11
+ end
12
+
13
+ it "can pass attribute options when declaring an attribute" do
14
+ Boat.attribute :color, :default => "black", :read_only => true
15
+ Boat.attributes[:color].default_value.should == "black"
16
+ Boat.attributes[:color].should be_read_only
17
+ end
18
+
19
+ it "creates an index if :indexed is set" do
20
+ Boat.attribute :size, :indexed => :value
21
+ Boat.indices.should have_key(:size)
22
+ end
23
+
24
+ it "creates a unique index if :indexed is :unique" do
25
+ Boat.attribute :size, :indexed => :unique
26
+ Boat.indices[:size].should be_a(Ricordami::UniqueIndex)
27
+ end
28
+
29
+ it "creates a value index if :indexed is :value" do
30
+ Boat.attribute :size, :indexed => :value
31
+ Boat.indices[:size].should be_a(Ricordami::ValueIndex)
32
+ end
33
+
34
+ it "replaces :initial value with a generator if it's a symbol" do
35
+ Boat.attribute :id, :initial => :sequence
36
+ attribute = Boat.attributes[:id]
37
+ attribute.initial_value.should == "1"
38
+ attribute.initial_value.should == "2"
39
+ attribute.initial_value.should == "3"
40
+ end
41
+ end
42
+
43
+ describe "an instance" do
44
+ uses_constants("User")
45
+
46
+ it "can be initialized with a hash of attribute values" do
47
+ User.attribute :name
48
+ User.attribute :age
49
+ User.attribute :ssn
50
+ user = User.new(:name => "jean", :age => "20", :ssn => "1234567890")
51
+ user.name.should == "jean"
52
+ user.age.should == "20"
53
+ user.ssn.should == "1234567890"
54
+ end
55
+
56
+ it "can set attributes using writers" do
57
+ User.attribute :email
58
+ user = User.new
59
+ user.email.should be_nil
60
+ user.email = "blah@toto.com"
61
+ user.email.should == "blah@toto.com"
62
+ end
63
+
64
+ it "sets default values when initializing without argument" do
65
+ User.attribute :age, :default => "18"
66
+ User.new.age.should == "18"
67
+ end
68
+
69
+ it "overwrites default values with arguments when initializing" do
70
+ User.attribute :age, :default => "18"
71
+ User.new(:age => "12").age.should == "12"
72
+ end
73
+
74
+ it "can't change a read-only attribute that was initialized" do
75
+ User.attribute :name
76
+ User.attribute :ssn, :read_only => true
77
+ user = User.new(:name => "James Bond", :ssn => "007")
78
+ lambda {
79
+ user.ssn = "1234567890"
80
+ }.should raise_error(Ricordami::ReadOnlyAttribute)
81
+ lambda {
82
+ user.name = "Titi"
83
+ }.should_not raise_error(Ricordami::ReadOnlyAttribute)
84
+ end
85
+
86
+ it "can set a read-only attribute only once if it was not initialized" do
87
+ User.attribute :name
88
+ User.attribute :ssn, :read_only => true
89
+ user = User.new
90
+ lambda {
91
+ user.ssn = "1234567890"
92
+ user.name = "Titi"
93
+ user.name = "Bob Loblaw"
94
+ }.should_not raise_error(Ricordami::ReadOnlyAttribute)
95
+ lambda {
96
+ user.ssn = "0987654321"
97
+ }.should raise_error(Ricordami::ReadOnlyAttribute)
98
+ end
99
+
100
+ it "has an 'id' attribute set by default to a sequence if not overriden when saved" do
101
+ user = User.create
102
+ user.id.should be_present
103
+ user.id.should == "1"
104
+ end
105
+
106
+ it "overides the value of its 'id' attribute when a value is passed" do
107
+ user = User.create(:id => "foo")
108
+ user.id.should == "foo"
109
+ end
110
+
111
+ it "defines 'id' as a read_only attribute" do
112
+ user = User.create
113
+ lambda {
114
+ user.id = "try me"
115
+ }.should raise_error(Ricordami::ReadOnlyAttribute)
116
+ end
117
+
118
+ it "returns the name of the attributes key with #attributes_key_name" do
119
+ user = User.new(:id => "ze_id")
120
+ user.instance_eval { attributes_key_name }.should == "User:att:ze_id"
121
+ end
122
+
123
+ it "updates the attribute values in memory with #update_mem_attributes!" do
124
+ User.attribute :age
125
+ User.attribute :sex
126
+ user = User.new(:age => "12", :sex => "male")
127
+ user.update_mem_attributes!(:age => "18", :sex => "female")
128
+ user.age.should == "18"
129
+ user.sex.should == "female"
130
+ end
131
+
132
+ it "can't change read-only attributes with #update_mem_attributes!" do
133
+ User.attribute :name
134
+ User.attribute :ssn, :read_only => true
135
+ user = User.new(:name => "James Bond", :ssn => "007")
136
+ lambda {
137
+ user.update_mem_attributes!(:ssn => "1234567890")
138
+ }.should raise_error(Ricordami::ReadOnlyAttribute)
139
+ end
140
+
141
+ it "updates the attribute values in memory with #update_mem_attributes" do
142
+ User.attribute :age
143
+ user = User.new(:age => "12")
144
+ user.update_mem_attributes(:age => "18")
145
+ user.age.should == "18"
146
+ end
147
+
148
+ it "changes read-only attributes with #update_mem_attributes" do
149
+ User.attribute :ssn, :read_only => true
150
+ user = User.new(:ssn => "007")
151
+ lambda {
152
+ user.update_mem_attributes(:ssn => "1234567890")
153
+ }.should_not raise_error
154
+ end
155
+
156
+ it "can't change the attributes of a model that was deleted" do
157
+ User.attribute :age
158
+ user = User.create(:age => "42")
159
+ user.delete
160
+ lambda { user.age = "12" }.should raise_error(Ricordami::ModelHasBeenDeleted)
161
+ end
162
+ end
163
+
164
+ describe "keeps track of dirty attributes" do
165
+ uses_constants("Plane")
166
+ before(:each) do
167
+ Plane.attribute :brand
168
+ Plane.attribute :model
169
+ end
170
+
171
+ describe "a new record" do
172
+ before(:each) { @plane = Plane.new }
173
+ let(:plane) { @plane }
174
+
175
+ it "was not changed if it doesn't have attributes" do
176
+ plane.should_not be_changed
177
+ plane.changed.should be_empty
178
+ plane.changes.should be_empty
179
+ end
180
+
181
+ it "was changed when initialized with attributes" do
182
+ plane = Plane.new(:brand => "Airbus", :model => "320")
183
+ plane.should be_changed
184
+ plane.changed.should =~ ["brand", "model"]
185
+ plane.changes.should == {"brand" => [nil, "Airbus"], "model" => [nil, "320"]}
186
+ end
187
+
188
+ it "knows when an attribute value changes" do
189
+ plane.brand = "Boeing"
190
+ plane.should be_changed
191
+ plane.changed.should == ["brand"]
192
+ plane.changes["brand"].should == [nil, "Boeing"]
193
+ end
194
+
195
+ it "knows when an attribute value actually didn't change" do
196
+ plane.brand = nil
197
+ plane.should_not be_changed
198
+ end
199
+
200
+ it "was not changed after it was saved" do
201
+ plane.model = "380"
202
+ plane.save
203
+ plane.should_not be_changed
204
+ end
205
+
206
+ it "knows when it was changed before being saved" do
207
+ plane.model = "380"
208
+ plane.save
209
+ plane.previous_changes["model"].should == [nil, "380"]
210
+ end
211
+ end
212
+
213
+ describe "a persisted record" do
214
+ before(:each) { Plane.create(:id => "A320", :brand => "Airbus", :model => "320") }
215
+ let(:plane) { Plane["A320"] }
216
+
217
+ it "was not changed when it's just loaded from the DB" do
218
+ plane.should_not be_changed
219
+ end
220
+
221
+ it "was not changed after reloading it with #reload" do
222
+ plane.brand = "Toys'R US"
223
+ plane.reload
224
+ plane.should_not be_changed
225
+ end
226
+
227
+ it "knows when an attribute value actually didn't change" do
228
+ plane.brand = "Airbus"
229
+ plane.should_not be_changed
230
+ plane.brand = "Boeing"
231
+ plane.brand = "Airbus"
232
+ plane.should_not be_changed
233
+ end
234
+
235
+ it "knows which attributes changed when running #update_attributes" do
236
+ plane.brand = "Boeing"
237
+ plane.update_attributes(:brand => "Airbus", :model => "380")
238
+ plane.should_not be_changed
239
+ plane.previous_changes.should == {"model" => ["320", "380"]}
240
+ end
241
+ end
242
+
243
+ describe "attribute value typing" do
244
+ uses_constants("Person")
245
+ before(:each) do
246
+ Person.attribute :name
247
+ Person.attribute :age, :type => :integer
248
+ Person.attribute :parts, :type => :float
249
+ end
250
+
251
+ it "converts value types when initializing an object" do
252
+ person = Person.new(:name => 123, :age => "21", :parts => "5.2")
253
+ person.name.should == "123"
254
+ person.age.should == 21
255
+ person.parts.should == 5.2
256
+ end
257
+
258
+ it "doesn't convert the value type if value is nil" do
259
+ person = Person.new(:name => nil, :age => nil, :parts => nil)
260
+ person.name.should be_nil
261
+ person.age.should be_nil
262
+ person.parts.should be_nil
263
+ end
264
+ end
265
+ end
266
+ end