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
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'bigdecimal'
|
3
|
+
require 'bigdecimal/util'
|
4
|
+
require File.join(File.dirname(__FILE__), '..', 'more', 'property')
|
5
|
+
|
6
|
+
class Time
|
7
|
+
# returns a local time value much faster than Time.parse
|
8
|
+
def self.mktime_with_offset(string)
|
9
|
+
string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2})([\+|\s|\-])*(\d{2}):?(\d{2})/
|
10
|
+
# $1 = year
|
11
|
+
# $2 = month
|
12
|
+
# $3 = day
|
13
|
+
# $4 = hours
|
14
|
+
# $5 = minutes
|
15
|
+
# $6 = seconds
|
16
|
+
# $7 = time zone direction
|
17
|
+
# $8 = tz difference
|
18
|
+
# utc time with wrong TZ info:
|
19
|
+
time = mktime($1, RFC2822_MONTH_NAME[$2.to_i - 1], $3, $4, $5, $6, $7)
|
20
|
+
tz_difference = ("#{$7 == '-' ? '+' : '-'}#{$8}".to_i * 3600)
|
21
|
+
time + tz_difference + zone_offset(time.zone)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module CouchRest
|
26
|
+
module More
|
27
|
+
module Typecast
|
28
|
+
|
29
|
+
def typecast_value(value, klass, init_method)
|
30
|
+
return nil if value.nil?
|
31
|
+
|
32
|
+
if value.instance_of?(klass) || klass.to_s == 'Object'
|
33
|
+
value
|
34
|
+
elsif ['String', 'TrueClass', 'Integer', 'Float', 'BigDecimal', 'DateTime', 'Time', 'Date', 'Class'].include?(klass.to_s)
|
35
|
+
send('typecast_to_'+klass.to_s.downcase, value)
|
36
|
+
else
|
37
|
+
# Allow the init_method to be defined as a Proc for advanced conversion
|
38
|
+
init_method.is_a?(Proc) ? init_method.call(value) : klass.send(init_method, value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
# Typecast a value to an Integer
|
45
|
+
def typecast_to_integer(value)
|
46
|
+
value.kind_of?(Integer) ? value : typecast_to_numeric(value, :to_i)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Typecast a value to a String
|
50
|
+
def typecast_to_string(value)
|
51
|
+
value.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
# Typecast a value to a true or false
|
55
|
+
def typecast_to_trueclass(value)
|
56
|
+
if value.kind_of?(Integer)
|
57
|
+
return true if value == 1
|
58
|
+
return false if value == 0
|
59
|
+
elsif value.respond_to?(:to_s)
|
60
|
+
return true if %w[ true 1 t ].include?(value.to_s.downcase)
|
61
|
+
return false if %w[ false 0 f ].include?(value.to_s.downcase)
|
62
|
+
end
|
63
|
+
value
|
64
|
+
end
|
65
|
+
|
66
|
+
# Typecast a value to a BigDecimal
|
67
|
+
def typecast_to_bigdecimal(value)
|
68
|
+
return value if value.kind_of?(BigDecimal)
|
69
|
+
|
70
|
+
if value.kind_of?(Integer)
|
71
|
+
value.to_s.to_d
|
72
|
+
else
|
73
|
+
typecast_to_numeric(value, :to_d)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Typecast a value to a Float
|
78
|
+
def typecast_to_float(value)
|
79
|
+
return value if value.kind_of?(Float)
|
80
|
+
typecast_to_numeric(value, :to_f)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Match numeric string
|
84
|
+
def typecast_to_numeric(value, method)
|
85
|
+
if value.respond_to?(:to_str)
|
86
|
+
if value.to_str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/
|
87
|
+
$1.send(method)
|
88
|
+
else
|
89
|
+
value
|
90
|
+
end
|
91
|
+
elsif value.respond_to?(method)
|
92
|
+
value.send(method)
|
93
|
+
else
|
94
|
+
value
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Typecasts an arbitrary value to a DateTime.
|
99
|
+
# Handles both Hashes and DateTime instances.
|
100
|
+
def typecast_to_datetime(value)
|
101
|
+
return value if value.kind_of?(DateTime)
|
102
|
+
|
103
|
+
if value.is_a?(Hash)
|
104
|
+
typecast_hash_to_datetime(value)
|
105
|
+
else
|
106
|
+
DateTime.parse(value.to_s)
|
107
|
+
end
|
108
|
+
rescue ArgumentError
|
109
|
+
value
|
110
|
+
end
|
111
|
+
|
112
|
+
# Typecasts an arbitrary value to a Date
|
113
|
+
# Handles both Hashes and Date instances.
|
114
|
+
def typecast_to_date(value)
|
115
|
+
return value if value.kind_of?(Date)
|
116
|
+
|
117
|
+
if value.is_a?(Hash)
|
118
|
+
typecast_hash_to_date(value)
|
119
|
+
else
|
120
|
+
Date.parse(value.to_s)
|
121
|
+
end
|
122
|
+
rescue ArgumentError
|
123
|
+
value
|
124
|
+
end
|
125
|
+
|
126
|
+
# Typecasts an arbitrary value to a Time
|
127
|
+
# Handles both Hashes and Time instances.
|
128
|
+
def typecast_to_time(value)
|
129
|
+
return value if value.kind_of?(Time)
|
130
|
+
|
131
|
+
if value.is_a?(Hash)
|
132
|
+
typecast_hash_to_time(value)
|
133
|
+
else
|
134
|
+
Time.mktime_with_offset(value.to_s)
|
135
|
+
end
|
136
|
+
rescue ArgumentError
|
137
|
+
value
|
138
|
+
rescue TypeError
|
139
|
+
value
|
140
|
+
end
|
141
|
+
|
142
|
+
# Creates a DateTime instance from a Hash with keys :year, :month, :day,
|
143
|
+
# :hour, :min, :sec
|
144
|
+
def typecast_hash_to_datetime(value)
|
145
|
+
DateTime.new(*extract_time(value))
|
146
|
+
end
|
147
|
+
|
148
|
+
# Creates a Date instance from a Hash with keys :year, :month, :day
|
149
|
+
def typecast_hash_to_date(value)
|
150
|
+
Date.new(*extract_time(value)[0, 3])
|
151
|
+
end
|
152
|
+
|
153
|
+
# Creates a Time instance from a Hash with keys :year, :month, :day,
|
154
|
+
# :hour, :min, :sec
|
155
|
+
def typecast_hash_to_time(value)
|
156
|
+
Time.local(*extract_time(value))
|
157
|
+
end
|
158
|
+
|
159
|
+
# Extracts the given args from the hash. If a value does not exist, it
|
160
|
+
# uses the value of Time.now.
|
161
|
+
def extract_time(value)
|
162
|
+
now = Time.now
|
163
|
+
|
164
|
+
[:year, :month, :day, :hour, :min, :sec].map do |segment|
|
165
|
+
typecast_to_numeric(value.fetch(segment, now.send(segment)), :to_i)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Typecast a value to a Class
|
170
|
+
def typecast_to_class(value)
|
171
|
+
return value if value.kind_of?(Class)
|
172
|
+
::CouchRest.constantize(value.to_s)
|
173
|
+
rescue NameError
|
174
|
+
value
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
@@ -37,7 +37,7 @@ module CouchRest
|
|
37
37
|
|
38
38
|
def call(target)
|
39
39
|
value = target.validation_property_value(field_name)
|
40
|
-
property = target.validation_property(field_name)
|
40
|
+
property = target.validation_property(field_name.to_s)
|
41
41
|
return true if present?(value, property)
|
42
42
|
|
43
43
|
error_message = @options[:message] || default_error(property)
|
@@ -66,7 +66,7 @@ module CouchRest
|
|
66
66
|
# Returns false for other property types.
|
67
67
|
# Returns false for non-properties.
|
68
68
|
def boolean_type?(property)
|
69
|
-
property ? property.type ==
|
69
|
+
property ? property.type == 'Boolean' : false
|
70
70
|
end
|
71
71
|
|
72
72
|
end # class RequiredFieldValidator
|
@@ -46,137 +46,74 @@ describe CouchRest do
|
|
46
46
|
it "should parse just a dbname" do
|
47
47
|
db = CouchRest.parse "my-db"
|
48
48
|
db[:database].should == "my-db"
|
49
|
-
db[:host].should == "127.0.0.1:5984"
|
49
|
+
db[:host].should == "http://127.0.0.1:5984"
|
50
50
|
end
|
51
51
|
it "should parse a host and db" do
|
52
52
|
db = CouchRest.parse "127.0.0.1/my-db"
|
53
53
|
db[:database].should == "my-db"
|
54
|
-
db[:host].should == "127.0.0.1"
|
55
|
-
end
|
56
|
-
it "should parse a host and db with http" do
|
57
|
-
db = CouchRest.parse "https://127.0.0.1/my-db"
|
58
|
-
db[:database].should == "my-db"
|
59
|
-
db[:host].should == "127.0.0.1"
|
60
|
-
end
|
61
|
-
it "should parse a host with a port and db" do
|
62
|
-
db = CouchRest.parse "127.0.0.1:5555/my-db"
|
63
|
-
db[:database].should == "my-db"
|
64
|
-
db[:host].should == "127.0.0.1:5555"
|
65
|
-
end
|
66
|
-
it "should parse a host with a port and db with http" do
|
67
|
-
db = CouchRest.parse "http://127.0.0.1:5555/my-db"
|
68
|
-
db[:database].should == "my-db"
|
69
|
-
db[:host].should == "127.0.0.1:5555"
|
70
|
-
end
|
71
|
-
it "should parse a host with a port and db with https" do
|
72
|
-
db = CouchRest.parse "https://127.0.0.1:5555/my-db"
|
73
|
-
db[:database].should == "my-db"
|
74
|
-
db[:host].should == "127.0.0.1:5555"
|
75
|
-
end
|
76
|
-
it "should parse just a host" do
|
77
|
-
db = CouchRest.parse "http://127.0.0.1:5555/"
|
78
|
-
db[:database].should be_nil
|
79
|
-
db[:host].should == "127.0.0.1:5555"
|
80
|
-
end
|
81
|
-
it "should parse just a host with https" do
|
82
|
-
db = CouchRest.parse "https://127.0.0.1:5555/"
|
83
|
-
db[:database].should be_nil
|
84
|
-
db[:host].should == "127.0.0.1:5555"
|
85
|
-
end
|
86
|
-
it "should parse just a host no slash" do
|
87
|
-
db = CouchRest.parse "http://127.0.0.1:5555"
|
88
|
-
db[:host].should == "127.0.0.1:5555"
|
89
|
-
db[:database].should be_nil
|
90
|
-
end
|
91
|
-
it "should parse just a host no slash and https" do
|
92
|
-
db = CouchRest.parse "https://127.0.0.1:5555"
|
93
|
-
db[:host].should == "127.0.0.1:5555"
|
94
|
-
db[:database].should be_nil
|
95
|
-
end
|
96
|
-
it "should get docid" do
|
97
|
-
db = CouchRest.parse "127.0.0.1:5555/my-db/my-doc"
|
98
|
-
db[:database].should == "my-db"
|
99
|
-
db[:host].should == "127.0.0.1:5555"
|
100
|
-
db[:doc].should == "my-doc"
|
101
|
-
end
|
102
|
-
it "should get docid with http" do
|
103
|
-
db = CouchRest.parse "http://127.0.0.1:5555/my-db/my-doc"
|
104
|
-
db[:database].should == "my-db"
|
105
|
-
db[:host].should == "127.0.0.1:5555"
|
106
|
-
db[:doc].should == "my-doc"
|
107
|
-
end
|
108
|
-
it "should get docid with https" do
|
109
|
-
db = CouchRest.parse "https://127.0.0.1:5555/my-db/my-doc"
|
110
|
-
db[:database].should == "my-db"
|
111
|
-
db[:host].should == "127.0.0.1:5555"
|
112
|
-
db[:doc].should == "my-doc"
|
113
|
-
end
|
114
|
-
it "should parse a host and db" do
|
115
|
-
db = CouchRest.parse "127.0.0.1/my-db"
|
116
|
-
db[:database].should == "my-db"
|
117
|
-
db[:host].should == "127.0.0.1"
|
54
|
+
db[:host].should == "http://127.0.0.1"
|
118
55
|
end
|
119
56
|
it "should parse a host and db with http" do
|
120
57
|
db = CouchRest.parse "http://127.0.0.1/my-db"
|
121
58
|
db[:database].should == "my-db"
|
122
|
-
db[:host].should == "127.0.0.1"
|
59
|
+
db[:host].should == "http://127.0.0.1"
|
123
60
|
end
|
124
61
|
it "should parse a host and db with https" do
|
125
62
|
db = CouchRest.parse "https://127.0.0.1/my-db"
|
126
63
|
db[:database].should == "my-db"
|
127
|
-
db[:host].should == "127.0.0.1"
|
64
|
+
db[:host].should == "https://127.0.0.1"
|
128
65
|
end
|
129
66
|
it "should parse a host with a port and db" do
|
130
67
|
db = CouchRest.parse "127.0.0.1:5555/my-db"
|
131
68
|
db[:database].should == "my-db"
|
132
|
-
db[:host].should == "127.0.0.1:5555"
|
69
|
+
db[:host].should == "http://127.0.0.1:5555"
|
133
70
|
end
|
134
71
|
it "should parse a host with a port and db with http" do
|
135
72
|
db = CouchRest.parse "http://127.0.0.1:5555/my-db"
|
136
73
|
db[:database].should == "my-db"
|
137
|
-
db[:host].should == "127.0.0.1:5555"
|
74
|
+
db[:host].should == "http://127.0.0.1:5555"
|
138
75
|
end
|
139
76
|
it "should parse a host with a port and db with https" do
|
140
|
-
db = CouchRest.parse "
|
77
|
+
db = CouchRest.parse "https://127.0.0.1:5555/my-db"
|
141
78
|
db[:database].should == "my-db"
|
142
|
-
db[:host].should == "127.0.0.1:5555"
|
79
|
+
db[:host].should == "https://127.0.0.1:5555"
|
143
80
|
end
|
144
81
|
it "should parse just a host" do
|
145
82
|
db = CouchRest.parse "http://127.0.0.1:5555/"
|
146
83
|
db[:database].should be_nil
|
147
|
-
db[:host].should == "127.0.0.1:5555"
|
84
|
+
db[:host].should == "http://127.0.0.1:5555"
|
148
85
|
end
|
149
86
|
it "should parse just a host with https" do
|
150
87
|
db = CouchRest.parse "https://127.0.0.1:5555/"
|
151
88
|
db[:database].should be_nil
|
152
|
-
db[:host].should == "127.0.0.1:5555"
|
89
|
+
db[:host].should == "https://127.0.0.1:5555"
|
153
90
|
end
|
154
91
|
it "should parse just a host no slash" do
|
155
92
|
db = CouchRest.parse "http://127.0.0.1:5555"
|
156
|
-
db[:host].should == "127.0.0.1:5555"
|
93
|
+
db[:host].should == "http://127.0.0.1:5555"
|
157
94
|
db[:database].should be_nil
|
158
95
|
end
|
159
96
|
it "should parse just a host no slash and https" do
|
160
97
|
db = CouchRest.parse "https://127.0.0.1:5555"
|
161
|
-
db[:host].should == "127.0.0.1:5555"
|
98
|
+
db[:host].should == "https://127.0.0.1:5555"
|
162
99
|
db[:database].should be_nil
|
163
100
|
end
|
164
101
|
it "should get docid" do
|
165
102
|
db = CouchRest.parse "127.0.0.1:5555/my-db/my-doc"
|
166
103
|
db[:database].should == "my-db"
|
167
|
-
db[:host].should == "127.0.0.1:5555"
|
104
|
+
db[:host].should == "http://127.0.0.1:5555"
|
168
105
|
db[:doc].should == "my-doc"
|
169
106
|
end
|
170
107
|
it "should get docid with http" do
|
171
108
|
db = CouchRest.parse "http://127.0.0.1:5555/my-db/my-doc"
|
172
109
|
db[:database].should == "my-db"
|
173
|
-
db[:host].should == "127.0.0.1:5555"
|
110
|
+
db[:host].should == "http://127.0.0.1:5555"
|
174
111
|
db[:doc].should == "my-doc"
|
175
112
|
end
|
176
113
|
it "should get docid with https" do
|
177
114
|
db = CouchRest.parse "https://127.0.0.1:5555/my-db/my-doc"
|
178
115
|
db[:database].should == "my-db"
|
179
|
-
db[:host].should == "127.0.0.1:5555"
|
116
|
+
db[:host].should == "https://127.0.0.1:5555"
|
180
117
|
db[:doc].should == "my-doc"
|
181
118
|
end
|
182
119
|
end
|
@@ -185,7 +122,7 @@ describe CouchRest do
|
|
185
122
|
it "should be possible without an explicit CouchRest instantiation" do
|
186
123
|
db = CouchRest.database "http://127.0.0.1:5984/couchrest-test"
|
187
124
|
db.should be_an_instance_of(CouchRest::Database)
|
188
|
-
db.host.should == "127.0.0.1:5984"
|
125
|
+
db.host.should == "http://127.0.0.1:5984"
|
189
126
|
end
|
190
127
|
# TODO add https support (need test environment...)
|
191
128
|
# it "should work with https" # do
|
@@ -703,12 +703,12 @@ describe CouchRest::Database do
|
|
703
703
|
end
|
704
704
|
end
|
705
705
|
|
706
|
-
describe "replicating a database" do
|
706
|
+
describe "simply replicating a database" do
|
707
707
|
before do
|
708
708
|
@db.save_doc({'_id' => 'test_doc', 'some-value' => 'foo'})
|
709
|
-
@other_db = @cr.database
|
709
|
+
@other_db = @cr.database REPLICATIONDB
|
710
710
|
@other_db.delete! rescue nil
|
711
|
-
@other_db = @cr.create_db
|
711
|
+
@other_db = @cr.create_db REPLICATIONDB
|
712
712
|
end
|
713
713
|
|
714
714
|
describe "via pulling" do
|
@@ -733,6 +733,53 @@ describe CouchRest::Database do
|
|
733
733
|
end
|
734
734
|
end
|
735
735
|
end
|
736
|
+
|
737
|
+
describe "continuously replicating a database" do
|
738
|
+
before do
|
739
|
+
@db.save_doc({'_id' => 'test_doc', 'some-value' => 'foo'})
|
740
|
+
@other_db = @cr.database REPLICATIONDB
|
741
|
+
@other_db.delete! rescue nil
|
742
|
+
@other_db = @cr.create_db REPLICATIONDB
|
743
|
+
end
|
744
|
+
|
745
|
+
describe "via pulling" do
|
746
|
+
before do
|
747
|
+
@other_db.replicate_from @db, true
|
748
|
+
end
|
749
|
+
|
750
|
+
it "contains the document from the original database" do
|
751
|
+
sleep(1) # Allow some time to replicate
|
752
|
+
doc = @other_db.get('test_doc')
|
753
|
+
doc['some-value'].should == 'foo'
|
754
|
+
end
|
755
|
+
|
756
|
+
it "contains documents saved after replication initiated" do
|
757
|
+
@db.save_doc({'_id' => 'test_doc_after', 'some-value' => 'bar'})
|
758
|
+
sleep(1) # Allow some time to replicate
|
759
|
+
doc = @other_db.get('test_doc_after')
|
760
|
+
doc['some-value'].should == 'bar'
|
761
|
+
end
|
762
|
+
end
|
763
|
+
|
764
|
+
describe "via pushing" do
|
765
|
+
before do
|
766
|
+
@db.replicate_to @other_db, true
|
767
|
+
end
|
768
|
+
|
769
|
+
it "copies the document to the other database" do
|
770
|
+
sleep(1) # Allow some time to replicate
|
771
|
+
doc = @other_db.get('test_doc')
|
772
|
+
doc['some-value'].should == 'foo'
|
773
|
+
end
|
774
|
+
|
775
|
+
it "copies documents saved after replication initiated" do
|
776
|
+
@db.save_doc({'_id' => 'test_doc_after', 'some-value' => 'bar'})
|
777
|
+
sleep(1) # Allow some time to replicate
|
778
|
+
doc = @other_db.get('test_doc_after')
|
779
|
+
doc['some-value'].should == 'bar'
|
780
|
+
end
|
781
|
+
end
|
782
|
+
end
|
736
783
|
|
737
784
|
describe "creating a database" do
|
738
785
|
before(:each) do
|
@@ -769,5 +816,25 @@ describe CouchRest::Database do
|
|
769
816
|
|
770
817
|
end
|
771
818
|
|
819
|
+
describe "searching a database" do
|
820
|
+
before(:each) do
|
821
|
+
search_function = { 'defaults' => {'store' => 'no', 'index' => 'analyzed_no_norms'},
|
822
|
+
'index' => "function(doc) { ret = new Document(); ret.add(doc['name'], {'field':'name'}); ret.add(doc['age'], {'field':'age'}); return ret; }" }
|
823
|
+
@db.save_doc({'_id' => '_design/search', 'fulltext' => {'people' => search_function}})
|
824
|
+
@db.save_doc({'_id' => 'john', 'name' => 'John', 'age' => '31'})
|
825
|
+
@db.save_doc({'_id' => 'jack', 'name' => 'Jack', 'age' => '32'})
|
826
|
+
@db.save_doc({'_id' => 'dave', 'name' => 'Dave', 'age' => '33'})
|
827
|
+
end
|
828
|
+
|
829
|
+
it "should be able to search a database using couchdb-lucene" do
|
830
|
+
if couchdb_lucene_available?
|
831
|
+
result = @db.search('search/people', :q => 'name:J*')
|
832
|
+
doc_ids = result['rows'].collect{ |row| row['id'] }
|
833
|
+
doc_ids.size.should == 2
|
834
|
+
doc_ids.should include('john')
|
835
|
+
doc_ids.should include('jack')
|
836
|
+
end
|
837
|
+
end
|
838
|
+
end
|
772
839
|
|
773
840
|
end
|