langalex-couch_potato 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/README.md +260 -0
  2. data/VERSION.yml +2 -2
  3. data/init.rb +3 -0
  4. data/lib/core_ext/date.rb +10 -0
  5. data/lib/core_ext/string.rb +15 -0
  6. data/lib/core_ext/time.rb +6 -9
  7. data/lib/couch_potato/database.rb +90 -0
  8. data/lib/couch_potato/persistence/belongs_to_property.rb +1 -1
  9. data/lib/couch_potato/persistence/callbacks.rb +14 -11
  10. data/lib/couch_potato/persistence/dirty_attributes.rb +13 -6
  11. data/lib/couch_potato/persistence/json.rb +9 -14
  12. data/lib/couch_potato/persistence/magic_timestamps.rb +13 -0
  13. data/lib/couch_potato/persistence/properties.rb +10 -8
  14. data/lib/couch_potato/persistence/simple_property.rb +14 -11
  15. data/lib/couch_potato/persistence.rb +11 -165
  16. data/lib/couch_potato/view/base_view_spec.rb +20 -0
  17. data/lib/couch_potato/view/custom_view_spec.rb +26 -0
  18. data/lib/couch_potato/view/custom_views.rb +30 -0
  19. data/lib/couch_potato/view/model_view_spec.rb +39 -0
  20. data/lib/couch_potato/view/properties_view_spec.rb +35 -0
  21. data/lib/couch_potato/view/raw_view_spec.rb +21 -0
  22. data/lib/couch_potato/view/view_query.rb +45 -0
  23. data/lib/couch_potato.rb +23 -6
  24. data/rails/init.rb +7 -0
  25. data/spec/callbacks_spec.rb +40 -43
  26. data/spec/create_spec.rb +9 -60
  27. data/spec/custom_view_spec.rb +93 -19
  28. data/spec/destroy_spec.rb +5 -4
  29. data/spec/property_spec.rb +22 -8
  30. data/spec/spec_helper.rb +12 -14
  31. data/spec/unit/attributes_spec.rb +26 -0
  32. data/spec/unit/create_spec.rb +58 -0
  33. data/spec/{dirty_attributes_spec.rb → unit/dirty_attributes_spec.rb} +31 -13
  34. data/spec/unit/string_spec.rb +13 -0
  35. data/spec/unit/view_query_spec.rb +2 -3
  36. data/spec/update_spec.rb +8 -7
  37. metadata +54 -32
  38. data/README.textile +0 -340
  39. data/lib/couch_potato/active_record/compatibility.rb +0 -9
  40. data/lib/couch_potato/ordering.rb +0 -84
  41. data/lib/couch_potato/persistence/bulk_save_queue.rb +0 -47
  42. data/lib/couch_potato/persistence/collection.rb +0 -51
  43. data/lib/couch_potato/persistence/custom_view.rb +0 -41
  44. data/lib/couch_potato/persistence/external_collection.rb +0 -83
  45. data/lib/couch_potato/persistence/external_has_many_property.rb +0 -72
  46. data/lib/couch_potato/persistence/find.rb +0 -21
  47. data/lib/couch_potato/persistence/finder.rb +0 -65
  48. data/lib/couch_potato/persistence/inline_has_many_property.rb +0 -43
  49. data/lib/couch_potato/persistence/view_query.rb +0 -81
  50. data/lib/couch_potato/versioning.rb +0 -46
  51. data/spec/attributes_spec.rb +0 -42
  52. data/spec/belongs_to_spec.rb +0 -55
  53. data/spec/find_spec.rb +0 -96
  54. data/spec/finder_spec.rb +0 -125
  55. data/spec/has_many_spec.rb +0 -241
  56. data/spec/inline_collection_spec.rb +0 -15
  57. data/spec/ordering_spec.rb +0 -95
  58. data/spec/reload_spec.rb +0 -50
  59. data/spec/unit/external_collection_spec.rb +0 -84
  60. data/spec/unit/finder_spec.rb +0 -10
  61. data/spec/versioning_spec.rb +0 -150
@@ -1,35 +1,28 @@
1
1
  require 'digest/md5'
