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,102 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Friendly::Cache::ByID" do
4
+ before do
5
+ @cache = stub(:set => nil)
6
+ @klass = stub(:name => "Product")
7
+ @id_cache = Friendly::Cache::ByID.new(@klass, [:id], {}, @cache)
8
+ @subject = @id_cache
9
+ end
10
+
11
+ it { should be_satisfies(query(:id => "asdf")) }
12
+ it { should be_satisfies(query(:id => ["asdf"])) }
13
+ it { should_not be_satisfies(query(:id => ["asdf"], :name => "asdf")) }
14
+ it { should_not be_satisfies(query(:name => "asdf")) }
15
+
16
+ it "has a default version of 0" do
17
+ @id_cache.version.should == 0
18
+ end
19
+
20
+ it "is possible to override version" do
21
+ Friendly::Cache::ByID.new(@klass, [:id], {:version => 1}).version.should == 1
22
+ end
23
+
24
+ describe "when an object is created" do
25
+ before do
26
+ @uuid = stub(:to_guid => "xxxx-xxx-xxx-xxxx")
27
+ @doc = stub(:id => @uuid)
28
+ @id_cache.create(@doc)
29
+ end
30
+
31
+ it "sets the cache value in the db" do
32
+ @cache.should have_received(:set).with("Product/0/#{@uuid.to_guid}", @doc)
33
+ end
34
+ end
35
+
36
+ describe "when an object is updated" do
37
+ before do
38
+ @uuid = stub(:to_guid => "xxxx-xxx-xxx-xxxx")
39
+ @doc = stub(:id => @uuid)
40
+ @id_cache.update(@doc)
41
+ end
42
+
43
+ it "sets the cache value in the db" do
44
+ @cache.should have_received(:set).with("Product/0/#{@uuid.to_guid}", @doc)
45
+ end
46
+ end
47
+
48
+ describe "when an object is destroyed" do
49
+ before do
50
+ @cache.stubs(:delete)
51
+ @uuid = stub(:to_guid => "xxxx-xxx-xxx-xxxx")
52
+ @doc = stub(:id => @uuid)
53
+ @id_cache.destroy(@doc)
54
+ end
55
+
56
+ it "deletes the object from cache" do
57
+ @cache.should have_received(:delete).with("Product/0/#{@uuid.to_guid}")
58
+ end
59
+ end
60
+
61
+ describe "finding a single object in cache" do
62
+ before do
63
+ @uuid = stub(:to_guid => "xxxx-xxx-xxx-xxxx")
64
+ @doc = stub
65
+ @cache.stubs(:get).with("Product/0/xxxx-xxx-xxx-xxxx").returns(@doc).yields
66
+ @block_called = true
67
+ @returned = @id_cache.first(query(:id => @uuid)) do
68
+ @block_called = true
69
+ end
70
+ end
71
+
72
+ it "returns the document" do
73
+ @returned.should == @doc
74
+ end
75
+
76
+ it "passes along the block to the memcached object" do
77
+ @block_called.should be_true
78
+ end
79
+ end
80
+
81
+ describe "finding many objects in the cache" do
82
+ before do
83
+ @uuid = stub(:to_guid => "xxxx-xxx-xxx-xxxx")
84
+ @doc = stub
85
+ @key = "Product/0/xxxx-xxx-xxx-xxxx"
86
+ @cache.stubs(:multiget).with([@key, @key]).
87
+ returns({@uuid.to_guid => @doc}).yields(@uuid.to_guid)
88
+ @block_called = []
89
+ @returned = @id_cache.all(query(:id => [@uuid, @uuid])) do |id|
90
+ @block_called << id
91
+ end
92
+ end
93
+
94
+ it "returns the values from the hash" do
95
+ @returned.should == [@doc]
96
+ end
97
+
98
+ it "passes the block along to the cache" do
99
+ @block_called.should == [@uuid.to_guid]
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,21 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Friendly::Cache" do
4
+ describe "getting a cache object for :id" do
5
+ before do
6
+ @cache = Friendly::Cache.cache_for(stub, [:id], {})
7
+ end
8
+
9
+ it "instantiates a Cache::ByID" do
10
+ @cache.should be_instance_of(Friendly::Cache::ByID)
11
+ end
12
+ end
13
+
14
+ describe "for other fields" do
15
+ it "raises NotSupported" do
16
+ lambda {
17
+ Friendly::Cache.cache_for(stub, [:asdf], {})
18
+ }.should raise_error(Friendly::NotSupported)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,201 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Friendly::DataStore" do
4
+ before do
5
+ @users = DatasetFake.new(:insert => 42)
6
+ @db = DatabaseFake.new("users" => @users)
7
+ @datastore = Friendly::DataStore.new(@db)
8
+ @klass = stub(:table_name => "users")
9
+ end
10
+
11
+ describe "inserting data" do
12
+ before do
13
+ @return = @datastore.insert(@klass, :name => "Stewie")
14
+ end
15
+
16
+ it "inserts it in to the table in the datastore" do
17
+ @users.inserts.length.should == 1
18
+ @users.inserts.should include(:name => "Stewie")
19
+ end
20
+
21
+ it "returns the id from the dataset" do
22
+ @return.should == 42
23
+ end
24
+ end
25
+
26
+ describe "retrieving all based on a query" do
27
+ before do
28
+ @users.where = {{:name => "Stewie"} => stub(:map => [{:id => 1}])}
29
+ @return = @datastore.all(@klass, query(:name => "Stewie"))
30
+ end
31
+
32
+ it "gets the data from the dataset for the klass and makes it an arary" do
33
+ @return.should == [{:id => 1}]
34
+ end
35
+ end
36
+
37
+ describe "retrieving all with a limit" do
38
+ before do
39
+ @filtered = stub
40
+ @filtered.stubs(:limit).with(10, nil).returns(stub(:map => [{:id => 1}]))
41
+ @users.where = {{:name => "Stewie"} => @filtered}
42
+ @query = query(:name => "Stewie", :limit! => 10)
43
+ @return = @datastore.all(@klass, @query)
44
+ end
45
+
46
+ it "limits the filtered dataset and returns the results" do
47
+ @return.should == [{:id => 1}]
48
+ end
49
+ end
50
+
51
+ describe "retrieving all with a offset" do
52
+ before do
53
+ @filtered = stub
54
+ @filtered.stubs(:limit).with(nil, 10).returns(stub(:map => [{:id => 1}]))
55
+ @users.where = {{:name => "Stewie"} => @filtered}
56
+ @query = query(:name => "Stewie", :offset! => 10)
57
+ @return = @datastore.all(@klass, @query)
58
+ end
59
+
60
+ it "offsets the filtered dataset and returns the results" do
61
+ @return.should == [{:id => 1}]
62
+ end
63
+ end
64
+
65
+ describe "retrieving all with order" do
66
+ before do
67
+ @filtered = stub
68
+ @filtered.stubs(:order).with(:created_at).returns(stub(:map => [{:id => 1}]))
69
+ @users.where = {{:name => "Stewie"} => @filtered}
70
+ @query = query(:name => "Stewie", :order! => :created_at)
71
+ @return = @datastore.all(@klass, @query)
72
+ end
73
+
74
+ it "orders the filtered dataset and returns the results" do
75
+ @return.should == [{:id => 1}]
76
+ end
77
+ end
78
+
79
+ describe "all without conditions" do
80
+ before do
81
+ @all = stub(:map => [])
82
+ @users.stubs(:order).with(:created_at).returns(@all)
83
+ @query = query(:order! => :created_at)
84
+ @return = @datastore.all(@klass, @query)
85
+ end
86
+
87
+ it "orders the filtered dataset and returns the results" do
88
+ @return.should == []
89
+ end
90
+ end
91
+
92
+ describe "retrieving first with conditions" do
93
+ before do
94
+ @users.first = {{:id => 1} => {:id => 1}}
95
+ @return = @datastore.first(@klass, query(:id => 1))
96
+ end
97
+
98
+ it "gets the first object matching the conditions from the dataset" do
99
+ @return.should == {:id => 1}
100
+ end
101
+ end
102
+
103
+ describe "updating data" do
104
+ before do
105
+ @filtered = DatasetFake.new(:update => true)
106
+ @users.where = {{:id => 1} => @filtered}
107
+ @return = @datastore.update(@klass, 1, :name => "Peter")
108
+ end
109
+
110
+ it "filter the dataset by id and update the filtered row" do
111
+ @filtered.updates.length.should == 1
112
+ @filtered.updates.should include(:name => "Peter")
113
+ end
114
+ end
115
+
116
+ describe "deleting data" do
117
+ before do
118
+ @filtered = stub
119
+ @filtered.stubs(:delete)
120
+ @users.where = {{:id => 1} => @filtered}
121
+ @datastore.delete(@klass, 1)
122
+ end
123
+
124
+ it "filters the dataset by id and deletes" do
125
+ @filtered.should have_received(:delete)
126
+ end
127
+ end
128
+
129
+ describe "when a batch transaction has been started" do
130
+ before do
131
+ @datastore.start_batch
132
+ @persistable = stub(:table_name => "some_table")
133
+ @datastore.insert(@persistable, {:some => "attrs"})
134
+ end
135
+
136
+ after { Thread.current[:friendly_batch] = nil }
137
+
138
+ it "adds the attributes to the batch for that table" do
139
+ inserts = Thread.current[:friendly_batch]["some_table"]
140
+ inserts.length.should == 1
141
+ inserts.should include(:some => "attrs")
142
+ end
143
+ end
144
+
145
+ describe "starting a batch" do
146
+ before do
147
+ @datastore.start_batch
148
+ end
149
+
150
+ after { Thread.current[:friendly_batch] = nil }
151
+
152
+ it "sets Thread.current[:friendly_batch] to empty hash" do
153
+ Thread.current[:friendly_batch].should == {}
154
+ end
155
+ end
156
+
157
+ describe "resetting a batch transaction" do
158
+ before do
159
+ @db.stubs(:from)
160
+ Thread.current[:friendly_batch] = {"users" => [{:a => "b"}]}
161
+ @datastore.reset_batch
162
+ end
163
+ after { Thread.current[:friendly_batch] = nil }
164
+
165
+ it "sets Thread.current[:friendly_batch] to nil without inserting" do
166
+ Thread.current[:friendly_batch].should be_nil
167
+ @db.should have_received(:from).never
168
+ end
169
+ end
170
+
171
+ describe "flushing a batch" do
172
+ before do
173
+ @records = [{:name => "Stewie"}, {:name => "Brian"}]
174
+ Thread.current[:friendly_batch] = {"users" => @records}
175
+ @users.stubs(:multi_insert)
176
+ @datastore.flush_batch
177
+ end
178
+ after { Thread.current[:friendly_batch] = nil }
179
+
180
+ it "performs the multi_insert on each table" do
181
+ @users.should have_received(:multi_insert).
182
+ with(@records, :commit_every => 1000)
183
+ end
184
+
185
+ it "resets the batch" do
186
+ Thread.current[:friendly_batch].should be_nil
187
+ end
188
+ end
189
+
190
+ describe "counting" do
191
+ before do
192
+ @filtered = stub(:count => 10)
193
+ @users.stubs(:where).with(:name => "James").returns(@filtered)
194
+ end
195
+
196
+ it "fitlers and counts in the db" do
197
+ @datastore.count(@klass, query(:name => "James")).should == 10
198
+ end
199
+ end
200
+ end
201
+
@@ -0,0 +1,130 @@
1
+ require File.expand_path("../../../spec_helper", __FILE__)
2
+
3
+ describe "Friendly::Document::Attributes" do
4
+ before do
5
+ @klass = Class.new do
6
+ include Friendly::Document::Attributes
7
+
8
+ attribute :name, String
9
+ end
10
+ end
11
+
12
+ describe "#initialize" do
13
+ it "sets the attributes using the setters" do
14
+ @doc = @klass.new :name => "Bond"
15
+ @doc.name.should == "Bond"
16
+ end
17
+
18
+ it "assigns the default values" do
19
+ @klass.attribute :id, Friendly::UUID
20
+ @klass.attributes[:id] = stub(:assign_default_value => nil)
21
+ @klass.attributes[:name] = stub(:assign_default_value => nil,
22
+ :typecast => "Bond")
23
+ @doc = @klass.new :name => "Bond"
24
+ @klass.attributes[:id].should have_received(:assign_default_value).with(@doc)
25
+ @klass.attributes[:name].should have_received(:assign_default_value).with(@doc)
26
+ end
27
+ end
28
+
29
+ describe "#attributes=" do
30
+ before do
31
+ @object = @klass.new
32
+ @object.attributes = {:name => "Bond"}
33
+ end
34
+
35
+ it "sets the attributes using the setters" do
36
+ @object.name.should == "Bond"
37
+ end
38
+
39
+ it "raises ArgumentError when there are duplicate keys of differing type" do
40
+ lambda {
41
+ @object.attributes = {:name => "Bond", "name" => "Bond"}
42
+ }.should raise_error(ArgumentError)
43
+ end
44
+ end
45
+
46
+ describe "#to_hash" do
47
+ before do
48
+ @object = @klass.new(:name => "Stewie")
49
+ end
50
+
51
+ it "creates a hash that contains its attributes" do
52
+ @object.to_hash.should == {:name => "Stewie"}
53
+ end
54
+ end
55
+
56
+ describe "#assign" do
57
+ before do
58
+ @object = @klass.new
59
+ @object.assign(:name, "James Bond")
60
+ end
61
+
62
+ it "assigns the value to the attribute" do
63
+ @object.name.should == "James Bond"
64
+ end
65
+ end
66
+
67
+ describe "#will_change" do
68
+ before do
69
+ @klass.send(:attr_accessor, :some_variable)
70
+ @object = @klass.new
71
+ @object.some_variable = "Some value"
72
+ @object.will_change(:some_variable)
73
+ end
74
+
75
+ it "makes the object #changed?" do
76
+ @object.should be_changed
77
+ end
78
+
79
+ it "returns the value of the variable for #attribute_was" do
80
+ @object.attribute_was(:some_variable).should == "Some value"
81
+ end
82
+
83
+ it "returns true for attribute_changed?(:some_variable)" do
84
+ @object.should be_attribute_changed(:some_variable)
85
+ end
86
+ end
87
+
88
+ describe "#reset_changes" do
89
+ before do
90
+ @klass.send(:attr_accessor, :some_variable)
91
+ @object = @klass.new
92
+ @object.some_variable = "Some value"
93
+ @object.will_change(:some_variable)
94
+ @object.reset_changes
95
+ end
96
+
97
+ it "resets the changed status of the object" do
98
+ @object.should_not be_changed
99
+ end
100
+
101
+ it "returns nil for attribute_was(:some_variable)" do
102
+ @object.attribute_was(:some_variable).should be_nil
103
+ end
104
+
105
+ it "returns false for attribute_changed?(:some_variable)" do
106
+ @object.should_not be_attribute_changed(:some_variable)
107
+ end
108
+ end
109
+
110
+ describe "#new_without_change_tracking" do
111
+ before do
112
+ @klass = Class.new do
113
+ attr_reader :name
114
+
115
+ def name=(name)
116
+ will_change(:name)
117
+ @name = name
118
+ end
119
+
120
+ include Friendly::Document::Attributes
121
+ end
122
+ @doc = @klass.new_without_change_tracking(:name => "James")
123
+ end
124
+
125
+ it "initializes and then calls reset_changes" do
126
+ @doc.name.should == "James"
127
+ @doc.should_not be_changed
128
+ end
129
+ end
130
+ end