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.
- data/.document +5 -0
- data/.gitignore +23 -0
- data/APACHE-LICENSE +202 -0
- data/LICENSE +20 -0
- data/README.md +173 -0
- data/Rakefile +67 -0
- data/VERSION +1 -0
- data/examples/friendly.yml +7 -0
- data/friendly.gemspec +201 -0
- data/lib/friendly/attribute.rb +65 -0
- data/lib/friendly/boolean.rb +6 -0
- data/lib/friendly/cache/by_id.rb +33 -0
- data/lib/friendly/cache.rb +24 -0
- data/lib/friendly/config.rb +5 -0
- data/lib/friendly/data_store.rb +72 -0
- data/lib/friendly/document.rb +165 -0
- data/lib/friendly/document_table.rb +56 -0
- data/lib/friendly/index.rb +73 -0
- data/lib/friendly/memcached.rb +48 -0
- data/lib/friendly/newrelic.rb +6 -0
- data/lib/friendly/query.rb +42 -0
- data/lib/friendly/sequel_monkey_patches.rb +35 -0
- data/lib/friendly/storage.rb +31 -0
- data/lib/friendly/storage_factory.rb +24 -0
- data/lib/friendly/storage_proxy.rb +103 -0
- data/lib/friendly/table.rb +15 -0
- data/lib/friendly/table_creator.rb +43 -0
- data/lib/friendly/time.rb +14 -0
- data/lib/friendly/translator.rb +32 -0
- data/lib/friendly/uuid.rb +143 -0
- data/lib/friendly.rb +49 -0
- data/rails/init.rb +3 -0
- data/spec/fakes/data_store_fake.rb +29 -0
- data/spec/fakes/database_fake.rb +12 -0
- data/spec/fakes/dataset_fake.rb +28 -0
- data/spec/fakes/document.rb +18 -0
- data/spec/fakes/serializer_fake.rb +12 -0
- data/spec/fakes/time_fake.rb +12 -0
- data/spec/integration/basic_object_lifecycle_spec.rb +114 -0
- data/spec/integration/batch_insertion_spec.rb +29 -0
- data/spec/integration/convenience_api_spec.rb +25 -0
- data/spec/integration/count_spec.rb +12 -0
- data/spec/integration/default_value_spec.rb +15 -0
- data/spec/integration/find_via_cache_spec.rb +101 -0
- data/spec/integration/finder_spec.rb +64 -0
- data/spec/integration/index_spec.rb +57 -0
- data/spec/integration/pagination_spec.rb +63 -0
- data/spec/integration/table_creator_spec.rb +52 -0
- data/spec/integration/write_through_cache_spec.rb +53 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +90 -0
- data/spec/unit/attribute_spec.rb +64 -0
- data/spec/unit/cache_by_id_spec.rb +102 -0
- data/spec/unit/cache_spec.rb +21 -0
- data/spec/unit/config_spec.rb +4 -0
- data/spec/unit/data_store_spec.rb +188 -0
- data/spec/unit/document_spec.rb +311 -0
- data/spec/unit/document_table_spec.rb +126 -0
- data/spec/unit/friendly_spec.rb +25 -0
- data/spec/unit/index_spec.rb +196 -0
- data/spec/unit/memcached_spec.rb +114 -0
- data/spec/unit/query_spec.rb +104 -0
- data/spec/unit/storage_factory_spec.rb +59 -0
- data/spec/unit/storage_proxy_spec.rb +218 -0
- data/spec/unit/translator_spec.rb +96 -0
- data/website/index.html +210 -0
- data/website/scripts/clipboard.swf +0 -0
- data/website/scripts/shBrushAS3.js +61 -0
- data/website/scripts/shBrushBash.js +66 -0
- data/website/scripts/shBrushCSharp.js +67 -0
- data/website/scripts/shBrushColdFusion.js +102 -0
- data/website/scripts/shBrushCpp.js +99 -0
- data/website/scripts/shBrushCss.js +93 -0
- data/website/scripts/shBrushDelphi.js +57 -0
- data/website/scripts/shBrushDiff.js +43 -0
- data/website/scripts/shBrushErlang.js +54 -0
- data/website/scripts/shBrushGroovy.js +69 -0
- data/website/scripts/shBrushJScript.js +52 -0
- data/website/scripts/shBrushJava.js +59 -0
- data/website/scripts/shBrushJavaFX.js +60 -0
- data/website/scripts/shBrushPerl.js +74 -0
- data/website/scripts/shBrushPhp.js +91 -0
- data/website/scripts/shBrushPlain.js +35 -0
- data/website/scripts/shBrushPowerShell.js +76 -0
- data/website/scripts/shBrushPython.js +66 -0
- data/website/scripts/shBrushRuby.js +57 -0
- data/website/scripts/shBrushScala.js +53 -0
- data/website/scripts/shBrushSql.js +68 -0
- data/website/scripts/shBrushVb.js +58 -0
- data/website/scripts/shBrushXml.js +71 -0
- data/website/scripts/shCore.js +30 -0
- data/website/scripts/shLegacy.js +30 -0
- data/website/styles/friendly.css +103 -0
- data/website/styles/help.png +0 -0
- data/website/styles/ie.css +35 -0
- data/website/styles/magnifier.png +0 -0
- data/website/styles/page_white_code.png +0 -0
- data/website/styles/page_white_copy.png +0 -0
- data/website/styles/print.css +29 -0
- data/website/styles/printer.png +0 -0
- data/website/styles/screen.css +257 -0
- data/website/styles/shCore.css +330 -0
- data/website/styles/shThemeDefault.css +173 -0
- data/website/styles/shThemeDjango.css +176 -0
- data/website/styles/shThemeEclipse.css +190 -0
- data/website/styles/shThemeEmacs.css +175 -0
- data/website/styles/shThemeFadeToGrey.css +177 -0
- data/website/styles/shThemeMidnight.css +175 -0
- data/website/styles/shThemeRDark.css +175 -0
- 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
|