thefool808-couch_potato 0.2.7

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 (42) hide show
  1. data/MIT-LICENSE.txt +19 -0
  2. data/README.md +269 -0
  3. data/VERSION.yml +4 -0
  4. data/init.rb +3 -0
  5. data/lib/core_ext/date.rb +10 -0
  6. data/lib/core_ext/object.rb +5 -0
  7. data/lib/core_ext/string.rb +15 -0
  8. data/lib/core_ext/time.rb +11 -0
  9. data/lib/couch_potato.rb +41 -0
  10. data/lib/couch_potato/database.rb +96 -0
  11. data/lib/couch_potato/persistence.rb +94 -0
  12. data/lib/couch_potato/persistence/belongs_to_property.rb +58 -0
  13. data/lib/couch_potato/persistence/callbacks.rb +96 -0
  14. data/lib/couch_potato/persistence/dirty_attributes.rb +27 -0
  15. data/lib/couch_potato/persistence/json.rb +45 -0
  16. data/lib/couch_potato/persistence/magic_timestamps.rb +13 -0
  17. data/lib/couch_potato/persistence/properties.rb +56 -0
  18. data/lib/couch_potato/persistence/simple_property.rb +77 -0
  19. data/lib/couch_potato/view/base_view_spec.rb +24 -0
  20. data/lib/couch_potato/view/custom_view_spec.rb +27 -0
  21. data/lib/couch_potato/view/custom_views.rb +44 -0
  22. data/lib/couch_potato/view/model_view_spec.rb +63 -0
  23. data/lib/couch_potato/view/properties_view_spec.rb +39 -0
  24. data/lib/couch_potato/view/raw_view_spec.rb +25 -0
  25. data/lib/couch_potato/view/view_query.rb +44 -0
  26. data/rails/init.rb +7 -0
  27. data/spec/callbacks_spec.rb +271 -0
  28. data/spec/create_spec.rb +22 -0
  29. data/spec/custom_view_spec.rb +134 -0
  30. data/spec/destroy_spec.rb +29 -0
  31. data/spec/property_spec.rb +64 -0
  32. data/spec/spec.opts +4 -0
  33. data/spec/spec_helper.rb +31 -0
  34. data/spec/unit/attributes_spec.rb +26 -0
  35. data/spec/unit/create_spec.rb +58 -0
  36. data/spec/unit/customs_views_spec.rb +15 -0
  37. data/spec/unit/database_spec.rb +18 -0
  38. data/spec/unit/dirty_attributes_spec.rb +113 -0
  39. data/spec/unit/string_spec.rb +13 -0
  40. data/spec/unit/view_query_spec.rb +9 -0
  41. data/spec/update_spec.rb +40 -0
  42. metadata +135 -0
