couchmodel 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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