wayne-friendly 0.5.1

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 (136) hide show
  1. data/.document +2 -0
  2. data/.gitignore +26 -0
  3. data/APACHE-LICENSE +202 -0
  4. data/CHANGELOG.md +28 -0
  5. data/CONTRIBUTORS.md +7 -0
  6. data/LICENSE +20 -0
  7. data/README.md +288 -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 +240 -0
  13. data/lib/friendly.rb +58 -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 +98 -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/data_store.rb +73 -0
  22. data/lib/friendly/document.rb +70 -0
  23. data/lib/friendly/document/associations.rb +50 -0
  24. data/lib/friendly/document/attributes.rb +114 -0
  25. data/lib/friendly/document/convenience.rb +41 -0
  26. data/lib/friendly/document/mixin.rb +15 -0
  27. data/lib/friendly/document/scoping.rb +66 -0
  28. data/lib/friendly/document/storage.rb +63 -0
  29. data/lib/friendly/document_table.rb +56 -0
  30. data/lib/friendly/index.rb +73 -0
  31. data/lib/friendly/indexer.rb +50 -0
  32. data/lib/friendly/memcached.rb +48 -0
  33. data/lib/friendly/newrelic.rb +6 -0
  34. data/lib/friendly/query.rb +42 -0
  35. data/lib/friendly/scope.rb +100 -0
  36. data/lib/friendly/scope_proxy.rb +43 -0
  37. data/lib/friendly/sequel_monkey_patches.rb +34 -0
  38. data/lib/friendly/storage.rb +31 -0
  39. data/lib/friendly/storage_factory.rb +24 -0
  40. data/lib/friendly/storage_proxy.rb +111 -0
  41. data/lib/friendly/table.rb +15 -0
  42. data/lib/friendly/table_creator.rb +50 -0
  43. data/lib/friendly/time.rb +14 -0
  44. data/lib/friendly/translator.rb +33 -0
  45. data/lib/friendly/uuid.rb +148 -0
  46. data/lib/tasks/friendly.rake +7 -0
  47. data/rails/init.rb +3 -0
  48. data/spec/config.yml.example +7 -0
  49. data/spec/fakes/data_store_fake.rb +29 -0
  50. data/spec/fakes/database_fake.rb +12 -0
  51. data/spec/fakes/dataset_fake.rb +28 -0
  52. data/spec/fakes/document.rb +18 -0
  53. data/spec/fakes/serializer_fake.rb +12 -0
  54. data/spec/fakes/time_fake.rb +12 -0
  55. data/spec/integration/ad_hoc_scopes_spec.rb +42 -0
  56. data/spec/integration/basic_object_lifecycle_spec.rb +114 -0
  57. data/spec/integration/batch_insertion_spec.rb +29 -0
  58. data/spec/integration/convenience_api_spec.rb +25 -0
  59. data/spec/integration/count_spec.rb +12 -0
  60. data/spec/integration/default_value_spec.rb +30 -0
  61. data/spec/integration/dirty_tracking_spec.rb +43 -0
  62. data/spec/integration/find_via_cache_spec.rb +101 -0
  63. data/spec/integration/finder_spec.rb +71 -0
  64. data/spec/integration/has_many_spec.rb +18 -0
  65. data/spec/integration/index_spec.rb +57 -0
  66. data/spec/integration/named_scope_spec.rb +34 -0
  67. data/spec/integration/offline_indexing_spec.rb +53 -0
  68. data/spec/integration/pagination_spec.rb +63 -0
  69. data/spec/integration/scope_chaining_spec.rb +22 -0
  70. data/spec/integration/table_creator_spec.rb +69 -0
  71. data/spec/integration/write_through_cache_spec.rb +53 -0
  72. data/spec/spec.opts +1 -0
  73. data/spec/spec_helper.rb +105 -0
  74. data/spec/unit/associations/association_spec.rb +57 -0
  75. data/spec/unit/associations/set_spec.rb +43 -0
  76. data/spec/unit/attribute_spec.rb +125 -0
  77. data/spec/unit/cache_by_id_spec.rb +102 -0
  78. data/spec/unit/cache_spec.rb +21 -0
  79. data/spec/unit/data_store_spec.rb +201 -0
  80. data/spec/unit/document/attributes_spec.rb +130 -0
  81. data/spec/unit/document_spec.rb +318 -0
  82. data/spec/unit/document_table_spec.rb +126 -0
  83. data/spec/unit/friendly_spec.rb +25 -0
  84. data/spec/unit/index_spec.rb +196 -0
  85. data/spec/unit/memcached_spec.rb +114 -0
  86. data/spec/unit/query_spec.rb +104 -0
  87. data/spec/unit/scope_proxy_spec.rb +44 -0
  88. data/spec/unit/scope_spec.rb +113 -0
  89. data/spec/unit/storage_factory_spec.rb +59 -0
  90. data/spec/unit/storage_proxy_spec.rb +244 -0
  91. data/spec/unit/translator_spec.rb +91 -0
  92. data/website/index.html +210 -0
  93. data/website/scripts/clipboard.swf +0 -0
  94. data/website/scripts/shBrushAS3.js +61 -0
  95. data/website/scripts/shBrushBash.js +66 -0
  96. data/website/scripts/shBrushCSharp.js +67 -0
  97. data/website/scripts/shBrushColdFusion.js +102 -0
  98. data/website/scripts/shBrushCpp.js +99 -0
  99. data/website/scripts/shBrushCss.js +93 -0
  100. data/website/scripts/shBrushDelphi.js +57 -0
  101. data/website/scripts/shBrushDiff.js +43 -0
  102. data/website/scripts/shBrushErlang.js +54 -0
  103. data/website/scripts/shBrushGroovy.js +69 -0
  104. data/website/scripts/shBrushJScript.js +52 -0
  105. data/website/scripts/shBrushJava.js +59 -0
  106. data/website/scripts/shBrushJavaFX.js +60 -0
  107. data/website/scripts/shBrushPerl.js +74 -0
  108. data/website/scripts/shBrushPhp.js +91 -0
  109. data/website/scripts/shBrushPlain.js +35 -0
  110. data/website/scripts/shBrushPowerShell.js +76 -0
  111. data/website/scripts/shBrushPython.js +66 -0
  112. data/website/scripts/shBrushRuby.js +57 -0
  113. data/website/scripts/shBrushScala.js +53 -0
  114. data/website/scripts/shBrushSql.js +68 -0
  115. data/website/scripts/shBrushVb.js +58 -0
  116. data/website/scripts/shBrushXml.js +71 -0
  117. data/website/scripts/shCore.js +30 -0
  118. data/website/scripts/shLegacy.js +30 -0
  119. data/website/styles/friendly.css +103 -0
  120. data/website/styles/help.png +0 -0
  121. data/website/styles/ie.css +35 -0
  122. data/website/styles/magnifier.png +0 -0
  123. data/website/styles/page_white_code.png +0 -0
  124. data/website/styles/page_white_copy.png +0 -0
  125. data/website/styles/print.css +29 -0
  126. data/website/styles/printer.png +0 -0
  127. data/website/styles/screen.css +257 -0
  128. data/website/styles/shCore.css +330 -0
  129. data/website/styles/shThemeDefault.css +173 -0
  130. data/website/styles/shThemeDjango.css +176 -0
  131. data/website/styles/shThemeEclipse.css +190 -0
  132. data/website/styles/shThemeEmacs.css +175 -0
  133. data/website/styles/shThemeFadeToGrey.css +177 -0
  134. data/website/styles/shThemeMidnight.css +175 -0
  135. data/website/styles/shThemeRDark.css +175 -0
  136. metadata +337 -0
