dm-couchdb-adapter 0.10.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/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
|