2
- require 'couchrest'
3
- require 'validatable'
4
- require File.dirname(__FILE__) + '/persistence/inline_collection'
5
- require File.dirname(__FILE__) + '/persistence/external_collection'
2
+ require File.dirname(__FILE__) + '/database'
6
3
  require File.dirname(__FILE__) + '/persistence/properties'
4
+ require File.dirname(__FILE__) + '/persistence/magic_timestamps'
7
5
  require File.dirname(__FILE__) + '/persistence/callbacks'
8
6
  require File.dirname(__FILE__) + '/persistence/json'
9
- require File.dirname(__FILE__) + '/persistence/bulk_save_queue'
10
- require File.dirname(__FILE__) + '/persistence/find'
11
7
  require File.dirname(__FILE__) + '/persistence/dirty_attributes'
12
- require File.dirname(__FILE__) + '/persistence/custom_view'
13
- require File.dirname(__FILE__) + '/persistence/view_query'
8
+ require File.dirname(__FILE__) + '/view/custom_views'
9
+ require File.dirname(__FILE__) + '/view/view_query'
10
+
14
11
 
15
12
  module CouchPotato
16
13
  module Persistence
17
14
 
18
- class ValidationsFailedError < ::Exception; end
19
- class UnsavedRecordError < ::Exception; end
20
-
21
15
  def self.included(base)
22
- base.send :extend, ClassMethods, Find
23
- base.send :include, Callbacks, Properties, Validatable, Json, DirtyAttributes, CustomView
16
+ base.send :include, Properties, Callbacks, Validatable, Json, CouchPotato::View::CustomViews
17
+ base.send :include, DirtyAttributes
18
+ base.send :include, MagicTimestamps
24
19
  base.class_eval do
25
- attr_accessor :_id, :_rev, :_attachments, :_deleted, :created_at, :updated_at
26
- attr_reader :bulk_save_queue
20
+ attr_accessor :_id, :_rev, :_attachments, :_deleted
27
21
  alias_method :id, :_id
28
22
  end
29
23
  end
30
24
 
31
25
  def initialize(attributes = {})
32
- @bulk_save_queue = BulkSaveQueue.new
33
26
  attributes.each do |name, value|
34
27
  self.send("#{name}=", value)
35
28
  end if attributes
@@ -41,11 +34,6 @@ module CouchPotato
41
34
  end
42
35
  end
43
36
 
44
- def update_attributes(hash)
45
- self.attributes = hash
46
- save
47
- end
48
-
49
37
  def attributes
50
38
  self.class.properties.inject({}) do |res, property|
51
39
  property.serialize(res, self)
@@ -53,159 +41,17 @@ module CouchPotato
53
41
  end
54
42
  end
55
43
 
56
- def save!
57
- save || raise(ValidationsFailedError.new(self.errors.full_messages))
58
- end
59
-
60
- def save
61
- if new_document?
62
- create_document
63
- else
64
- update_document
65
- end
66
- end
67
-
68
- def destroy
69
- run_callbacks(:before_destroy)
70
- self._deleted = true
71
- bulk_save_queue << self
72
- destroy_dependent_objects
73
- bulk_save_queue.save do |res|
74
- self._id = nil
75
- self._rev = nil
76
- end
77
- run_callbacks(:after_destroy)
78
- end
79
-
80
- def reload
81
- raise(UnsavedRecordError.new) unless _id
82
- json = self.class.db.get _id
83
- self.class.properties.each do |property|
84
- property.build self, json
85
- end
86
- end
87
-
88
- def new_document?
89
- _id.nil?
44
+ def new?
45
+ _rev.nil?
90
46
  end
91
47
 
92
48
  def to_param
93
49
  _id
94
50
  end
95
51
 
96
- def [](name)
97
- self.send name
98
- end
99
-
100
52
  def ==(other)
101
53
  other.class == self.class && self.to_json == other.to_json
102
54
  end
103
55
 
