speedmax-couch_potato 0.2.0

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 (41) hide show
  1. data/MIT-LICENSE.txt +19 -0
  2. data/README.md +260 -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/database.rb +90 -0
  10. data/lib/couch_potato/persistence/belongs_to_property.rb +58 -0
  11. data/lib/couch_potato/persistence/callbacks.rb +108 -0
  12. data/lib/couch_potato/persistence/dirty_attributes.rb +26 -0
  13. data/lib/couch_potato/persistence/inline_collection.rb +14 -0
  14. data/lib/couch_potato/persistence/json.rb +41 -0
  15. data/lib/couch_potato/persistence/magic_timestamps.rb +13 -0
  16. data/lib/couch_potato/persistence/properties.rb +42 -0
  17. data/lib/couch_potato/persistence/simple_property.rb +64 -0
  18. data/lib/couch_potato/persistence.rb +57 -0
  19. data/lib/couch_potato/view/base_view_spec.rb +20 -0
  20. data/lib/couch_potato/view/custom_view_spec.rb +26 -0
  21. data/lib/couch_potato/view/custom_views.rb +30 -0
  22. data/lib/couch_potato/view/model_view_spec.rb +39 -0
  23. data/lib/couch_potato/view/properties_view_spec.rb +35 -0
  24. data/lib/couch_potato/view/raw_view_spec.rb +21 -0
  25. data/lib/couch_potato/view/view_query.rb +45 -0
  26. data/lib/couch_potato.rb +37 -0
  27. data/rails/init.rb +7 -0
  28. data/spec/callbacks_spec.rb +245 -0
  29. data/spec/create_spec.rb +22 -0
  30. data/spec/custom_view_spec.rb +118 -0
  31. data/spec/destroy_spec.rb +29 -0
  32. data/spec/property_spec.rb +64 -0
  33. data/spec/spec.opts +4 -0
  34. data/spec/spec_helper.rb +30 -0
  35. data/spec/unit/attributes_spec.rb +26 -0
  36. data/spec/unit/create_spec.rb +58 -0
  37. data/spec/unit/dirty_attributes_spec.rb +100 -0
  38. data/spec/unit/string_spec.rb +13 -0
  39. data/spec/unit/view_query_spec.rb +9 -0
  40. data/spec/update_spec.rb +40 -0
  41. metadata +127 -0
