georgepalmer-couch_foo 0.7.1
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.rdoc +113 -0
- data/VERSION.yml +4 -0
- data/lib/boolean.rb +3 -0
- data/lib/couch_foo/associations/association_collection.rb +346 -0
- data/lib/couch_foo/associations/association_proxy.rb +204 -0
- data/lib/couch_foo/associations/belongs_to_association.rb +57 -0
- data/lib/couch_foo/associations/belongs_to_polymorphic_association.rb +48 -0
- data/lib/couch_foo/associations/has_and_belongs_to_many_association.rb +111 -0
- data/lib/couch_foo/associations/has_many_association.rb +97 -0
- data/lib/couch_foo/associations/has_one_association.rb +95 -0
- data/lib/couch_foo/associations.rb +1118 -0
- data/lib/couch_foo/attribute_methods.rb +316 -0
- data/lib/couch_foo/base.rb +2117 -0
- data/lib/couch_foo/calculations.rb +117 -0
- data/lib/couch_foo/callbacks.rb +311 -0
- data/lib/couch_foo/database.rb +157 -0
- data/lib/couch_foo/dirty.rb +142 -0
- data/lib/couch_foo/named_scope.rb +168 -0
- data/lib/couch_foo/observer.rb +195 -0
- data/lib/couch_foo/reflection.rb +239 -0
- data/lib/couch_foo/timestamp.rb +41 -0
- data/lib/couch_foo/validations.rb +927 -0
- data/lib/couch_foo/view_methods.rb +234 -0
- data/lib/couch_foo.rb +43 -0
- data/test/couch_foo_test.rb +7 -0
- data/test/test_helper.rb +10 -0
- metadata +116 -0
@@ -0,0 +1,234 @@
|
|
1
|
+
# The view code uses some of the code developed by Alexander Lang (http://upstream-berlin.com/) in
|
2
|
+
# CouchPotato.
|
3
|
+
module CouchFoo
|
4
|
+
module ViewMethods
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
# Method that does find_by_id, find_by_username_and_type views. If the CouchDB version is
|
13
|
+
# greater than 0.8 it will add the counter function into the same design document but add an
|
14
|
+
# option to not perform it. This makes any calls to count the number of records more efficient
|
15
|
+
# in future as there's only one index to keep updated
|
16
|
+
def find_view(options)
|
17
|
+
search_fields = search_fields(options)
|
18
|
+
|
19
|
+
reduce_function = nil
|
20
|
+
if database.version > 0.8
|
21
|
+
reduce_function = count_documents_function
|
22
|
+
search_values[:reduce] = false
|
23
|
+
end
|
24
|
+
|
25
|
+
generic_view(get_view_name(search_fields), find_by_function(search_fields), reduce_function, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Find the number of documents in a view. If the CouchDB version is greater than 0.8 then we
|
29
|
+
# use the same view as the finder so we only need to keep one view for both finding and counting
|
30
|
+
# on the same attributes
|
31
|
+
def count_view(options)
|
32
|
+
search_fields = search_fields(options)
|
33
|
+
|
34
|
+
if database.version > 0.8
|
35
|
+
view_name = get_view_name(search_fields)
|
36
|
+
else
|
37
|
+
view_name = get_view_name(search_fields, "count")
|
38
|
+
end
|
39
|
+
|
40
|
+
options[:return_json] = true
|
41
|
+
result = generic_view(view_name, find_by_function(search_fields), count_documents_function, options)
|
42
|
+
|
43
|
+
result['rows'].first['value'] rescue 0
|
44
|
+
end
|
45
|
+
|
46
|
+
# Perform a view operation with passed name, functions and options
|
47
|
+
def generic_view(view_name, find_function, reduce_function = nil, options = {})
|
48
|
+
return_json = options.delete(:return_json)
|
49
|
+
order = options.delete(:order) || default_sort_order
|
50
|
+
readonly = options.delete(:readonly)
|
51
|
+
|
52
|
+
if options.delete(:view_type) == :slow
|
53
|
+
result = query_slow_view(find_function, reduce_function, options)
|
54
|
+
else
|
55
|
+
result = query_view(view_name, find_function, reduce_function, options)
|
56
|
+
end
|
57
|
+
|
58
|
+
if return_json
|
59
|
+
result
|
60
|
+
else
|
61
|
+
instantiate_instances(result, readonly, order)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
# Takes JSON and makes objects. Also handles readonly and ordering
|
67
|
+
def instantiate_instances(result, readonly = false, order = false)
|
68
|
+
documents = result['rows'].map{|doc| doc['value']}.map{|attrs| instantiate(attrs) }
|
69
|
+
documents.each { |record| record.readonly! } if readonly
|
70
|
+
documents.sort! {|a,b| a.send(order) <=> b.send(order)} if order
|
71
|
+
documents
|
72
|
+
end
|
73
|
+
|
74
|
+
# Shouldn't really be used apart from debugging
|
75
|
+
def query_slow_view(map_function, reduce_function, options)
|
76
|
+
database.slow_view({:map => map_function, :reduce => reduce_function}, search_values(options))
|
77
|
+
end
|
78
|
+
|
79
|
+
# Assume the view exists and if we get a 404 then create the view before doing the query again
|
80
|
+
def query_view(view_name, map_function, reduce_function, options)
|
81
|
+
search_values = search_values(options)
|
82
|
+
begin
|
83
|
+
do_query_view(view_name, search_values)
|
84
|
+
rescue DocumentNotFound
|
85
|
+
create_view(view_name, map_function, reduce_function)
|
86
|
+
do_query_view(view_name, search_values)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns a name for a view
|
91
|
+
def get_view_name(search_fields, prefix = "find")
|
92
|
+
ending = search_fields.empty? ? "id" : search_fields.join('_and_')
|
93
|
+
prefix + "_by_" + ending
|
94
|
+
end
|
95
|
+
|
96
|
+
# Submit query to database
|
97
|
+
def do_query_view(view_name, view_options)
|
98
|
+
database.view "#{self.name.underscore}/#{view_name}", view_options
|
99
|
+
end
|
100
|
+
|
101
|
+
# Create a named view. If the design document exists we append to the document, else we create it
|
102
|
+
def create_view(view_name, map_function, reduce_function = nil)
|
103
|
+
design_doc = database.get "_design/#{self.name.underscore}" rescue nil
|
104
|
+
if design_doc
|
105
|
+
design_doc["views"][view_name] = {:map => map_function, :reduce => reduce_function}
|
106
|
+
else
|
107
|
+
design_doc = {
|
108
|
+
"_id" => "_design/#{self.name.underscore}",
|
109
|
+
:views => {
|
110
|
+
view_name => {
|
111
|
+
:map => map_function,
|
112
|
+
:reduce => reduce_function
|
113
|
+
}
|
114
|
+
}
|
115
|
+
}
|
116
|
+
end
|
117
|
+
database.save(design_doc)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Find the fields to search on, :view takes preference if provided
|
121
|
+
def search_fields(options)
|
122
|
+
search_fields = options.delete(:use_key)
|
123
|
+
if search_fields
|
124
|
+
search_fields = [search_fields] unless search_fields.is_a?(Array)
|
125
|
+
else
|
126
|
+
search_fields = options[:conditions].to_a.sort_by{|f| f.first.to_s}.map(&:first)
|
127
|
+
end
|
128
|
+
search_fields
|
129
|
+
end
|
130
|
+
|
131
|
+
# Find the values that will be used to select the appropriate records. In CouchDB this works
|
132
|
+
# as keys on a view, so if you wanted all documents where cost=5 you would create a view that
|
133
|
+
# exposes cost as the key and use a key=5 query string on the database query. Ranges are also
|
134
|
+
# supported by using startkey and endkey, for example startkey=4&endkey=9 would find all
|
135
|
+
# documents where the cost is between 4 and 9.
|
136
|
+
#
|
137
|
+
# Multiple part keys are also possible so if you wanted all documents where the cost is between
|
138
|
+
# 4 and 9 and the weight between 1 and 3 you could use (assuming you exposed the key as [cost, weight])
|
139
|
+
# startkey=[4,1]&endkey=[9,3]
|
140
|
+
#
|
141
|
+
# As of CouchDB 0.9 there is another option to find documents with a specific list of keys.
|
142
|
+
# This allows you to pass an explicit list, for example to find all clothes with size 6, 8 or 10
|
143
|
+
# where the clothes size is exposed as a key, you would use keys=[6,8,10]
|
144
|
+
#
|
145
|
+
# Note: :keys works differently at the CouchDB level (requiring a POST rather than a GET) and is
|
146
|
+
# currently not usable with any other condition (such as :startkey, :endkey)
|
147
|
+
def search_values(view_options)
|
148
|
+
conditions = view_options.delete(:conditions)
|
149
|
+
switch_limit_name_if_necessary!(view_options)
|
150
|
+
search_values = conditions.to_a.sort_by{|f| f.first.to_s}.map(&:last)
|
151
|
+
result = {}
|
152
|
+
|
153
|
+
# Get the hash of values to use
|
154
|
+
if search_values.select{|v| v.is_a?(Range)}.any?
|
155
|
+
result = {:startkey => search_values.map{|v| v.is_a?(Range) ? v.first : v},
|
156
|
+
:endkey => search_values.map{|v| v.is_a?(Range) ? v.last : v}}.merge(view_options)
|
157
|
+
elsif search_values.select{|v| v.is_a?(Array)}.any?
|
158
|
+
result = {:keys => prepare_multi_key_search(search_values)}.merge(view_options)
|
159
|
+
else
|
160
|
+
result = view_options.merge(search_values.any? ? {:key => search_values} : {})
|
161
|
+
end
|
162
|
+
|
163
|
+
result.delete_if {|key,value| value.nil? }
|
164
|
+
switch_mysql_terms!(result)
|
165
|
+
switch_keys_if_descending!(result)
|
166
|
+
ensure_keys_are_arrays!(result)
|
167
|
+
result
|
168
|
+
end
|
169
|
+
|
170
|
+
# Deal with legacy CouchDB versions
|
171
|
+
def switch_limit_name_if_necessary!(view_options)
|
172
|
+
if (database.version < 0.9)
|
173
|
+
limit = view_options.delete(:limit)
|
174
|
+
view_options[:count] = limit unless limit.nil?
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# We allow MySQL options on finders for compatability and switch them under the covers
|
179
|
+
def switch_mysql_terms!(result)
|
180
|
+
offset = result.delete(:offset)
|
181
|
+
result[:skip] = offset if offset
|
182
|
+
end
|
183
|
+
|
184
|
+
# Swap start and end keys if user has set descending (as CouchDB descends before applying
|
185
|
+
# the key filtering)
|
186
|
+
def switch_keys_if_descending!(result)
|
187
|
+
if result[:descending]
|
188
|
+
startkey = result[:startkey]
|
189
|
+
endkey = result[:endkey]
|
190
|
+
result[:startkey] = endkey unless endkey.nil?
|
191
|
+
result[:endkey] = startkey unless startkey.nil?
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Ensure start and end keys are specified as arrays
|
196
|
+
def ensure_keys_are_arrays!(result)
|
197
|
+
if result[:startkey] && !result[:startkey].is_a?(Array)
|
198
|
+
result[:startkey] = [result[:startkey]]
|
199
|
+
end
|
200
|
+
if result[:endkey] && !result[:endkey].is_a?(Array)
|
201
|
+
result[:endkey] = [result[:endkey]]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def prepare_multi_key_search(values)
|
206
|
+
array = values.select{|v| v.is_a?(Array)}.first
|
207
|
+
index = values.index array
|
208
|
+
array.map do |item|
|
209
|
+
copy = values.dup
|
210
|
+
copy[index] = item
|
211
|
+
copy
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Map function for find
|
216
|
+
def find_by_function(search_fields)
|
217
|
+
"function(doc) {
|
218
|
+
if(doc.ruby_class == '#{self.name}') {
|
219
|
+
emit(
|
220
|
+
[#{(search_fields).map{|attr| 'doc.' + attr.to_s}.join(', ')}], doc
|
221
|
+
);
|
222
|
+
}
|
223
|
+
}"
|
224
|
+
end
|
225
|
+
|
226
|
+
# Reduce function for counting the number of matched documents
|
227
|
+
def count_documents_function
|
228
|
+
"function(keys, values) {
|
229
|
+
return values.length;
|
230
|
+
}"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
data/lib/couch_foo.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support'
|
3
|
+
require 'json'
|
4
|
+
require 'json/add/core'
|
5
|
+
require 'json/add/rails'
|
6
|
+
require 'uuid'
|
7
|
+
require 'ostruct'
|
8
|
+
|
9
|
+
require 'boolean'
|
10
|
+
require 'couch_foo/base'
|
11
|
+
require 'couch_foo/database'
|
12
|
+
require 'couch_foo/view_methods'
|
13
|
+
require 'couch_foo/named_scope'
|
14
|
+
require 'couch_foo/observer'
|
15
|
+
#require 'active_record/query_cache'
|
16
|
+
require 'couch_foo/validations'
|
17
|
+
require 'couch_foo/callbacks'
|
18
|
+
require 'couch_foo/reflection'
|
19
|
+
require 'couch_foo/associations'
|
20
|
+
#require 'active_record/association_preload'
|
21
|
+
#require 'active_record/aggregations'
|
22
|
+
require 'couch_foo/timestamp'
|
23
|
+
require 'active_record/calculations'
|
24
|
+
require 'couch_foo/attribute_methods'
|
25
|
+
require 'couch_foo/dirty'
|
26
|
+
|
27
|
+
CouchFoo::Base.class_eval do
|
28
|
+
# extend ActiveRecord::QueryCache
|
29
|
+
include CouchFoo::Validations
|
30
|
+
include CouchFoo::AttributeMethods
|
31
|
+
include CouchFoo::Database
|
32
|
+
include CouchFoo::ViewMethods
|
33
|
+
include CouchFoo::Dirty
|
34
|
+
include CouchFoo::Callbacks
|
35
|
+
include CouchFoo::Observing
|
36
|
+
include CouchFoo::Timestamp
|
37
|
+
include CouchFoo::Associations
|
38
|
+
include CouchFoo::NamedScope
|
39
|
+
# include ActiveRecord::AssociationPreload
|
40
|
+
# include ActiveRecord::Aggregations
|
41
|
+
include CouchFoo::Reflection
|
42
|
+
include CouchFoo::Calculations
|
43
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: georgepalmer-couch_foo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- George Palmer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-04 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: json
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: activesupport
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: "0"
|
32
|
+
version:
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: jchris-couchrest
|
35
|
+
version_requirement:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.9.12
|
41
|
+
version:
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: uuid
|
44
|
+
version_requirement:
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "2.0"
|
50
|
+
version:
|
51
|
+
description: CouchFoo provides an ActiveRecord API interface to CouchDB
|
52
|
+
email: george.palmer@gmail.com
|
53
|
+
executables: []
|
54
|
+
|
55
|
+
extensions: []
|
56
|
+
|
57
|
+
extra_rdoc_files: []
|
58
|
+
|
59
|
+
files:
|
60
|
+
- VERSION.yml
|
61
|
+
- README.rdoc
|
62
|
+
- lib/boolean.rb
|
63
|
+
- lib/couch_foo.rb
|
64
|
+
- lib/couch_foo
|
65
|
+
- lib/couch_foo/database.rb
|
66
|
+
- lib/couch_foo/dirty.rb
|
67
|
+
- lib/couch_foo/associations.rb
|
68
|
+
- lib/couch_foo/associations
|
69
|
+
- lib/couch_foo/associations/has_one_association.rb
|
70
|
+
- lib/couch_foo/associations/association_collection.rb
|
71
|
+
- lib/couch_foo/associations/has_many_association.rb
|
72
|
+
- lib/couch_foo/associations/belongs_to_association.rb
|
73
|
+
- lib/couch_foo/associations/association_proxy.rb
|
74
|
+
- lib/couch_foo/associations/belongs_to_polymorphic_association.rb
|
75
|
+
- lib/couch_foo/associations/has_and_belongs_to_many_association.rb
|
76
|
+
- lib/couch_foo/observer.rb
|
77
|
+
- lib/couch_foo/base.rb
|
78
|
+
- lib/couch_foo/calculations.rb
|
79
|
+
- lib/couch_foo/view_methods.rb
|
80
|
+
- lib/couch_foo/attribute_methods.rb
|
81
|
+
- lib/couch_foo/named_scope.rb
|
82
|
+
- lib/couch_foo/callbacks.rb
|
83
|
+
- lib/couch_foo/reflection.rb
|
84
|
+
- lib/couch_foo/validations.rb
|
85
|
+
- lib/couch_foo/timestamp.rb
|
86
|
+
- test/couch_foo_test.rb
|
87
|
+
- test/test_helper.rb
|
88
|
+
has_rdoc: true
|
89
|
+
homepage: http://github.com/georgepalmer/couch_foo
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options:
|
92
|
+
- --inline-source
|
93
|
+
- --charset=UTF-8
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: "0"
|
101
|
+
version:
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: "0"
|
107
|
+
version:
|
108
|
+
requirements: []
|
109
|
+
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 1.2.0
|
112
|
+
signing_key:
|
113
|
+
specification_version: 2
|
114
|
+
summary: CouchFoo provides an ActiveRecord API interface to CouchDB
|
115
|
+
test_files: []
|
116
|
+
|