friendly_postgres 0.4.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 (129) hide show
  1. data/.document +2 -0
  2. data/.gitignore +26 -0
  3. data/APACHE-LICENSE +202 -0
  4. data/CHANGELOG.md +19 -0
  5. data/CONTRIBUTORS.md +7 -0
  6. data/LICENSE +20 -0
  7. data/README.md +265 -0
  8. data/Rakefile +68 -0
  9. data/TODO.md +5 -0
  10. data/VERSION +1 -0
  11. data/examples/friendly.yml +7 -0
  12. data/friendly.gemspec +232 -0
  13. data/lib/friendly.rb +54 -0
  14. data/lib/friendly/associations.rb +7 -0
  15. data/lib/friendly/associations/association.rb +34 -0
  16. data/lib/friendly/associations/set.rb +37 -0
  17. data/lib/friendly/attribute.rb +91 -0
  18. data/lib/friendly/boolean.rb +10 -0
  19. data/lib/friendly/cache.rb +24 -0
  20. data/lib/friendly/cache/by_id.rb +33 -0
  21. data/lib/friendly/config.rb +5 -0
  22. data/lib/friendly/data_store.rb +72 -0
  23. data/lib/friendly/document.rb +257 -0
  24. data/lib/friendly/document_table.rb +56 -0
  25. data/lib/friendly/index.rb +73 -0
  26. data/lib/friendly/memcached.rb +48 -0
  27. data/lib/friendly/named_scope.rb +17 -0
  28. data/lib/friendly/newrelic.rb +6 -0
  29. data/lib/friendly/query.rb +42 -0
  30. data/lib/friendly/scope.rb +100 -0
  31. data/lib/friendly/scope_proxy.rb +45 -0
  32. data/lib/friendly/sequel_monkey_patches.rb +34 -0
  33. data/lib/friendly/storage.rb +31 -0
  34. data/lib/friendly/storage_factory.rb +24 -0
  35. data/lib/friendly/storage_proxy.rb +103 -0
  36. data/lib/friendly/table.rb +15 -0
  37. data/lib/friendly/table_creator.rb +48 -0
  38. data/lib/friendly/time.rb +14 -0
  39. data/lib/friendly/translator.rb +32 -0
  40. data/lib/friendly/uuid.rb +148 -0
  41. data/rails/init.rb +3 -0
  42. data/spec/config.yml.example +7 -0
  43. data/spec/fakes/data_store_fake.rb +29 -0
  44. data/spec/fakes/database_fake.rb +12 -0
  45. data/spec/fakes/dataset_fake.rb +28 -0
  46. data/spec/fakes/document.rb +18 -0
  47. data/spec/fakes/serializer_fake.rb +12 -0
  48. data/spec/fakes/time_fake.rb +12 -0
  49. data/spec/integration/ad_hoc_scopes_spec.rb +42 -0
  50. data/spec/integration/basic_object_lifecycle_spec.rb +114 -0
  51. data/spec/integration/batch_insertion_spec.rb +29 -0
  52. data/spec/integration/convenience_api_spec.rb +25 -0
  53. data/spec/integration/count_spec.rb +12 -0
  54. data/spec/integration/default_value_spec.rb +15 -0
  55. data/spec/integration/find_via_cache_spec.rb +101 -0
  56. data/spec/integration/finder_spec.rb +64 -0
  57. data/spec/integration/has_many_spec.rb +18 -0
  58. data/spec/integration/index_spec.rb +57 -0
  59. data/spec/integration/named_scope_spec.rb +34 -0
  60. data/spec/integration/pagination_spec.rb +63 -0
  61. data/spec/integration/scope_chaining_spec.rb +22 -0
  62. data/spec/integration/table_creator_spec.rb +64 -0
  63. data/spec/integration/write_through_cache_spec.rb +53 -0
  64. data/spec/spec.opts +1 -0
  65. data/spec/spec_helper.rb +103 -0
  66. data/spec/unit/associations/association_spec.rb +57 -0
  67. data/spec/unit/associations/set_spec.rb +43 -0
  68. data/spec/unit/attribute_spec.rb +105 -0
  69. data/spec/unit/cache_by_id_spec.rb +102 -0
  70. data/spec/unit/cache_spec.rb +21 -0
  71. data/spec/unit/config_spec.rb +4 -0
  72. data/spec/unit/data_store_spec.rb +188 -0
  73. data/spec/unit/document_spec.rb +358 -0
  74. data/spec/unit/document_table_spec.rb +126 -0
  75. data/spec/unit/friendly_spec.rb +25 -0
  76. data/spec/unit/index_spec.rb +196 -0
  77. data/spec/unit/memcached_spec.rb +114 -0
  78. data/spec/unit/named_scope_spec.rb +16 -0
  79. data/spec/unit/query_spec.rb +104 -0
  80. data/spec/unit/scope_proxy_spec.rb +44 -0
  81. data/spec/unit/scope_spec.rb +113 -0
  82. data/spec/unit/storage_factory_spec.rb +59 -0
  83. data/spec/unit/storage_proxy_spec.rb +218 -0
  84. data/spec/unit/translator_spec.rb +96 -0
  85. data/website/index.html +210 -0
  86. data/website/scripts/clipboard.swf +0 -0
  87. data/website/scripts/shBrushAS3.js +61 -0
  88. data/website/scripts/shBrushBash.js +66 -0
  89. data/website/scripts/shBrushCSharp.js +67 -0
  90. data/website/scripts/shBrushColdFusion.js +102 -0
  91. data/website/scripts/shBrushCpp.js +99 -0
  92. data/website/scripts/shBrushCss.js +93 -0
  93. data/website/scripts/shBrushDelphi.js +57 -0
  94. data/website/scripts/shBrushDiff.js +43 -0
  95. data/website/scripts/shBrushErlang.js +54 -0
  96. data/website/scripts/shBrushGroovy.js +69 -0
  97. data/website/scripts/shBrushJScript.js +52 -0
  98. data/website/scripts/shBrushJava.js +59 -0
  99. data/website/scripts/shBrushJavaFX.js +60 -0
  100. data/website/scripts/shBrushPerl.js +74 -0
  101. data/website/scripts/shBrushPhp.js +91 -0
  102. data/website/scripts/shBrushPlain.js +35 -0
  103. data/website/scripts/shBrushPowerShell.js +76 -0
  104. data/website/scripts/shBrushPython.js +66 -0
  105. data/website/scripts/shBrushRuby.js +57 -0
  106. data/website/scripts/shBrushScala.js +53 -0
  107. data/website/scripts/shBrushSql.js +68 -0
  108. data/website/scripts/shBrushVb.js +58 -0
  109. data/website/scripts/shBrushXml.js +71 -0
  110. data/website/scripts/shCore.js +30 -0
  111. data/website/scripts/shLegacy.js +30 -0
  112. data/website/styles/friendly.css +103 -0
  113. data/website/styles/help.png +0 -0
  114. data/website/styles/ie.css +35 -0
  115. data/website/styles/magnifier.png +0 -0
  116. data/website/styles/page_white_code.png +0 -0
  117. data/website/styles/page_white_copy.png +0 -0
  118. data/website/styles/print.css +29 -0
  119. data/website/styles/printer.png +0 -0
  120. data/website/styles/screen.css +257 -0
  121. data/website/styles/shCore.css +330 -0
  122. data/website/styles/shThemeDefault.css +173 -0
  123. data/website/styles/shThemeDjango.css +176 -0
  124. data/website/styles/shThemeEclipse.css +190 -0
  125. data/website/styles/shThemeEmacs.css +175 -0
  126. data/website/styles/shThemeFadeToGrey.css +177 -0
  127. data/website/styles/shThemeMidnight.css +175 -0
  128. data/website/styles/shThemeRDark.css +175 -0
  129. metadata +302 -0
