couchmodel 0.1.1 → 0.1.2
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 +1 -1
- data/Rakefile +5 -5
- data/lib/core_extension/string.rb +10 -7
- data/lib/couch_model/base.rb +9 -9
- data/lib/couch_model/base/accessor.rb +2 -0
- data/lib/couch_model/collection.rb +3 -3
- data/lib/couch_model/database.rb +4 -4
- data/lib/couch_model/design.rb +11 -11
- data/lib/couch_model/server.rb +5 -5
- data/lib/transport/base.rb +65 -0
- data/lib/transport/json.rb +69 -0
- data/lib/transport/request/builder.rb +59 -0
- data/lib/transport/request/parameter/serializer.rb +60 -0
- data/spec/fake_transport_helper.rb +7 -2
- data/spec/lib/couch_model/active_model_spec.rb +6 -1
- data/spec/lib/couch_model/{core → base}/accessor_spec.rb +21 -9
- data/spec/lib/couch_model/{core → base}/association_spec.rb +0 -0
- data/spec/lib/couch_model/{core → base}/finder_spec.rb +0 -0
- data/spec/lib/couch_model/{core → base}/setup_spec.rb +0 -0
- data/spec/lib/couch_model/base_spec.rb +4 -4
- data/spec/lib/couch_model/collection_spec.rb +2 -2
- data/spec/lib/couch_model/database_spec.rb +4 -4
- data/spec/lib/transport/base_spec.rb +49 -0
- data/spec/lib/transport/json_spec.rb +56 -0
- data/spec/lib/transport/request/builder_spec.rb +51 -0
- data/spec/lib/transport/request/parameter/serializer_spec.rb +16 -0
- metadata +26 -15
- data/lib/couch_model/transport.rb +0 -178
- data/spec/lib/couch_model/transport_spec.rb +0 -114
data/README.rdoc
CHANGED
@@ -6,7 +6,7 @@ implementation to integrate into an Rails 3 application.
|
|
6
6
|
|
7
7
|
The current version is under development and open for everyone to find bugs and post them into the issue tracker.
|
8
8
|
|
9
|
-
The code has been tested Ruby 1.8.7 and 1.9.1, CouchDB 0.10.0 and Rails 3.0.0.
|
9
|
+
The code has been tested Ruby 1.8.7 and 1.9.1, CouchDB 0.10.0 and Rails 3.0.0.beta4.
|
10
10
|
|
11
11
|
== Dependencies
|
12
12
|
|
data/Rakefile
CHANGED
@@ -9,8 +9,8 @@ task :default => :spec
|
|
9
9
|
|
10
10
|
specification = Gem::Specification.new do |specification|
|
11
11
|
specification.name = "couchmodel"
|
12
|
-
specification.version = "0.1.
|
13
|
-
specification.date = "2010-
|
12
|
+
specification.version = "0.1.2"
|
13
|
+
specification.date = "2010-06-12"
|
14
14
|
|
15
15
|
specification.authors = [ "Philipp Bruell" ]
|
16
16
|
specification.email = "b.phifty@gmail.com"
|
@@ -27,7 +27,7 @@ specification = Gem::Specification.new do |specification|
|
|
27
27
|
|
28
28
|
specification.test_files = Dir["spec/**/*_spec.rb"]
|
29
29
|
|
30
|
-
specification.add_development_dependency "rspec"
|
30
|
+
specification.add_development_dependency "rspec", ">= 1.3.0"
|
31
31
|
end
|
32
32
|
|
33
33
|
Rake::GemPackageTask.new(specification) do |package|
|
@@ -47,10 +47,10 @@ Spec::Rake::SpecTask.new do |task|
|
|
47
47
|
end
|
48
48
|
|
49
49
|
namespace :spec do
|
50
|
-
|
50
|
+
|
51
51
|
desc "Run all integration specs in spec/integration directory"
|
52
52
|
Spec::Rake::SpecTask.new(:integration) do |task|
|
53
53
|
task.spec_files = FileList["spec/integration/**/*_spec.rb"]
|
54
54
|
end
|
55
55
|
|
56
|
-
end
|
56
|
+
end
|
@@ -3,13 +3,7 @@ class String # :nodoc:
|
|
3
3
|
|
4
4
|
# This method converts a CamelCaseString into an underscore_string.
|
5
5
|
def underscore
|
6
|
-
|
7
|
-
word.gsub!(/::/, '/')
|
8
|
-
word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
9
|
-
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
10
|
-
word.tr!("-", "_")
|
11
|
-
word.downcase!
|
12
|
-
word
|
6
|
+
self.class.underscore self.to_s.dup
|
13
7
|
end
|
14
8
|
|
15
9
|
# This method converts an underscore_string into a CamelCaseString.
|
@@ -25,4 +19,13 @@ class String # :nodoc:
|
|
25
19
|
self.gsub(/(?:^|_)(.)/) { $1.upcase }
|
26
20
|
end
|
27
21
|
|
22
|
+
def self.underscore(word)
|
23
|
+
word.gsub!(/::/, '/')
|
24
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
25
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
26
|
+
word.tr!("-", "_")
|
27
|
+
word.downcase!
|
28
|
+
word
|
29
|
+
end
|
30
|
+
|
28
31
|
end
|
data/lib/couch_model/base.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "..", "core_extension", "string"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "transport", "json"))
|
2
3
|
require File.join(File.dirname(__FILE__), "configuration")
|
3
|
-
require File.join(File.dirname(__FILE__), "transport")
|
4
4
|
require File.join(File.dirname(__FILE__), "server")
|
5
5
|
require File.join(File.dirname(__FILE__), "database")
|
6
6
|
require File.join(File.dirname(__FILE__), "design")
|
@@ -63,9 +63,9 @@ module CouchModel
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def load
|
66
|
-
load_response
|
66
|
+
load_response Transport::JSON.request(:get, url, :expected_status_code => 200)
|
67
67
|
true
|
68
|
-
rescue
|
68
|
+
rescue Transport::UnexpectedStatusCodeError => error
|
69
69
|
upgrade_unexpected_status_error error
|
70
70
|
end
|
71
71
|
|
@@ -77,10 +77,10 @@ module CouchModel
|
|
77
77
|
|
78
78
|
def destroy
|
79
79
|
return false if new?
|
80
|
-
|
80
|
+
Transport::JSON.request :delete, self.url, :headers => { "If-Match" => self.rev }, :expected_status_code => 200
|
81
81
|
clear_rev
|
82
82
|
true
|
83
|
-
rescue
|
83
|
+
rescue Transport::UnexpectedStatusCodeError => error
|
84
84
|
upgrade_unexpected_status_error error
|
85
85
|
end
|
86
86
|
|
@@ -105,19 +105,19 @@ module CouchModel
|
|
105
105
|
end
|
106
106
|
|
107
107
|
def create
|
108
|
-
response =
|
108
|
+
response = Transport::JSON.request :post, self.database.url, :body => self.attributes, :expected_status_code => 201
|
109
109
|
self.id = response["id"]
|
110
110
|
self.rev = response["rev"]
|
111
111
|
true
|
112
|
-
rescue
|
112
|
+
rescue Transport::UnexpectedStatusCodeError
|
113
113
|
false
|
114
114
|
end
|
115
115
|
|
116
116
|
def update
|
117
|
-
response =
|
117
|
+
response = Transport::JSON.request :put, self.url, :body => self.attributes, :expected_status_code => 201
|
118
118
|
self.rev = response["rev"]
|
119
119
|
true
|
120
|
-
rescue
|
120
|
+
rescue Transport::UnexpectedStatusCodeError
|
121
121
|
false
|
122
122
|
end
|
123
123
|
|
@@ -15,6 +15,7 @@ module CouchModel
|
|
15
15
|
module ClassMethods
|
16
16
|
|
17
17
|
def key_reader(key, options = { })
|
18
|
+
raise ArgumentError, "method #{key} is already defined" if method_defined?(:"#{key}")
|
18
19
|
set_default key, options[:default] if options.has_key?(:default)
|
19
20
|
define_method :"#{key}" do
|
20
21
|
@attributes[key.to_s]
|
@@ -22,6 +23,7 @@ module CouchModel
|
|
22
23
|
end
|
23
24
|
|
24
25
|
def key_writer(key, options = { })
|
26
|
+
raise ArgumentError, "method #{key}= is already defined" if method_defined?(:"#{key}=")
|
25
27
|
set_default key, options[:default] if options.has_key?(:default)
|
26
28
|
define_method :"#{key}=" do |value|
|
27
29
|
@attributes[key.to_s] = value
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), "transport")
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "transport", "json"))
|
2
2
|
require File.join(File.dirname(__FILE__), "row")
|
3
3
|
|
4
4
|
module CouchModel
|
@@ -64,7 +64,7 @@ module CouchModel
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def fetch_response
|
67
|
-
@response =
|
67
|
+
@response = Transport::JSON.request(
|
68
68
|
:get, url,
|
69
69
|
:parameters => request_parameters,
|
70
70
|
:expected_status_code => 200
|
@@ -72,7 +72,7 @@ module CouchModel
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def fetch_meta_response
|
75
|
-
@response =
|
75
|
+
@response = Transport::JSON.request(
|
76
76
|
:get, url,
|
77
77
|
:parameters => request_parameters.merge(:limit => 0),
|
78
78
|
:expected_status_code => 200
|
data/lib/couch_model/database.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), "transport")
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "transport", "json"))
|
2
2
|
require File.join(File.dirname(__FILE__), "server")
|
3
3
|
require File.join(File.dirname(__FILE__), "collection")
|
4
4
|
|
@@ -25,7 +25,7 @@ module CouchModel
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def create!
|
28
|
-
|
28
|
+
Transport::JSON.request :put, url, :expected_status_code => 201
|
29
29
|
end
|
30
30
|
|
31
31
|
def create_if_missing!
|
@@ -33,7 +33,7 @@ module CouchModel
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def delete!
|
36
|
-
|
36
|
+
Transport::JSON.request :delete, url, :expected_status_code => 200
|
37
37
|
end
|
38
38
|
|
39
39
|
def delete_if_exists!
|
@@ -41,7 +41,7 @@ module CouchModel
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def informations
|
44
|
-
|
44
|
+
Transport::JSON.request :get, url, :expected_status_code => 200
|
45
45
|
end
|
46
46
|
|
47
47
|
def exists?
|
data/lib/couch_model/design.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "transport", "json"))
|
1
2
|
require File.join(File.dirname(__FILE__), "configuration")
|
2
|
-
require File.join(File.dirname(__FILE__), "transport")
|
3
3
|
require File.join(File.dirname(__FILE__), "base")
|
4
4
|
require File.join(File.dirname(__FILE__), "view")
|
5
5
|
require 'yaml'
|
@@ -34,7 +34,7 @@ module CouchModel
|
|
34
34
|
|
35
35
|
def load_file
|
36
36
|
hash = YAML::load_file self.filename
|
37
|
-
symbolize_hash_keys hash
|
37
|
+
self.class.symbolize_hash_keys hash
|
38
38
|
self.id, self.language, self.views = hash.values_at(:id, :language, :views)
|
39
39
|
true
|
40
40
|
rescue Errno::ENOENT
|
@@ -67,17 +67,17 @@ module CouchModel
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def exists?
|
70
|
-
|
70
|
+
Transport::JSON.request :get, self.url, :expected_status_code => 200
|
71
71
|
true
|
72
|
-
rescue Transport::UnexpectedStatusCodeError
|
72
|
+
rescue Transport::JSON::UnexpectedStatusCodeError
|
73
73
|
false
|
74
74
|
end
|
75
75
|
|
76
76
|
def push
|
77
77
|
url = self.url
|
78
|
-
evaluate
|
78
|
+
evaluate Transport::JSON.request(:get, url)
|
79
79
|
|
80
|
-
|
80
|
+
Transport::JSON.request :put, url, :body => self.to_hash, :expected_status_code => 201
|
81
81
|
true
|
82
82
|
end
|
83
83
|
|
@@ -89,7 +89,11 @@ module CouchModel
|
|
89
89
|
|
90
90
|
attr_writer :rev
|
91
91
|
|
92
|
-
def
|
92
|
+
def evaluate(response)
|
93
|
+
self.rev = response["_rev"] if response.has_key?("_rev")
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.symbolize_hash_keys(hash)
|
93
97
|
hash.keys.each do |key|
|
94
98
|
value = hash.delete key
|
95
99
|
symbolize_hash_keys value if value.is_a?(Hash)
|
@@ -97,10 +101,6 @@ module CouchModel
|
|
97
101
|
end
|
98
102
|
end
|
99
103
|
|
100
|
-
def evaluate(response)
|
101
|
-
self.rev = response["_rev"] if response.has_key?("_rev")
|
102
|
-
end
|
103
|
-
|
104
104
|
end
|
105
105
|
|
106
106
|
end
|
data/lib/couch_model/server.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), "transport")
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "transport", "json"))
|
2
2
|
|
3
3
|
module CouchModel
|
4
4
|
|
@@ -19,19 +19,19 @@ module CouchModel
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def informations
|
22
|
-
|
22
|
+
Transport::JSON.request :get, url + "/", :expected_status_code => 200
|
23
23
|
end
|
24
24
|
|
25
25
|
def statistics
|
26
|
-
|
26
|
+
Transport::JSON.request :get, url + "/_stats", :expected_status_code => 200
|
27
27
|
end
|
28
28
|
|
29
29
|
def database_names
|
30
|
-
|
30
|
+
Transport::JSON.request :get, url + "/_all_dbs", :expected_status_code => 200
|
31
31
|
end
|
32
32
|
|
33
33
|
def uuids(count = 1)
|
34
|
-
response =
|
34
|
+
response = Transport::JSON.request :get, url + "/_uuids", :expected_status_code => 200, :parameters => { :count => count }
|
35
35
|
response["uuids"]
|
36
36
|
end
|
37
37
|
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "request", "builder")
|
2
|
+
require 'uri'
|
3
|
+
require 'net/http'
|
4
|
+
|
5
|
+
module Transport
|
6
|
+
|
7
|
+
# Common transport layer for http transfers.
|
8
|
+
class Base
|
9
|
+
|
10
|
+
attr_reader :response
|
11
|
+
|
12
|
+
def initialize(http_method, url, options = { })
|
13
|
+
@request_builder = Request::Builder.new http_method, url, options
|
14
|
+
@uri = @request_builder.uri
|
15
|
+
@request = @request_builder.request
|
16
|
+
|
17
|
+
@expected_status_code = options[:expected_status_code]
|
18
|
+
end
|
19
|
+
|
20
|
+
def perform
|
21
|
+
perform_request
|
22
|
+
check_status_code
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.request(http_method, url, options = { })
|
26
|
+
transport = new http_method, url, options
|
27
|
+
transport.perform
|
28
|
+
transport.response
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def perform_request
|
34
|
+
@response = Net::HTTP.start(@uri.host, @uri.port) do |connection|
|
35
|
+
connection.request @request
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def check_status_code
|
40
|
+
return unless @expected_status_code
|
41
|
+
response_code = @response.code
|
42
|
+
response_body = @response.body
|
43
|
+
raise UnexpectedStatusCodeError.new(response_code.to_i, response_body) if @expected_status_code.to_s != response_code
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
# The UnexpectedStatusCodeError is raised if the :expected_status_code option is given to
|
49
|
+
# the :request method and the responded status code is different from the expected one.
|
50
|
+
class UnexpectedStatusCodeError < StandardError
|
51
|
+
|
52
|
+
attr_reader :status_code
|
53
|
+
attr_reader :message
|
54
|
+
|
55
|
+
def initialize(status_code, message = nil)
|
56
|
+
@status_code, @message = status_code, message
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_s
|
60
|
+
"#{super} received status code #{self.status_code}" + (@message ? " [#{@message}]" : "")
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'json'
|
2
|
+
require File.join(File.dirname(__FILE__), "base")
|
3
|
+
|
4
|
+
module Transport
|
5
|
+
|
6
|
+
# Extended transport layer for http transfers. Basic authorization and JSON transfers are supported.
|
7
|
+
class JSON < Base
|
8
|
+
|
9
|
+
attr_reader :expected_status_code
|
10
|
+
attr_reader :auth_type
|
11
|
+
attr_reader :username
|
12
|
+
attr_reader :password
|
13
|
+
|
14
|
+
def initialize(http_method, url, options = { })
|
15
|
+
@options = options
|
16
|
+
modify_headers
|
17
|
+
modify_parameters
|
18
|
+
modify_body
|
19
|
+
super http_method, url, @options
|
20
|
+
initialize_authentication
|
21
|
+
end
|
22
|
+
|
23
|
+
def perform
|
24
|
+
super
|
25
|
+
parse_response
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def modify_headers
|
31
|
+
headers = (@options[:headers] || { }).merge("Accept" => "application/json")
|
32
|
+
headers.merge! "Content-Type" => "application/json" if @options[:body]
|
33
|
+
@options[:headers] = headers
|
34
|
+
end
|
35
|
+
|
36
|
+
def modify_parameters
|
37
|
+
parameters = @options[:parameters]
|
38
|
+
if parameters
|
39
|
+
parameters.each do |key, value|
|
40
|
+
parameters[key] = value.to_json if value.respond_to?(:to_json)
|
41
|
+
end
|
42
|
+
@options[:parameters] = parameters
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def modify_body
|
47
|
+
body = @options[:body]
|
48
|
+
@options[:body] = body.to_json if body
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize_authentication
|
52
|
+
auth_type = @options[:auth_type]
|
53
|
+
if auth_type == :basic
|
54
|
+
@request.basic_auth @options[:username], @options[:password]
|
55
|
+
elsif auth_type
|
56
|
+
raise NotImplementedError, "the given auth_type [#{auth_type}] is not implemented"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_response
|
61
|
+
body = @response.body
|
62
|
+
@response = body.nil? ? nil : ::JSON.parse(body)
|
63
|
+
rescue ::JSON::ParserError
|
64
|
+
@response = body.to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "parameter", "serializer")
|
2
|
+
|
3
|
+
module Transport
|
4
|
+
|
5
|
+
module Request
|
6
|
+
|
7
|
+
# Builder for the transport layer requests
|
8
|
+
class Builder
|
9
|
+
|
10
|
+
HTTP_METHODS_WITH_PARAMETERS = [ :get, :delete ].freeze unless defined?(HTTP_METHODS_WITH_PARAMETERS)
|
11
|
+
HTTP_METHODS_WITH_BODY = [ :post, :put ].freeze unless defined?(HTTP_METHODS_WITH_BODY)
|
12
|
+
|
13
|
+
def initialize(http_method, url, options = { })
|
14
|
+
@http_method = http_method
|
15
|
+
@uri = URI.parse url
|
16
|
+
@headers = options[:headers] || { }
|
17
|
+
@parameter_serializer = Parameter::Serializer.new options[:parameters]
|
18
|
+
@body = options[:body]
|
19
|
+
end
|
20
|
+
|
21
|
+
def uri
|
22
|
+
@uri
|
23
|
+
end
|
24
|
+
|
25
|
+
def request
|
26
|
+
initialize_request_class
|
27
|
+
initialize_request_path
|
28
|
+
initialize_request
|
29
|
+
initialize_request_body
|
30
|
+
@request
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def initialize_request_class
|
36
|
+
request_class_name = @http_method.to_s.capitalize
|
37
|
+
raise NotImplementedError, "the request method #{http_method} is not implemented" unless Net::HTTP.const_defined?(request_class_name)
|
38
|
+
@request_class = Net::HTTP.const_get request_class_name
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize_request_path
|
42
|
+
query = HTTP_METHODS_WITH_PARAMETERS.include?(@http_method.to_sym) ? @parameter_serializer.query : nil
|
43
|
+
@request_path = @uri.path + (query ? "?" + query : "")
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize_request
|
47
|
+
@request = @request_class.new @request_path, @headers
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize_request_body
|
51
|
+
return unless HTTP_METHODS_WITH_BODY.include?(@http_method.to_sym)
|
52
|
+
@request.body = @body ? @body : @parameter_serializer.query
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|