encoded_attachment 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +107 -0
- data/lib/activerecord/base.rb +63 -0
- data/lib/activeresource/base.rb +118 -0
- data/lib/activeresource/connection.rb +8 -0
- data/lib/encoded_attachment/version.rb +3 -0
- data/lib/encoded_attachment.rb +41 -0
- metadata +89 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Nick Ragaz
|
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/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
EncodedAttachment
|
2
|
+
=================
|
3
|
+
|
4
|
+
This is the bestest and certainly the easiest way to handle file uploads/downloads to [Paperclip](http://github.com/thoughtbot/paperclip)-using Active Record-backed resources using Active Resource.
|
5
|
+
|
6
|
+
Rather than trying to create a multipart form submission, it just embeds the file's binary data in the Active Record model's <tt>to_xml</tt>. You can also embed binary data into XML to POST or PUT using Active Resource. These tags will automatically be parsed by Active Record and Active Resource to create files.
|
7
|
+
|
8
|
+
|
9
|
+
Usage
|
10
|
+
-----
|
11
|
+
|
12
|
+
In a Rails application:
|
13
|
+
|
14
|
+
# Gemfile
|
15
|
+
gem "encoded_attachment", :git => "git://github.com/nragaz/encoded_attachment"
|
16
|
+
|
17
|
+
This will load the class methods into both ActiveRecord and ActiveResource. Nothing will really "happen" unless you use the methods described below in your models.
|
18
|
+
|
19
|
+
Note that the ActiveRecord code is designed to be used with [Paperclip](http://github.com/thoughtbot/paperclip).
|
20
|
+
|
21
|
+
Outside of Rails, the gem can be required directly. It will load itself into ActiveRecord::Base and/or ActiveResource::Base if they have already been loaded. (You can manually include the needed methods using <tt>EncodedAttachment.setup_activerecord</tt> and <tt>EncodedAttachment.setup_activeresource</tt> if you really want to require this gem first.)
|
22
|
+
|
23
|
+
|
24
|
+
Functionality
|
25
|
+
=============
|
26
|
+
|
27
|
+
Active Record
|
28
|
+
-------------
|
29
|
+
|
30
|
+
Adds a class method called <tt>encode_attachment_in_xml</tt> to Active Record that can be used alongside Paperclip's <tt>has_attached_file</tt> to automatically generate useful and usable binary XML tags for the attachment's original file by wrapping <tt>to_xml</tt>.
|
31
|
+
|
32
|
+
These tags will not be generated on new or destroyed records (because the Paperclip file needs to be saved to disk before it is encoded). You can disable file generation at any time using <tt>to_xml(:include_attachments => false)</tt>.
|
33
|
+
|
34
|
+
Note that by default, the XML will *not* include Paperclip attributes such as <tt>attachment_file_name</tt>, <tt>attachment_file_size</tt>, <tt>attachment_content_type</tt> and <tt>attachment_updated_at</tt>. The file name and content type are in the XML tag as attributes. Using <tt>to_xml(:include_attachments => false)</tt> will restore these attributes to your XML.
|
35
|
+
|
36
|
+
|
37
|
+
Active Resource
|
38
|
+
---------------
|
39
|
+
|
40
|
+
Adds a class method called <tt>has_encoded_attachment</tt> to Active Resource that generates a schema for the file's attributes and then embeds the file's binary content in <tt>to_xml</tt> if the file has been changed or the record is new.
|
41
|
+
|
42
|
+
You can force embedding using <tt>to_xml(:include_attachments => true)</tt>. File setters include <tt>file=</tt> and <tt>file_path=</tt>. MIME types are detected based on the file name.
|
43
|
+
|
44
|
+
|
45
|
+
Downloading Files using URLs
|
46
|
+
----------------------------
|
47
|
+
|
48
|
+
To avoid transmitting huge XML files (particularly in index actions), you can choose to have Active Record send the URL of the file instead of the encoded path using:
|
49
|
+
|
50
|
+
encode_attachment_in_xml :attachment_name, :send_urls => true, :root_url => "http://yourdomain"
|
51
|
+
|
52
|
+
<tt>:root_url</tt> is optional.
|
53
|
+
|
54
|
+
You can force the file's binary data to be embedded using <tt>to_xml(:encode_attachments => true)</tt>.
|
55
|
+
|
56
|
+
The URL will be downloaded separately by the Active Resource object using the same protocol settings as its native connection (e.g. authentication will be preserved).
|
57
|
+
|
58
|
+
There are some limitations to this: your file URL must be on the same domain as the resource's base URL, and the URL must include the file name and extension (e.g. "/images/*rails.png*") for MIME type and file name detection.
|
59
|
+
|
60
|
+
There is no support for Active Resource submitting a file URL back to Active Record. Using the <tt>file=</tt> or <tt>file_path=</tt> methods on your Active Resource object will obliterate the <tt>file_url</tt> attribute so that it doesn't appear in your PUT.
|
61
|
+
|
62
|
+
Potential use cases include:
|
63
|
+
|
64
|
+
class MyModel < ActiveRecord::Base
|
65
|
+
encode_attachment_in_xml :my_file, :send_urls => true, :root_url => "http://yourdomain"
|
66
|
+
end
|
67
|
+
|
68
|
+
class MyModelsController < ActionController::Base
|
69
|
+
def index
|
70
|
+
MyModel.all.to_xml # sends URLs
|
71
|
+
end
|
72
|
+
|
73
|
+
def show
|
74
|
+
MyModel.find(params[:id]).to_xml(:encode_attachments => true) # sends encoded files
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
Example
|
80
|
+
=======
|
81
|
+
|
82
|
+
In Active Record:
|
83
|
+
|
84
|
+
class MyModel < ActiveRecord::Base
|
85
|
+
has_attached_file :pdf
|
86
|
+
encode_attachment_in_xml :pdf
|
87
|
+
end
|
88
|
+
|
89
|
+
my_model = MyModel.create(:pdf => File.open('example.pdf'))
|
90
|
+
my_model.to_xml => '<my-model>\n<pdf type="file" name="example.pdf" content-type="application/pdf">[binary data]</pdf>\n</my-model>'
|
91
|
+
|
92
|
+
|
93
|
+
In Active Resource:
|
94
|
+
|
95
|
+
class MyModelResource < ActiveResource::Base
|
96
|
+
self.element_name = "my_model"
|
97
|
+
|
98
|
+
has_encoded_attachment :pdf
|
99
|
+
end
|
100
|
+
|
101
|
+
my_model_resource = MyModelResource.new.from_xml(my_model.to_xml)
|
102
|
+
my_model_resource.pdf # => <StringIO> containing binary data
|
103
|
+
my_model_resource.save_pdf_as("my_downloaded_file.pdf")
|
104
|
+
|
105
|
+
my_model_resource.pdf = File.open('example-downloaded.pdf')
|
106
|
+
my_model_resource.pdf_file_name # => "example_downloaded.pdf"
|
107
|
+
my_model_resource.pdf_content_type # => "application/pdf"
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module EncodedAttachment
|
2
|
+
module ActiveRecordClassMethods
|
3
|
+
def encode_attachment_in_xml(name, attachment_options={})
|
4
|
+
attachment_options[:send_urls] = false unless attachment_options[:send_urls]
|
5
|
+
|
6
|
+
@_attachment_handling ||= {}
|
7
|
+
@_attachment_handling[name] = {}
|
8
|
+
@_attachment_handling[name][:send_urls] = attachment_options[:send_urls]
|
9
|
+
@_attachment_handling[name][:root_url] = attachment_options[:root_url] || nil
|
10
|
+
|
11
|
+
if attachment_options[:send_urls]
|
12
|
+
# Placeholder method to avoid MethodMissing exceptions on Model.from_xml(Model.to_xml)
|
13
|
+
define_method "#{name}_url=" do |file_url|
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
define_method "to_xml_with_encoded_#{name}" do |*args|
|
19
|
+
# You can exclude file tags completely by using :include_files => false
|
20
|
+
# If :send_urls => true, force file encoding using :encode => true
|
21
|
+
options, block = args
|
22
|
+
|
23
|
+
options ||= {}
|
24
|
+
options[:include_attachments] = true unless options.has_key?(:include_attachments)
|
25
|
+
options[:encode_attachments] = false unless options.has_key?(:encode_attachments)
|
26
|
+
options[:procs] ||= []
|
27
|
+
if options[:include_attachments]
|
28
|
+
# strip Paperclip methods
|
29
|
+
options[:except] ||= []
|
30
|
+
options[:except] = (options[:except] + [:"#{name}_file_name", :"#{name}_file_size",
|
31
|
+
:"#{name}_content_type", :"#{name}_updated_at"]).uniq
|
32
|
+
|
33
|
+
# get URL handling variables if :send_urls => true
|
34
|
+
send_urls = send(:class).instance_variable_get("@_attachment_handling")[name][:send_urls]
|
35
|
+
root_url = send(:class).instance_variable_get("@_attachment_handling")[name][:root_url] if send_urls
|
36
|
+
|
37
|
+
options[:procs] << Proc.new { |options, record|
|
38
|
+
file_options = { :type => 'file'}
|
39
|
+
if !(new_record? || frozen?) && send(name).file? && (!(send_urls) || options[:encode_attachments])
|
40
|
+
file_options.merge! :name => send("#{name}_file_name"), :"content-type" => send("#{name}_content_type")
|
41
|
+
options[:builder].tag!(name, file_options) { options[:builder].cdata! EncodedAttachment.encode(send(name)) }
|
42
|
+
elsif !(new_record? || frozen?) && send(name).file? && send_urls
|
43
|
+
file_options.merge! :type => :string
|
44
|
+
url = root_url ? URI.join(root_url, send(name).url(:original, false)) : send(name).url(:original, false)
|
45
|
+
options[:builder].tag! "#{name}_url", url, file_options
|
46
|
+
elsif send_urls && (new_record? || frozen? || !(send(name).file?))
|
47
|
+
file_options.merge! :type => :string, :nil => true
|
48
|
+
options[:builder].tag! "#{name}_url", nil, file_options
|
49
|
+
else
|
50
|
+
# the file can't be included if the record is not persisted yet, because of how Paperclip works
|
51
|
+
file_options.merge! :nil => true
|
52
|
+
options[:builder].tag! name, "", file_options
|
53
|
+
end
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
send("to_xml_without_encoded_#{name}", options, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
alias_method_chain :to_xml, :"encoded_#{name}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module EncodedAttachment
|
2
|
+
module ActiveResourceClassMethods
|
3
|
+
def has_encoded_attachment(name)
|
4
|
+
schema do
|
5
|
+
string "#{name}_file_name", "#{name}_content_type"
|
6
|
+
integer "#{name}_file_size"
|
7
|
+
attribute "#{name}_updated_at", "string"
|
8
|
+
attribute name, "string"
|
9
|
+
end
|
10
|
+
|
11
|
+
define_method "#{name}_updated_at" do
|
12
|
+
Time.parse(attributes["#{name}_updated_at"]) if attributes["#{name}_updated_at"].is_a?(String)
|
13
|
+
end
|
14
|
+
|
15
|
+
define_method "to_xml_with_encoded_#{name}" do |*args|
|
16
|
+
# Normally, the file's XML is only included if the file has been changed in the resource
|
17
|
+
# using file= or file_path= or file_url=
|
18
|
+
# You can force file tag generation (i.e. even if the file has not changed) by using to_xml(:include_files => true)
|
19
|
+
options, block = args
|
20
|
+
|
21
|
+
options ||= {}
|
22
|
+
options[:except] ||= []
|
23
|
+
options[:except] = (options[:except] + [:"#{name}", :"#{name}_updated_at", :"#{name}_file_size"]).uniq
|
24
|
+
options[:except] = (options[:except] + [:"#{name}_file_name", :"#{name}_content_type"]).uniq unless send("#{name}_changed?")
|
25
|
+
options[:procs] ||= []
|
26
|
+
|
27
|
+
options[:procs] << Proc.new { |options, record|
|
28
|
+
file_options = { :type => 'file'}
|
29
|
+
if send("#{name}_changed?") || options[:include_files] || (new_record? && !(send("#{name}").nil?))
|
30
|
+
file_options.merge! :name => send("#{name}_file_name"), :"content-type" => send("#{name}_content_type")
|
31
|
+
options[:builder].tag!(name, file_options) { options[:builder].cdata! EncodedAttachment.encode_io(send(name)) }
|
32
|
+
elsif send("#{name}_changed?") || options[:include_files]
|
33
|
+
file_options.merge! :nil => true
|
34
|
+
options[:builder].tag! name, "", file_options
|
35
|
+
end
|
36
|
+
}
|
37
|
+
|
38
|
+
send "to_xml_without_encoded_#{name}", options, &block
|
39
|
+
end
|
40
|
+
alias_method_chain :to_xml, :"encoded_#{name}"
|
41
|
+
|
42
|
+
define_method "load_with_attached_#{name}" do |attrs|
|
43
|
+
attrs = attrs.stringify_keys
|
44
|
+
if attrs.has_key?("#{name}")
|
45
|
+
send "#{name}=", attrs.delete("#{name}"), @attributes.has_key?(name)
|
46
|
+
elsif attrs.has_key?("#{name}_url")
|
47
|
+
send "#{name}_url=", attrs.delete("#{name}_url"), @attributes.has_key?(name)
|
48
|
+
end
|
49
|
+
send "load_without_attached_#{name}", attrs
|
50
|
+
end
|
51
|
+
alias_method_chain :load, :"attached_#{name}"
|
52
|
+
|
53
|
+
# Prevents someone from assigning the attachment attributes directly and skipping the handling methods
|
54
|
+
define_method "attributes=" do |attrs|
|
55
|
+
send :load, attrs
|
56
|
+
end
|
57
|
+
|
58
|
+
define_method "#{name}_changed=" do |bool|
|
59
|
+
instance_variable_set("@#{name}_changed", bool)
|
60
|
+
end
|
61
|
+
|
62
|
+
define_method "#{name}_changed?" do
|
63
|
+
instance_variable_get("@#{name}_changed") || false
|
64
|
+
end
|
65
|
+
|
66
|
+
define_method "#{name}_path=" do |file_path|
|
67
|
+
send "#{name}=", File.open(file_path)
|
68
|
+
send "#{name}_file_name=", File.basename(file_path)
|
69
|
+
send "#{name}_content_type=", MIME::Types.type_for(File.basename(file_path)).first.content_type
|
70
|
+
end
|
71
|
+
|
72
|
+
define_method "#{name}_url=" do |*args|
|
73
|
+
file_url, changed = args
|
74
|
+
changed = (changed.nil? || changed) ? true : false
|
75
|
+
if file_url
|
76
|
+
url = URI.parse(file_url.to_s)
|
77
|
+
send "#{name}=", StringIO.new(connection.get_attachment(url.path,
|
78
|
+
'Accept' => send("#{name}_content_type")).body), changed
|
79
|
+
send "#{name}_file_name=", File.basename(url.path)
|
80
|
+
send "#{name}_content_type=", MIME::Types.type_for(File.basename(url.path)).first.content_type
|
81
|
+
else
|
82
|
+
send "#{name}=", nil, changed
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
define_method "#{name}=" do |*args|
|
87
|
+
io, changed = args
|
88
|
+
changed = (changed.nil? || changed) ? true : false
|
89
|
+
attributes[name] = io
|
90
|
+
if io.respond_to?(:original_filename)
|
91
|
+
send "#{name}_file_name=", io.original_filename
|
92
|
+
send "#{name}_content_type=", MIME::Types.type_for(io.original_filename).first.content_type
|
93
|
+
elsif io.nil?
|
94
|
+
send "#{name}_file_name=", nil
|
95
|
+
send "#{name}_content_type=", nil
|
96
|
+
end
|
97
|
+
attributes.delete "#{name}_url"
|
98
|
+
attributes.delete "#{name}_file_size"
|
99
|
+
attributes.delete "#{name}_updated_at"
|
100
|
+
send "#{name}_changed=", changed
|
101
|
+
end
|
102
|
+
|
103
|
+
define_method "save_#{name}_as" do |*args|
|
104
|
+
raise "File not set - cannot be saved" if attributes[name].nil? || !(attributes[name].respond_to?(:read))
|
105
|
+
path, overwrite = args
|
106
|
+
overwrite = true if overwrite.nil?
|
107
|
+
unless !(overwrite) && File.exist?(path)
|
108
|
+
send(name).pos = 0
|
109
|
+
File.open(path, 'w') { |f| f << send(name).read }
|
110
|
+
return true
|
111
|
+
else
|
112
|
+
raise "File not saved - file already exists at #{path}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module EncodedAttachment
|
4
|
+
class << self
|
5
|
+
def encode(attachment, style = :original)
|
6
|
+
encode_io( File.open(attachment.path(style)) )
|
7
|
+
end
|
8
|
+
|
9
|
+
def encode_io(io)
|
10
|
+
io.pos = 0
|
11
|
+
Base64.encode64(io.read)
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup_activerecord
|
15
|
+
require File.dirname(__FILE__) + '/activerecord/base'
|
16
|
+
ActiveRecord::Base.extend ActiveRecordClassMethods
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup_activeresource
|
20
|
+
require File.dirname(__FILE__) + '/activeresource/base'
|
21
|
+
require File.dirname(__FILE__) + '/activeresource/connection'
|
22
|
+
ActiveResource::Base.extend ActiveResourceClassMethods
|
23
|
+
ActiveResource::Connection.send :include, ActiveResourceConnectionMethods
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Initialization
|
29
|
+
if defined?(Rails::Railtie)
|
30
|
+
ActiveSupport.on_load(:active_record) do
|
31
|
+
EncodedAttachment.setup_activerecord
|
32
|
+
end
|
33
|
+
|
34
|
+
ActiveSupport.on_load(:active_resource) do
|
35
|
+
EncodedAttachment.setup_activeresource
|
36
|
+
end
|
37
|
+
else
|
38
|
+
# Load right away if required outside of Rails initialization
|
39
|
+
EncodedAttachment.setup_activerecord if defined?(ActiveRecord)
|
40
|
+
EncodedAttachment.setup_activeresource if defined?(ActiveResource)
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: encoded_attachment
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 19
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 4
|
10
|
+
version: 0.1.4
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Nick Ragaz
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-06-06 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: mime-types
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
description: Adds methods to ActiveRecord::Base and ActiveResource::Base to transmit file attachments via REST, either as binary tags in XML or via a separate URL
|
36
|
+
email:
|
37
|
+
- nick.ragaz@gmail.com
|
38
|
+
executables: []
|
39
|
+
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files: []
|
43
|
+
|
44
|
+
files:
|
45
|
+
- lib/activerecord/base.rb
|
46
|
+
- lib/activeresource/base.rb
|
47
|
+
- lib/activeresource/connection.rb
|
48
|
+
- lib/encoded_attachment/version.rb
|
49
|
+
- lib/encoded_attachment.rb
|
50
|
+
- LICENSE
|
51
|
+
- README.md
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://github.com/nragaz/encoded_attachment
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
hash: 3
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 23
|
76
|
+
segments:
|
77
|
+
- 1
|
78
|
+
- 3
|
79
|
+
- 6
|
80
|
+
version: 1.3.6
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project: encoded_attachment
|
84
|
+
rubygems_version: 1.3.7
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Handles downloading and uploading Paperclip attachments using Active Record and Active Resource
|
88
|
+
test_files: []
|
89
|
+
|