104
- private
105
-
106
- def create_document
107
- run_callbacks :before_validation_on_save
108
- run_callbacks :before_validation_on_create
109
- return unless valid?
110
- run_callbacks :before_save
111
- run_callbacks :before_create
112
- self.created_at = Time.now
113
- self.updated_at = Time.now
114
- self._id = generate_uuid
115
- bulk_save_queue << self
116
- save_dependent_objects
117
- bulk_save_queue.save do |res|
118
- self._rev = extract_rev(res)
119
- end
120
- run_callbacks :after_save
121
- run_callbacks :after_create
122
- true
123
- end
124
-
125
- def generate_uuid
126
- self.class.server.next_uuid rescue Digest::MD5.hexdigest(rand(1000000000000).to_s) # only works with couchdb 0.9
127
- end
128
-
129
- def extract_rev(res)
130
- res['new_revs'].select{|hash| hash['id'] == self.id}.first['rev']
131
- end
132
-
133
- def update_document
134
- run_callbacks(:before_validation_on_save)
135
- run_callbacks(:before_validation_on_update)
136
- return unless valid?
137
- run_callbacks :before_save
138
- run_callbacks :before_update
139
- self.updated_at = Time.now
140
- bulk_save_queue << self
141
- save_dependent_objects
142
- bulk_save_queue.save do |res|
143
- self._rev = extract_rev(res)
144
- end
145
- run_callbacks :after_save
146
- run_callbacks :after_update
147
- true
148
- end
149
-
150
- def save_dependent_objects
151
- self.class.properties.each do |property|
152
- property.save(self)
153
- end
154
- end
155
-
156
- def destroy_dependent_objects
157
- self.class.properties.each do |property|
158
- property.destroy(self)
159
- end
160
- end
161
-
162
- module ClassMethods
163
-
164
- def create!(attributes = {})
165
- instance = self.new attributes
166
- instance.save!
167
- instance
168
- end
169
-
170
- def create(attributes = {})
171
- instance = self.new attributes
172
- instance.save
173
- instance
174
- end
175
-
176
- def get(id)
177
- begin
178
- self.json_create db.get(id)
179
- rescue(RestClient::ResourceNotFound)
180
- nil
181
- end
182
- end
183
-
184
- def db(name = nil)
185
- ::CouchPotato::Persistence.Db(name)
186
- end
187
-
188
- end
189
-
190
- def self.Db(database_name = nil)
191
- @@__database ||= CouchRest.database(full_url_to_database(database_name))
192
- end
193
-
194
- def self.Server(database_name = nil)
195
- @@_server ||= Db(database_name).server
196
- end
197
-
198
- def self.Db!(database_name = nil)
199
- CouchRest.database!(full_url_to_database(database_name))
200
- end
201
-
202
- def self.full_url_to_database(database_name)
203
- database_name ||= CouchPotato::Config.database_name || raise('No Database configured. Set CouchPotato::Config.database_name')
204
- url = database_name
205
- if url !~ /^http:\/\//
206
- url = "http://localhost:5984/#{database_name}"
207
- end
208
- url
209
- end
210
56
  end
211
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
data/lib/couch_potato.rb CHANGED
@@ -1,20 +1,37 @@
1
- require 'rubygems'
2
- require 'active_support'
1
+ require 'couchrest'
3
2
  require 'json'
4
3
  require 'json/add/core'
5
4
  require 'json/add/rails'
6
5
 
7
6
  require 'ostruct'
8
7
 
8
+ require 'validatable'
9
+
10
+
9
11
  module CouchPotato
10
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
11
30
  end
12
31
 
13
32
  require File.dirname(__FILE__) + '/core_ext/object'
14
33
  require File.dirname(__FILE__) + '/core_ext/time'
15
-
34
+ require File.dirname(__FILE__) + '/core_ext/date'
35
+ require File.dirname(__FILE__) + '/core_ext/string'
16
36
  require File.dirname(__FILE__) + '/couch_potato/persistence'
17
- require File.dirname(__FILE__) + '/couch_potato/versioning'
18
- require File.dirname(__FILE__) + '/couch_potato/ordering'
19
- require File.dirname(__FILE__) + '/couch_potato/active_record/compatibility'
20
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__}"