@@ -0,0 +1,318 @@
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 "table name" do
64
+ it "by default: is the class name, converted with pluralize.underscore" do
65
+ User.table_name.should == "users"
66
+ end
67
+
68
+ it "is overridable" do
69
+ @klass.table_name = "ASDF"
70
+ @klass.table_name.should == "ASDF"
71
+ end
72
+ end
73
+
74
+ describe "new record" do
75
+ before do
76
+ @object = @klass.new
77
+ end
78
+
79
+ it "is new_record by default" do
80
+ @object.should be_new_record
81
+ end
82
+
83
+ it "is not new_record when new_record is set to false" do
84
+ @object.new_record = false
85
+ @object.should_not be_new_record
86
+ end
87
+ end
88
+
89
+ describe "object equality" do
90
+ it "is never equal if both objects are new_records" do
91
+ @klass.new(:name => "x").should_not == @klass.new(:name => "x")
92
+ end
93
+
94
+ it "is equal if both objects have the same id" do
95
+ uuid = Friendly::UUID.new
96
+ one = @klass.new(:id => uuid, :new_record => false)
97
+ one.should == @klass.new(:id => uuid, :new_record => false)
98
+ end
99
+
100
+ it "is equal if the objects point to the same reference" do
101
+ obj = @klass.new
102
+ obj.should == obj
103
+ end
104
+
105
+ it "is not equal if two objects are of differing types with the same id" do
106
+ @klass.new(:id => 1).should_not == User.new(:id => 1)
107
+ end
108
+ end
109
+
110
+ describe "adding an index" do
111
+ before do
112
+ @storage_proxy.stubs(:add)
113
+ @klass = Class.new { include Friendly::Document }
114
+ @klass.storage_proxy = @storage_proxy
115
+ @klass.indexes :name
116
+ end
117
+
118
+ it "delegates to the storage_proxy" do
119
+ @klass.storage_proxy.should have_received(:add).with([:name])
120
+ end
121
+ end
122
+
123
+ describe "adding a cache" do
124
+ before do
125
+ @storage_proxy.stubs(:cache)
126
+ @klass.caches_by(:name, :created_at)
127
+ end
128
+
129
+ it "delegates to the storage_proxy" do
130
+ @storage_proxy.should have_received(:cache).with([:name, :created_at], {})
131
+ end
132
+ end
133
+
134
+ describe "Document.first" do
135
+ before do
136
+ @doc = stub
137
+ @query = stub
138
+ @query_klass = stub
139
+ @klass.query_klass = @query_klass
140
+ @query_klass.stubs(:new).with(:id => 1).returns(@query)
141
+ @storage_proxy.stubs(:first).with(@query).returns(@doc)
142
+ end
143
+
144
+ it "creates a query object and delegates to the storage proxy" do
145
+ @klass.first(:id => 1).should == @doc
146
+ end
147
+ end
148
+
149
+ describe "Document.all" do
150
+ before do
151
+ @docs = stub
152
+ @query = stub
153
+ @query_klass = stub
154
+ @klass.query_klass = @query_klass
155
+ @query_klass.stubs(:new).with(:name => "x").returns(@query)
156
+ @storage_proxy.stubs(:all).with(@query).returns(@docs)
157
+ end
158
+
159
+ it "delegates to the storage proxy" do
160
+ @klass.all(:name => "x").should == @docs
161
+ end
162
+ end
163
+
164
+ describe "Document.find" do
165
+ describe "when an object is found" do
166
+ before do
167
+ @doc = stub
168
+ @query = stub
169
+ @query_klass = stub
170
+ @klass.query_klass = @query_klass
171
+ @query_klass.stubs(:new).with(:id => 1).returns(@query)
172
+ @storage_proxy.stubs(:first).with(@query).returns(@doc)
173
+ end
174
+
175
+ it "queries the storage proxy" do
176
+ @klass.find(1).should == @doc
177
+ end
178
+ end
179
+
180
+ describe "when no object is found" do
181
+ before do
182
+ @storage_proxy.stubs(:first).returns(nil)
183
+ end
184
+
185
+ it "raises RecordNotFound" do
186
+ lambda {
187
+ @klass.find(1)
188
+ }.should raise_error(Friendly::RecordNotFound)
189
+ end
190
+ end
191
+ end
192
+
193
+ describe "Document.all" do
194
+ before do
195
+ @query = stub
196
+ @query_klass = stub
197
+ @klass.query_klass = @query_klass
198
+ @query_klass.stubs(:new).with(:name => "x").returns(@query)
199
+ @storage_proxy.stubs(:count).with(@query).returns(25)
200
+ end
201
+
202
+ it "delegates to the storage proxy" do
203
+ @klass.count(:name => "x").should == 25
204
+ end
205
+ end
206
+
207
+ describe "Document.create" do
208
+ before do
209
+ @storage_proxy.stubs(:create)
210
+ @doc = @klass.create(:name => "James")
211
+ end
212
+
213
+ it "initializes, then saves the document and returns it" do
214
+ @storage_proxy.should have_received(:create).with(@doc)
215
+ @doc.should be_kind_of(@klass)
216
+ end
217
+ end
218
+
219
+ describe "Document#update_attributes" do
220
+ before do
221
+ @storage_proxy.stubs(:update)
222
+ @doc = @klass.new(:name => "James", :new_record => false)
223
+ @doc.update_attributes :name => "Steve"
224
+ end
225
+
226
+ it "sets the attributes" do
227
+ @doc.name.should == "Steve"
228
+ end
229
+
230
+ it "saves the document" do
231
+ @storage_proxy.should have_received(:update).with(@doc)
232
+ end
233
+ end
234
+
235
+ describe "when document has been included" do
236
+ after { Friendly::Document.documents = [] }
237
+ it "adds the document to the collection" do
238
+ Friendly::Document.documents.should include(@klass)
239
+ end
240
+ end
241
+
242
+ describe "Document.paginate" do
243
+ before do
244
+ @conditions = {:name => "Stewie", :page! => 1, :per_page! => 20}
245
+ @query = stub(:page => 1, :per_page => 20)
246
+ @docs = stub
247
+ @query_klass = stub
248
+ @klass.query_klass = @query_klass
249
+ @count = 10
250
+ @collection_klass = stub
251
+ @collection = stub
252
+ @klass.collection_klass = @collection_klass
253
+ @collection.stubs(:replace).returns(@collection)
254
+ @collection_klass.stubs(:new).with(1, 20, @count).returns(@collection)
255
+ @query_klass.stubs(:new).returns(@query)
256
+ @storage_proxy.stubs(:count).with(@query).returns(@count)
257
+ @storage_proxy.stubs(:all).with(@query).returns(@docs)
258
+
259
+ @pagination = @klass.paginate(@conditions)
260
+ end
261
+
262
+ it "creates an instance of the collection klass and returns it" do
263
+ @pagination.should == @collection
264
+ end
265
+
266
+ it "fills the collection with objects from the datastore" do
267
+ @collection.should have_received(:replace).with(@docs)
268
+ end
269
+ end
270
+
271
+ describe "creating a named_scope" do
272
+ before do
273
+ @scope_proxy = stub(:add_named => nil)
274
+ @klass.scope_proxy = @scope_proxy
275
+ @klass.named_scope(:by_name, :order => :name)
276
+ end
277
+
278
+ it "asks the named_scope_set to add it" do
279
+ @klass.scope_proxy.should have_received(:add_named).
280
+ with(:by_name, :order => :name)
281
+ end
282
+ end
283
+
284
+ describe "Document.has_named_scope?" do
285
+ it "delegates to the scope_proxy" do
286
+ @scope_proxy = stub
287
+ @scope_proxy.stubs(:has_named_scope?).with(:whatever).returns(true)
288
+ @klass.scope_proxy = @scope_proxy
289
+ @klass.has_named_scope?(:whatever).should be_true
290
+ end
291
+ end
292
+
293
+ describe "accessing an ad-hoc scope" do
294
+ before do
295
+ @scope = stub
296
+ @scope_proxy = stub
297
+ @scope_proxy.stubs(:ad_hoc).with(:order! => :name).returns(@scope)
298
+ @klass.scope_proxy = @scope_proxy
299
+ end
300
+
301
+ it "asks the named_scope_set to add it" do
302
+ @klass.scope(:order! => :name).should == @scope
303
+ end
304
+ end
305
+
306
+ describe "adding an association" do
307
+ before do
308
+ @assoc_set = stub(:add => nil)
309
+ @klass.association_set = @assoc_set
310
+ @klass.has_many :addresses
311
+ end
312
+
313
+ it "asks the association set to add it" do
314
+ @assoc_set.should have_received(:add).with(:addresses, {})
315
+ end
316
+ end
317
+ end
318
+
@@ -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