friendly 0.3.3

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 (110) hide show
  1. data/.document +5 -0
  2. data/.gitignore +23 -0
  3. data/APACHE-LICENSE +202 -0
  4. data/LICENSE +20 -0
  5. data/README.md +173 -0
  6. data/Rakefile +67 -0
  7. data/VERSION +1 -0
  8. data/examples/friendly.yml +7 -0
  9. data/friendly.gemspec +201 -0
  10. data/lib/friendly/attribute.rb +65 -0
  11. data/lib/friendly/boolean.rb +6 -0
  12. data/lib/friendly/cache/by_id.rb +33 -0
  13. data/lib/friendly/cache.rb +24 -0
  14. data/lib/friendly/config.rb +5 -0
  15. data/lib/friendly/data_store.rb +72 -0
  16. data/lib/friendly/document.rb +165 -0
  17. data/lib/friendly/document_table.rb +56 -0
  18. data/lib/friendly/index.rb +73 -0
  19. data/lib/friendly/memcached.rb +48 -0
  20. data/lib/friendly/newrelic.rb +6 -0
  21. data/lib/friendly/query.rb +42 -0
  22. data/lib/friendly/sequel_monkey_patches.rb +35 -0
  23. data/lib/friendly/storage.rb +31 -0
  24. data/lib/friendly/storage_factory.rb +24 -0
  25. data/lib/friendly/storage_proxy.rb +103 -0
  26. data/lib/friendly/table.rb +15 -0
  27. data/lib/friendly/table_creator.rb +43 -0
  28. data/lib/friendly/time.rb +14 -0
  29. data/lib/friendly/translator.rb +32 -0
  30. data/lib/friendly/uuid.rb +143 -0
  31. data/lib/friendly.rb +49 -0
  32. data/rails/init.rb +3 -0
  33. data/spec/fakes/data_store_fake.rb +29 -0
  34. data/spec/fakes/database_fake.rb +12 -0
  35. data/spec/fakes/dataset_fake.rb +28 -0
  36. data/spec/fakes/document.rb +18 -0
  37. data/spec/fakes/serializer_fake.rb +12 -0
  38. data/spec/fakes/time_fake.rb +12 -0
  39. data/spec/integration/basic_object_lifecycle_spec.rb +114 -0
  40. data/spec/integration/batch_insertion_spec.rb +29 -0
  41. data/spec/integration/convenience_api_spec.rb +25 -0
  42. data/spec/integration/count_spec.rb +12 -0
  43. data/spec/integration/default_value_spec.rb +15 -0
  44. data/spec/integration/find_via_cache_spec.rb +101 -0
  45. data/spec/integration/finder_spec.rb +64 -0
  46. data/spec/integration/index_spec.rb +57 -0
  47. data/spec/integration/pagination_spec.rb +63 -0
  48. data/spec/integration/table_creator_spec.rb +52 -0
  49. data/spec/integration/write_through_cache_spec.rb +53 -0
  50. data/spec/spec.opts +1 -0
  51. data/spec/spec_helper.rb +90 -0
  52. data/spec/unit/attribute_spec.rb +64 -0
  53. data/spec/unit/cache_by_id_spec.rb +102 -0
  54. data/spec/unit/cache_spec.rb +21 -0
  55. data/spec/unit/config_spec.rb +4 -0
  56. data/spec/unit/data_store_spec.rb +188 -0
  57. data/spec/unit/document_spec.rb +311 -0
  58. data/spec/unit/document_table_spec.rb +126 -0
  59. data/spec/unit/friendly_spec.rb +25 -0
  60. data/spec/unit/index_spec.rb +196 -0
  61. data/spec/unit/memcached_spec.rb +114 -0
  62. data/spec/unit/query_spec.rb +104 -0
  63. data/spec/unit/storage_factory_spec.rb +59 -0
  64. data/spec/unit/storage_proxy_spec.rb +218 -0
  65. data/spec/unit/translator_spec.rb +96 -0
  66. data/website/index.html +210 -0
  67. data/website/scripts/clipboard.swf +0 -0
  68. data/website/scripts/shBrushAS3.js +61 -0
  69. data/website/scripts/shBrushBash.js +66 -0
  70. data/website/scripts/shBrushCSharp.js +67 -0
  71. data/website/scripts/shBrushColdFusion.js +102 -0
  72. data/website/scripts/shBrushCpp.js +99 -0
  73. data/website/scripts/shBrushCss.js +93 -0
  74. data/website/scripts/shBrushDelphi.js +57 -0
  75. data/website/scripts/shBrushDiff.js +43 -0
  76. data/website/scripts/shBrushErlang.js +54 -0
  77. data/website/scripts/shBrushGroovy.js +69 -0
  78. data/website/scripts/shBrushJScript.js +52 -0
  79. data/website/scripts/shBrushJava.js +59 -0
  80. data/website/scripts/shBrushJavaFX.js +60 -0
  81. data/website/scripts/shBrushPerl.js +74 -0
  82. data/website/scripts/shBrushPhp.js +91 -0
  83. data/website/scripts/shBrushPlain.js +35 -0
  84. data/website/scripts/shBrushPowerShell.js +76 -0
  85. data/website/scripts/shBrushPython.js +66 -0
  86. data/website/scripts/shBrushRuby.js +57 -0
  87. data/website/scripts/shBrushScala.js +53 -0
  88. data/website/scripts/shBrushSql.js +68 -0
  89. data/website/scripts/shBrushVb.js +58 -0
  90. data/website/scripts/shBrushXml.js +71 -0
  91. data/website/scripts/shCore.js +30 -0
  92. data/website/scripts/shLegacy.js +30 -0
  93. data/website/styles/friendly.css +103 -0
  94. data/website/styles/help.png +0 -0
  95. data/website/styles/ie.css +35 -0
  96. data/website/styles/magnifier.png +0 -0
  97. data/website/styles/page_white_code.png +0 -0
  98. data/website/styles/page_white_copy.png +0 -0
  99. data/website/styles/print.css +29 -0
  100. data/website/styles/printer.png +0 -0
  101. data/website/styles/screen.css +257 -0
  102. data/website/styles/shCore.css +330 -0
  103. data/website/styles/shThemeDefault.css +173 -0
  104. data/website/styles/shThemeDjango.css +176 -0
  105. data/website/styles/shThemeEclipse.css +190 -0
  106. data/website/styles/shThemeEmacs.css +175 -0
  107. data/website/styles/shThemeFadeToGrey.css +177 -0
  108. data/website/styles/shThemeMidnight.css +175 -0
  109. data/website/styles/shThemeRDark.css +175 -0
  110. metadata +264 -0
