attachment_magic 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/attachment_magic.gemspec +22 -0
- data/lib/attachment_magic.rb +299 -0
- data/lib/attachment_magic/backends/file_system_backend.rb +122 -0
- data/lib/attachment_magic/version.rb +3 -0
- metadata +112 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "attachment_magic/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "attachment_magic"
|
7
|
+
s.version = AttachmentMagic::VERSION
|
8
|
+
s.authors = ["Thomas von Deyen"]
|
9
|
+
s.email = ["tvdeyen@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/magiclabs/attachment_magic"
|
11
|
+
s.summary = %q{A simple file attachment gem for Rails 3}
|
12
|
+
s.description = %q{A Rails 3 Gem based on attachment_fu, but without the image processing fudge and multiple backend crap! Just simple file attachments with a little mime type magic ;)}
|
13
|
+
|
14
|
+
s.rubyforge_project = "attachment_magic"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
s.add_runtime_dependency(%q<rails>, ["< 3.1", ">= 3.0.7"])
|
21
|
+
s.add_runtime_dependency(%q<mimetype-fu>, ["~> 0.1.2"])
|
22
|
+
end
|
@@ -0,0 +1,299 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require 'mimetype_fu'
|
3
|
+
require "attachment_magic/version"
|
4
|
+
require "attachment_magic/backends/file_system_backend"
|
5
|
+
|
6
|
+
module AttachmentMagic
|
7
|
+
@@content_types = [
|
8
|
+
'image/jpeg',
|
9
|
+
'image/pjpeg',
|
10
|
+
'image/jpg',
|
11
|
+
'image/gif',
|
12
|
+
'image/png',
|
13
|
+
'image/x-png',
|
14
|
+
'image/jpg',
|
15
|
+
'image/x-ms-bmp',
|
16
|
+
'image/bmp',
|
17
|
+
'image/x-bmp',
|
18
|
+
'image/x-bitmap',
|
19
|
+
'image/x-xbitmap',
|
20
|
+
'image/x-win-bitmap',
|
21
|
+
'image/x-windows-bmp',
|
22
|
+
'image/ms-bmp',
|
23
|
+
'application/bmp',
|
24
|
+
'application/x-bmp',
|
25
|
+
'application/x-win-bitmap',
|
26
|
+
'application/preview',
|
27
|
+
'image/jp_',
|
28
|
+
'application/jpg',
|
29
|
+
'application/x-jpg',
|
30
|
+
'image/pipeg',
|
31
|
+
'image/vnd.swiftview-jpeg',
|
32
|
+
'image/x-xbitmap',
|
33
|
+
'application/png',
|
34
|
+
'application/x-png',
|
35
|
+
'image/gi_',
|
36
|
+
'image/x-citrix-pjpeg'
|
37
|
+
]
|
38
|
+
mattr_reader :content_types, :tempfile_path
|
39
|
+
mattr_writer :tempfile_path
|
40
|
+
|
41
|
+
class ThumbnailError < StandardError; end
|
42
|
+
class AttachmentError < StandardError; end
|
43
|
+
|
44
|
+
def self.tempfile_path
|
45
|
+
@@tempfile_path ||= Rails.root.join('tmp', 'attachment_magic')
|
46
|
+
end
|
47
|
+
|
48
|
+
module ActMethods
|
49
|
+
# Options:
|
50
|
+
# * <tt>:content_type</tt> - Allowed content types. Allows all by default. Use :image to allow all standard image types.
|
51
|
+
# * <tt>:min_size</tt> - Minimum size allowed. 1 byte is the default.
|
52
|
+
# * <tt>:max_size</tt> - Maximum size allowed. 1.megabyte is the default.
|
53
|
+
# * <tt>:size</tt> - Range of sizes allowed. (1..1.megabyte) is the default. This overrides the :min_size and :max_size options.
|
54
|
+
# * <tt>:path_prefix</tt> - path to store the uploaded files. Uses public/#{table_name} by default.
|
55
|
+
# * <tt>:storage</tt> - Use :file_system to specify the attachment data is stored with the file system. Defaults to :file_system.
|
56
|
+
#
|
57
|
+
# Examples:
|
58
|
+
# has_attachment :max_size => 1.kilobyte
|
59
|
+
# has_attachment :size => 1.megabyte..2.megabytes
|
60
|
+
# has_attachment :content_type => 'application/pdf'
|
61
|
+
# has_attachment :content_type => ['application/pdf', 'application/msword', 'text/plain']
|
62
|
+
def has_attachment(options = {})
|
63
|
+
# this allows you to redefine the acts' options for each subclass, however
|
64
|
+
options[:min_size] ||= 1
|
65
|
+
options[:max_size] ||= 1.megabyte
|
66
|
+
options[:size] ||= (options[:min_size]..options[:max_size])
|
67
|
+
options[:content_type] = [options[:content_type]].flatten.collect! { |t| t == :image ? AttachmentMagic.content_types : t }.flatten unless options[:content_type].nil?
|
68
|
+
|
69
|
+
extend ClassMethods unless (class << self; included_modules; end).include?(ClassMethods)
|
70
|
+
include InstanceMethods unless included_modules.include?(InstanceMethods)
|
71
|
+
|
72
|
+
parent_options = attachment_options || {}
|
73
|
+
# doing these shenanigans so that #attachment_options is available to processors and backends
|
74
|
+
self.attachment_options = options
|
75
|
+
|
76
|
+
attachment_options[:storage] ||= :file_system
|
77
|
+
attachment_options[:storage] ||= parent_options[:storage]
|
78
|
+
attachment_options[:path_prefix] ||= attachment_options[:file_system_path]
|
79
|
+
if attachment_options[:path_prefix].nil?
|
80
|
+
File.join("public", table_name)
|
81
|
+
end
|
82
|
+
attachment_options[:path_prefix] = attachment_options[:path_prefix][1..-1] if options[:path_prefix].first == '/'
|
83
|
+
|
84
|
+
unless File.directory?(AttachmentMagic.tempfile_path)
|
85
|
+
FileUtils.mkdir_p(AttachmentMagic.tempfile_path)
|
86
|
+
end
|
87
|
+
|
88
|
+
storage_mod = AttachmentMagic::Backends.const_get("#{options[:storage].to_s.classify}Backend")
|
89
|
+
include storage_mod unless included_modules.include?(storage_mod)
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
def load_related_exception?(e) #:nodoc: implementation specific
|
94
|
+
case
|
95
|
+
when e.kind_of?(LoadError), e.kind_of?(MissingSourceFile), $!.class.name == "CompilationError"
|
96
|
+
# We can't rescue CompilationError directly, as it is part of the RubyInline library.
|
97
|
+
# We must instead rescue RuntimeError, and check the class' name.
|
98
|
+
true
|
99
|
+
else
|
100
|
+
false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
private :load_related_exception?
|
104
|
+
end
|
105
|
+
|
106
|
+
module ClassMethods
|
107
|
+
delegate :content_types, :to => AttachmentMagic
|
108
|
+
|
109
|
+
# Performs common validations for attachment models.
|
110
|
+
def validates_as_attachment
|
111
|
+
validates_presence_of :size, :content_type, :filename
|
112
|
+
validate :attachment_attributes_valid?
|
113
|
+
end
|
114
|
+
|
115
|
+
# Returns true or false if the given content type is recognized as an image.
|
116
|
+
def image?(content_type)
|
117
|
+
content_types.include?(content_type)
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.extended(base)
|
121
|
+
base.class_inheritable_accessor :attachment_options
|
122
|
+
base.before_validation :set_size_from_temp_path
|
123
|
+
base.after_save :after_process_attachment
|
124
|
+
base.after_destroy :destroy_file
|
125
|
+
base.after_validation :process_attachment
|
126
|
+
end
|
127
|
+
|
128
|
+
# Copies the given file path to a new tempfile, returning the closed tempfile.
|
129
|
+
def copy_to_temp_file(file, temp_base_name)
|
130
|
+
Tempfile.new(temp_base_name, AttachmentMagic.tempfile_path).tap do |tmp|
|
131
|
+
tmp.close
|
132
|
+
FileUtils.cp file, tmp.path
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Writes the given data to a new tempfile, returning the closed tempfile.
|
137
|
+
def write_to_temp_file(data, temp_base_name)
|
138
|
+
Tempfile.new(temp_base_name, AttachmentMagic.tempfile_path).tap do |tmp|
|
139
|
+
tmp.binmode
|
140
|
+
tmp.write data
|
141
|
+
tmp.close
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
module InstanceMethods
|
147
|
+
def self.included(base)
|
148
|
+
base.define_callbacks *[:after_attachment_saved] if base.respond_to?(:define_callbacks)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Sets the content type.
|
152
|
+
def content_type=(new_type)
|
153
|
+
write_attribute :content_type, new_type.to_s.strip
|
154
|
+
end
|
155
|
+
|
156
|
+
# Detects the mime-type if content_type is 'application/octet-stream'
|
157
|
+
def detect_mimetype(file_data)
|
158
|
+
if file_data.content_type.strip == "application/octet-stream"
|
159
|
+
return File.mime_type?(file_data.original_filename)
|
160
|
+
else
|
161
|
+
return file_data.content_type
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Sanitizes a filename.
|
166
|
+
def filename=(new_name)
|
167
|
+
write_attribute :filename, sanitize_filename(new_name)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns true if the attachment data will be written to the storage system on the next save
|
171
|
+
def save_attachment?
|
172
|
+
File.file?(temp_path.class == String ? temp_path : temp_path.to_filename)
|
173
|
+
end
|
174
|
+
|
175
|
+
# nil placeholder in case this field is used in a form.
|
176
|
+
def uploaded_data() nil; end
|
177
|
+
|
178
|
+
# This method handles the uploaded file object. If you set the field name to uploaded_data, you don't need
|
179
|
+
# any special code in your controller.
|
180
|
+
#
|
181
|
+
# <% form_for :attachment, :html => { :multipart => true } do |f| -%>
|
182
|
+
# <p><%= f.file_field :uploaded_data %></p>
|
183
|
+
# <p><%= submit_tag :Save %>
|
184
|
+
# <% end -%>
|
185
|
+
#
|
186
|
+
# @attachment = Attachment.create! params[:attachment]
|
187
|
+
#
|
188
|
+
# TODO: Allow it to work with Merb tempfiles too.
|
189
|
+
def uploaded_data=(file_data)
|
190
|
+
if file_data.respond_to?(:content_type)
|
191
|
+
return nil if file_data.size == 0
|
192
|
+
self.content_type = detect_mimetype(file_data)
|
193
|
+
self.filename = file_data.original_filename if respond_to?(:filename)
|
194
|
+
else
|
195
|
+
return nil if file_data.blank? || file_data['size'] == 0
|
196
|
+
self.content_type = file_data['content_type']
|
197
|
+
self.filename = file_data['filename']
|
198
|
+
file_data = file_data['tempfile']
|
199
|
+
end
|
200
|
+
if file_data.is_a?(StringIO)
|
201
|
+
file_data.rewind
|
202
|
+
set_temp_data file_data.read
|
203
|
+
else
|
204
|
+
self.temp_paths.unshift file_data.tempfile.path
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Gets the latest temp path from the collection of temp paths. While working with an attachment,
|
209
|
+
# multiple Tempfile objects may be created for various processing purposes (resizing, for example).
|
210
|
+
# An array of all the tempfile objects is stored so that the Tempfile instance is held on to until
|
211
|
+
# it's not needed anymore. The collection is cleared after saving the attachment.
|
212
|
+
def temp_path
|
213
|
+
p = temp_paths.first
|
214
|
+
p.respond_to?(:path) ? p.path : p.to_s
|
215
|
+
end
|
216
|
+
|
217
|
+
# Gets an array of the currently used temp paths. Defaults to a copy of #full_filename.
|
218
|
+
def temp_paths
|
219
|
+
@temp_paths ||= (new_record? || !respond_to?(:full_filename) || !File.exist?(full_filename) ?
|
220
|
+
[] : [copy_to_temp_file(full_filename)])
|
221
|
+
end
|
222
|
+
|
223
|
+
# Gets the data from the latest temp file. This will read the file into memory.
|
224
|
+
def temp_data
|
225
|
+
save_attachment? ? File.read(temp_path) : nil
|
226
|
+
end
|
227
|
+
|
228
|
+
# Writes the given data to a Tempfile and adds it to the collection of temp files.
|
229
|
+
def set_temp_data(data)
|
230
|
+
temp_paths.unshift write_to_temp_file data unless data.nil?
|
231
|
+
end
|
232
|
+
|
233
|
+
# Copies the given file to a randomly named Tempfile.
|
234
|
+
def copy_to_temp_file(file)
|
235
|
+
self.class.copy_to_temp_file file, random_tempfile_filename
|
236
|
+
end
|
237
|
+
|
238
|
+
# Writes the given file to a randomly named Tempfile.
|
239
|
+
def write_to_temp_file(data)
|
240
|
+
self.class.write_to_temp_file data, random_tempfile_filename
|
241
|
+
end
|
242
|
+
|
243
|
+
# Stub for creating a temp file from the attachment data. This should be defined in the backend module.
|
244
|
+
def create_temp_file() end
|
245
|
+
|
246
|
+
protected
|
247
|
+
# Generates a unique filename for a Tempfile.
|
248
|
+
def random_tempfile_filename
|
249
|
+
"#{rand Time.now.to_i}#{filename || 'attachment'}"
|
250
|
+
end
|
251
|
+
|
252
|
+
def sanitize_filename(filename)
|
253
|
+
return unless filename
|
254
|
+
filename.strip.tap do |name|
|
255
|
+
# NOTE: File.basename doesn't work right with Windows paths on Unix
|
256
|
+
# get only the filename, not the whole path
|
257
|
+
name.gsub! /^.*(\\|\/)/, ''
|
258
|
+
|
259
|
+
# Finally, replace all non alphanumeric, underscore or periods with underscore
|
260
|
+
name.gsub! /[^A-Za-z0-9\.\-]/, '_'
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# before_validation callback.
|
265
|
+
def set_size_from_temp_path
|
266
|
+
self.size = File.size(temp_path) if save_attachment?
|
267
|
+
end
|
268
|
+
|
269
|
+
# validates the size and content_type attributes according to the current model's options
|
270
|
+
def attachment_attributes_valid?
|
271
|
+
[:size, :content_type].each do |attr_name|
|
272
|
+
enum = attachment_options[attr_name]
|
273
|
+
errors.add attr_name, I18n.translate("activerecord.errors.messages.inclusion", attr_name => enum) unless enum.nil? || enum.include?(send(attr_name))
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Stub for a #process_attachment method in a processor
|
278
|
+
def process_attachment
|
279
|
+
@saved_attachment = save_attachment?
|
280
|
+
end
|
281
|
+
|
282
|
+
# Cleans up after processing. Thumbnails are created, the attachment is stored to the backend, and the temp_paths are cleared.
|
283
|
+
def after_process_attachment
|
284
|
+
if @saved_attachment
|
285
|
+
save_to_storage
|
286
|
+
@temp_paths.clear
|
287
|
+
@saved_attachment = nil
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def callback_with_args(method, arg = self)
|
292
|
+
send(method, arg) if respond_to?(method)
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|
296
|
+
|
297
|
+
end
|
298
|
+
|
299
|
+
ActiveRecord::Base.send(:extend, AttachmentMagic::ActMethods)
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'digest/sha2'
|
3
|
+
|
4
|
+
module AttachmentMagic # :nodoc:
|
5
|
+
module Backends
|
6
|
+
# Methods for file system backed attachments
|
7
|
+
module FileSystemBackend
|
8
|
+
def self.included(base) #:nodoc:
|
9
|
+
base.before_update :rename_file
|
10
|
+
end
|
11
|
+
|
12
|
+
# Gets the full path to the filename in this format:
|
13
|
+
#
|
14
|
+
# # This assumes a model name like MyModel
|
15
|
+
# # public/#{table_name} is the default filesystem path
|
16
|
+
# Rails.root.to_s/public/my_models/5/blah.jpg
|
17
|
+
#
|
18
|
+
# Overwrite this method in your model to customize the filename.
|
19
|
+
def full_filename
|
20
|
+
file_system_path = self.attachment_options[:path_prefix].to_s
|
21
|
+
Rails.root.join(file_system_path, *partitioned_path(filename)).to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
# Used as the base path that #public_filename strips off full_filename to create the public path
|
25
|
+
def base_path
|
26
|
+
@base_path ||= Rails.root.join('public')
|
27
|
+
end
|
28
|
+
|
29
|
+
# The attachment ID used in the full path of a file
|
30
|
+
def attachment_path_id
|
31
|
+
((respond_to?(:parent_id) && parent_id) || id) || 0
|
32
|
+
end
|
33
|
+
|
34
|
+
# Partitions the given path into an array of path components.
|
35
|
+
#
|
36
|
+
# For example, given an <tt>*args</tt> of ["foo", "bar"], it will return
|
37
|
+
# <tt>["0000", "0001", "foo", "bar"]</tt> (assuming that that id returns 1).
|
38
|
+
#
|
39
|
+
# If the id is not an integer, then path partitioning will be performed by
|
40
|
+
# hashing the string value of the id with SHA-512, and splitting the result
|
41
|
+
# into 4 components. If the id a 128-bit UUID (as set by :uuid_primary_key => true)
|
42
|
+
# then it will be split into 2 components.
|
43
|
+
#
|
44
|
+
# To turn this off entirely, set :partition => false.
|
45
|
+
def partitioned_path(*args)
|
46
|
+
if respond_to?(:attachment_options) && attachment_options[:partition] == false
|
47
|
+
args
|
48
|
+
elsif attachment_options[:uuid_primary_key]
|
49
|
+
# Primary key is a 128-bit UUID in hex format. Split it into 2 components.
|
50
|
+
path_id = attachment_path_id.to_s
|
51
|
+
component1 = path_id[0..15] || "-"
|
52
|
+
component2 = path_id[16..-1] || "-"
|
53
|
+
[component1, component2] + args
|
54
|
+
else
|
55
|
+
path_id = attachment_path_id
|
56
|
+
if path_id.is_a?(Integer)
|
57
|
+
# Primary key is an integer. Split it after padding it with 0.
|
58
|
+
("%08d" % path_id).scan(/..../) + args
|
59
|
+
else
|
60
|
+
# Primary key is a String. Hash it, then split it into 4 components.
|
61
|
+
hash = Digest::SHA512.hexdigest(path_id.to_s)
|
62
|
+
[hash[0..31], hash[32..63], hash[64..95], hash[96..127]] + args
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Gets the public path to the file
|
68
|
+
def public_filename()
|
69
|
+
full_filename.gsub %r(^#{Regexp.escape(base_path)}), ''
|
70
|
+
end
|
71
|
+
|
72
|
+
def filename=(value)
|
73
|
+
@old_filename = full_filename unless filename.nil? || @old_filename
|
74
|
+
write_attribute :filename, sanitize_filename(value)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Creates a temp file from the currently saved file.
|
78
|
+
def create_temp_file
|
79
|
+
copy_to_temp_file full_filename
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
# Destroys the file. Called in the after_destroy callback
|
84
|
+
def destroy_file
|
85
|
+
FileUtils.rm full_filename
|
86
|
+
# remove directory also if it is now empty
|
87
|
+
Dir.rmdir(File.dirname(full_filename)) if (Dir.entries(File.dirname(full_filename))-['.','..']).empty?
|
88
|
+
rescue
|
89
|
+
logger.info "Exception destroying #{full_filename.inspect}: [#{$!.class.name}] #{$1.to_s}"
|
90
|
+
logger.warn $!.backtrace.collect { |b| " > #{b}" }.join("\n")
|
91
|
+
end
|
92
|
+
|
93
|
+
# Renames the given file before saving
|
94
|
+
def rename_file
|
95
|
+
return unless @old_filename && @old_filename != full_filename
|
96
|
+
if save_attachment? && File.exists?(@old_filename)
|
97
|
+
FileUtils.rm @old_filename
|
98
|
+
elsif File.exists?(@old_filename)
|
99
|
+
FileUtils.mv @old_filename, full_filename
|
100
|
+
end
|
101
|
+
@old_filename = nil
|
102
|
+
true
|
103
|
+
end
|
104
|
+
|
105
|
+
# Saves the file to the file system
|
106
|
+
def save_to_storage
|
107
|
+
if save_attachment?
|
108
|
+
# TODO: This overwrites the file if it exists, maybe have an allow_overwrite option?
|
109
|
+
FileUtils.mkdir_p(File.dirname(full_filename))
|
110
|
+
FileUtils.cp(temp_path, full_filename)
|
111
|
+
FileUtils.chmod(attachment_options[:chmod] || 0644, full_filename)
|
112
|
+
end
|
113
|
+
@old_filename = nil
|
114
|
+
true
|
115
|
+
end
|
116
|
+
|
117
|
+
def current_data
|
118
|
+
File.file?(full_filename) ? File.read(full_filename) : nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: attachment_magic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Thomas von Deyen
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-08-09 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rails
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - <
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 5
|
30
|
+
segments:
|
31
|
+
- 3
|
32
|
+
- 1
|
33
|
+
version: "3.1"
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
hash: 9
|
37
|
+
segments:
|
38
|
+
- 3
|
39
|
+
- 0
|
40
|
+
- 7
|
41
|
+
version: 3.0.7
|
42
|
+
type: :runtime
|
43
|
+
version_requirements: *id001
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: mimetype-fu
|
46
|
+
prerelease: false
|
47
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ~>
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
hash: 31
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
- 1
|
56
|
+
- 2
|
57
|
+
version: 0.1.2
|
58
|
+
type: :runtime
|
59
|
+
version_requirements: *id002
|
60
|
+
description: A Rails 3 Gem based on attachment_fu, but without the image processing fudge and multiple backend crap! Just simple file attachments with a little mime type magic ;)
|
61
|
+
email:
|
62
|
+
- tvdeyen@gmail.com
|
63
|
+
executables: []
|
64
|
+
|
65
|
+
extensions: []
|
66
|
+
|
67
|
+
extra_rdoc_files: []
|
68
|
+
|
69
|
+
files:
|
70
|
+
- .gitignore
|
71
|
+
- Gemfile
|
72
|
+
- Rakefile
|
73
|
+
- attachment_magic.gemspec
|
74
|
+
- lib/attachment_magic.rb
|
75
|
+
- lib/attachment_magic/backends/file_system_backend.rb
|
76
|
+
- lib/attachment_magic/version.rb
|
77
|
+
has_rdoc: true
|
78
|
+
homepage: https://github.com/magiclabs/attachment_magic
|
79
|
+
licenses: []
|
80
|
+
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
hash: 3
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
version: "0"
|
104
|
+
requirements: []
|
105
|
+
|
106
|
+
rubyforge_project: attachment_magic
|
107
|
+
rubygems_version: 1.6.2
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: A simple file attachment gem for Rails 3
|
111
|
+
test_files: []
|
112
|
+
|