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.
@@ -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
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class CouchFooTest < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'couch_foo'
8
+
9
+ class Test::Unit::TestCase
10
+ end
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
+