langalex-couch_potato 0.1.1 → 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 (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__}"