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.
- data/README.md +260 -0
- data/VERSION.yml +2 -2
- data/init.rb +3 -0
- data/lib/core_ext/date.rb +10 -0
- data/lib/core_ext/string.rb +15 -0
- data/lib/core_ext/time.rb +6 -9
- data/lib/couch_potato/database.rb +90 -0
- data/lib/couch_potato/persistence/belongs_to_property.rb +1 -1
- data/lib/couch_potato/persistence/callbacks.rb +14 -11
- data/lib/couch_potato/persistence/dirty_attributes.rb +13 -6
- data/lib/couch_potato/persistence/json.rb +9 -14
- data/lib/couch_potato/persistence/magic_timestamps.rb +13 -0
- data/lib/couch_potato/persistence/properties.rb +10 -8
- data/lib/couch_potato/persistence/simple_property.rb +14 -11
- data/lib/couch_potato/persistence.rb +11 -165
- data/lib/couch_potato/view/base_view_spec.rb +20 -0
- data/lib/couch_potato/view/custom_view_spec.rb +26 -0
- data/lib/couch_potato/view/custom_views.rb +30 -0
- data/lib/couch_potato/view/model_view_spec.rb +39 -0
- data/lib/couch_potato/view/properties_view_spec.rb +35 -0
- data/lib/couch_potato/view/raw_view_spec.rb +21 -0
- data/lib/couch_potato/view/view_query.rb +45 -0
- data/lib/couch_potato.rb +23 -6
- data/rails/init.rb +7 -0
- data/spec/callbacks_spec.rb +40 -43
- data/spec/create_spec.rb +9 -60
- data/spec/custom_view_spec.rb +93 -19
- data/spec/destroy_spec.rb +5 -4
- data/spec/property_spec.rb +22 -8
- data/spec/spec_helper.rb +12 -14
- data/spec/unit/attributes_spec.rb +26 -0
- data/spec/unit/create_spec.rb +58 -0
- data/spec/{dirty_attributes_spec.rb → unit/dirty_attributes_spec.rb} +31 -13
- data/spec/unit/string_spec.rb +13 -0
- data/spec/unit/view_query_spec.rb +2 -3
- data/spec/update_spec.rb +8 -7
- metadata +54 -32
- data/README.textile +0 -340
- data/lib/couch_potato/active_record/compatibility.rb +0 -9
- data/lib/couch_potato/ordering.rb +0 -84
- data/lib/couch_potato/persistence/bulk_save_queue.rb +0 -47
- data/lib/couch_potato/persistence/collection.rb +0 -51
- data/lib/couch_potato/persistence/custom_view.rb +0 -41
- data/lib/couch_potato/persistence/external_collection.rb +0 -83
- data/lib/couch_potato/persistence/external_has_many_property.rb +0 -72
- data/lib/couch_potato/persistence/find.rb +0 -21
- data/lib/couch_potato/persistence/finder.rb +0 -65
- data/lib/couch_potato/persistence/inline_has_many_property.rb +0 -43
- data/lib/couch_potato/persistence/view_query.rb +0 -81
- data/lib/couch_potato/versioning.rb +0 -46
- data/spec/attributes_spec.rb +0 -42
- data/spec/belongs_to_spec.rb +0 -55
- data/spec/find_spec.rb +0 -96
- data/spec/finder_spec.rb +0 -125
- data/spec/has_many_spec.rb +0 -241
- data/spec/inline_collection_spec.rb +0 -15
- data/spec/ordering_spec.rb +0 -95
- data/spec/reload_spec.rb +0 -50
- data/spec/unit/external_collection_spec.rb +0 -84
- data/spec/unit/finder_spec.rb +0 -10
- data/spec/versioning_spec.rb +0 -150
@@ -1,35 +1,28 @@
|
|
1
1
|
require 'digest/md5'
|
2
|
-
require '
|
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__) + '/
|
13
|
-
require File.dirname(__FILE__) + '/
|
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 :
|
23
|
-
base.send :include,
|
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
|
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
|
57
|
-
|
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 '
|
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__}"
|