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 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.beta3.
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.1"
13
- specification.date = "2010-05-05"
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
- word = self.to_s.dup
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
@@ -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 ExtendedTransport.request(:get, url, :expected_status_code => 200)
66
+ load_response Transport::JSON.request(:get, url, :expected_status_code => 200)
67
67
  true
68
- rescue ExtendedTransport::UnexpectedStatusCodeError => error
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
- ExtendedTransport.request :delete, self.url, :headers => { "If-Match" => self.rev }, :expected_status_code => 200
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 ExtendedTransport::UnexpectedStatusCodeError => error
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 = ExtendedTransport.request :post, self.database.url, :body => self.attributes, :expected_status_code => 201
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 ExtendedTransport::UnexpectedStatusCodeError
112
+ rescue Transport::UnexpectedStatusCodeError
113
113
  false
114
114
  end
115
115
 
116
116
  def update
117
- response = ExtendedTransport.request :put, self.url, :body => self.attributes, :expected_status_code => 201
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 ExtendedTransport::UnexpectedStatusCodeError
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 = ExtendedTransport.request(
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 = ExtendedTransport.request(
75
+ @response = Transport::JSON.request(
76
76
  :get, url,
77
77
  :parameters => request_parameters.merge(:limit => 0),
78
78
  :expected_status_code => 200
@@ -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
- ExtendedTransport.request :put, url, :expected_status_code => 201
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
- ExtendedTransport.request :delete, url, :expected_status_code => 200
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
- ExtendedTransport.request :get, url, :expected_status_code => 200
44
+ Transport::JSON.request :get, url, :expected_status_code => 200
45
45
  end
46
46
 
47
47
  def exists?
@@ -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
- ExtendedTransport.request :get, self.url, :expected_status_code => 200
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 ExtendedTransport.request(:get, url)
78
+ evaluate Transport::JSON.request(:get, url)
79
79
 
80
- ExtendedTransport.request :put, url, :body => self.to_hash, :expected_status_code => 201
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 symbolize_hash_keys(hash)
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
@@ -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
- ExtendedTransport.request :get, url + "/", :expected_status_code => 200
22
+ Transport::JSON.request :get, url + "/", :expected_status_code => 200
23
23
  end
24
24
 
25
25
  def statistics
26
- ExtendedTransport.request :get, url + "/_stats", :expected_status_code => 200
26
+ Transport::JSON.request :get, url + "/_stats", :expected_status_code => 200
27
27
  end
28
28
 
29
29
  def database_names
30
- ExtendedTransport.request :get, url + "/_all_dbs", :expected_status_code => 200
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 = ExtendedTransport.request :get, url + "/_uuids", :expected_status_code => 200, :parameters => { :count => count }
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