@@ -0,0 +1,42 @@
1
+ require File.dirname(__FILE__) + '/simple_property'
2
+ require File.dirname(__FILE__) + '/belongs_to_property'
3
+
4
+ module CouchPotato
5
+ module Persistence
6
+ module Properties
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ base.class_eval do
10
+ def self.properties
11
+ @properties ||= {}
12
+ @properties[self.name] ||= []
13
+ end
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ def property_names
19
+ properties.map(&:name)
20
+ end
21
+
22
+ def json_create(json)
23
+ instance = super
24
+ instance.attributes.each do |name, value|
25
+ instance.instance_variable_set("@#{name}_was", value)
26
+ end
27
+ instance
28
+ end
29
+
30
+ def property(name, options = {})
31
+ clazz = options.delete(:class)
32
+ properties << (clazz || SimpleProperty).new(self, name, options)
33
+ end
34
+
35
+ def belongs_to(name)
36
+ property name, :class => BelongsToProperty
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,64 @@
1
+ module CouchPotato
2
+ module Persistence
3
+ class SimpleProperty
4
+ attr_accessor :name, :type
5
+
6
+ def initialize(owner_clazz, name, options = {})
7
+ self.name = name
8
+ self.type = options[:type]
9
+ owner_clazz.class_eval do
10
+ attr_reader name, "#{name}_was"
11
+
12
+ def initialize(attributes = {})
13
+ super attributes
14
+ attributes.each do |name, value|
15
+ self.instance_variable_set("@#{name}_was", value)
16
+ end if attributes
17
+ end
18
+
19
+ define_method "#{name}=" do |value|
20
+ self.instance_variable_set("@#{name}", value)
21
+ end
22
+
23
+ define_method "#{name}?" do
24
+ !self.send(name).nil? && !self.send(name).try(:blank?)
25
+ end
26
+
27
+ define_method "#{name}_changed?" do
28
+ !self.instance_variable_get("@#{name}_not_changed") && self.send(name) != self.send("#{name}_was")
29
+ end
30
+
31
+ define_method "#{name}_not_changed" do
32
+ self.instance_variable_set("@#{name}_not_changed", true)
33
+ end
34
+ end
35
+ end
36
+
37
+ def build(object, json)
38
+ value = json[name.to_s] || json[name.to_sym]
39
+ typecasted_value = if type
40
+ type.json_create value
41
+ else
42
+ value
43
+ end
44
+ object.send "#{name}=", typecasted_value
45
+ end
46
+
47
+ def dirty?(object)
48
+ object.send("#{name}_changed?")
49
+ end
50
+
51
+ def save(object)
52
+
53
+ end
54
+
55
+ def destroy(object)
56
+
57
+ end
58
+
59
+ def serialize(json, object)
60
+ json[name] = object.send name
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,57 @@
1
+ require 'digest/md5'
2
+ require File.dirname(__FILE__) + '/database'
3
+ require File.dirname(__FILE__) + '/persistence/properties'
4
+ require File.dirname(__FILE__) + '/persistence/magic_timestamps'
5
+ require File.dirname(__FILE__) + '/persistence/callbacks'
6
+ require File.dirname(__FILE__) + '/persistence/json'
7
+ require File.dirname(__FILE__) + '/persistence/dirty_attributes'
8
+ require File.dirname(__FILE__) + '/view/custom_views'
9
+ require File.dirname(__FILE__) + '/view/view_query'
10
+
11
+
12
+ module CouchPotato
13
+ module Persistence
14
+
15
+ def self.included(base)
16
+ base.send :include, Properties, Callbacks, Validatable, Json, CouchPotato::View::CustomViews
17
+ base.send :include, DirtyAttributes
18
+ base.send :include, MagicTimestamps
19
+ base.class_eval do
20
+ attr_accessor :_id, :_rev, :_attachments, :_deleted
21
+ alias_method :id, :_id
22
+ end
23
+ end
24
+
25
+ def initialize(attributes = {})
26
+ attributes.each do |name, value|
27
+ self.send("#{name}=", value)
28
+ end if attributes
29
+ end
30
+
31
+ def attributes=(hash)
32
+ hash.each do |attribute, value|
33
+ self.send "#{attribute}=", value
34
+ end
35
+ end
36
+
37
+ def attributes
38
+ self.class.properties.inject({}) do |res, property|
39
+ property.serialize(res, self)
40
+ res
41
+ end
42
+ end
43
+
44
+ def new?
45
+ _rev.nil?
46
+ end
47
+
48
+ def to_param
49
+ _id
50
+ end
51
+
52
+ def ==(other)
53
+ other.class == self.class && self.to_json == other.to_json
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,20 @@
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 = options.select{|key, value| [:group, :include_docs, :descending, :group_level, :limit].include?(key.to_sym)}.merge(view_parameters)
13
+ end
14
+
15
+ def process_results(results)
16
+ results
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ module CouchPotato
2
+ module View
3
+ # a view for custom map/reduce functions that still returns model instances
4
+ # example: view :my_custom_view, :map => "function(doc) { emit(doc._id, null); }", :include_docs => true, :type => :custom, :reduce => nil
5
+
6
+ class CustomViewSpec < BaseViewSpec
7
+ def map_function
8
+ options[:map]
9
+ end
10
+
11
+ def reduce_function
12
+ options[:reduce]
13
+ end
14
+
15
+ def view_parameters
16
+ {:include_docs => options[:include_docs] || false}.merge(super)
17
+ end
18
+
19
+ def process_results(results)
20
+ results['rows'].map do |row|
21
+ klass.json_create row['doc'] || row['value'].merge(:_id => row['id'])
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,30 @@
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
+
18
+ # declare a couchdb view, for examples on how to use see the *ViewSpec classes in CouchPotato::View
19
+ def view(view_name, options)
20
+ self.class.instance_eval do
21
+ define_method view_name do |view_parameters = {}|
22
+ klass = options[:type] ? options[:type].to_s.camelize : 'Model'
23
+ CouchPotato::View.const_get("#{klass}ViewSpec").new self, view_name, options, view_parameters
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,39 @@
1
+ module CouchPotato
2
+ module View
3
+ # a view to return model instances by searching its properties
4
+ # example: view :my_view, :key => :name
5
+ class ModelViewSpec < BaseViewSpec
6
+
7
+ def view_parameters
8
+ {:include_docs => true}.merge(super)
9
+ end
10
+
11
+ def map_function
12
+ "function(doc) {
13
+ emit(#{formatted_key(key)}, null);
14
+ }"
15
+ end
16
+
17
+ def process_results(results)
18
+ results['rows'].map do |row|
19
+ klass.json_create row['doc']
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def key
26
+ options[:key]
27
+ end
28
+
29
+ def formatted_key(key)
30
+ if key.is_a? Array
31
+ '[' + key.map{|attribute| formatted_key(attribute)}.join(', ') + ']'
32
+ else
33
+ "doc['#{key}']"
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,35 @@
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
+ # example: view :my_view, :key => :name, :properties => [:name, :author], :type => :properties
5
+ class PropertiesViewSpec < ModelViewSpec
6
+ def map_function
7
+ "function(doc) {
8
+ emit(#{formatted_key(key)}, #{properties_for_map(properties)});
9
+ }"
10
+ end
11
+
12
+ def process_results(results)
13
+ results['rows'].map do |row|
14
+ klass.json_create row['value'].merge(:_id => row['id'])
15
+ end
16
+ end
17
+
18
+ def view_parameters
19
+ {:include_docs => false}.merge(super)
20
+ end
21
+
22
+ private
23
+
24
+ def properties
25
+ options[:properties]
26
+ end
27
+
28
+ def properties_for_map(properties)
29
+ '{' + properties.map{|p| "#{p}: doc.#{p}"}.join(', ') + '}'
30
+ end
31
+
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ module CouchPotato
2
+ # a view for custom map/reduce functions that returns the raw data fromcouchdb
3
+ # example: view :my_custom_view, :map => "function(doc) { emit(doc._id, null); }", :type => :raw, :reduce => nil
4
+ # optionally you can pass in a results filter which you can use to process the raw couchdb results before returning them
5
+ # example: view :my_custom_view, :map => "function(doc) { emit(doc._id, null); }", :type => :raw, :results_filter => lambda{|results| results['rows].map{|row| row['value']}}
6
+ module View
7
+ class RawViewSpec < BaseViewSpec
8
+ def map_function
9
+ options[:map]
10
+ end
11
+
12
+ def process_results(results)
13
+ options[:results_filter] ? options[:results_filter].call(results) : results
14
+ end
15
+
16
+ def reduce_function
17
+ options[:reduce]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,45 @@
1
+ module CouchPotato
2
+ module View
3
+
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
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,37 @@
1
+ require 'couchrest'
2
+ require 'json'
3
+ require 'json/add/core'
4
+ require 'json/add/rails'
5
+
6
+ require 'ostruct'
7
+
8
+ require 'validatable'
9
+
10
+
11
+ module CouchPotato
12
+ Config = OpenStruct.new
13
+
14
+ def self.database
15
+ @@__database ||= Database.new(self.couchrest_database)
16
+ end
17
+
18
+ def self.couchrest_database
19
+ @@__couchrest_database ||= CouchRest.database(full_url_to_database)
20
+ end
21
+
22
+ def self.full_url_to_database
23
+ database_name = CouchPotato::Config.database_name || raise('No Database configured. Set CouchPotato::Config.database_name')
24
+ url = database_name
25
+ if url !~ /^http:\/\//
26
+ url = "http://localhost:5984/#{database_name}"
27
+ end
28
+ url
29
+ end
30
+ end
31
+
32
+ require File.dirname(__FILE__) + '/core_ext/object'
33
+ require File.dirname(__FILE__) + '/core_ext/time'
34
+ require File.dirname(__FILE__) + '/core_ext/date'
35
+ require File.dirname(__FILE__) + '/core_ext/string'
36
+ require File.dirname(__FILE__) + '/couch_potato/persistence'
37
+
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,245 @@
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
+ attr_accessor :lambda_works
24
+ before_create lambda {|model| model.lambda_works = true}
25
+
26
+ def callbacks
27
+ @callbacks ||= []
28
+ end
29
+
30
+ end
31
+
32
+ describe "multiple callbacks at once" do
33
+
34
+ class Monkey
35
+ include CouchPotato::Persistence
36
+ attr_accessor :eaten_banana, :eaten_apple
37
+
38
+ before_create :eat_apple, :eat_banana
39
+
40
+ private
41
+
42
+ def eat_banana
43
+ self.eaten_banana = true
44
+ end
45
+
46
+ def eat_apple
47
+ self.eaten_apple = true
48
+ end
49
+ end
50
+ it "should run all callback methods given to the callback method call" do
51
+ monkey = Monkey.new
52
+ CouchPotato.database.save_document! monkey
53
+ monkey.eaten_banana.should be_true
54
+ monkey.eaten_apple.should be_true
55
+ end
56
+ end
57
+
58
+ describe 'create callbacks' do
59
+
60
+ before(:each) do
61
+ @recorder = CallbackRecorder.new
62
+ couchrest_database = stub 'couchrest_database', :save_doc => {'id' => '1', 'rev' => '2'}
63
+ @db = CouchPotato::Database.new(couchrest_database)
64
+ end
65
+
66
+ describe "successful create" do
67
+ before(:each) do
68
+ @recorder.required_property = 1
69
+ end
70
+
71
+ it "should call before_validation_on_create" do
72
+ @db.save_document! @recorder
73
+ @recorder.callbacks.should include(:before_validation_on_create)
74
+ end
75
+
76
+ it "should call before_validation_on_save" do
77
+ @db.save_document! @recorder
78
+ @recorder.callbacks.should include(:before_validation_on_save)
79
+ end
80
+
81
+ it "should call before_save" do
82
+ @db.save_document! @recorder
83
+ @recorder.callbacks.should include(:before_save)
84
+ end
85
+
86
+ it "should call after_save" do
87
+ @db.save_document! @recorder
88
+ @recorder.callbacks.should include(:after_save)
89
+ end
90
+
91
+ it "should call before_create" do
92
+ @db.save_document! @recorder
93
+ @recorder.callbacks.should include(:before_create)
94
+ end
95
+
96
+ it "should call after_create" do
97
+ @db.save_document! @recorder
98
+ @recorder.callbacks.should include(:after_create)
99
+ end
100
+
101
+ end
102
+
103
+ describe "failed create" do
104
+
105
+ it "should call before_validation_on_create" do
106
+ @db.save_document @recorder
107
+ @recorder.callbacks.should include(:before_validation_on_create)
108
+ end
109
+
110
+ it "should call before_validation_on_save" do
111
+ @db.save_document @recorder
112
+ @recorder.callbacks.should include(:before_validation_on_save)
113
+ end
114
+
115
+ it "should not call before_save" do
116
+ @db.save_document @recorder
117
+ @recorder.callbacks.should_not include(:before_save)
118
+ end
119
+
120
+ it "should not call after_save" do
121
+ @db.save_document @recorder
122
+ @recorder.callbacks.should_not include(:after_save)
123
+ end
124
+
125
+ it "should not call before_create" do
126
+ @db.save_document @recorder
127
+ @recorder.callbacks.should_not include(:before_create)
128
+ end
129
+
130
+ it "should not call after_create" do
131
+ @db.save_document @recorder
132
+ @recorder.callbacks.should_not include(:after_create)
133
+ end
134
+ end
135
+ end
136
+
137
+ describe "update callbacks" do
138
+
139
+ before(:each) do
140
+ @recorder = CallbackRecorder.new :required_property => 1
141
+
142
+ couchrest_database = stub 'couchrest_database', :save_doc => {'id' => '1', 'rev' => '2'}
143
+ @db = CouchPotato::Database.new(couchrest_database)
144
+ @db.save_document! @recorder
145
+
146
+ @recorder.required_property = 2
147
+ @recorder.callbacks.clear
148
+ end
149
+
150
+ describe "successful update" do
151
+
152
+ before(:each) do
153
+ @db.save_document! @recorder
154
+ end
155
+
156
+ it "should call before_validation_on_update" do
157
+ @recorder.callbacks.should include(:before_validation_on_update)
158
+ end
159
+
160
+ it "should call before_validation_on_save" do
161
+ @recorder.callbacks.should include(:before_validation_on_save)
162
+ end
163
+
164
+ it "should call before_save" do
165
+ @recorder.callbacks.should include(:before_save)
166
+ end
167
+
168
+ it "should call after_save" do
169
+ @recorder.callbacks.should include(:after_save)
170
+ end
171
+
172
+ it "should call before_update" do
173
+ @recorder.callbacks.should include(:before_update)
174
+ end
175
+
176
+ it "should call after_update" do
177
+ @recorder.callbacks.should include(:after_update)
178
+ end
179
+
180
+ end
181
+
182
+ describe "failed update" do
183
+
184
+ before(:each) do
185
+ @recorder.required_property = nil
186
+ @db.save_document @recorder
187
+ end
188
+
189
+ it "should call before_validation_on_update" do
190
+ @recorder.callbacks.should include(:before_validation_on_update)
191
+ end
192
+
193
+ it "should call before_validation_on_save" do
194
+ @recorder.callbacks.should include(:before_validation_on_save)
195
+ end
196
+
197
+ it "should not call before_save" do
198
+ @recorder.callbacks.should_not include(:before_save)
199
+ end
200
+
201
+ it "should not call after_save" do
202
+ @recorder.callbacks.should_not include(:after_save)
203
+ end
204
+
205
+ it "should not call before_update" do
206
+ @recorder.callbacks.should_not include(:before_update)
207
+ end
208
+
209
+ it "should not call after_update" do
210
+ @recorder.callbacks.should_not include(:after_update)
211
+ end
212
+
213
+ end
214
+
215
+ end
216
+
217
+ describe "destroy callbacks" do
218
+
219
+ before(:each) do
220
+ @recorder = CallbackRecorder.new :required_property => 1
221
+ couchrest_database = stub 'couchrest_database', :save_doc => {'id' => '1', 'rev' => '2'}, :delete_doc => nil
222
+ @db = CouchPotato::Database.new(couchrest_database)
223
+ @db.save_document! @recorder
224
+
225
+ @recorder.callbacks.clear
226
+ end
227
+
228
+ it "should call before_destroy" do
229
+ @db.destroy_document @recorder
230
+ @recorder.callbacks.should include(:before_destroy)
231
+ end
232
+
233
+ it "should call after_destroy" do
234
+ @db.destroy_document @recorder
235
+ @recorder.callbacks.should include(:after_destroy)
236
+ end
237
+ end
238
+
239
+ describe "lambda callbacks" do
240
+ it "should run the lambda" do
241
+ recorder = CallbackRecorder.new
242
+ recorder.run_callbacks :before_create
243
+ recorder.lambda_works.should be_true
244
+ end
245
+ end
@@ -0,0 +1,22 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "create" do
4
+ before(:all) do
5
+ recreate_db
6
+ end
7
+ describe "succeeds" do
8
+ it "should store the class" do
9
+ @comment = Comment.new :title => 'my_title'
10
+ CouchPotato.database.save_document! @comment
11
+ CouchPotato.couchrest_database.get(@comment.id)['ruby_class'].should == 'Comment'
12
+ end
13
+ end
14
+ describe "fails" do
15
+ it "should not store anything" do
16
+ @comment = Comment.new
17
+ CouchPotato.database.save_document @comment
18
+ CouchPotato.couchrest_database.documents['rows'].should be_empty
19
+ end
20
+ end
21
+ end
22
+