couchrest 0.35 → 0.36
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/Rakefile +2 -2
- data/history.txt +10 -0
- data/lib/couchrest.rb +15 -12
- data/lib/couchrest/core/database.rb +25 -7
- data/lib/couchrest/mixins/collection.rb +45 -9
- data/lib/couchrest/mixins/properties.rb +11 -53
- data/lib/couchrest/monkeypatches.rb +1 -1
- data/lib/couchrest/more/extended_document.rb +5 -4
- data/lib/couchrest/more/property.rb +8 -9
- data/lib/couchrest/more/typecast.rb +180 -0
- data/lib/couchrest/validation/validators/required_field_validator.rb +2 -2
- data/spec/couchrest/core/couchrest_spec.rb +16 -79
- data/spec/couchrest/core/database_spec.rb +70 -3
- data/spec/couchrest/more/casted_model_spec.rb +1 -1
- data/spec/couchrest/more/extended_doc_spec.rb +58 -7
- data/spec/couchrest/more/property_spec.rb +424 -85
- data/spec/fixtures/more/article.rb +2 -2
- data/spec/fixtures/more/course.rb +13 -5
- data/spec/fixtures/more/event.rb +1 -2
- data/spec/fixtures/more/person.rb +1 -1
- data/spec/fixtures/more/question.rb +1 -1
- data/spec/fixtures/more/service.rb +1 -1
- data/spec/spec_helper.rb +13 -1
- metadata +27 -13
data/Rakefile
CHANGED
@@ -20,7 +20,7 @@ begin
|
|
20
20
|
gemspec.description = "CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments."
|
21
21
|
gemspec.email = "jchris@apache.org"
|
22
22
|
gemspec.homepage = "http://github.com/couchrest/couchrest"
|
23
|
-
gemspec.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos"]
|
23
|
+
gemspec.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber"]
|
24
24
|
gemspec.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
|
25
25
|
gemspec.files = %w( LICENSE README.md Rakefile THANKS.md history.txt) + Dir["{examples,lib,spec,utils}/**/*"] - Dir["spec/tmp"]
|
26
26
|
gemspec.has_rdoc = true
|
@@ -64,4 +64,4 @@ module Rake
|
|
64
64
|
end
|
65
65
|
|
66
66
|
Rake.remove_task("github:release")
|
67
|
-
Rake.remove_task("release")
|
67
|
+
Rake.remove_task("release")
|
data/history.txt
CHANGED
@@ -4,6 +4,16 @@
|
|
4
4
|
|
5
5
|
* Minor enhancements
|
6
6
|
|
7
|
+
== 0.36
|
8
|
+
|
9
|
+
* Major enhancements
|
10
|
+
* Adds support for continuous replication (sauy7)
|
11
|
+
* Automatic Type Casting (Alexander Uvarov, Sam Lown, Tim Heighes, Will Leinweber)
|
12
|
+
* Added a search method to CouchRest:Database to search the documents in a given database. (Dave Farkas, Arnaud Berthomier, John Wood)
|
13
|
+
|
14
|
+
* Minor enhancements
|
15
|
+
* Provide a description of the timeout error (John Wood)
|
16
|
+
|
7
17
|
== 0.35
|
8
18
|
|
9
19
|
* Major enhancements
|
data/lib/couchrest.rb
CHANGED
@@ -28,7 +28,7 @@ require 'couchrest/monkeypatches'
|
|
28
28
|
|
29
29
|
# = CouchDB, close to the metal
|
30
30
|
module CouchRest
|
31
|
-
VERSION = '0.
|
31
|
+
VERSION = '0.36' unless self.const_defined?("VERSION")
|
32
32
|
|
33
33
|
autoload :Server, 'couchrest/core/server'
|
34
34
|
autoload :Database, 'couchrest/core/database'
|
@@ -96,15 +96,18 @@ module CouchRest
|
|
96
96
|
|
97
97
|
def parse url
|
98
98
|
case url
|
99
|
-
when /^https?:\/\/(.*)\/(.*)\/(.*)/
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
99
|
+
when /^(https?:\/\/)(.*)\/(.*)\/(.*)/
|
100
|
+
scheme = $1
|
101
|
+
host = $2
|
102
|
+
db = $3
|
103
|
+
docid = $4
|
104
|
+
when /^(https?:\/\/)(.*)\/(.*)/
|
105
|
+
scheme = $1
|
106
|
+
host = $2
|
107
|
+
db = $3
|
108
|
+
when /^(https?:\/\/)(.*)/
|
109
|
+
scheme = $1
|
110
|
+
host = $2
|
108
111
|
when /(.*)\/(.*)\/(.*)/
|
109
112
|
host = $1
|
110
113
|
db = $2
|
@@ -117,9 +120,9 @@ module CouchRest
|
|
117
120
|
end
|
118
121
|
|
119
122
|
db = nil if db && db.empty?
|
120
|
-
|
123
|
+
|
121
124
|
{
|
122
|
-
:host => host || "127.0.0.1:5984",
|
125
|
+
:host => (scheme || "http://") + (host || "127.0.0.1:5984"),
|
123
126
|
:database => db,
|
124
127
|
:doc => docid
|
125
128
|
}
|
@@ -44,7 +44,14 @@ module CouchRest
|
|
44
44
|
CouchRest.get url
|
45
45
|
end
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
|
+
# Query a CouchDB-Lucene search view
|
49
|
+
def search(name, params={})
|
50
|
+
# -> http://localhost:5984/yourdb/_fti/YourDesign/by_name?include_docs=true&q=plop*'
|
51
|
+
url = CouchRest.paramify_url "#{root}/_fti/#{name}", params
|
52
|
+
CouchRest.get url
|
53
|
+
end
|
54
|
+
|
48
55
|
# load a set of documents by passing an array of ids
|
49
56
|
def get_bulk(ids)
|
50
57
|
documents(:keys => ids, :include_docs => true)
|
@@ -297,15 +304,13 @@ module CouchRest
|
|
297
304
|
end
|
298
305
|
|
299
306
|
# Replicates via "pulling" from another database to this database. Makes no attempt to deal with conflicts.
|
300
|
-
def replicate_from other_db
|
301
|
-
|
302
|
-
CouchRest.post "#{@host}/_replicate", :source => other_db.root, :target => name
|
307
|
+
def replicate_from other_db, continuous=false
|
308
|
+
replicate other_db, continuous, :target => name
|
303
309
|
end
|
304
310
|
|
305
311
|
# Replicates via "pushing" to another database. Makes no attempt to deal with conflicts.
|
306
|
-
def replicate_to other_db
|
307
|
-
|
308
|
-
CouchRest.post "#{@host}/_replicate", :target => other_db.root, :source => name
|
312
|
+
def replicate_to other_db, continuous=false
|
313
|
+
replicate other_db, continuous, :source => name
|
309
314
|
end
|
310
315
|
|
311
316
|
# DELETE the database itself. This is not undoable and could be rather
|
@@ -317,6 +322,19 @@ module CouchRest
|
|
317
322
|
|
318
323
|
private
|
319
324
|
|
325
|
+
def replicate other_db, continuous, options
|
326
|
+
raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database)
|
327
|
+
raise ArgumentError, "must provide a target or source option" unless (options.key?(:target) || options.key?(:source))
|
328
|
+
payload = options
|
329
|
+
if options.has_key?(:target)
|
330
|
+
payload[:source] = other_db.root
|
331
|
+
else
|
332
|
+
payload[:target] = other_db.root
|
333
|
+
end
|
334
|
+
payload[:continuous] = continuous
|
335
|
+
CouchRest.post "#{@host}/_replicate", payload
|
336
|
+
end
|
337
|
+
|
320
338
|
def clear_extended_doc_fresh_cache
|
321
339
|
::CouchRest::ExtendedDocument.subclasses.each{|klass| klass.design_doc_fresh = false if klass.respond_to?(:design_doc_fresh=) }
|
322
340
|
end
|
@@ -20,7 +20,7 @@ module CouchRest
|
|
20
20
|
class_eval <<-END, __FILE__, __LINE__ + 1
|
21
21
|
def self.find_all_#{collection_name}(options = {})
|
22
22
|
view_options = #{view_options.inspect} || {}
|
23
|
-
CollectionProxy.new(
|
23
|
+
CollectionProxy.new(database, "#{design_doc}", "#{view_name}", view_options.merge(options), Kernel.const_get('#{self}'))
|
24
24
|
end
|
25
25
|
END
|
26
26
|
end
|
@@ -42,7 +42,12 @@ module CouchRest
|
|
42
42
|
#
|
43
43
|
# Defaults are used if these options are not specified.
|
44
44
|
def paginated_each(options, &block)
|
45
|
-
|
45
|
+
search = options.delete(:search)
|
46
|
+
unless search == true
|
47
|
+
proxy = create_collection_proxy(options)
|
48
|
+
else
|
49
|
+
proxy = create_search_collection_proxy(options)
|
50
|
+
end
|
46
51
|
proxy.paginated_each(options, &block)
|
47
52
|
end
|
48
53
|
|
@@ -58,7 +63,12 @@ module CouchRest
|
|
58
63
|
|
59
64
|
def create_collection_proxy(options)
|
60
65
|
design_doc, view_name, view_options = parse_view_options(options)
|
61
|
-
CollectionProxy.new(
|
66
|
+
CollectionProxy.new(database, design_doc, view_name, view_options, self)
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_search_collection_proxy(options)
|
70
|
+
design_doc, search_name, search_options = parse_search_options(options)
|
71
|
+
CollectionProxy.new(@database, design_doc, search_name, search_options, self, :search)
|
62
72
|
end
|
63
73
|
|
64
74
|
def parse_view_options(options)
|
@@ -75,6 +85,18 @@ module CouchRest
|
|
75
85
|
|
76
86
|
[design_doc, view_name, view_options]
|
77
87
|
end
|
88
|
+
|
89
|
+
def parse_search_options(options)
|
90
|
+
design_doc = options.delete(:design_doc)
|
91
|
+
raise ArgumentError, 'design_doc is required' if design_doc.nil?
|
92
|
+
|
93
|
+
search_name = options.delete(:view_name)
|
94
|
+
raise ArgumentError, 'search_name is required' if search_name.nil?
|
95
|
+
|
96
|
+
search_options = options.clone
|
97
|
+
[design_doc, search_name, search_options]
|
98
|
+
end
|
99
|
+
|
78
100
|
end
|
79
101
|
|
80
102
|
class CollectionProxy
|
@@ -91,11 +113,12 @@ module CouchRest
|
|
91
113
|
#
|
92
114
|
# The CollectionProxy provides support for paginating over a collection
|
93
115
|
# via the paginate, and paginated_each methods.
|
94
|
-
def initialize(database, design_doc, view_name, view_options = {}, container_class = nil)
|
116
|
+
def initialize(database, design_doc, view_name, view_options = {}, container_class = nil, query_type = :view)
|
95
117
|
raise ArgumentError, "database is a required parameter" if database.nil?
|
96
118
|
|
97
119
|
@database = database
|
98
120
|
@container_class = container_class
|
121
|
+
@query_type = query_type
|
99
122
|
|
100
123
|
strip_pagination_options(view_options)
|
101
124
|
@view_options = view_options
|
@@ -110,10 +133,22 @@ module CouchRest
|
|
110
133
|
# See Collection.paginate
|
111
134
|
def paginate(options = {})
|
112
135
|
page, per_page = parse_options(options)
|
113
|
-
results = @database.
|
136
|
+
results = @database.send(@query_type, @view_name, pagination_options(page, per_page))
|
114
137
|
remember_where_we_left_off(results, page)
|
115
|
-
|
116
|
-
|
138
|
+
instances = convert_to_container_array(results)
|
139
|
+
|
140
|
+
begin
|
141
|
+
if Kernel.const_get('WillPaginate')
|
142
|
+
total_rows = results['total_rows'].to_i
|
143
|
+
paginated = WillPaginate::Collection.create(page, per_page, total_rows) do |pager|
|
144
|
+
pager.replace(instances)
|
145
|
+
end
|
146
|
+
return paginated
|
147
|
+
end
|
148
|
+
rescue NameError
|
149
|
+
# When not using will_paginate, not much we could do about this. :x
|
150
|
+
end
|
151
|
+
return instances
|
117
152
|
end
|
118
153
|
|
119
154
|
# See Collection.paginated_each
|
@@ -152,7 +187,8 @@ module CouchRest
|
|
152
187
|
|
153
188
|
def load_target
|
154
189
|
unless loaded?
|
155
|
-
|
190
|
+
@view_options.merge!({:include_docs => true}) if @query_type == :search
|
191
|
+
results = @database.send(@query_type, @view_name, @view_options)
|
156
192
|
@target = convert_to_container_array(results)
|
157
193
|
end
|
158
194
|
@loaded = true
|
@@ -189,7 +225,7 @@ module CouchRest
|
|
189
225
|
|
190
226
|
def pagination_options(page, per_page)
|
191
227
|
view_options = @view_options.clone
|
192
|
-
if @last_key && @last_docid && @last_page == page - 1
|
228
|
+
if @query_type == :view && @last_key && @last_docid && @last_page == page - 1
|
193
229
|
key = view_options.delete(:key)
|
194
230
|
end_key = view_options[:endkey] || key
|
195
231
|
options = { :startkey => @last_key, :endkey => end_key, :startkey_docid => @last_docid, :limit => per_page, :skip => 1 }
|
@@ -1,24 +1,6 @@
|
|
1
1
|
require 'time'
|
2
2
|
require File.join(File.dirname(__FILE__), '..', 'more', 'property')
|
3
|
-
|
4
|
-
class Time
|
5
|
-
# returns a local time value much faster than Time.parse
|
6
|
-
def self.mktime_with_offset(string)
|
7
|
-
string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2})([\+|\s|\-])*(\d{2}):?(\d{2})/
|
8
|
-
# $1 = year
|
9
|
-
# $2 = month
|
10
|
-
# $3 = day
|
11
|
-
# $4 = hours
|
12
|
-
# $5 = minutes
|
13
|
-
# $6 = seconds
|
14
|
-
# $7 = time zone direction
|
15
|
-
# $8 = tz difference
|
16
|
-
# utc time with wrong TZ info:
|
17
|
-
time = mktime($1, RFC2822_MONTH_NAME[$2.to_i - 1], $3, $4, $5, $6, $7)
|
18
|
-
tz_difference = ("#{$7 == '-' ? '+' : '-'}#{$8}".to_i * 3600)
|
19
|
-
time + tz_difference + zone_offset(time.zone)
|
20
|
-
end
|
21
|
-
end
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'more', 'typecast')
|
22
4
|
|
23
5
|
module CouchRest
|
24
6
|
module Mixins
|
@@ -26,6 +8,8 @@ module CouchRest
|
|
26
8
|
|
27
9
|
class IncludeError < StandardError; end
|
28
10
|
|
11
|
+
include ::CouchRest::More::Typecast
|
12
|
+
|
29
13
|
def self.included(base)
|
30
14
|
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
31
15
|
extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
|
@@ -67,28 +51,24 @@ module CouchRest
|
|
67
51
|
return unless self[key]
|
68
52
|
if property.type.is_a?(Array)
|
69
53
|
klass = ::CouchRest.constantize(property.type[0])
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
end
|
54
|
+
self[key] = [self[key]] unless self[key].is_a?(Array)
|
55
|
+
arr = self[key].collect do |value|
|
56
|
+
value = typecast_value(value, klass, property.init_method)
|
74
57
|
associate_casted_to_parent(value, assigned)
|
75
58
|
value
|
76
59
|
end
|
77
60
|
self[key] = klass != String ? CastedArray.new(arr) : arr
|
78
61
|
self[key].casted_by = self if self[key].respond_to?(:casted_by)
|
79
62
|
else
|
80
|
-
if property.type == 'boolean'
|
63
|
+
if property.type.downcase == 'boolean'
|
81
64
|
klass = TrueClass
|
82
65
|
else
|
83
66
|
klass = ::CouchRest.constantize(property.type)
|
84
67
|
end
|
85
68
|
|
86
|
-
|
87
|
-
|
88
|
-
end
|
89
|
-
associate_casted_to_parent(self[property.name], assigned)
|
69
|
+
self[key] = typecast_value(self[key], klass, property.init_method)
|
70
|
+
associate_casted_to_parent(self[key], assigned)
|
90
71
|
end
|
91
|
-
|
92
72
|
end
|
93
73
|
|
94
74
|
def associate_casted_to_parent(casted, assigned)
|
@@ -96,21 +76,6 @@ module CouchRest
|
|
96
76
|
casted.document_saved = true if !assigned && casted.respond_to?(:document_saved)
|
97
77
|
end
|
98
78
|
|
99
|
-
def convert_property_value(property, klass, value)
|
100
|
-
if ((property.init_method == 'new') && klass == Time)
|
101
|
-
# Using custom time parsing method because Ruby's default method is toooo slow
|
102
|
-
value.is_a?(String) ? Time.mktime_with_offset(value.dup) : value
|
103
|
-
# Float instances don't get initialized with #new
|
104
|
-
elsif ((property.init_method == 'new') && klass == Float)
|
105
|
-
cast_float(value)
|
106
|
-
# 'boolean' type is simply used to generate a property? accessor method
|
107
|
-
elsif ((property.init_method == 'new') && klass == TrueClass)
|
108
|
-
value
|
109
|
-
else
|
110
|
-
klass.send(property.init_method, value.dup)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
79
|
def cast_property_by_name(property_name)
|
115
80
|
return unless self.class.properties
|
116
81
|
property = self.class.properties.detect{|property| property.name == property_name}
|
@@ -118,14 +83,7 @@ module CouchRest
|
|
118
83
|
cast_property(property, true)
|
119
84
|
end
|
120
85
|
|
121
|
-
|
122
|
-
begin
|
123
|
-
Float(value)
|
124
|
-
rescue
|
125
|
-
value
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
86
|
+
|
129
87
|
module ClassMethods
|
130
88
|
|
131
89
|
def property(name, options={})
|
@@ -141,7 +99,7 @@ module CouchRest
|
|
141
99
|
# make sure to use a mutex.
|
142
100
|
def define_property(name, options={})
|
143
101
|
# check if this property is going to casted
|
144
|
-
options[:casted] = options[:cast_as]
|
102
|
+
options[:casted] = !!(options[:cast_as] || options[:type])
|
145
103
|
property = CouchRest::Property.new(name, (options.delete(:cast_as) || options.delete(:type)), options)
|
146
104
|
create_property_getter(property)
|
147
105
|
create_property_setter(property) unless property.read_only == true
|
@@ -14,7 +14,7 @@ module CouchRest
|
|
14
14
|
include CouchRest::Mixins::ExtendedAttachments
|
15
15
|
include CouchRest::Mixins::ClassProxy
|
16
16
|
include CouchRest::Mixins::Collection
|
17
|
-
|
17
|
+
include CouchRest::Mixins::AttributeProtection
|
18
18
|
|
19
19
|
def self.subclasses
|
20
20
|
@subclasses ||= []
|
@@ -58,6 +58,7 @@ module CouchRest
|
|
58
58
|
unless self['_id'] && self['_rev']
|
59
59
|
self['couchrest-type'] = self.class.to_s
|
60
60
|
end
|
61
|
+
after_initialize if respond_to?(:after_initialize)
|
61
62
|
end
|
62
63
|
|
63
64
|
# Defines an instance and save it directly to the database
|
@@ -84,9 +85,9 @@ module CouchRest
|
|
84
85
|
# on the document whenever saving occurs. CouchRest uses a pretty
|
85
86
|
# decent time format by default. See Time#to_json
|
86
87
|
def self.timestamps!
|
87
|
-
class_eval <<-EOS, __FILE__, __LINE__
|
88
|
-
property(:updated_at, :read_only => true, :
|
89
|
-
property(:created_at, :read_only => true, :
|
88
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
89
|
+
property(:updated_at, :read_only => true, :type => 'Time', :auto_validation => false)
|
90
|
+
property(:created_at, :read_only => true, :type => 'Time', :auto_validation => false)
|
90
91
|
|
91
92
|
set_callback :save, :before do |object|
|
92
93
|
object['updated_at'] = Time.now
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module CouchRest
|
2
|
-
|
2
|
+
|
3
3
|
# Basic attribute support for adding getter/setter + validation
|
4
4
|
class Property
|
5
5
|
attr_reader :name, :type, :read_only, :alias, :default, :casted, :init_method, :options
|
6
|
-
|
6
|
+
|
7
7
|
# attribute to define
|
8
8
|
def initialize(name, type = nil, options = {})
|
9
9
|
@name = name.to_s
|
@@ -11,20 +11,19 @@ module CouchRest
|
|
11
11
|
parse_options(options)
|
12
12
|
self
|
13
13
|
end
|
14
|
-
|
15
|
-
|
14
|
+
|
16
15
|
private
|
17
|
-
|
16
|
+
|
18
17
|
def parse_type(type)
|
19
18
|
if type.nil?
|
20
19
|
@type = 'String'
|
21
20
|
elsif type.is_a?(Array) && type.empty?
|
22
|
-
@type = '
|
21
|
+
@type = ['Object']
|
23
22
|
else
|
24
23
|
@type = type.is_a?(Array) ? [type.first.to_s] : type.to_s
|
25
24
|
end
|
26
25
|
end
|
27
|
-
|
26
|
+
|
28
27
|
def parse_options(options)
|
29
28
|
return if options.empty?
|
30
29
|
@validation_format = options.delete(:format) if options[:format]
|
@@ -35,7 +34,7 @@ module CouchRest
|
|
35
34
|
@init_method = options[:init_method] ? options.delete(:init_method) : 'new'
|
36
35
|
@options = options
|
37
36
|
end
|
38
|
-
|
37
|
+
|
39
38
|
end
|
40
39
|
end
|
41
40
|
|
@@ -56,4 +55,4 @@ class CastedArray < Array
|
|
56
55
|
obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
|
57
56
|
super(index, obj)
|
58
57
|
end
|
59
|
-
end
|
58
|
+
end
|