@@ -0,0 +1,358 @@
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
+
311
+ describe "creating a named_scope" do
312
+ before do
313
+ @scope_proxy = stub(:add_named => nil)
314
+ @klass.scope_proxy = @scope_proxy
315
+ @klass.named_scope(:by_name, :order => :name)
316
+ end
317
+
318
+ it "asks the named_scope_set to add it" do
319
+ @klass.scope_proxy.should have_received(:add_named).
320
+ with(:by_name, :order => :name)
321
+ end
322
+ end
323
+
324
+ describe "Document.has_named_scope?" do
325
+ it "delegates to the scope_proxy" do
326
+ @scope_proxy = stub
327
+ @scope_proxy.stubs(:has_named_scope?).with(:whatever).returns(true)
328
+ @klass.scope_proxy = @scope_proxy
329
+ @klass.has_named_scope?(:whatever).should be_true
330
+ end
331
+ end
332
+
333
+ describe "accessing an ad-hoc scope" do
334
+ before do
335
+ @scope = stub
336
+ @scope_proxy = stub
337
+ @scope_proxy.stubs(:ad_hoc).with(:order! => :name).returns(@scope)
338
+ @klass.scope_proxy = @scope_proxy
339
+ end
340
+
341
+ it "asks the named_scope_set to add it" do
342
+ @klass.scope(:order! => :name).should == @scope
343
+ end
344
+ end
345
+
346
+ describe "adding an association" do
347
+ before do
348
+ @assoc_set = stub(:add => nil)
349
+ @klass.association_set = @assoc_set
350
+ @klass.has_many :addresses
351
+ end
352
+
353
+ it "asks the association set to add it" do
354
+ @assoc_set.should have_received(:add).with(:addresses, {})
355
+ end
356
+ end
357
+ end
358
+
@@ -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