encoded_attachment 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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,8 @@
1
+ module EncodedAttachment
2
+ module ActiveResourceConnectionMethods
3
+ def get_attachment(path, headers = {})
4
+ request_headers = build_request_headers(headers, :get, self.site.merge(path))
5
+ with_auth { request(:get, path, request_headers) }
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module EncodedAttachment
2
+ VERSION = "0.1.4"
3
+ 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
+