ricordami 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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