@@ -0,0 +1,24 @@
1
+ module CouchPotato
2
+ module View
3
+ class BaseViewSpec
4
+ attr_reader :reduce_function, :design_document, :view_name, :view_parameters, :klass, :options
5
+ private :klass, :options
6
+
7
+ def initialize(klass, view_name, options, view_parameters)
8
+ @klass = klass
9
+ @design_document = klass.to_s.underscore
10
+ @view_name = view_name
11
+ @options = options
12
+ @view_parameters = {}
13
+ [:group, :include_docs, :descending, :group_level, :limit].each do |key|
14
+ @view_parameters[key] = options[key] if options.include?(key)
15
+ end
16
+ @view_parameters.merge!(view_parameters)
17
+ end
18
+
19
+ def process_results(results)
20
+ results
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module CouchPotato
2
+ module View
3
+ # a view for custom map/reduce functions that still returns model instances
4
+ #
5
+ # example:
6
+ # view :my_custom_view, :map => "function(doc) { emit(doc._id, null); }", :include_docs => true, :type => :custom, :reduce => nil
7
+ class CustomViewSpec < BaseViewSpec
8
+ def map_function
9
+ options[:map]
10
+ end
11
+
12
+ def reduce_function
13
+ options[:reduce]
14
+ end
15
+
16
+ def view_parameters
17
+ {:include_docs => options[:include_docs] || false}.merge(super)
18
+ end
19
+
20
+ def process_results(results)
21
+ results['rows'].map do |row|
22
+ klass.json_create row['doc'] || row['value'].merge(:_id => row['id'])
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,44 @@
1
+ require File.dirname(__FILE__) + '/base_view_spec'
2
+ require File.dirname(__FILE__) + '/model_view_spec'
3
+ require File.dirname(__FILE__) + '/properties_view_spec'
4
+ require File.dirname(__FILE__) + '/custom_view_spec'
5
+ require File.dirname(__FILE__) + '/raw_view_spec'
6
+
7
+
8
+ module CouchPotato
9
+ module View
10
+ module CustomViews
11
+
12
+ def self.included(base)
13
+ base.extend ClassMethods
14
+ end
15
+
16
+ module ClassMethods
17
+ # Declare a CouchDB view, for examples on how to use see the *ViewSpec classes in CouchPotato::View
18
+ def views
19
+ @views ||= {}
20
+ end
21
+
22
+ def execute_view(view_name, view_parameters)
23
+ view_spec_class(views[view_name][:type]).new(self, view_name, views[view_name], view_parameters)
24
+ end
25
+
26
+ def view(view_name, options)
27
+ view_name = view_name.to_s
28
+ views[view_name] = options
29
+ method_str = "def #{view_name}(view_parameters = {}); execute_view(\"#{view_name}\", view_parameters); end"
30
+ self.instance_eval(method_str)
31
+ end
32
+
33
+ def view_spec_class(type)
34
+ if type && type.is_a?(Class)
35
+ type
36
+ else
37
+ name = type.nil? ? 'Model' : type.to_s.camelize
38
+ CouchPotato::View.const_get("#{name}ViewSpec")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,63 @@
1
+ module CouchPotato
2
+ module View
3
+ # A view to return model instances by searching its properties.
4
+ # If you pass reduce => true will count instead
5
+ #
6
+ # example:
7
+ # view :my_view, :key => :name
8
+ class ModelViewSpec < BaseViewSpec
9
+
10
+ def view_parameters
11
+ _super = super
12
+ if _super[:reduce]
13
+ _super
14
+ else
15
+ {:include_docs => true, :reduce => false}.merge(_super)
16
+ end
17
+ end
18
+
19
+ def map_function
20
+ "function(doc) {
21
+ if(doc.ruby_class && doc.ruby_class == '#{@klass.name}') {
22
+ emit(#{formatted_key(key)}, null);
23
+ }
24
+ }"
25
+ end
26
+
27
+ def reduce_function
28
+ "function(key, values) {
29
+ return values.length;
30
+ }"
31
+ end
32
+
33
+ def process_results(results)
34
+ if count?
35
+ results['rows'].first.try(:[], 'value') || 0
36
+ else
37
+ results['rows'].map do |row|
38
+ klass.json_create row['doc']
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def count?
46
+ view_parameters[:reduce]
47
+ end
48
+
49
+ def key
50
+ options[:key]
51
+ end
52
+
53
+ def formatted_key(key)
54
+ if key.is_a? Array
55
+ '[' + key.map{|attribute| formatted_key(attribute)}.join(', ') + ']'
56
+ else
57
+ "doc['#{key}']"
58
+ end
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,39 @@
1
+ module CouchPotato
2
+ module View
3
+ # A view to return model instances with only some properties poulated by searching its properties, e.g. for very large documents where you are only interested in some of their data
4
+ #
5
+ # example:
6
+ # view :my_view, :key => :name, :properties => [:name, :author], :type => :properties
7
+ class PropertiesViewSpec < ModelViewSpec
8
+ def map_function
9
+ "function(doc) {
10
+ if(doc.ruby_class && doc.ruby_class == '#{@klass.name}') {
11
+ emit(#{formatted_key(key)}, #{properties_for_map(properties)});
12
+ }
13
+ }"
14
+ end
15
+
16
+ def process_results(results)
17
+ results['rows'].map do |row|
18
+ klass.json_create row['value'].merge(:_id => row['id'])
19
+ end
20
+ end
21
+
22
+ def view_parameters
23
+ {:include_docs => false}.merge(super)
24
+ end
25
+
26
+ private
27
+
28
+ def properties
29
+ options[:properties]
30
+ end
31
+
32
+ def properties_for_map(properties)
33
+ '{' + properties.map{|p| "#{p}: doc.#{p}"}.join(', ') + '}'
34
+ end
35
+
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ module CouchPotato
2
+ module View
3
+ # A view for custom map/reduce functions that returns the raw data fromcouchdb
4
+ #
5
+ # example:
6
+ # view :my_custom_view, :map => "function(doc) { emit(doc._id, null); }", :type => :raw, :reduce => nil
7
+ # optionally you can pass in a results filter which you can use to process the raw couchdb results before returning them
8
+ #
9
+ # example:
10
+ # view :my_custom_view, :map => "function(doc) { emit(doc._id, null); }", :type => :raw, :results_filter => lambda{|results| results['rows].map{|row| row['value']}}
11
+ class RawViewSpec < BaseViewSpec
12
+ def map_function
13
+ options[:map]
14
+ end
15
+
16
+ def process_results(results)
17
+ options[:results_filter] ? options[:results_filter].call(results) : results
18
+ end
19
+
20
+ def reduce_function
21
+ options[:reduce]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,44 @@
1
+ module CouchPotato
2
+ module View
3
+ # Used to query views (and create them if they don't exist). Usually you won't have to use this class directly. Instead it is used internally by the CouchPotato::Database.view method.
4
+ class ViewQuery
5
+ def initialize(couchrest_database, design_document_name, view_name, map_function, reduce_function = nil)
6
+ @database = couchrest_database
7
+ @design_document_name = design_document_name
8
+ @view_name = view_name
9
+ @map_function = map_function
10
+ @reduce_function = reduce_function
11
+ end
12
+
13
+ def query_view!(parameters = {})
14
+ begin
15
+ query_view parameters
16
+ rescue RestClient::ResourceNotFound# => e
17
+ create_view
18
+ retry
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def create_view
25
+ design_doc = @database.get "_design/#{@design_document_name}" rescue nil
26
+ design_doc ||= {'views' => {}, "_id" => "_design/#{@design_document_name}"}
27
+ design_doc['views'][@view_name.to_s] = {
28
+ 'map' => @map_function,
29
+ 'reduce' => @reduce_function
30
+ }
31
+ @database.save_doc(design_doc)
32
+ end
33
+
34
+ def query_view(parameters)
35
+ @database.view view_url, parameters
36
+ end
37
+
38
+ def view_url
39
+ "#{@design_document_name}/#{@view_name}"
40
+ end
41
+
42
+ end
43
+ end
44
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,7 @@
1
+ # this is for rails only
2
+
3
+ require File.dirname(__FILE__) + '/../lib/couch_potato'
4
+
5
+ CouchPotato::Config.database_name = YAML::load(File.read(Rails.root.to_s + '/config/couchdb.yml'))[RAILS_ENV]
6
+
7
+ RAILS_DEFAULT_LOGGER.info "** couch_potato: initialized from #{__FILE__}"
@@ -0,0 +1,271 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ class CallbackRecorder
4
+ include CouchPotato::Persistence
5
+
6
+ property :required_property
7
+
8
+ validates_presence_of :required_property
9
+
10
+ [:before_validation_on_create,
11
+ :before_validation_on_save, :before_validation_on_update,
12
+ :before_save, :before_create, :before_create,
13
+ :after_save, :after_create, :after_create,
14
+ :before_update, :after_update,
15
+ :before_destroy, :after_destroy
16
+ ].each do |callback|
17
+ define_method callback do
18
+ callbacks << callback
19
+ end
20
+ self.send callback, callback
21
+ end
22
+
23
+ view :all, :key => :required_property
24
+
25
+ attr_accessor :lambda_works
26
+ before_create lambda {|model| model.lambda_works = true }
27
+ after_create lambda {|model, db| db.view CallbackRecorder.all}
28
+ before_update :method_callback_with_argument
29
+
30
+ def callbacks
31
+ @callbacks ||= []
32
+ end
33
+
34
+ private
35
+
36
+ def method_callback_with_argument(db)
37
+ db.view CallbackRecorder.all
38
+ end
39
+
40
+ end
41
+
42
+ describe "multiple callbacks at once" do
43
+
44
+ class Monkey
45
+ include CouchPotato::Persistence
46
+ attr_accessor :eaten_banana, :eaten_apple
47
+
48
+ before_create :eat_apple, :eat_banana
49
+
50
+ private
51
+
52
+ def eat_banana
53
+ self.eaten_banana = true
54
+ end
55
+
56
+ def eat_apple
57
+ self.eaten_apple = true
58
+ end
59
+ end
60
+ it "should run all callback methods given to the callback method call" do
61
+ monkey = Monkey.new
62
+ CouchPotato.database.save_document! monkey
63
+ monkey.eaten_banana.should be_true
64
+ monkey.eaten_apple.should be_true
65
+ end
66
+ end
67
+
68
+ describe 'create callbacks' do
69
+
70
+ before(:each) do
71
+ @recorder = CallbackRecorder.new
72
+ couchrest_database = stub 'couchrest_database', :save_doc => {'id' => '1', 'rev' => '2'}, :view => {'rows' => []}, :info => nil
73
+ @db = CouchPotato::Database.new(couchrest_database)
74
+ end
75
+
76
+ describe "successful create" do
77
+ before(:each) do
78
+ @recorder.required_property = 1
79
+ end
80
+
81
+ it "should call before_validation_on_create" do
82
+ @db.save_document! @recorder
83
+ @recorder.callbacks.should include(:before_validation_on_create)
84
+ end
85
+
86
+ it "should call before_validation_on_save" do
87
+ @db.save_document! @recorder
88
+ @recorder.callbacks.should include(:before_validation_on_save)
89
+ end
90
+
91
+ it "should call before_save" do
92
+ @db.save_document! @recorder
93
+ @recorder.callbacks.should include(:before_save)
94
+ end
95
+
96
+ it "should call after_save" do
97
+ @db.save_document! @recorder
98
+ @recorder.callbacks.should include(:after_save)
99
+ end
100
+
101
+ it "should call before_create" do
102
+ @db.save_document! @recorder
103
+ @recorder.callbacks.should include(:before_create)
104
+ end
105
+
106
+ it "should call after_create" do
107
+ @db.save_document! @recorder
108
+ @recorder.callbacks.should include(:after_create)
109
+ end
110
+
111
+ end
112
+
113
+ describe "failed create" do
114
+
115
+ it "should call before_validation_on_create" do
116
+ @db.save_document @recorder
117
+ @recorder.callbacks.should include(:before_validation_on_create)
118
+ end
119
+
120
+ it "should call before_validation_on_save" do
121
+ @db.save_document @recorder
122
+ @recorder.callbacks.should include(:before_validation_on_save)
123
+ end
124
+
125
+ it "should not call before_save" do
126
+ @db.save_document @recorder
127
+ @recorder.callbacks.should_not include(:before_save)
128
+ end
129
+
130
+ it "should not call after_save" do
131
+ @db.save_document @recorder
132
+ @recorder.callbacks.should_not include(:after_save)
133
+ end
134
+
135
+ it "should not call before_create" do
136
+ @db.save_document @recorder
137
+ @recorder.callbacks.should_not include(:before_create)
138
+ end
139
+
140
+ it "should not call after_create" do
141
+ @db.save_document @recorder
142
+ @recorder.callbacks.should_not include(:after_create)
143
+ end
144
+ end
145
+ end
146
+
147
+ describe "update callbacks" do
148
+
149
+ before(:each) do
150
+ @recorder = CallbackRecorder.new :required_property => 1
151
+
152
+ couchrest_database = stub 'couchrest_database', :save_doc => {'id' => '1', 'rev' => '2'}, :view => {'rows' => []}, :info => nil
153
+ @db = CouchPotato::Database.new(couchrest_database)
154
+ @db.save_document! @recorder
155
+
156
+ @recorder.required_property = 2
157
+ @recorder.callbacks.clear
158
+ end
159
+
160
+ describe "successful update" do
161
+
162
+ before(:each) do
163
+ @db.save_document! @recorder
164
+ end
165
+
166
+ it "should call before_validation_on_update" do
167
+ @recorder.callbacks.should include(:before_validation_on_update)
168
+ end
169
+
170
+ it "should call before_validation_on_save" do
171
+ @recorder.callbacks.should include(:before_validation_on_save)
172
+ end
173
+
174
+ it "should call before_save" do
175
+ @recorder.callbacks.should include(:before_save)
176
+ end
177
+
178
+ it "should call after_save" do
179
+ @recorder.callbacks.should include(:after_save)
180
+ end
181
+
182
+ it "should call before_update" do
183
+ @recorder.callbacks.should include(:before_update)
184
+ end
185
+
186
+ it "should call after_update" do
187
+ @recorder.callbacks.should include(:after_update)
188
+ end
189
+
190
+ end
191
+
192
+ describe "failed update" do
193
+
194
+ before(:each) do
195
+ @recorder.required_property = nil
196
+ @db.save_document @recorder
197
+ end
198
+
199
+ it "should call before_validation_on_update" do
200
+ @recorder.callbacks.should include(:before_validation_on_update)
201
+ end
202
+
203
+ it "should call before_validation_on_save" do
204
+ @recorder.callbacks.should include(:before_validation_on_save)
205
+ end
206
+
207
+ it "should not call before_save" do
208
+ @recorder.callbacks.should_not include(:before_save)
209
+ end
210
+
211
+ it "should not call after_save" do
212
+ @recorder.callbacks.should_not include(:after_save)
213
+ end
214
+
215
+ it "should not call before_update" do
216
+ @recorder.callbacks.should_not include(:before_update)
217
+ end
218
+
219
+ it "should not call after_update" do
220
+ @recorder.callbacks.should_not include(:after_update)
221
+ end
222
+
223
+ end
224
+
225
+ end
226
+
227
+ describe "destroy callbacks" do
228
+
229
+ before(:each) do
230
+ @recorder = CallbackRecorder.new :required_property => 1
231
+ couchrest_database = stub 'couchrest_database', :save_doc => {'id' => '1', 'rev' => '2'}, :delete_doc => nil, :view => {'rows' => []}, :info => nil
232
+ @db = CouchPotato::Database.new(couchrest_database)
233
+ @db.save_document! @recorder
234
+
235
+ @recorder.callbacks.clear
236
+ end
237
+
238
+ it "should call before_destroy" do
239
+ @db.destroy_document @recorder
240
+ @recorder.callbacks.should include(:before_destroy)
241
+ end
242
+
243
+ it "should call after_destroy" do
244
+ @db.destroy_document @recorder
245
+ @recorder.callbacks.should include(:after_destroy)
246
+ end
247
+ end
248
+
249
+ describe "method callbacks" do
250
+ it "should pass the database to a method with arity 1" do
251
+ recorder = CallbackRecorder.new
252
+ db = stub 'db'
253
+ db.should_receive(:view)
254
+ recorder.run_callbacks :before_update, db
255
+ end
256
+ end
257
+
258
+ describe "lambda callbacks" do
259
+ it "should run the lambda" do
260
+ recorder = CallbackRecorder.new
261
+ recorder.run_callbacks :before_create, stub('db')
262
+ recorder.lambda_works.should be_true
263
+ end
264
+
265
+ it "should pass the database to a lambda with arity 2" do
266
+ recorder = CallbackRecorder.new
267
+ db = stub 'db'
268
+ db.should_receive(:view)
269
+ recorder.run_callbacks :after_create, db
270
+ end
271
+ end