@@ -0,0 +1,311 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Friendly::Document" do
4
+ before do
5
+ @klass = Class.new { include Friendly::Document }
6
+ @klass.attribute(:name, String)
7
+ @storage_proxy = stub
8
+ @klass.storage_proxy = @storage_proxy
9
+ end
10
+
11
+ it "delegates table_name to it's class" do
12
+ User.new.table_name.should == User.table_name
13
+ end
14
+
15
+ it "always has an id attribute" do
16
+ @klass.attributes[:id].type.should == Friendly::UUID
17
+ end
18
+
19
+ it "always has a created_at attribute" do
20
+ @klass.attributes[:created_at].type.should == Time
21
+ end
22
+
23
+ it "always has a updated_at attribute" do
24
+ @klass.attributes[:updated_at].type.should == Time
25
+ end
26
+
27
+ describe "saving a new document" do
28
+ before do
29
+ @user = @klass.new(:name => "whatever")
30
+ @storage_proxy.stubs(:create)
31
+ @user.save
32
+ end
33
+
34
+ it "asks the storage_proxy to create" do
35
+ @storage_proxy.should have_received(:create).with(@user)
36
+ end
37
+ end
38
+
39
+ describe "saving an existing document" do
40
+ before do
41
+ @user = @klass.new(:name => "whatever", :id => 42, :new_record => false)
42
+ @storage_proxy.stubs(:update)
43
+ @user.save
44
+ end
45
+
46
+ it "asks the storage_proxy to update" do
47
+ @storage_proxy.should have_received(:update).with(@user)
48
+ end
49
+ end
50
+
51
+ describe "destroying a document" do
52
+ before do
53
+ @user = @klass.new
54
+ @storage_proxy.stubs(:destroy)
55
+ @user.destroy
56
+ end
57
+
58
+ it "delegates to the storage proxy" do
59
+ @storage_proxy.should have_received(:destroy).with(@user)
60
+ end
61
+ end
62
+
63
+ describe "converting a document to a hash" do
64
+ before do
65
+ @object = @klass.new(:name => "Stewie")
66
+ end
67
+
68
+ it "creates a hash that contains its attributes" do
69
+ @object.to_hash.should == {:name => "Stewie",
70
+ :id => @object.id,
71
+ :created_at => @object.created_at,
72
+ :updated_at => @object.updated_at}
73
+ end
74
+ end
75
+
76
+ describe "setting the attributes all at once" do
77
+ before do
78
+ @object = @klass.new
79
+ @object.attributes = {:name => "Bond"}
80
+ end
81
+
82
+ it "sets the attributes using the setters" do
83
+ @object.name.should == "Bond"
84
+ end
85
+
86
+ it "raises ArgumentError when there are duplicate keys of differing type" do
87
+ lambda {
88
+ @object.attributes = {:name => "Bond", "name" => "Bond"}
89
+ }.should raise_error(ArgumentError)
90
+ end
91
+ end
92
+
93
+ describe "initializing a document" do
94
+ before do
95
+ @doc = @klass.new :name => "Bond"
96
+ end
97
+
98
+ it "sets the attributes using the setters" do
99
+ @doc.name.should == "Bond"
100
+ end
101
+ end
102
+
103
+ describe "table name" do
104
+ it "by default: is the class name, converted with pluralize.underscore" do
105
+ User.table_name.should == "users"
106
+ end
107
+
108
+ it "is overridable" do
109
+ @klass.table_name = "ASDF"
110
+ @klass.table_name.should == "ASDF"
111
+ end
112
+ end
113
+
114
+ describe "new record" do
115
+ before do
116
+ @object = @klass.new
117
+ end
118
+
119
+ it "is new_record by default" do
120
+ @object.should be_new_record
121
+ end
122
+
123
+ it "is not new_record when new_record is set to false" do
124
+ @object.new_record = false
125
+ @object.should_not be_new_record
126
+ end
127
+ end
128
+
129
+ describe "object equality" do
130
+ it "is never equal if both objects are new_records" do
131
+ @klass.new(:name => "x").should_not == @klass.new(:name => "x")
132
+ end
133
+
134
+ it "is equal if both objects have the same id" do
135
+ uuid = Friendly::UUID.new
136
+ one = @klass.new(:id => uuid, :new_record => false)
137
+ one.should == @klass.new(:id => uuid, :new_record => false)
138
+ end
139
+
140
+ it "is equal if the objects point to the same reference" do
141
+ obj = @klass.new
142
+ obj.should == obj
143
+ end
144
+
145
+ it "is not equal if two objects are of differing types with the same id" do
146
+ @klass.new(:id => 1).should_not == User.new(:id => 1)
147
+ end
148
+ end
149
+
150
+ describe "adding an index" do
151
+ before do
152
+ @storage_proxy.stubs(:add)
153
+ @klass = Class.new { include Friendly::Document }
154
+ @klass.storage_proxy = @storage_proxy
155
+ @klass.indexes :name
156
+ end
157
+
158
+ it "delegates to the storage_proxy" do
159
+ @klass.storage_proxy.should have_received(:add).with([:name])
160
+ end
161
+ end
162
+
163
+ describe "adding a cache" do
164
+ before do
165
+ @storage_proxy.stubs(:cache)
166
+ @klass.caches_by(:name, :created_at)
167
+ end
168
+
169
+ it "delegates to the storage_proxy" do
170
+ @storage_proxy.should have_received(:cache).with([:name, :created_at], {})
171
+ end
172
+ end
173
+
174
+ describe "Document.first" do
175
+ before do
176
+ @doc = stub
177
+ @query = stub
178
+ @query_klass = stub
179
+ @klass.query_klass = @query_klass
180
+ @query_klass.stubs(:new).with(:id => 1).returns(@query)
181
+ @storage_proxy.stubs(:first).with(@query).returns(@doc)
182
+ end
183
+
184
+ it "creates a query object and delegates to the storage proxy" do
185
+ @klass.first(:id => 1).should == @doc
186
+ end
187
+ end
188
+
189
+ describe "Document.all" do
190
+ before do
191
+ @docs = stub
192
+ @query = stub
193
+ @query_klass = stub
194
+ @klass.query_klass = @query_klass
195
+ @query_klass.stubs(:new).with(:name => "x").returns(@query)
196
+ @storage_proxy.stubs(:all).with(@query).returns(@docs)
197
+ end
198
+
199
+ it "delegates to the storage proxy" do
200
+ @klass.all(:name => "x").should == @docs
201
+ end
202
+ end
203
+
204
+ describe "Document.find" do
205
+ describe "when an object is found" do
206
+ before do
207
+ @doc = stub
208
+ @query = stub
209
+ @query_klass = stub
210
+ @klass.query_klass = @query_klass
211
+ @query_klass.stubs(:new).with(:id => 1).returns(@query)
212
+ @storage_proxy.stubs(:first).with(@query).returns(@doc)
213
+ end
214
+
215
+ it "queries the storage proxy" do
216
+ @klass.find(1).should == @doc
217
+ end
218
+ end
219
+
220
+ describe "when no object is found" do
221
+ before do
222
+ @storage_proxy.stubs(:first).returns(nil)
223
+ end
224
+
225
+ it "raises RecordNotFound" do
226
+ lambda {
227
+ @klass.find(1)
228
+ }.should raise_error(Friendly::RecordNotFound)
229
+ end
230
+ end
231
+ end
232
+
233
+ describe "Document.all" do
234
+ before do
235
+ @query = stub
236
+ @query_klass = stub
237
+ @klass.query_klass = @query_klass
238
+ @query_klass.stubs(:new).with(:name => "x").returns(@query)
239
+ @storage_proxy.stubs(:count).with(@query).returns(25)
240
+ end
241
+
242
+ it "delegates to the storage proxy" do
243
+ @klass.count(:name => "x").should == 25
244
+ end
245
+ end
246
+
247
+ describe "Document.create" do
248
+ before do
249
+ @storage_proxy.stubs(:create)
250
+ @doc = @klass.create(:name => "James")
251
+ end
252
+
253
+ it "initializes, then saves the document and returns it" do
254
+ @storage_proxy.should have_received(:create).with(@doc)
255
+ @doc.should be_kind_of(@klass)
256
+ end
257
+ end
258
+
259
+ describe "Document#update_attributes" do
260
+ before do
261
+ @storage_proxy.stubs(:update)
262
+ @doc = @klass.new(:name => "James", :new_record => false)
263
+ @doc.update_attributes :name => "Steve"
264
+ end
265
+
266
+ it "sets the attributes" do
267
+ @doc.name.should == "Steve"
268
+ end
269
+
270
+ it "saves the document" do
271
+ @storage_proxy.should have_received(:update).with(@doc)
272
+ end
273
+ end
274
+
275
+ describe "when document has been included" do
276
+ after { Friendly::Document.documents = [] }
277
+ it "adds the document to the collection" do
278
+ Friendly::Document.documents.should include(@klass)
279
+ end
280
+ end
281
+
282
+ describe "Document.paginate" do
283
+ before do
284
+ @conditions = {:name => "Stewie", :page! => 1, :per_page! => 20}
285
+ @query = stub(:page => 1, :per_page => 20)
286
+ @docs = stub
287
+ @query_klass = stub
288
+ @klass.query_klass = @query_klass
289
+ @count = 10
290
+ @collection_klass = stub
291
+ @collection = stub
292
+ @klass.collection_klass = @collection_klass
293
+ @collection.stubs(:replace).returns(@collection)
294
+ @collection_klass.stubs(:new).with(1, 20, @count).returns(@collection)
295
+ @query_klass.stubs(:new).returns(@query)
296
+ @storage_proxy.stubs(:count).with(@query).returns(@count)
297
+ @storage_proxy.stubs(:all).with(@query).returns(@docs)
298
+
299
+ @pagination = @klass.paginate(@conditions)
300
+ end
301
+
302
+ it "creates an instance of the collection klass and returns it" do
303
+ @pagination.should == @collection
304
+ end
305
+
306
+ it "fills the collection with objects from the datastore" do
307
+ @collection.should have_received(:replace).with(@docs)
308
+ end
309
+ end
310
+ end
311
+
@@ -0,0 +1,126 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Friendly::DocumentTable" do
4
+ before do
5
+ @datastore = stub(:insert => 42, :update => nil, :delete => nil)
6
+ @klass = stub(:table_name => "users")
7
+ @translator = stub
8
+ @table = Friendly::DocumentTable.new(@klass, @datastore, @translator)
9
+ @subject = @table
10
+ @document = FakeDocument.new
11
+ end
12
+
13
+ it "has a table name of klass.table_name" do
14
+ @table.table_name.should == "users"
15
+ end
16
+
17
+ it { should be_satisfies(query(:id => 1)) }
18
+ it { should_not be_satisfies(query(:id => 1, :name => "x")) }
19
+ it { should_not be_satisfies(query(:name => "x")) }
20
+ it { should_not be_satisfies(query(:id => 1, :order! => :created_at.desc)) }
21
+
22
+ describe "saving an object" do
23
+ before do
24
+ @document_hash = {:name => "whatever"}
25
+ @document = FakeDocument.new :table_name => "users",
26
+ :to_hash => @document_hash
27
+ @record = {:created_at => Time.new, :updated_at => Time.new}
28
+ @translator.stubs(:to_record).with(@document).returns(@record)
29
+ end
30
+
31
+ describe "when it is a new_record?" do
32
+ before do
33
+ @document.new_record = true
34
+ @table.create(@document)
35
+ end
36
+
37
+ it "saves the record from the translator to the database" do
38
+ @datastore.should have_received(:insert).with(@document, @record)
39
+ end
40
+
41
+ it "sets the created_at on the document" do
42
+ @document.created_at.should == @record[:created_at]
43
+ end
44
+
45
+ it "sets the updated_at on the document" do
46
+ @document.updated_at.should == @record[:updated_at]
47
+ end
48
+
49
+ it "sets new_record to false" do
50
+ @document.should_not be_new_record
51
+ end
52
+ end
53
+
54
+ describe "updating a record" do
55
+ before do
56
+ @document.id = 24
57
+ @document.new_record = false
58
+ @table.update(@document)
59
+ end
60
+
61
+ it "saves the record from the translator" do
62
+ @datastore.should have_received(:update).with(@document, 24, @record)
63
+ end
64
+
65
+ it "sets the created_at from the translator" do
66
+ @document.created_at.should == @record[:created_at]
67
+ end
68
+
69
+ it "sets the updated_at from the translator" do
70
+ @document.updated_at.should == @record[:updated_at]
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "destroying an object" do
76
+ before do
77
+ @document.id = 42
78
+ @table.destroy(@document)
79
+ end
80
+
81
+ it "asks the datastore to delete" do
82
+ @datastore.should have_received(:delete).with(@document, 42)
83
+ end
84
+ end
85
+
86
+ describe "finding the first object" do
87
+ describe "when the object is found" do
88
+ before do
89
+ @record = {:id => 1}
90
+ @document = stub
91
+ @datastore.stubs(:first).with(@klass, :id => 1).returns(@record)
92
+ @translator.stubs(:to_object).with(@klass, @record).returns(@document)
93
+ end
94
+
95
+ it "queries the datastore and translates the object" do
96
+ @table.first(:id => 1).should == @document
97
+ end
98
+ end
99
+
100
+ describe "when the object is not found" do
101
+ before do
102
+ @datastore.stubs(:first).with(@klass, :id => 1).returns(nil)
103
+ end
104
+
105
+ it "returns nil" do
106
+ @table.first(:id => 1).should be_nil
107
+ end
108
+ end
109
+ end
110
+
111
+ describe "finding many objects" do
112
+ before do
113
+ @records = [row(:id => 1), row(:id => 2)]
114
+ @document = stub
115
+ @query = query(:id => [1,2])
116
+ @records.each do |r|
117
+ @translator.stubs(:to_object).with(@klass, r).returns(@document).once
118
+ end
119
+ @datastore.stubs(:all).with(@klass, @query).returns(@records)
120
+ end
121
+
122
+ it "queries the datastore and translates the returned records" do
123
+ @table.all(@query).should == [@document, @document]
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Friendly" do
4
+ describe "configuring friendly" do
5
+ before do
6
+ @datastore = stub
7
+ Friendly::DataStore.stubs(:new).returns(@datastore)
8
+ @db = stub(:meta_def => nil)
9
+ Sequel.stubs(:connect).returns(@db)
10
+ Friendly.configure(:host => "localhost")
11
+ end
12
+
13
+ it "creates a db object by delegating to Sequel" do
14
+ Sequel.should have_received(:connect).with(:host => "localhost")
15
+ end
16
+
17
+ it "creates a datastore object with the db object" do
18
+ Friendly::DataStore.should have_received(:new).with(@db)
19
+ end
20
+
21
+ it "sets the datastore as the default" do
22
+ Friendly.datastore.should == @datastore
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,196 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Friendly::Index" do
4
+ before do
5
+ @klass = stub(:table_name => "users")
6
+ @index = Friendly::Index.new(@klass, [:name, :age])
7
+ end
8
+
9
+ it "satisfies query when all the fields are indexed" do
10
+ @index.should be_satisfies(query({:name => "x", :age => "y"}))
11
+ end
12
+
13
+ it "doesn't satisfy query when some fields are not indexed" do
14
+ @index.should_not be_satisfies(query({:name => "x", :dob => "12/01/1980"}))
15
+ end
16
+
17
+ it "doesn't satisfy if it only uses keys on the right of the index" do
18
+ @index.should_not be_satisfies(query({:age => "y"}))
19
+ end
20
+
21
+ it "satisfies if it only uses keys on the left of the index" do
22
+ @index.should be_satisfies(query({:name => "y"}))
23
+ end
24
+
25
+ it "satisfies an ordered query if it uses all fields and order is rightmost" do
26
+ @index.should be_satisfies(query(:name => "y", :order! => :age.desc))
27
+ end
28
+
29
+ it "doesn't satisfy an ordered query if it uses all fields and order ! leftmost" do
30
+ @index.should_not be_satisfies(query(:name => "Stewie", :order! => :name.desc))
31
+ end
32
+
33
+ it "doesn't satisfy an ordered query if it uses a field after a gap" do
34
+ ix = Friendly::Index.new(@klass, [:name, :height, :age])
35
+ ix.should_not be_satisfies(query(:name => "James", :order! => :age.desc))
36
+ end
37
+
38
+ describe "with one field" do
39
+ before do
40
+ @index = Friendly::Index.new(@klass, [:name])
41
+ end
42
+
43
+ it "has an appropriate table name" do
44
+ @index.table_name.should == "index_users_on_name"
45
+ end
46
+ end
47
+
48
+ describe "with multiple fields" do
49
+ before do
50
+ @index = Friendly::Index.new(@klass, [:name, :age])
51
+ end
52
+
53
+ it "has an appropriate table name" do
54
+ @index.table_name.should == "index_users_on_name_and_age"
55
+ end
56
+ end
57
+
58
+ describe "finding the first record matching a query" do
59
+ before do
60
+ @result = row(:id => 42)
61
+ @datastore = stub(:first => @result)
62
+ @index = Friendly::Index.new(@klass, [:name], @datastore)
63
+ @doc = stub
64
+ @klass.stubs(:first).with(:id => 42).returns(@doc)
65
+ @result = @index.first(:name => "x")
66
+ end
67
+
68
+ it "queries the datastore with the attributes from the query" do
69
+ @datastore.should have_received(:first).once
70
+ @datastore.should have_received(:first).with(@index, :name => "x")
71
+ end
72
+
73
+ it "finds the document by the id returned by the datastore" do
74
+ @klass.should have_received(:first).with(:id => 42)
75
+ end
76
+
77
+ it "returns the document returned by the klass" do
78
+ @result.should == @doc
79
+ end
80
+
81
+ describe "when no result is found" do
82
+ before do
83
+ @datastore.stubs(:first).returns(nil)
84
+ @result = @index.first(:name => "x")
85
+ end
86
+
87
+ it "returns nil" do
88
+ @result.should be_nil
89
+ end
90
+ end
91
+ end
92
+
93
+ describe "finding all the rows matching a query" do
94
+ before do
95
+ @results = [row(:id => 42), row(:id => 43), row(:id => 44)]
96
+ @query = query(:name => "x")
97
+ @datastore = stub(:all => @results)
98
+ @index = Friendly::Index.new(@klass, [:name], @datastore)
99
+ @documents = stub
100
+ @klass.stubs(:all).with(:id => [42, 43, 44],
101
+ :preserve_order! => false).returns(@documents)
102
+ @result = @index.all(@query)
103
+ end
104
+
105
+ it "queries the datastore with the conditions" do
106
+ @datastore.should have_received(:all).once
107
+ @datastore.should have_received(:all).with(@index, @query)
108
+ end
109
+
110
+ it "then queries the klass for the ids it found in the index" do
111
+ @klass.should have_received(:all).with(:id => [42, 43, 44],
112
+ :preserve_order! => false)
113
+ end
114
+
115
+ it "returns the result from the klass.all call" do
116
+ @result.should == @documents
117
+ end
118
+ end
119
+
120
+ describe "finding all the rows matching a query in order" do
121
+ before do
122
+ @results = [row(:id => 42), row(:id => 43), row(:id => 44)]
123
+ @query = query(:name => "x", :order! => :created_at.desc)
124
+ @datastore = stub(:all => @results)
125
+ @index = Friendly::Index.new(@klass, [:name], @datastore)
126
+ @documents = stub
127
+ @klass.stubs(:all).with(:id => [42, 43, 44],
128
+ :preserve_order! => true).returns(@documents)
129
+ @result = @index.all(@query)
130
+ end
131
+
132
+ it "queries the klass with preserve_order! => true" do
133
+ @klass.should have_received(:all).with(:id => [42, 43, 44],
134
+ :preserve_order! => true)
135
+ end
136
+ end
137
+
138
+ describe "updating the indexes" do
139
+ before do
140
+ @datastore = stub(:insert => nil, :update => nil)
141
+ @index = Friendly::Index.new(stub, [:name], @datastore)
142
+ @document = stub(:name => "Stewie",
143
+ :indexes => [@index],
144
+ :id => 42)
145
+ @index_record = {:name => "Stewie", :id => 42}
146
+ end
147
+
148
+ describe "indexing a new document" do
149
+ before do
150
+ @index.create(@document)
151
+ end
152
+
153
+ it "inserts a record in to the datastore with the indexed vals and id" do
154
+ @datastore.should have_received(:insert).with(@index, @index_record)
155
+ end
156
+ end
157
+
158
+ describe "indexing an existing document" do
159
+ before do
160
+ @index.update(@document)
161
+ end
162
+
163
+ it "updates the index records in the database" do
164
+ @datastore.should have_received(:update).with(@index, 42, @index_record)
165
+ end
166
+ end
167
+ end
168
+
169
+ describe "destroying the index rows" do
170
+ before do
171
+ @datastore = stub(:delete => nil)
172
+ @index = Friendly::Index.new(stub, [:name], @datastore)
173
+ @document = stub(:name => "Stewie",
174
+ :indexes => [@index],
175
+ :id => 42)
176
+ @index.destroy(@document)
177
+ end
178
+
179
+ it "deletes the records in the index" do
180
+ @datastore.should have_received(:delete).with(@index, 42)
181
+ end
182
+ end
183
+
184
+ describe "counting rows matching a query" do
185
+ before do
186
+ @datastore = stub
187
+ @query = query(:name => "Stewie")
188
+ @index = Friendly::Index.new(@klass, [:name], @datastore)
189
+ @datastore.stubs(:count).with(@index, @query).returns(10)
190
+ end
191
+
192
+ it "delegates to the datastore" do
193
+ @index.count(@query).should == 10
194
+ end
195
+ end
196
+ end