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,93 @@
1
+ require "acceptance_helper"
2
+ require "ricordami/can_be_queried"
3
+
4
+ class Singer
5
+ include Ricordami::Model
6
+ include Ricordami::CanBeQueried
7
+
8
+ attribute :username
9
+ attribute :email
10
+ attribute :first_name
11
+ attribute :last_name
12
+ attribute :deceased, :default => "false", :indexed => :value
13
+
14
+ index :unique => :username, :get_by => true
15
+ end
16
+
17
+ feature "Query model" do
18
+ def create_12_singers
19
+ [
20
+ ["Serge", "Gainsbourg"], ["Alain", "Bashung"], ["Benjamin", "Biolay"],
21
+ ["Charles", "Aznavour"], ["Yves", "Montand", true], ["Nino", "Ferrer", true],
22
+ ["Johnny", "Hallyday"], ["David", "Guetta"], ["Bruno", "Benabar"],
23
+ ["Alain", "Souchon"], ["Jacques", "Dutronc"], ["Georges", "Brasens"]
24
+ ].each do |first, last, deceased|
25
+ Singer.create(:username => (first + last[0..0]).downcase, :first_name => first,
26
+ :last_name => last, :email => "#{first.downcase}@#{last.downcase}.fr",
27
+ :deceased => deceased ? "true" : "false").should be_persisted
28
+ end
29
+ end
30
+
31
+ scenario "retrieve models" do
32
+ Singer.create(:username => "lucien", :email => "serge@gainsbourg.com",
33
+ :first_name => "Serge", :last_name => "Gainsbourg")
34
+ Singer.get_by_username("lucien").id.should == "1"
35
+
36
+ Singer.create(:username => "bashung", :email => "alain@bashung.com",
37
+ :first_name => "Alain", :last_name => "Bashung")
38
+ Singer.get_by_username("bashung").id.should == "2"
39
+
40
+ Singer.create(:username => "ben", :email => "benjamin@biolay.com",
41
+ :first_name => "Benjamin", :last_name => "Biolay")
42
+ Singer.get_by_username("ben").id.should == "3"
43
+
44
+ Singer.count.should == 3
45
+ Singer["1"].email.should == "serge@gainsbourg.com"
46
+
47
+ Singer.get_by_username("bashung").first_name.should == "Alain"
48
+ end
49
+
50
+ scenario "finding models using basic queries" do
51
+ Singer.create(:username => "ben", :email => "benjamin@biolay.com",
52
+ :first_name => "Benjamin", :last_name => "Biolay", :deceased => "false")
53
+ Singer.create(:username => "lucien", :email => "serge@gainsbourg.com",
54
+ :first_name => "Serge", :last_name => "Gainsbourg", :deceased => "true")
55
+ Singer.create(:username => "bashung", :email => "alain@bashung.com",
56
+ :first_name => "Alain", :last_name => "Bashung", :deceased => "true")
57
+
58
+ deceased = Singer.and(:deceased => "true").all
59
+ deceased.map(&:username).should =~ %w(lucien bashung)
60
+ q = Singer.where(:deceased => "true")
61
+ q.sort(:first_name, :asc_alpha).first.email.should == "alain@bashung.com"
62
+ q.sort(:first_name, :desc_alpha).last.email.should == "alain@bashung.com"
63
+ Singer.not(:deceased => true).map(&:username).should == ["ben"]
64
+ first = Singer.first.username
65
+ last = Singer.last.username
66
+ rand = Singer.rand.username
67
+ first.should_not == last
68
+ Singer.all.map(&:username).should include(first)
69
+ Singer.all.map(&:username).should include(last)
70
+ Singer.all.map(&:username).should include(rand)
71
+ end
72
+
73
+ scenario "paginate list of models" do
74
+ create_12_singers
75
+
76
+ Singer.paginate(:page => 1, :per_page => 5).should have(5).singers
77
+ Singer.paginate(:page => 2, :per_page => 5).should have(5).singers
78
+ Singer.paginate(:page => 3, :per_page => 5).should have(2).singers
79
+ Singer.paginate(:page => 4, :per_page => 5).should have(0).singers
80
+
81
+ page1 = Singer.sort(:last_name, :asc_alpha).paginate(:page => 1, :per_page => 5)
82
+ page1.map(&:last_name).should == ["Aznavour", "Bashung", "Benabar", "Biolay", "Brasens"]
83
+ page3 = Singer.sort(:last_name, :asc_alpha).paginate(:page => 3, :per_page => 5)
84
+ page3.map(&:last_name).should == ["Montand", "Souchon"]
85
+ end
86
+
87
+ scenario "query using #and, #any and #not filters" do
88
+ Singer.index :value => :first_name
89
+ create_12_singers
90
+
91
+ Singer.where(:first_name => "Alain").all.map(&:last_name).should =~ %w(Bashung Souchon)
92
+ end
93
+ end
@@ -0,0 +1,2 @@
1
+ require File.expand_path("../spec_helper", __FILE__)
2
+ require "steak"
@@ -0,0 +1,113 @@
1
+ require "spec_helper"
2
+
3
+ describe Ricordami::Attribute do
4
+ subject { Ricordami::Attribute }
5
+
6
+ describe "an instance" do
7
+ it "has a name" do
8
+ attribute = subject.new(:singer)
9
+ attribute.name.should == :singer
10
+ end
11
+
12
+ it "accepts a string for its name" do
13
+ attribute = subject.new("string")
14
+ attribute.name.should == :string
15
+ end
16
+
17
+ it "accepts also a symbol for its name" do
18
+ attribute = subject.new(:string)
19
+ attribute.name.should == :string
20
+ end
21
+
22
+ it "has an option :default for a default value" do
23
+ attribute = subject.new(:georges, :default => "jungle")
24
+ attribute.default_value.should == "jungle"
25
+ end
26
+
27
+ it "doesn't have a default value if :default is not specified" do
28
+ subject.new(:no_defaults).default_value.should be_nil
29
+ end
30
+
31
+ it "allows to use a block as a default value for dynamic values" do
32
+ i = 0
33
+ attribute = subject.new(:sequence, :default => Proc.new { i += 1 })
34
+ attribute.default_value.should == 1
35
+ attribute.default_value.should == 2
36
+ attribute.default_value.should == 3
37
+ end
38
+
39
+ it "retuns if a default value is set with #default_value?" do
40
+ subject.new(:blah, :default => "1").default_value?.should be_true
41
+ without = subject.new(:foo).default_value?.should be_false
42
+ end
43
+
44
+ it "has an option :read_only when the attribute value can only be set once" do
45
+ attribute = subject.new(:georges, :read_only => true)
46
+ attribute.should be_read_only
47
+ end
48
+
49
+ it "its value can be set more than once when :read_only is not set" do
50
+ subject.new(:not_read_only).should_not be_read_only
51
+ end
52
+
53
+ it "has an option :indexed to index the attribute as unique" do
54
+ attribute = subject.new(:georges, :indexed => :unique)
55
+ attribute.indexed.should == :unique
56
+ end
57
+
58
+ it "has an option :indexed to index the attribute by value" do
59
+ attribute = subject.new(:georges, :indexed => :value)
60
+ attribute.indexed.should == :value
61
+ end
62
+
63
+ it "raises an error if :indexed is not :unique or :value" do
64
+ lambda {
65
+ subject.new(:georges, :indexed => :blah)
66
+ }.should raise_error(Ricordami::InvalidIndexDefinition)
67
+ lambda {
68
+ subject.new(:georges)
69
+ }.should_not raise_error
70
+ end
71
+
72
+ it "its value can be used for queries when :indexed is not set" do
73
+ subject.new(:not_indexed).should_not be_indexed
74
+ end
75
+
76
+ it "has an option :initial for an initial value set when saved the first time" do
77
+ attribute = subject.new(:id, :initial => "123")
78
+ attribute.initial_value.should == "123"
79
+ end
80
+
81
+ it "doesn't have an initial value if :initial is not specified" do
82
+ subject.new(:no_initials).initial_value.should be_nil
83
+ end
84
+
85
+ it "allows to use a block as an initial value for dynamic values" do
86
+ i = 1
87
+ attribute = subject.new(:id, :initial => Proc.new { i *= 2 })
88
+ attribute.initial_value.should == 2
89
+ attribute.initial_value.should == 4
90
+ attribute.initial_value.should == 8
91
+ end
92
+
93
+ it "retuns if an initial value is set with #initial_value?" do
94
+ subject.new(:id, :initial => "1").initial_value?.should be_true
95
+ without = subject.new(:foo).initial_value?.should be_false
96
+ end
97
+
98
+ it "can spefify the attribute type with :type option" do
99
+ attribute = subject.new(:id, :type => :integer)
100
+ attribute.type.should == :integer
101
+ end
102
+
103
+ it "has a default type :string if not specified" do
104
+ subject.new(:name).type.should == :string
105
+ end
106
+
107
+ it "returns the converter method with #converter" do
108
+ subject.new(:name, :type => :string).converter.should == :to_s
109
+ subject.new(:name, :type => :integer).converter.should == :to_i
110
+ subject.new(:name, :type => :float).converter.should == :to_f
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,254 @@
1
+ require "spec_helper"
2
+ require "ricordami/can_be_queried"
3
+
4
+ describe Ricordami::CanBeQueried do
5
+ uses_constants("Customer")
6
+
7
+ before(:each) do
8
+ Customer.send(:include, Ricordami::CanBeQueried)
9
+ Customer.attribute :country, :indexed => :value
10
+ Customer.attribute :sex, :indexed => :value
11
+ Customer.attribute :name, :indexed => :value
12
+ Customer.attribute :kind, :indexed => :value
13
+ Customer.attribute :no_index
14
+ end
15
+
16
+ describe "building queries" do
17
+ describe "#and" do
18
+ it "returns a new query" do
19
+ query = Customer.and
20
+ query.should be_a(Ricordami::Query)
21
+ end
22
+
23
+ it "passes self as the query runner to the query" do
24
+ query = Customer.and
25
+ query.runner.should == Customer
26
+ end
27
+
28
+ it "delegates #and to the new query" do
29
+ query = Customer.and(:key => "value")
30
+ query.expressions.should == [[:and, {:key => "value"}]]
31
+ end
32
+ end
33
+
34
+ describe "#not" do
35
+ it "returns a new query" do
36
+ query = Customer.not
37
+ query.should be_a(Ricordami::Query)
38
+ end
39
+
40
+ it "delegates #not to the new query" do
41
+ query = Customer.not(:key => "value")
42
+ query.expressions.should == [[:not, {:key => "value"}]]
43
+ end
44
+ end
45
+
46
+ describe "#any" do
47
+ it "returns a new query" do
48
+ query = Customer.any
49
+ query.should be_a(Ricordami::Query)
50
+ end
51
+
52
+ it "delegates #any to the new query" do
53
+ query = Customer.any(:key => "value")
54
+ query.expressions.should == [[:any, {:key => "value"}]]
55
+ end
56
+ end
57
+
58
+ describe "#sort" do
59
+ it "returns a new query" do
60
+ query = Customer.sort(:sex)
61
+ query.should be_a(Ricordami::Query)
62
+ end
63
+
64
+ it "delegates #sort to the new query" do
65
+ query = Customer.sort(:sex, :desc_alpha)
66
+ query.sort_by.should == :sex
67
+ query.sort_dir.should == :desc_alpha
68
+ end
69
+ end
70
+ end
71
+
72
+ describe "running queries" do
73
+ before(:each) do
74
+ Customer.create(:name => "Zhanna", :sex => "F", :country => "Latvia", :kind => "human")
75
+ Customer.create(:name => "Mathieu", :sex => "M", :country => "France", :kind => "human")
76
+ Customer.create(:name => "Sophie", :sex => "F", :country => "USA", :kind => "human")
77
+ Customer.create(:name => "Brioche", :sex => "F", :country => "USA", :kind => "dog")
78
+ end
79
+
80
+ describe ":and" do
81
+ it "raises an error if there's no value index for one of the conditions" do
82
+ lambda {
83
+ Customer.and(:no_index => "Blah").all
84
+ }.should raise_error(Ricordami::MissingIndex)
85
+ end
86
+
87
+ it "returns all entries if no conditions where passed" do
88
+ Customer.and.all.map(&:name).should =~ %w(Zhanna Mathieu Sophie Brioche)
89
+ end
90
+
91
+ it "returns the models found with #all (1 condition, 1 result)" do
92
+ Customer.index :value => :name
93
+ found = Customer.and(:name => "Zhanna").all
94
+ found.map(&:name).should == ["Zhanna"]
95
+ end
96
+
97
+ it "returns the models found with #all (2 conditions, 2 results)" do
98
+ found = Customer.and(:country => "USA", :sex => "F").all
99
+ found.map(&:name).should =~ ["Sophie", "Brioche"]
100
+ end
101
+
102
+ it "returns the models found with #all for a composed query" do
103
+ found = Customer.and(:country => "USA").and(:sex => "F").all
104
+ found.map(&:name).should =~ ["Sophie", "Brioche"]
105
+ end
106
+
107
+ it "can use #where instead of #and" do
108
+ found = Customer.where(:country => "USA").and(:sex => "F").all
109
+ found.map(&:name).should =~ ["Sophie", "Brioche"]
110
+ end
111
+
112
+ it "doesn't require #all if another method call is chained" do
113
+ Customer.where(:country => "USA").and(:sex => "F").map(&:name).should =~ ["Sophie", "Brioche"]
114
+ end
115
+ end
116
+
117
+ describe ":any" do
118
+ it "raises an error if there's no value index for one of the conditions" do
119
+ lambda {
120
+ Customer.any(:no_index => "Blah").all
121
+ }.should raise_error(Ricordami::MissingIndex)
122
+ end
123
+
124
+ it "returns all entries if no conditions where passed" do
125
+ Customer.any.all.map(&:name).should =~ %w(Zhanna Mathieu Sophie Brioche)
126
+ end
127
+
128
+ it "returns the models found with #all (1 condition, 1 result)" do
129
+ Customer.index :value => :name
130
+ found = Customer.any(:name => "Zhanna").all
131
+ found.map(&:name).should == ["Zhanna"]
132
+ end
133
+
134
+ it "returns the models found with #all (2 conditions, 3 results)" do
135
+ found = Customer.any(:country => "USA", :sex => "F").all
136
+ found.map(&:name).should =~ ["Sophie", "Brioche", "Zhanna"]
137
+ end
138
+
139
+ it "returns the models found with #all for a composed query" do
140
+ found = Customer.where(:country => "USA").any(:name => "Sophie", :kind => "dog").all
141
+ found.map(&:name).should =~ ["Sophie", "Brioche"]
142
+ found = Customer.where(:country => "USA").any(:name => "Sophie", :kind => "human").all
143
+ found.map(&:name).should == ["Sophie"]
144
+ end
145
+
146
+ it "doesn't require #all if another method call is chained" do
147
+ Customer.any(:country => "USA", :sex => "F").map(&:name).should =~ ["Sophie", "Brioche", "Zhanna"]
148
+ end
149
+ end
150
+
151
+ describe ":not" do
152
+ it "raises an error if there's no value index for one of the conditions" do
153
+ lambda {
154
+ Customer.not(:no_index => "Blah").all
155
+ }.should raise_error(Ricordami::MissingIndex)
156
+ end
157
+
158
+ it "returns all entries if no conditions where passed" do
159
+ Customer.not.all.map(&:name).should =~ %w(Zhanna Mathieu Sophie Brioche)
160
+ end
161
+
162
+ it "returns the models found with #all (1 condition, 1 result)" do
163
+ Customer.index :value => :name
164
+ found = Customer.not(:name => "Zhanna").all
165
+ found.map(&:name).should =~ ["Sophie", "Brioche", "Mathieu"]
166
+ end
167
+
168
+ it "returns the models found with #all (2 conditions, 1 result)" do
169
+ found = Customer.not(:country => "USA", :sex => "F").all
170
+ found.map(&:name).should == ["Mathieu"]
171
+ end
172
+
173
+ it "returns the models found with #all for a composed query" do
174
+ found = Customer.where(:country => "USA").not(:name => "Sophie", :kind => "dog").all
175
+ found.map(&:name).should be_empty
176
+ found = Customer.where(:country => "USA").not(:name => "Sophie", :kind => "human").all
177
+ found.map(&:name).should == ["Brioche"]
178
+ end
179
+
180
+ it "doesn't require #all if another method call is chained" do
181
+ Customer.not(:country => "USA", :sex => "F").map(&:name).should == ["Mathieu"]
182
+ end
183
+ end
184
+ end
185
+
186
+ describe "sorting result" do
187
+ uses_constants("Student")
188
+
189
+ before(:each) do
190
+ Student.send(:include, Ricordami::CanBeQueried)
191
+ Student.attribute :name, :indexed => :value
192
+ Student.attribute :grade, :indexed => :value
193
+ Student.attribute :school, :indexed => :value
194
+ [["Zhanna", 12], ["Sophie", 19],
195
+ ["Brioche", 4], ["Mathieu", 15]].each do |name, grade|
196
+ Student.create(:name => name, :grade => grade, :school => "Lajoo")
197
+ end
198
+ end
199
+
200
+ it "can sort the result alphanumerically with #sort" do
201
+ result = Student.where(:school => "Lajoo").sort(:name, :asc_alpha)
202
+ result.map(&:name).should == %w(Brioche Mathieu Sophie Zhanna)
203
+ end
204
+
205
+ it "can sort the result numerically with #sort" do
206
+ result = Student.where(:school => "Lajoo").sort(:grade, :asc_num)
207
+ result.map(&:name).should == %w(Brioche Zhanna Mathieu Sophie)
208
+ end
209
+
210
+ it "defaults to sorting ascending / alphanumerically" do
211
+ result = Student.where(:school => "Lajoo").sort(:name)
212
+ result.map(&:name).should == %w(Brioche Mathieu Sophie Zhanna)
213
+ end
214
+ end
215
+
216
+ describe "fetching result" do
217
+ before(:each) do
218
+ Student.send(:include, Ricordami::CanBeQueried)
219
+ Student.attribute :name, :indexed => :value
220
+ Student.attribute :grade, :indexed => :value
221
+ Student.attribute :school, :indexed => :value
222
+ [["Zhanna", 12], ["Sophie", 19],
223
+ ["Brioche", 4], ["Mathieu", 15]].each do |name, grade|
224
+ Student.create(:name => name, :grade => grade, :school => "Lajoo")
225
+ end
226
+ @query = Student.where(:school => "Lajoo").sort(:name)
227
+ end
228
+ let(:query) { @query }
229
+
230
+ it "fetches all the results with #all" do
231
+ query.all.map(&:name).should == %w(Brioche Mathieu Sophie Zhanna)
232
+ end
233
+
234
+ it "fetches the requested page of the results with #paginate" do
235
+ fetched = query.paginate(:page => 1, :per_page => 2)
236
+ fetched.map(&:name).should == %w(Brioche Mathieu)
237
+ fetched = query.paginate(:page => 2, :per_page => 2)
238
+ fetched.map(&:name).should == %w(Sophie Zhanna)
239
+ query.paginate(:page => 3, :per_page => 2).should be_empty
240
+ end
241
+
242
+ it "fetches the first instance with #first" do
243
+ query.first.name.should == "Brioche"
244
+ end
245
+
246
+ it "fetches the last instance with #last" do
247
+ query.last.name.should == "Zhanna"
248
+ end
249
+
250
+ it "fetches a random instance with #rand" do
251
+ %w(Brioche Mathieu Sophie Zhanna).should include(query.rand.name)
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,115 @@
1
+ require "spec_helper"
2
+ require "ricordami/can_be_validated"
3
+
4
+ describe Ricordami::CanBeValidated do
5
+ describe "Active Model validations" do
6
+ uses_constants("Call")
7
+
8
+ before(:each) do
9
+ Call.class_eval do
10
+ include Ricordami::CanBeValidated
11
+ attribute :ani
12
+ attribute :dnis
13
+ validates_presence_of :ani
14
+ validates_numericality_of :dnis, :allow_blank => true, :message => "is not numeric"
15
+ validate :cant_call_iself
16
+
17
+ private
18
+ def cant_call_iself
19
+ return true if ani.blank?
20
+ errors.add(:ani, "can't be the same as dnis") if ani == dnis
21
+ end
22
+ end
23
+ end
24
+
25
+ it "can validate the presence of an attribute value" do
26
+ call = Call.new
27
+ call.should_not be_valid
28
+ call.should have(1).errors
29
+ call.errors[:ani].should == ["can't be blank"]
30
+ end
31
+
32
+ it "can validate that a field is numeric" do
33
+ call = Call.new(:ani => "123", :dnis => "nope")
34
+ call.should_not be_valid
35
+ call.should have(1).errors
36
+ call.errors[:dnis].should == ["is not numeric"]
37
+ end
38
+
39
+ it "can do custom validation" do
40
+ call = Call.new(:ani => "123", :dnis => "123")
41
+ call.should_not be_valid
42
+ call.should have(1).errors
43
+ call.errors[:ani].should == ["can't be the same as dnis"]
44
+ end
45
+
46
+ it "can't validate a model that was deleted" do
47
+ call = Call.create(:ani => "123", :dnis => "456")
48
+ call.delete
49
+ lambda { call.valid? }.should raise_error(Ricordami::ModelHasBeenDeleted)
50
+ end
51
+ end
52
+
53
+ describe "#save" do
54
+ uses_constants("Post")
55
+
56
+ before(:each) do
57
+ Post.class_eval do
58
+ include Ricordami::CanBeValidated
59
+ attribute :title
60
+ validates_presence_of :title
61
+ end
62
+ end
63
+
64
+ it "can't #save if it is not valid" do
65
+ post = Post.new
66
+ post.save.should be_false
67
+ end
68
+
69
+ it "can save if it is not valid but passed :validate => false" do
70
+ Post.new.save(:validate => false).should be_true
71
+ end
72
+ end
73
+
74
+ describe "validate attribute uniqueness" do
75
+ uses_constants("User")
76
+
77
+ before(:each) do
78
+ User.class_eval do
79
+ include Ricordami::CanBeValidated
80
+ attribute :username, :read_only => true
81
+ attribute :wife
82
+ validates_uniqueness_of :username
83
+ end
84
+ end
85
+
86
+ it "is valid if no other instance uses the same attribute value" do
87
+ user = User.new(:username => "Serge Gainsbourg")
88
+ user.should be_valid
89
+ end
90
+
91
+ it "is not valid if another instance uses the same attribute value" do
92
+ serge = User.new(:username => "Gainsbourg")
93
+ serge.save.should be_true
94
+ serge.should be_valid
95
+
96
+ usurpateur = User.new(:username => "Gainsbourg")
97
+ usurpateur.should_not be_valid
98
+ usurpateur.should have(1).error
99
+ usurpateur.errors[:username].should == ["is already used"]
100
+ end
101
+
102
+ it "allows to validate the uniqueness of an attribute that can be changed" do
103
+ User.validates_uniqueness_of(:wife)
104
+ User.create(:id => "serge", :username => "Gainsbourg", :wife => "Rita")
105
+ serge = User["serge"]
106
+ serge.should be_valid
107
+ serge.save
108
+
109
+ fred = User.new(:username => "Chichin", :wife => "Rita")
110
+ fred.should_not be_valid
111
+ fred.should have(1).error
112
+ fred.errors[:wife].should == ["is already used"]
113
+ end
114
+ end
115
+ end