dm-couchdb-adapter 0.10.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/History.txt +33 -0
- data/LICENSE +20 -0
- data/Manifest.txt +21 -0
- data/README.rdoc +68 -0
- data/Rakefile +65 -0
- data/TODO +0 -0
- data/VERSION +1 -0
- data/lib/couchdb_adapter/adapter.rb +165 -0
- data/lib/couchdb_adapter/attachments.rb +121 -0
- data/lib/couchdb_adapter/collection.rb +7 -0
- data/lib/couchdb_adapter/conditions.rb +190 -0
- data/lib/couchdb_adapter/couch_resource.rb +45 -0
- data/lib/couchdb_adapter/design.rb +5 -0
- data/lib/couchdb_adapter/json_object.rb +23 -0
- data/lib/couchdb_adapter/migrations.rb +25 -0
- data/lib/couchdb_adapter/model.rb +9 -0
- data/lib/couchdb_adapter/query.rb +7 -0
- data/lib/couchdb_adapter/resource.rb +19 -0
- data/lib/couchdb_adapter/version.rb +5 -0
- data/lib/couchdb_adapter/view.rb +41 -0
- data/lib/couchdb_adapter.rb +31 -0
- data/spec/integration/couchdb_adapter_spec.rb +291 -0
- data/spec/integration/couchdb_attachments_spec.rb +116 -0
- data/spec/integration/couchdb_view_spec.rb +47 -0
- data/spec/integration_spec.rb +76 -0
- data/spec/shared/adapter_shared_spec.rb +310 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/testfile.txt +1 -0
- data/spec/unit/couch_db_adapter_spec.rb +8 -0
- data/tasks/install.rb +13 -0
- data/tasks/spec.rb +25 -0
- metadata +139 -0
data/Gemfile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
source "http://rubygems.org"
|
data/History.txt
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
=== 0.9.10 / 2009-01-19
|
2
|
+
|
3
|
+
* 2 minor enhancements:
|
4
|
+
|
5
|
+
* Confirmed upload feature working
|
6
|
+
* Implemented multi-key fetch
|
7
|
+
* Return a Hash instead of Struct increasing performance
|
8
|
+
|
9
|
+
* 4 bug fixes:
|
10
|
+
|
11
|
+
* Fixed conflict with to_json from dm-serializer
|
12
|
+
* Stop escaping the slash in auto_migrate to match new CouchDB
|
13
|
+
behavior
|
14
|
+
* Fixed lazy evaluation of views
|
15
|
+
* Internal fixes for CouchDB r731863
|
16
|
+
|
17
|
+
=== 0.9.9 / 2009-01-04
|
18
|
+
|
19
|
+
* 1 bug fix:
|
20
|
+
|
21
|
+
* Escape the slash in destroy_model_storage
|
22
|
+
|
23
|
+
=== 0.9.8 / 2008-12-07
|
24
|
+
|
25
|
+
* 1 minor enhancement:
|
26
|
+
|
27
|
+
* Correct parsing of a params hash
|
28
|
+
|
29
|
+
* 3 bug fixes:
|
30
|
+
|
31
|
+
* Correct escaping of view and attachment URLs
|
32
|
+
* couch adapter name no longer mandated
|
33
|
+
* Correct content type now sent
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Kabari Hendrick
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
.gitignore
|
2
|
+
History.txt
|
3
|
+
LICENSE
|
4
|
+
Manifest.txt
|
5
|
+
README.txt
|
6
|
+
Rakefile
|
7
|
+
TODO
|
8
|
+
lib/couchdb_adapter.rb
|
9
|
+
lib/couchdb_adapter/attachments.rb
|
10
|
+
lib/couchdb_adapter/couch_resource.rb
|
11
|
+
lib/couchdb_adapter/json_object.rb
|
12
|
+
lib/couchdb_adapter/version.rb
|
13
|
+
lib/couchdb_adapter/view.rb
|
14
|
+
spec/couchdb_adapter_spec.rb
|
15
|
+
spec/couchdb_attachments_spec.rb
|
16
|
+
spec/couchdb_view_spec.rb
|
17
|
+
spec/spec.opts
|
18
|
+
spec/spec_helper.rb
|
19
|
+
spec/testfile.txt
|
20
|
+
tasks/install.rb
|
21
|
+
tasks/spec.rb
|
data/README.rdoc
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
This is a datamapper adapter to couchdb.
|
2
|
+
|
3
|
+
NOTE: some functionality and their specs are based on functionality that is in
|
4
|
+
edge couch but not in stable. If you want everything to work, use edge.
|
5
|
+
Otherwise, your milage may vary. Good luck and let me know about any bugs.
|
6
|
+
|
7
|
+
== Setup
|
8
|
+
Install with the rest of the dm-more package, using:
|
9
|
+
gem install dm-more
|
10
|
+
|
11
|
+
Setting up:
|
12
|
+
The easiest way is to pass a full url, here is an example:
|
13
|
+
"couchdb://localhost:5984/my_app_development"
|
14
|
+
|
15
|
+
You can break it out like this:
|
16
|
+
"#{adapter}://#{host}:#{port}/#{database}"
|
17
|
+
- adapter should be :couchdb
|
18
|
+
- database (should be the name of your database)
|
19
|
+
- host (probably localhost)
|
20
|
+
- port should be specified (couchdb defaults to port 5984)
|
21
|
+
|
22
|
+
If you haven't you'll need to create this database.
|
23
|
+
The easiest way is with curl in the terminal, like so:
|
24
|
+
'curl -X PUT localhost:5984/my_app_development'
|
25
|
+
You should use the same address here as you did to connect (just leave out the 'couchdb://' part)
|
26
|
+
|
27
|
+
Now, if you want to have a model stored in couch you can just use:
|
28
|
+
include DataMapper::CouchResource
|
29
|
+
instead of the normal:
|
30
|
+
include DataMapper::Resource
|
31
|
+
|
32
|
+
This adds the following reserved properties (which have special meaning in Couch, so don't overwrite them):
|
33
|
+
property :id, String, :key => true, :field => '_id'
|
34
|
+
property :rev, String, :field => '_rev'
|
35
|
+
property :attachments, DataMapper::Types::JsonObject, :field => '_attachments'
|
36
|
+
|
37
|
+
If you want the model to use your couch repository by default, be sure to also add the following(replacing :couch with your repository name):
|
38
|
+
def self.default_repository_name
|
39
|
+
:couch
|
40
|
+
end
|
41
|
+
|
42
|
+
You should now be able to use resources and their properties and have them stored to couchdb.
|
43
|
+
NOTE: 'couchdb_type' is a reserved property, used to map documents to their ruby models.
|
44
|
+
|
45
|
+
== Views
|
46
|
+
Special consideration has been made to work with CouchDB views.
|
47
|
+
You should do ALL queries you'll be repeating this way, doing 'User.all(:something => 'this)' will work, but it is much slower and more inefficient than running views you already created.
|
48
|
+
You define them in the model with the view function and use Model.auto_migrate! to add the views for that Model to the database, or DataMapper.auto_migrate! to add the views for all models to the database.
|
49
|
+
|
50
|
+
An example class with views:
|
51
|
+
|
52
|
+
class User
|
53
|
+
include DataMapper::Resource
|
54
|
+
|
55
|
+
property :name, String
|
56
|
+
view(:by_name_only_this_model) {{ "map" => "function(doc) { if (doc.couchdb_type == 'User') { emit(doc.name, doc); } }" }}
|
57
|
+
view(:by_name_with_descendants) {{ "map" => "function(doc) { if (#{couchdb_types_condition}) { emit(doc.name, doc); } }" }}
|
58
|
+
end
|
59
|
+
|
60
|
+
couchdb_types_condition builds a condition for you if you want a view that checks to see if the couchdb_type of the record is that of the current model or any of its descendants, just load your models and run Model.couchdb_types_condition and copy/paste the output as the condition in the models view. I will be making this smoother/cleaner, as I need to reimplement view handling.
|
61
|
+
|
62
|
+
You could then call User.by_name to get a listing of users ordered by name, or pass a key to try and find a specific user by their name, ie User.by_name(:key => 'username').
|
63
|
+
|
64
|
+
# TODO: add details about other view options
|
65
|
+
|
66
|
+
== Example
|
67
|
+
For a working example of this functionality checkout muddle, my merb based tumblelog, which uses this adapter to save its posts, at:
|
68
|
+
http://github.com/geemus/muddle
|
data/Rakefile
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'rubygems'
|
3
|
+
require "rake"
|
4
|
+
|
5
|
+
ROOT = Pathname(__FILE__).dirname.expand_path
|
6
|
+
JRUBY = RUBY_PLATFORM =~ /java/
|
7
|
+
WINDOWS = Gem.win_platform?
|
8
|
+
SUDO = (WINDOWS || JRUBY) ? '' : ('sudo' unless ENV['SUDOLESS'])
|
9
|
+
|
10
|
+
require ROOT + 'lib/couchdb_adapter/version'
|
11
|
+
|
12
|
+
GEM_NAME = 'dm-couchdb-adapter'
|
13
|
+
GEM_VERSION = DataMapper::CouchDBAdapter::VERSION
|
14
|
+
GEM_DEPENDENCIES = [['dm-core', "~>#{GEM_VERSION}"], ['mime-types', '~>1.15']]
|
15
|
+
GEM_CLEAN = %w[ log pkg coverage ]
|
16
|
+
GEM_EXTRAS = { :has_rdoc => true, :extra_rdoc_files => %w[ README.txt LICENSE TODO History.txt ] }
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
begin
|
21
|
+
require 'jeweler'
|
22
|
+
Jeweler::Tasks.new do |gem|
|
23
|
+
gem.name = GEM_NAME
|
24
|
+
gem.summary = %Q{CouchDB Adapter for DataMapper}
|
25
|
+
gem.email = 'kabari [a] gmail [d] com'
|
26
|
+
gem.homepage = "http://github.com/kabari/#{GEM_NAME}/tree/master"
|
27
|
+
gem.authors = ["Kabari Hendrick"]
|
28
|
+
# gem is a Gem::Specification... see
|
29
|
+
# for additional settings
|
30
|
+
gem.required_ruby_version = '>= 1.8.6'
|
31
|
+
gem.add_dependency("extlib", ">= 0.9.11")
|
32
|
+
gem.add_dependency('mime-types', '~>1.15')
|
33
|
+
end
|
34
|
+
Jeweler::GemcutterTasks.new
|
35
|
+
rescue LoadError
|
36
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
37
|
+
end
|
38
|
+
|
39
|
+
require 'spec/rake/spectask'
|
40
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
41
|
+
spec.libs << 'lib' << 'spec'
|
42
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
43
|
+
spec.fail_on_error = false
|
44
|
+
end
|
45
|
+
|
46
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
47
|
+
spec.libs << 'lib' << 'spec'
|
48
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
49
|
+
spec.rcov = true
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
task :default => :spec
|
54
|
+
|
55
|
+
require 'rake/rdoctask'
|
56
|
+
Rake::RDocTask.new do |rdoc|
|
57
|
+
rdoc.rdoc_dir = 'rdoc'
|
58
|
+
rdoc.title = "#{GEM_NAME} #{GEM_VERSION}"
|
59
|
+
rdoc.rdoc_files.include('README*')
|
60
|
+
rdoc.rdoc_files.include("LICENSE")
|
61
|
+
rdoc.rdoc_files.include("TODO")
|
62
|
+
rdoc.rdoc_files.include("History.txt")
|
63
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
64
|
+
end
|
65
|
+
|
data/TODO
ADDED
File without changes
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.10.2
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Adapters
|
3
|
+
class CouchDBAdapter < AbstractAdapter
|
4
|
+
ConnectionError = Class.new(StandardError)
|
5
|
+
|
6
|
+
# Persists one or many new resources
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# adapter.create(collection) # => 1
|
10
|
+
#
|
11
|
+
# Adapters provide specific implementation of this method
|
12
|
+
#
|
13
|
+
# @param [Enumerable<Resource>] resources
|
14
|
+
# The list of resources (model instances) to create
|
15
|
+
#
|
16
|
+
# @return [Integer]
|
17
|
+
# The number of records that were actually saved into the data-store
|
18
|
+
#
|
19
|
+
# @api semipublic
|
20
|
+
def create(resources)
|
21
|
+
raise NotImplementedError, "#{self.class}#create not implemented"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Reads one or many resources from a datastore
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# adapter.read(query) # => [ { 'name' => 'Dan Kubb' } ]
|
28
|
+
#
|
29
|
+
# Adapters provide specific implementation of this method
|
30
|
+
#
|
31
|
+
# @param [Query] query
|
32
|
+
# the query to match resources in the datastore
|
33
|
+
#
|
34
|
+
# @return [Enumerable<Hash>]
|
35
|
+
# an array of hashes to become resources
|
36
|
+
#
|
37
|
+
# @api semipublic
|
38
|
+
def read(query)
|
39
|
+
with_connection do |connection|
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Updates one or many existing resources
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# adapter.update(attributes, collection) # => 1
|
48
|
+
#
|
49
|
+
# Adapters provide specific implementation of this method
|
50
|
+
#
|
51
|
+
# @param [Hash(Property => Object)] attributes
|
52
|
+
# hash of attribute values to set, keyed by Property
|
53
|
+
# @param [Collection] collection
|
54
|
+
# collection of records to be updated
|
55
|
+
#
|
56
|
+
# @return [Integer]
|
57
|
+
# the number of records updated
|
58
|
+
#
|
59
|
+
# @api semipublic
|
60
|
+
def update(attributes, collection)
|
61
|
+
raise NotImplementedError, "#{self.class}#update not implemented"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Deletes one or many existing resources
|
65
|
+
#
|
66
|
+
# @example
|
67
|
+
# adapter.delete(collection) # => 1
|
68
|
+
#
|
69
|
+
# Adapters provide specific implementation of this method
|
70
|
+
#
|
71
|
+
# @param [Collection] collection
|
72
|
+
# collection of records to be deleted
|
73
|
+
#
|
74
|
+
# @return [Integer]
|
75
|
+
# the number of records deleted
|
76
|
+
#
|
77
|
+
# @api semipublic
|
78
|
+
def delete(collection)
|
79
|
+
raise NotImplementedError, "#{self.class}#delete not implemented"
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the name of the CouchDB database.
|
83
|
+
#
|
84
|
+
# @raise [RuntimeError] if the CouchDB database name is invalid.
|
85
|
+
def db_name
|
86
|
+
result = options[:path].scan(/^\/?([-_+%()$a-z0-9]+?)\/?$/).flatten[0]
|
87
|
+
if result != nil
|
88
|
+
return Addressable::URI.unencode_component(result)
|
89
|
+
else
|
90
|
+
raise StandardError, "Invalid database path: '#{options[:path]}'"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the name of the CouchDB database after being escaped.
|
95
|
+
def escaped_db_name
|
96
|
+
return Addressable::URI.encode_component(
|
97
|
+
self.db_name, Addressable::URI::CharacterClasses::UNRESERVED)
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def initialize(repo_name, options = {})
|
104
|
+
super
|
105
|
+
|
106
|
+
# When giving a repository URI rather than a hash, the database name
|
107
|
+
# is :path, with a leading slash.
|
108
|
+
if options[:path] && options[:database].nil?
|
109
|
+
options[:database] = db_name
|
110
|
+
end
|
111
|
+
|
112
|
+
@resource_naming_convention = NamingConventions::Resource::Underscored
|
113
|
+
@uri = Addressable::URI.new(options.only(:scheme, :host, :path, :port))
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
# Returns the CouchRest::Database instance for this process.
|
118
|
+
#
|
119
|
+
# @return [CouchRest::Database]
|
120
|
+
#
|
121
|
+
# @raise [ConnectionError]
|
122
|
+
# If the database requires you to authenticate, and the given username
|
123
|
+
# or password was not correct, a ConnectionError exception will be
|
124
|
+
# raised.
|
125
|
+
#
|
126
|
+
# @api semipublic
|
127
|
+
def database
|
128
|
+
unless defined?(@database)
|
129
|
+
@database = connection.database!(@options[:database])
|
130
|
+
end
|
131
|
+
@database
|
132
|
+
rescue Errno::ECONNREFUSED
|
133
|
+
DataMapper.logger.error("Could Not Connect to Database!")
|
134
|
+
raise(ConnectionError, "The adapter could not connect to Couchdb running at '#{@uri}'")
|
135
|
+
end
|
136
|
+
|
137
|
+
def with_connection
|
138
|
+
begin
|
139
|
+
yield connection
|
140
|
+
rescue => e
|
141
|
+
DataMapper.logger.error(exception.to_s)
|
142
|
+
raise e
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# @see #connection
|
147
|
+
def connection
|
148
|
+
@connection ||= open_connection
|
149
|
+
end
|
150
|
+
|
151
|
+
# Returns CouchRest::Server instance
|
152
|
+
# @return [CouchRest::Server]
|
153
|
+
# @todo reset! connection and allow #uuid_batch_count to change
|
154
|
+
# also....do I need to use #chainable for this?
|
155
|
+
# @api semipublic
|
156
|
+
def open_connection
|
157
|
+
CouchRest::Server.new(@uri)
|
158
|
+
end
|
159
|
+
end # CouchDBAdapter
|
160
|
+
|
161
|
+
# Required naming scheme.
|
162
|
+
CouchdbAdapter = CouchDBAdapter
|
163
|
+
const_added(:CouchdbAdapter)
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'net/http'
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
gem 'mime-types', '~>1.15'
|
6
|
+
require 'mime/types'
|
7
|
+
|
8
|
+
module DataMapper
|
9
|
+
module CouchResource
|
10
|
+
module Attachments
|
11
|
+
|
12
|
+
def self.included(mod)
|
13
|
+
mod.class_eval do
|
14
|
+
|
15
|
+
def add_attachment(file, options = {})
|
16
|
+
assert_attachments_property
|
17
|
+
|
18
|
+
filename = File.basename(file.path)
|
19
|
+
|
20
|
+
content_type = options[:content_type] || begin
|
21
|
+
mime_types = MIME::Types.of(filename)
|
22
|
+
mime_types.empty? ? 'application/octet-stream' : mime_types.first.content_type
|
23
|
+
end
|
24
|
+
|
25
|
+
name = options[:name] || filename
|
26
|
+
data = file.read
|
27
|
+
|
28
|
+
if new_record? || !model.properties.has_property?(:rev)
|
29
|
+
self.attachments ||= {}
|
30
|
+
self.attachments[name] = {
|
31
|
+
'content_type' => content_type,
|
32
|
+
'data' => Base64.encode64(data).chomp,
|
33
|
+
}
|
34
|
+
else
|
35
|
+
adapter = repository.adapter
|
36
|
+
http = Net::HTTP.new(adapter.uri.host, adapter.uri.port)
|
37
|
+
uri = Addressable::URI.encode_component("#{attachment_path(name)}?rev=#{self.rev}")
|
38
|
+
headers = {
|
39
|
+
'Content-Length' => data.size.to_s,
|
40
|
+
'Content-Type' => content_type,
|
41
|
+
}
|
42
|
+
http.put(uri, data, headers)
|
43
|
+
self.reload
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete_attachment(name)
|
49
|
+
assert_attachments_property
|
50
|
+
|
51
|
+
attachment = self.attachments[name] if self.attachments
|
52
|
+
|
53
|
+
unless attachment
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
|
57
|
+
response = unless new_record?
|
58
|
+
adapter = repository.adapter
|
59
|
+
http = Net::HTTP.new(adapter.uri.host, adapter.uri.port)
|
60
|
+
uri = Addressable::URI.encode_component("#{attachment_path(name)}?rev=#{self.rev}")
|
61
|
+
http.delete(uri, 'Content-Type' => attachment['content_type'])
|
62
|
+
end
|
63
|
+
|
64
|
+
if response && !response.kind_of?(Net::HTTPSuccess)
|
65
|
+
false
|
66
|
+
else
|
67
|
+
self.attachments.delete(name)
|
68
|
+
self.attachments = nil if self.attachments.empty?
|
69
|
+
true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# TODO: cache data on model? (don't want to make resource dirty though...)
|
74
|
+
def get_attachment(name)
|
75
|
+
assert_attachments_property
|
76
|
+
|
77
|
+
attachment = self.attachments[name] if self.attachments
|
78
|
+
|
79
|
+
unless self.id && attachment
|
80
|
+
nil
|
81
|
+
else
|
82
|
+
adapter = repository.adapter
|
83
|
+
http = Net::HTTP.new(adapter.uri.host, adapter.uri.port)
|
84
|
+
uri = Addressable::URI.encode_component(attachment_path(name))
|
85
|
+
response, data = http.get(uri, 'Content-Type' => attachment['content_type'])
|
86
|
+
|
87
|
+
unless response.kind_of?(Net::HTTPSuccess)
|
88
|
+
nil
|
89
|
+
else
|
90
|
+
data
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def attachment_path(name)
|
99
|
+
if new_record?
|
100
|
+
nil
|
101
|
+
else
|
102
|
+
"/#{repository.adapter.escaped_db_name}/#{self.id}/#{name}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def assert_attachments_property
|
107
|
+
property = model.properties[:attachments]
|
108
|
+
|
109
|
+
unless property &&
|
110
|
+
property.type == DataMapper::Types::JsonObject &&
|
111
|
+
property.field == '_attachments'
|
112
|
+
raise ArgumentError, "Attachments require property :attachments, JsonObject, :field => '_attachments'"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|