attachment-san 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +44 -0
- data/README +10 -0
- data/Rakefile +31 -0
- data/TODO +6 -0
- data/VERSION +1 -0
- data/attachment-san.gemspec +73 -0
- data/examples/photo.rb +41 -0
- data/examples/photos_controller.rb +22 -0
- data/examples/post.rb +3 -0
- data/lib/attachment_san/has.rb +49 -0
- data/lib/attachment_san/test/helper.rb +49 -0
- data/lib/attachment_san/test/rails.png +0 -0
- data/lib/attachment_san/variant.rb +153 -0
- data/lib/attachment_san.rb +58 -0
- data/napkin.rb +41 -0
- data/rails/init.rb +4 -0
- data/test/attachment_san_test.rb +148 -0
- data/test/fixtures/models/attachment.rb +20 -0
- data/test/fixtures/models/document.rb +39 -0
- data/test/fixtures/models/options_stub.rb +20 -0
- data/test/has_test.rb +116 -0
- data/test/lib/schema.rb +14 -0
- data/test/test_helper.rb +66 -0
- data/test/variant_test.rb +167 -0
- metadata +92 -0
data/LICENSE
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
Copyright (c) 2008 Manfred Stienstra, Fingertips <manfred@fngtps.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
19
|
+
|
20
|
+
--------------------------------------------------------------------------------
|
21
|
+
|
22
|
+
Some parts of attachment-san were originally included in ‘attachment_fu’ and
|
23
|
+
‘acts_as_attachment’ under the following license:
|
24
|
+
|
25
|
+
Copyright (c) 2005 Rick Olson
|
26
|
+
|
27
|
+
Unless noted specifically, all plugins in this repository are MIT licensed:
|
28
|
+
|
29
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
30
|
+
this software and associated documentation files (the "Software"), to deal in
|
31
|
+
the Software without restriction, including without limitation the rights to
|
32
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
33
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
34
|
+
subject to the following conditions:
|
35
|
+
|
36
|
+
The above copyright notice and this permission notice shall be included in all
|
37
|
+
copies or substantial portions of the Software.
|
38
|
+
|
39
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
40
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
41
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
42
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
43
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
44
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Attachment-San
|
2
|
+
|
3
|
+
Plugin for easy and rich attachment manipulation.
|
4
|
+
|
5
|
+
Although the ideas behind Attachment-San are different, it borrows heavily on both attachment_fu and acts_as_attachment by Rick Olson.
|
6
|
+
|
7
|
+
Dependencies
|
8
|
+
|
9
|
+
General: A recent Rails (at least 2.0, though it's most often tested on Edge)
|
10
|
+
Development: test/spec and SQLite3
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rake/rdoctask'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
desc "Run all tests by default"
|
5
|
+
task :default => :test
|
6
|
+
|
7
|
+
Rake::TestTask.new do |t|
|
8
|
+
t.test_files = FileList['test/**/*_test.rb']
|
9
|
+
t.verbose = true
|
10
|
+
end
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'jeweler'
|
14
|
+
Jeweler::Tasks.new do |s|
|
15
|
+
s.name = "attachment-san"
|
16
|
+
s.homepage = "http://github.com/Fingertips/attachment-san"
|
17
|
+
s.email = ["eloy@fngtps.com", "manfred@fngtps.com"]
|
18
|
+
s.authors = ["Eloy Duran", "Manfred Stienstra"]
|
19
|
+
s.summary = s.description = "Rails plugin for easy and rich attachment manipulation."
|
20
|
+
s.files -= %w{ .gitignore .kick }
|
21
|
+
end
|
22
|
+
rescue LoadError
|
23
|
+
end
|
24
|
+
|
25
|
+
namespace :docs do
|
26
|
+
Rake::RDocTask.new(:generate) do |rd|
|
27
|
+
rd.main = "README"
|
28
|
+
rd.rdoc_files.include("README", "LICENSE", "lib/**/*.rb")
|
29
|
+
rd.options << "--all" << "--charset" << "utf-8"
|
30
|
+
end
|
31
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
* Make it actually work with more than one attachment class.
|
2
|
+
* has_attachment :watermark, :superclass => WatermarkSuperclass
|
3
|
+
* has_attachment :watermark, :class => WatermarkClass
|
4
|
+
* Nest created classes under the superclass.
|
5
|
+
* Do we need to sanitize the original filename, eg lowercase.
|
6
|
+
* Make :filename_scheme => :record_identifier also use nesting under parent resources. Eg /resources/1/images/2/filename
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{attachment-san}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Eloy Duran", "Manfred Stienstra"]
|
12
|
+
s.date = %q{2009-11-19}
|
13
|
+
s.description = %q{Rails plugin for easy and rich attachment manipulation.}
|
14
|
+
s.email = ["eloy@fngtps.com", "manfred@fngtps.com"]
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
"LICENSE",
|
21
|
+
"README",
|
22
|
+
"Rakefile",
|
23
|
+
"TODO",
|
24
|
+
"VERSION",
|
25
|
+
"examples/photo.rb",
|
26
|
+
"examples/photos_controller.rb",
|
27
|
+
"examples/post.rb",
|
28
|
+
"lib/attachment_san.rb",
|
29
|
+
"lib/attachment_san/has.rb",
|
30
|
+
"lib/attachment_san/test/helper.rb",
|
31
|
+
"lib/attachment_san/test/rails.png",
|
32
|
+
"lib/attachment_san/variant.rb",
|
33
|
+
"napkin.rb",
|
34
|
+
"rails/init.rb",
|
35
|
+
"test/attachment_san_test.rb",
|
36
|
+
"test/fixtures/models/attachment.rb",
|
37
|
+
"test/fixtures/models/document.rb",
|
38
|
+
"test/fixtures/models/options_stub.rb",
|
39
|
+
"test/has_test.rb",
|
40
|
+
"test/lib/schema.rb",
|
41
|
+
"test/test_helper.rb",
|
42
|
+
"test/variant_test.rb"
|
43
|
+
]
|
44
|
+
s.homepage = %q{http://github.com/Fingertips/attachment-san}
|
45
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
46
|
+
s.require_paths = ["lib"]
|
47
|
+
s.rubygems_version = %q{1.3.5}
|
48
|
+
s.summary = %q{Rails plugin for easy and rich attachment manipulation.}
|
49
|
+
s.test_files = [
|
50
|
+
"test/attachment_san_test.rb",
|
51
|
+
"test/fixtures/models/attachment.rb",
|
52
|
+
"test/fixtures/models/document.rb",
|
53
|
+
"test/fixtures/models/options_stub.rb",
|
54
|
+
"test/has_test.rb",
|
55
|
+
"test/lib/schema.rb",
|
56
|
+
"test/test_helper.rb",
|
57
|
+
"test/variant_test.rb",
|
58
|
+
"examples/photo.rb",
|
59
|
+
"examples/photos_controller.rb",
|
60
|
+
"examples/post.rb"
|
61
|
+
]
|
62
|
+
|
63
|
+
if s.respond_to? :specification_version then
|
64
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
65
|
+
s.specification_version = 3
|
66
|
+
|
67
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
68
|
+
else
|
69
|
+
end
|
70
|
+
else
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
data/examples/photo.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
class Photo < ActiveRecord::Base
|
2
|
+
attachment_san
|
3
|
+
after_upload :make_black
|
4
|
+
|
5
|
+
has_one :thumbnail, :class => 'Photo'
|
6
|
+
belongs_to :large_photo, :class => 'Photo'
|
7
|
+
before_create :do_create_thumbnail
|
8
|
+
|
9
|
+
def do_create_thumbnail
|
10
|
+
unless large_photo
|
11
|
+
create_thumbnail :uploaded_data = uploaded_data, :size => "<300x400"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# ---
|
17
|
+
|
18
|
+
class Photo < ActiveRecord::Base
|
19
|
+
has_one :thumbnail
|
20
|
+
has_one :full_size
|
21
|
+
|
22
|
+
def uploaded_data=(data)
|
23
|
+
build_thumbnail :uploaded_data => data
|
24
|
+
build_full_size :uploaded_data => data
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Attachment < ActiveRecord::Base
|
29
|
+
end
|
30
|
+
|
31
|
+
class Thumbnail < Attachment
|
32
|
+
attachment_san
|
33
|
+
|
34
|
+
def resize(image)
|
35
|
+
attachment.resize(image)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class FullSize < Attachment
|
40
|
+
attachment_san :keep_filenames => true
|
41
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class PhotosController < ActionController::Base
|
2
|
+
before_filter :find_post
|
3
|
+
|
4
|
+
def new
|
5
|
+
end
|
6
|
+
|
7
|
+
def create
|
8
|
+
@photo = @post.photos.build params[:photo]
|
9
|
+
if @photo.save
|
10
|
+
flash[:hilight] = dom_id(@photo)
|
11
|
+
redirect_to @post
|
12
|
+
else
|
13
|
+
render :new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def find_post
|
20
|
+
@post = Post.find params[:post_id]
|
21
|
+
end
|
22
|
+
end
|
data/examples/post.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module AttachmentSan
|
2
|
+
module Has
|
3
|
+
VALID_OPTIONS = [:name, :process, :class, :variants, :filename_scheme]
|
4
|
+
|
5
|
+
def has_attachment(name, options = {})
|
6
|
+
define_attachment_association :has_one, name, options
|
7
|
+
end
|
8
|
+
|
9
|
+
def has_attachments(name, options = {})
|
10
|
+
define_attachment_association :has_many, name, options
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def define_attachment_association(macro, name, options)
|
16
|
+
define_variants(name, extract_variant_options!(options))
|
17
|
+
send(macro, name, options) unless reflect_on_association(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def extract_variant_options!(options)
|
21
|
+
options.symbolize_keys!
|
22
|
+
variant_options = options.slice(*VALID_OPTIONS)
|
23
|
+
options.except!(*VALID_OPTIONS)
|
24
|
+
variant_options
|
25
|
+
end
|
26
|
+
|
27
|
+
def define_variants(name, options)
|
28
|
+
model = create_model(name)
|
29
|
+
|
30
|
+
original = { :class => Variant::Original }
|
31
|
+
original.merge!(options) unless options.has_key?(:variants)
|
32
|
+
model.send(:define_variant, :original, original)
|
33
|
+
|
34
|
+
if variants = options[:variants]
|
35
|
+
variants.each do |name, options|
|
36
|
+
model.send(:define_variant, name, options)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# TODO: Currently creates these classes in the top level namespace
|
42
|
+
def create_model(name)
|
43
|
+
name = name.to_s.classify
|
44
|
+
::Object.const_get(name)
|
45
|
+
rescue NameError
|
46
|
+
::Object.const_set name, Class.new(AttachmentSan.attachment_class)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module AttachmentSan
|
6
|
+
module UploadHelpers
|
7
|
+
# Generates a Tempfile object similar to the object you'd get from the standard library CGI
|
8
|
+
# module in a multipart request.
|
9
|
+
#
|
10
|
+
# Borrowed from action_controller/test_process.
|
11
|
+
class FakeUploadFile
|
12
|
+
# The filename, *not* including the path, of the "uploaded" file
|
13
|
+
attr_reader :original_filename
|
14
|
+
|
15
|
+
# The content type of the "uploaded" file
|
16
|
+
attr_reader :content_type
|
17
|
+
|
18
|
+
def initialize(path, content_type = Mime::TEXT, binary = false)
|
19
|
+
path = path.to_s
|
20
|
+
raise "#{path} file does not exist" unless File.exist?(path)
|
21
|
+
@content_type = content_type
|
22
|
+
@original_filename = path.sub(/^.*#{File::SEPARATOR}([^#{File::SEPARATOR}]+)$/) { $1 }
|
23
|
+
@tempfile = Tempfile.new(@original_filename)
|
24
|
+
@tempfile.binmode if binary
|
25
|
+
FileUtils.copy_file(path, @tempfile.path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def path #:nodoc:
|
29
|
+
@tempfile.path
|
30
|
+
end
|
31
|
+
|
32
|
+
alias local_path path
|
33
|
+
|
34
|
+
def method_missing(method_name, *args, &block) #:nodoc:
|
35
|
+
@tempfile.send(method_name, *args, &block)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def uploaded_file(filename, content_type)
|
40
|
+
FakeUploadFile.new(filename, content_type)
|
41
|
+
end
|
42
|
+
|
43
|
+
def rails_icon
|
44
|
+
uploaded_file(File.expand_path('../rails.png', __FILE__), 'image/png')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Test::Unit::TestCase.send(:include, AttachmentSan::UploadHelpers)
|
Binary file
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require "digest"
|
2
|
+
require "fileutils"
|
3
|
+
|
4
|
+
module AttachmentSan
|
5
|
+
class Variant
|
6
|
+
module ClassMethods
|
7
|
+
def self.extended(model)
|
8
|
+
model.class_inheritable_accessor :variant_reflections
|
9
|
+
model.variant_reflections = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def reflect_on_variant(name)
|
13
|
+
variant_reflections.find { |r| r[:name] == name.to_sym }
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def define_variant(name, options_or_class_or_proc)
|
19
|
+
return if reflect_on_variant(name)
|
20
|
+
|
21
|
+
reflection =
|
22
|
+
case x = options_or_class_or_proc
|
23
|
+
when Hash
|
24
|
+
{ :class => Variant }.merge(x.symbolize_keys)
|
25
|
+
when Class
|
26
|
+
{ :class => x }
|
27
|
+
when Proc
|
28
|
+
{ :class => Variant, :process => x }
|
29
|
+
when nil
|
30
|
+
{ :class => const_get(name.to_s.camelize) }
|
31
|
+
else
|
32
|
+
raise TypeError, "Please specify a options hash, variant class, or process proc. Can't use `#{x.inspect}'."
|
33
|
+
end
|
34
|
+
|
35
|
+
reflection[:name] = name = name.to_sym
|
36
|
+
variant_reflections << reflection
|
37
|
+
|
38
|
+
# def original
|
39
|
+
# @original ||= begin
|
40
|
+
# reflection = self.class.reflect_on_variant(:original)
|
41
|
+
# reflection.klass.new(self, reflection)
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
class_eval <<-DEF, __FILE__, __LINE__ + 1
|
45
|
+
def #{name}
|
46
|
+
@#{name} ||= begin
|
47
|
+
reflection = self.class.reflect_on_variant(:#{name})
|
48
|
+
reflection[:class].new(self, reflection)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
DEF
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Variant
|
57
|
+
attr_reader :record, :reflection
|
58
|
+
|
59
|
+
def initialize(record, reflection)
|
60
|
+
@record, @reflection = record, reflection
|
61
|
+
end
|
62
|
+
|
63
|
+
def base_options
|
64
|
+
@record.class.attachment_san_options
|
65
|
+
end
|
66
|
+
|
67
|
+
def base_path
|
68
|
+
base_options[:base_path]
|
69
|
+
end
|
70
|
+
|
71
|
+
def public_base_path
|
72
|
+
base_options[:public_base_path]
|
73
|
+
end
|
74
|
+
|
75
|
+
def filename_scheme
|
76
|
+
@reflection[:filename_scheme] || base_options[:filename_scheme]
|
77
|
+
end
|
78
|
+
|
79
|
+
def original
|
80
|
+
@record.original
|
81
|
+
end
|
82
|
+
|
83
|
+
def name
|
84
|
+
@reflection[:name]
|
85
|
+
end
|
86
|
+
|
87
|
+
def extension
|
88
|
+
(ext = base_options[:extension]) == :keep_original ? @record.extension : ext
|
89
|
+
end
|
90
|
+
|
91
|
+
def token
|
92
|
+
@record.token.scan(/.{2}/)
|
93
|
+
end
|
94
|
+
|
95
|
+
def filename
|
96
|
+
unless @filename
|
97
|
+
@filename =
|
98
|
+
case filename_scheme
|
99
|
+
when :variant_name
|
100
|
+
name.to_s
|
101
|
+
when :keep_original
|
102
|
+
@record.filename
|
103
|
+
when :record_identifier
|
104
|
+
@record_class_name ||= @record.class.name.underscore.pluralize
|
105
|
+
"/#{@record_class_name}/#{@record.to_param}/#{name}"
|
106
|
+
when :token
|
107
|
+
File.join(token, "#{@record.filename_without_extension}.#{name}")
|
108
|
+
else
|
109
|
+
raise ArgumentError, "The :filename_scheme option should be one of `:token', `:filename_scheme', `:record_identifier', or `:variant_name', it currently is `#{filename_scheme.inspect}'."
|
110
|
+
end
|
111
|
+
@filename << ".#{extension}" unless extension.blank?
|
112
|
+
end
|
113
|
+
@filename
|
114
|
+
end
|
115
|
+
|
116
|
+
def file_path
|
117
|
+
File.join(base_path, filename)
|
118
|
+
end
|
119
|
+
|
120
|
+
def public_path
|
121
|
+
File.join(public_base_path, filename)
|
122
|
+
end
|
123
|
+
|
124
|
+
def dir_path
|
125
|
+
File.dirname(file_path)
|
126
|
+
end
|
127
|
+
|
128
|
+
def mkdir!
|
129
|
+
FileUtils.mkdir_p(dir_path)
|
130
|
+
end
|
131
|
+
|
132
|
+
def process!
|
133
|
+
mkdir!
|
134
|
+
@reflection[:process].call(self)
|
135
|
+
end
|
136
|
+
|
137
|
+
class Original < Variant
|
138
|
+
def original
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
142
|
+
def filename
|
143
|
+
@filename ||= (filename_scheme == :token ? File.join(token, @record.filename) : super)
|
144
|
+
end
|
145
|
+
|
146
|
+
def process!
|
147
|
+
return super if @reflection[:process]
|
148
|
+
mkdir!
|
149
|
+
File.open(file_path, 'w') { |f| f.write @record.uploaded_file.read }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "attachment_san/has"
|
2
|
+
require "attachment_san/variant"
|
3
|
+
|
4
|
+
module AttachmentSan
|
5
|
+
module Initializer
|
6
|
+
def attachment_san(options = {})
|
7
|
+
include AttachmentSan
|
8
|
+
|
9
|
+
opt = self.attachment_san_options = {
|
10
|
+
:public_base_path => '',
|
11
|
+
:extension => :keep_original,
|
12
|
+
:filename_scheme => :variant_name
|
13
|
+
}.merge(options)
|
14
|
+
# Defaults :base_path to expanded :public_base_path.
|
15
|
+
opt[:base_path] ||= Rails.root + File.join('public', opt[:public_base_path])
|
16
|
+
opt
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
mattr_accessor :attachment_class
|
21
|
+
|
22
|
+
def self.included(model)
|
23
|
+
self.attachment_class = model
|
24
|
+
model.extend Variant::ClassMethods
|
25
|
+
|
26
|
+
model.class_inheritable_accessor :attachment_san_options
|
27
|
+
model.define_callbacks :before_upload, :after_upload
|
28
|
+
model.after_create :process_variants!
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :uploaded_file
|
32
|
+
|
33
|
+
def uploaded_file=(uploaded_file)
|
34
|
+
callback :before_upload
|
35
|
+
|
36
|
+
@uploaded_file = uploaded_file
|
37
|
+
self.filename = uploaded_file.original_filename
|
38
|
+
self.content_type = uploaded_file.content_type
|
39
|
+
|
40
|
+
callback :after_upload
|
41
|
+
end
|
42
|
+
|
43
|
+
def extension
|
44
|
+
filename.split('.').last if filename.include?('.')
|
45
|
+
end
|
46
|
+
|
47
|
+
def filename_without_extension
|
48
|
+
filename.include?('.') ? filename.split('.')[0..-2].join('.') : filename
|
49
|
+
end
|
50
|
+
|
51
|
+
def variants
|
52
|
+
self.class.variant_reflections.map { |reflection| send(reflection[:name]) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def process_variants!
|
56
|
+
variants.each(&:process!)
|
57
|
+
end
|
58
|
+
end
|
data/napkin.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Variant:
|
2
|
+
# * Returns path information
|
3
|
+
# * /a2/e0/dd/fits_within_200x200.jpg - "many many files"
|
4
|
+
# * /organizations/12/logos/main.jpg - "one or two files"
|
5
|
+
# * /cant/guess/me/82734mhsdf923184jnshdf91238ukjhsdf.jpg - "secret location"
|
6
|
+
|
7
|
+
|
8
|
+
member.avatar.medium # => member.avatar.image('fits_within_8x8')
|
9
|
+
|
10
|
+
company.logo.original # =>
|
11
|
+
|
12
|
+
{ :company => { :logo => { :data => IOWEETIKVEEL } } }
|
13
|
+
|
14
|
+
# abstract model for data about a file, and has variants, defined by the model that has attachments
|
15
|
+
class Attachment
|
16
|
+
belongs_to :attachable
|
17
|
+
|
18
|
+
include Att # makes this an attachment model
|
19
|
+
end
|
20
|
+
|
21
|
+
class Member
|
22
|
+
extend Att::Has # adds declerative class methods
|
23
|
+
include Att::Variant # defines filters to handle variants
|
24
|
+
|
25
|
+
attaches_one :avatar
|
26
|
+
attaches_many
|
27
|
+
|
28
|
+
has_attachment :avatar, :variants => {
|
29
|
+
'medium' => { ... }, # => Class.new(Att:Variant)
|
30
|
+
'fits_within_80x80' => { :fits_within => [80, 80] }
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
# TODO
|
35
|
+
# * filters on a variant basis?
|
36
|
+
# * actual sizes
|
37
|
+
# * module Variants
|
38
|
+
# class Medium
|
39
|
+
# undef :foo
|
40
|
+
# end
|
41
|
+
# end
|
data/rails/init.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "AttachmentSan, concerning base options" do
|
4
|
+
default = OptionsStub.new_subclass do
|
5
|
+
attachment_san
|
6
|
+
end
|
7
|
+
|
8
|
+
default_with_public_path = OptionsStub.new_subclass do
|
9
|
+
attachment_san :public_base_path => '/files/assets'
|
10
|
+
end
|
11
|
+
|
12
|
+
specified = OptionsStub.new_subclass do
|
13
|
+
attachment_san :base_path => '/some/base/path', :public_base_path => '/files/assets', :extension => :png, :filename_scheme => :token
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should default the base_path to `public'" do
|
17
|
+
default.attachment_san_options[:base_path].should == Rails.root + 'public/'
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should use the specified base_path" do
|
21
|
+
specified.attachment_san_options[:base_path].should == '/some/base/path'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should default the public_base_path to an empty string" do
|
25
|
+
default.attachment_san_options[:public_base_path].should == ''
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should use the specified public_base_path" do
|
29
|
+
specified.attachment_san_options[:public_base_path].should == '/files/assets'
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should default the base_path to `public' + public_base_path" do
|
33
|
+
default_with_public_path.attachment_san_options[:base_path].should == Rails.root + 'public/files/assets'
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should default to use the variant's name as the filename for variants" do
|
37
|
+
default.attachment_san_options[:filename_scheme].should == :variant_name
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should use a token version for variant filenames" do
|
41
|
+
specified.attachment_san_options[:filename_scheme].should == :token
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should use the unique records identifier for variant filenames" do
|
45
|
+
OptionsStub.new_subclass do
|
46
|
+
attachment_san :filename_scheme => :record_identifier
|
47
|
+
end.attachment_san_options[:filename_scheme].should == :record_identifier
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should default to use the original file's extension for variants" do
|
51
|
+
default.attachment_san_options[:extension].should == :keep_original end
|
52
|
+
|
53
|
+
it "should use the specified file extension for variants" do
|
54
|
+
specified.attachment_san_options[:extension].should == :png
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "AttachmentSan, class methods" do
|
59
|
+
before do
|
60
|
+
@upload = rails_icon
|
61
|
+
@attachment = Attachment.new(:uploaded_file => @upload)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should assign the base attachment class when attachment_san is called" do
|
65
|
+
AttachmentSan.attachment_class.should.be Attachment
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should call a before_upload filter chain before actually assigning the new uploaded file" do
|
69
|
+
new_upload = rails_icon
|
70
|
+
@attachment.uploaded_file = new_upload
|
71
|
+
@attachment.file_before_upload.should.be @upload
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should call an after_upload filter chain after assigning the new uploaded file" do
|
75
|
+
@attachment.file_after_upload.should.be @upload
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should define a variant with options" do
|
79
|
+
Logo.variant_reflections.map { |r| r[:name] }.should == [:original, :header]
|
80
|
+
Logo.new.header.should.be.instance_of MyVariant
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "AttachmentSan, instance methods" do
|
85
|
+
before do
|
86
|
+
@upload = rails_icon
|
87
|
+
@attachment = Attachment.new(:uploaded_file => @upload)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should make a model accept a file upload" do
|
91
|
+
@attachment.uploaded_file.should.be @upload
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should assign the original filename to the model" do
|
95
|
+
@attachment.filename.should == @upload.original_filename
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should assign the content type to the model" do
|
99
|
+
@attachment.content_type.should == @upload.content_type
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should return the original file's extension" do
|
103
|
+
@attachment.extension.should == 'png'
|
104
|
+
|
105
|
+
@attachment.stubs(:filename).returns('Rakefile')
|
106
|
+
@attachment.extension.should.be nil
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should return the original filename without extension" do
|
110
|
+
@attachment.filename_without_extension.should == 'rails'
|
111
|
+
|
112
|
+
@attachment.stubs(:filename).returns('Rakefile')
|
113
|
+
@attachment.filename_without_extension.should == 'Rakefile'
|
114
|
+
|
115
|
+
@attachment.stubs(:filename).returns('some.dotted.spaced.file.txt')
|
116
|
+
@attachment.filename_without_extension.should == 'some.dotted.spaced.file'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "An AttachmentSan instance, concerning variants" do
|
121
|
+
before do
|
122
|
+
@upload = rails_icon
|
123
|
+
@document = Document.new
|
124
|
+
@document.build_logo :uploaded_file => @upload
|
125
|
+
@document.build_watermark :uploaded_file => @upload
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should call process on each of it's variants after create" do
|
129
|
+
@document.watermark.original.expects(:process!)
|
130
|
+
@document.logo.original.expects(:process!)
|
131
|
+
@document.logo.header.expects(:process!)
|
132
|
+
@document.save!
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should not call process when the model already exists" do
|
136
|
+
@document.save!
|
137
|
+
|
138
|
+
@document.watermark.original.expects(:process!).never
|
139
|
+
@document.logo.original.expects(:process!).never
|
140
|
+
@document.logo.header.expects(:process!).never
|
141
|
+
@document.save!
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should return it's variants" do
|
145
|
+
@document.watermark.variants.map(&:name).should == [:original]
|
146
|
+
@document.logo.variants.map(&:name).should == [:original, :header]
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Attachment < ActiveRecord::Base
|
2
|
+
attachment_san :base_path => TMP_DIR, :public_base_path => '/files/assets'
|
3
|
+
|
4
|
+
# Test code
|
5
|
+
|
6
|
+
def self.reset!
|
7
|
+
FileUtils.rm_rf TMP_DIR
|
8
|
+
FileUtils.mkdir_p TMP_DIR
|
9
|
+
end
|
10
|
+
|
11
|
+
# User code
|
12
|
+
|
13
|
+
belongs_to :attachable, :polymorphic => true
|
14
|
+
|
15
|
+
attr_accessor :file_before_upload
|
16
|
+
before_upload { |record| record.file_before_upload = record.uploaded_file }
|
17
|
+
|
18
|
+
attr_accessor :file_after_upload
|
19
|
+
after_upload { |record| record.file_after_upload = record.uploaded_file }
|
20
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class MyVariant < AttachmentSan::Variant
|
2
|
+
def process!
|
3
|
+
end
|
4
|
+
end
|
5
|
+
|
6
|
+
class MyProcessor
|
7
|
+
def initialize(variant)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class AddressCard < Attachment
|
12
|
+
class SmallCard < AttachmentSan::Variant
|
13
|
+
def process!
|
14
|
+
puts 'SMALL!'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class BigCard < AttachmentSan::Variant
|
19
|
+
def process!
|
20
|
+
puts 'BIG!'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Document < ActiveRecord::Base
|
26
|
+
extend AttachmentSan::Has
|
27
|
+
|
28
|
+
has_attachment :watermark
|
29
|
+
has_attachment :logo, :variants => { :header => MyVariant }
|
30
|
+
|
31
|
+
has_attachments :misc_files, :filename_scheme => :keep_original, :process => proc { :from_process_proc }
|
32
|
+
has_attachments :images, :variants => {
|
33
|
+
:thumbnail => proc { |v| MyProcessor.new(v) },
|
34
|
+
:medium_sized => proc {},
|
35
|
+
:download => proc {}
|
36
|
+
}
|
37
|
+
|
38
|
+
has_attachments :address_cards, :variants => [:small_card, :big_card]
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class OptionsStub
|
2
|
+
def self.new_subclass(&block)
|
3
|
+
before = AttachmentSan.attachment_class
|
4
|
+
model = Class.new(self)
|
5
|
+
model.instance_eval(&block)
|
6
|
+
AttachmentSan.attachment_class = before
|
7
|
+
model
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.define_callbacks(*args)
|
11
|
+
# no we don't!
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.after_create(m)
|
15
|
+
# no we don't!
|
16
|
+
end
|
17
|
+
|
18
|
+
class_inheritable_accessor :attachment_san_options
|
19
|
+
extend AttachmentSan::Initializer
|
20
|
+
end
|
data/test/has_test.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "AttachmentSan::Has" do
|
4
|
+
it "should pass on any unknown options to the has_one macro" do
|
5
|
+
Document.expects(:has_one).with(:other_file, :as => :attachable, :order => :updated_at)
|
6
|
+
Document.expects(:define_variants).with(:other_file, :process => proc {}, :class => MyVariant, :filename_scheme => :token, :variants => [:hoge, :fuga])
|
7
|
+
|
8
|
+
Document.has_attachment :other_file, :as => :attachable, :order => :updated_at,
|
9
|
+
:process => proc {}, :class => MyVariant, :filename_scheme => :token, :variants => [:hoge, :fuga]
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should pass on any unknown options to the has_many macro" do
|
13
|
+
Document.expects(:has_many).with(:other_files, :as => :attachable, :order => :updated_at)
|
14
|
+
Document.expects(:define_variants).with(:other_files, :process => proc {}, :class => MyVariant, :filename_scheme => :token, :variants => [:hoge, :fuga])
|
15
|
+
|
16
|
+
Document.has_attachments :other_files, :as => :attachable, :order => :updated_at,
|
17
|
+
:process => proc {}, :class => MyVariant, :filename_scheme => :token, :variants => [:hoge, :fuga]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should not define an association if an association for the given name exists" do
|
21
|
+
Document.expects(:has_one).never
|
22
|
+
Document.expects(:define_variants).with(:watermark, {})
|
23
|
+
|
24
|
+
Document.expects(:has_many).never
|
25
|
+
Document.expects(:define_variants).with(:images, {})
|
26
|
+
|
27
|
+
Document.has_attachment :watermark, :as => :attachable
|
28
|
+
Document.has_attachments :images, :as => :attachable
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "AttachmentSan::Has, concerning a single associated attachment" do
|
33
|
+
before do
|
34
|
+
@document = Document.new
|
35
|
+
@document.build_logo :uploaded_file => rails_icon
|
36
|
+
@document.build_watermark :uploaded_file => rails_icon
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should create a model class and a has_one association" do
|
40
|
+
reflection = Document.reflect_on_association(:logo)
|
41
|
+
reflection.macro.should == :has_one
|
42
|
+
reflection.klass.should == Logo
|
43
|
+
Logo.superclass.should.be AttachmentSan.attachment_class
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should store the variant options on the new model class" do
|
47
|
+
Watermark.variant_reflections.length.should == 1
|
48
|
+
Watermark.variant_reflections.first[:name].should == :original
|
49
|
+
Watermark.variant_reflections.first[:class].should == AttachmentSan::Variant::Original
|
50
|
+
|
51
|
+
Logo.variant_reflections.length.should == 2
|
52
|
+
Logo.variant_reflections.last[:name].should == :header
|
53
|
+
Logo.variant_reflections.last[:class].should == MyVariant
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should define only a default original variant if no other variants are given" do
|
57
|
+
variant = @document.watermark.original
|
58
|
+
variant.name.should == :original
|
59
|
+
variant.should.be.instance_of AttachmentSan::Variant::Original
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should define a default original variant and the ones specified" do
|
63
|
+
%w{ original header }.each do |name|
|
64
|
+
variant = @document.logo.send(name)
|
65
|
+
variant.name.to_s.should == name
|
66
|
+
variant.should.be.instance_of Logo.reflect_on_variant(name)[:class]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "AttachmentSan::Has, concerning a collection of associated attachments" do
|
72
|
+
before do
|
73
|
+
@document = Document.new
|
74
|
+
2.times { @document.images.build :uploaded_file => rails_icon }
|
75
|
+
2.times { @document.misc_files.build :uploaded_file => rails_icon }
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should create a model class and a has_many association" do
|
79
|
+
reflection = Document.reflect_on_association(:images)
|
80
|
+
reflection.macro.should == :has_many
|
81
|
+
reflection.klass.should == Image
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should define only a default original variant if no others are given" do
|
85
|
+
variants = @document.misc_files.map(&:original)
|
86
|
+
variants.should.all { |v| v.name == :original }
|
87
|
+
variants.should.all { |v| v.instance_of? AttachmentSan::Variant }
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should define a default original variant and the ones specified" do
|
91
|
+
%w{ original thumbnail medium_sized download }.each do |name|
|
92
|
+
variants = @document.images.map(&name.to_sym)
|
93
|
+
variants.should.all { |v| v.name == name }
|
94
|
+
variants.should.all { |v| v.instance_of? AttachmentSan::Variant }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "AttachmentSan::Has, concerning attachment definitions with only a default original variant" do
|
100
|
+
it "should pass the options hash on to the variant" do
|
101
|
+
MiscFile.variant_reflections.length.should == 1
|
102
|
+
MiscFile.variant_reflections.first[:name].should == :original
|
103
|
+
MiscFile.variant_reflections.first[:class].should == AttachmentSan::Variant::Original
|
104
|
+
MiscFile.variant_reflections.first[:filename_scheme].should == :keep_original
|
105
|
+
MiscFile.variant_reflections.first[:process].call.should == :from_process_proc
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "AttachmentSan::Has, concerning attachment definitions with an array of variants" do
|
110
|
+
it "should simply assume the variant class exists" do
|
111
|
+
@document = Document.new
|
112
|
+
card = @document.address_cards.build(:uploaded_file => rails_icon)
|
113
|
+
card.small_card.should.be.instance_of AddressCard::SmallCard
|
114
|
+
card.big_card.should.be.instance_of AddressCard::BigCard
|
115
|
+
end
|
116
|
+
end
|
data/test/lib/schema.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
ActiveRecord::Migration.verbose = false
|
2
|
+
ActiveRecord::Schema.define(:version => 1) do
|
3
|
+
|
4
|
+
create_table :attachments, :force => true do |t|
|
5
|
+
t.string :filename
|
6
|
+
t.string :content_type
|
7
|
+
t.string :attachable_type
|
8
|
+
t.integer :attachable_id
|
9
|
+
end
|
10
|
+
|
11
|
+
create_table :documents, :force => true do |t|
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
TEST_ROOT_DIR = File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
frameworks = %w(activesupport activerecord actionpack)
|
4
|
+
|
5
|
+
rails = [
|
6
|
+
File.expand_path('../../../rails', TEST_ROOT_DIR),
|
7
|
+
File.expand_path('../../rails', TEST_ROOT_DIR)
|
8
|
+
].detect do |possible_rails|
|
9
|
+
begin
|
10
|
+
entries = Dir.entries(possible_rails)
|
11
|
+
frameworks.all? { |framework| entries.include?(framework) }
|
12
|
+
rescue Errno::ENOENT
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
raise "Couldn't find Rails directory!" unless rails
|
18
|
+
|
19
|
+
frameworks.each { |framework| $:.unshift(File.join(rails, framework, 'lib')) }
|
20
|
+
$:.unshift File.join(TEST_ROOT_DIR, '/../lib')
|
21
|
+
$:.unshift TEST_ROOT_DIR
|
22
|
+
|
23
|
+
ENV['RAILS_ENV'] = 'test'
|
24
|
+
|
25
|
+
# Rails libs
|
26
|
+
begin
|
27
|
+
require 'active_support'
|
28
|
+
require 'active_record'
|
29
|
+
require 'action_controller'
|
30
|
+
rescue LoadError
|
31
|
+
raise "Please install Attachment-San as Rails plugin before running the tests."
|
32
|
+
end
|
33
|
+
|
34
|
+
require "pathname"
|
35
|
+
module Rails
|
36
|
+
def self.env
|
37
|
+
'test'
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.root
|
41
|
+
Pathname.new('/path/to/app')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
require File.expand_path('../../rails/init', __FILE__)
|
46
|
+
|
47
|
+
# Libraries for testing
|
48
|
+
require 'rubygems' rescue LoadError
|
49
|
+
require 'sqlite3'
|
50
|
+
require 'test/spec'
|
51
|
+
require 'mocha'
|
52
|
+
require 'fileutils'
|
53
|
+
|
54
|
+
logdir = File.join(TEST_ROOT_DIR, 'log')
|
55
|
+
FileUtils.mkdir_p(logdir)
|
56
|
+
ActiveRecord::Base.logger = Logger.new File.join(logdir, 'test.log')
|
57
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ":memory:")
|
58
|
+
|
59
|
+
TMP_DIR = File.join(TEST_ROOT_DIR, 'tmp')
|
60
|
+
|
61
|
+
# Classes and methods to aid testing
|
62
|
+
require 'lib/schema'
|
63
|
+
|
64
|
+
require 'fixtures/models/attachment'
|
65
|
+
require 'fixtures/models/document'
|
66
|
+
require 'fixtures/models/options_stub'
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "AttachmentSan's variant class methods" do
|
4
|
+
it "should return the variant class to use" do
|
5
|
+
reflection = Logo.reflect_on_variant(:header)
|
6
|
+
reflection[:class].should.be MyVariant
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should by default use the AttachmentSan::Variant class" do
|
10
|
+
reflection = Image.reflect_on_variant(:thumbnail)
|
11
|
+
reflection[:class].should.be AttachmentSan::Variant
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should not define a variant twice" do
|
15
|
+
count_before = Logo.variant_reflections.length
|
16
|
+
Document.has_attachment :logo, :variants => { :header => MyVariant }
|
17
|
+
Logo.variant_reflections.length.should == count_before
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "A AttachmentSan::Variant instance in general" do
|
22
|
+
before do
|
23
|
+
@upload = rails_icon
|
24
|
+
@document = Document.new
|
25
|
+
|
26
|
+
@image = @document.images.build(:uploaded_file => @upload)
|
27
|
+
@thumbnail = @image.thumbnail
|
28
|
+
@medium_sized = @image.medium_sized
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return the record it is a variant of" do
|
32
|
+
@image.original.record.should == @image
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should return the `original' variant" do
|
36
|
+
@image.original.should == @image.original
|
37
|
+
@thumbnail.original.should == @image.original
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should call the process proc" do
|
41
|
+
MyProcessor.expects(:new).with(@thumbnail)
|
42
|
+
@thumbnail.process!
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should return the base_path specified in the attachment_san_options" do
|
46
|
+
@thumbnail.base_path.should == Attachment.attachment_san_options[:base_path]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should return the public_base_path specified in the attachment_san_options" do
|
50
|
+
@thumbnail.public_base_path.should == Attachment.attachment_san_options[:public_base_path]
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should return the extension of the original file" do
|
54
|
+
Image.attachment_san_options[:extension] = :keep_original
|
55
|
+
@thumbnail.extension.should == 'png'
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should use the extension specified in the attachment_san_options" do
|
59
|
+
Image.attachment_san_options[:extension] = :jpeg
|
60
|
+
@thumbnail.extension.should == :jpeg
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should create the directory that the file_path returns" do
|
64
|
+
Image.attachment_san_options[:filename_scheme] = :record_identifier
|
65
|
+
|
66
|
+
@thumbnail.dir_path.should == File.dirname(@thumbnail.file_path)
|
67
|
+
File.should.not.exist @thumbnail.dir_path
|
68
|
+
@thumbnail.mkdir!
|
69
|
+
File.should.exist @thumbnail.dir_path
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should return a file_path without trailing dot if the original filename has no extension" do
|
73
|
+
variant = @document.misc_files.build(
|
74
|
+
:uploaded_file => uploaded_file(File.expand_path('../../Rakefile', __FILE__), 'text/plain')
|
75
|
+
).original
|
76
|
+
|
77
|
+
variant.file_path.should == File.join(variant.base_path, 'Rakefile')
|
78
|
+
end
|
79
|
+
|
80
|
+
{ :keep_original => 'png', :jpg => 'jpg' }.each do |setting, ext|
|
81
|
+
it "should return a filename named after the variant name plus #{setting}" do
|
82
|
+
Image.attachment_san_options[:filename_scheme] = :variant_name
|
83
|
+
Image.attachment_san_options[:extension] = setting
|
84
|
+
|
85
|
+
@thumbnail.filename.should == "thumbnail.#{ext}"
|
86
|
+
@thumbnail.file_path.should == File.join(@thumbnail.base_path, "thumbnail.#{ext}")
|
87
|
+
@thumbnail.public_path.should == File.join(@thumbnail.public_base_path, "thumbnail.#{ext}")
|
88
|
+
|
89
|
+
@medium_sized.filename.should == "medium_sized.#{ext}"
|
90
|
+
@medium_sized.file_path.should == File.join(@thumbnail.base_path, "medium_sized.#{ext}")
|
91
|
+
@medium_sized.public_path.should == File.join(@thumbnail.public_base_path, "medium_sized.#{ext}")
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should return a filename which should be a random token from the record and append #{setting}" do
|
95
|
+
Image.attachment_san_options[:filename_scheme] = :token
|
96
|
+
Image.attachment_san_options[:extension] = setting
|
97
|
+
@image.stubs(:token).returns('556d2e8e')
|
98
|
+
|
99
|
+
@image.original.filename.should == "55/6d/2e/8e/rails.png"
|
100
|
+
@image.original.file_path.should == File.join(@thumbnail.base_path, "55/6d/2e/8e/rails.png")
|
101
|
+
@image.original.public_path.should == File.join(@thumbnail.public_base_path, "55/6d/2e/8e/rails.png")
|
102
|
+
|
103
|
+
@thumbnail.filename.should == "55/6d/2e/8e/rails.thumbnail.#{ext}"
|
104
|
+
@thumbnail.file_path.should == File.join(@thumbnail.base_path, "55/6d/2e/8e/rails.thumbnail.#{ext}")
|
105
|
+
@thumbnail.public_path.should == File.join(@thumbnail.public_base_path, "55/6d/2e/8e/rails.thumbnail.#{ext}")
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should return a filename which is based on the record identifier and variant name plus #{setting}" do
|
109
|
+
Attachment.reset!
|
110
|
+
|
111
|
+
Image.attachment_san_options[:extension] = setting
|
112
|
+
Image.attachment_san_options[:filename_scheme] = :record_identifier
|
113
|
+
@image.save!
|
114
|
+
|
115
|
+
@thumbnail.filename.should == "/images/#{@image.id}/thumbnail.#{ext}"
|
116
|
+
@thumbnail.file_path.should == File.join(@thumbnail.base_path, "/images/#{@image.id}/thumbnail.#{ext}")
|
117
|
+
@thumbnail.public_path.should == File.join(@thumbnail.public_base_path, "/images/#{@image.id}/thumbnail.#{ext}")
|
118
|
+
|
119
|
+
@medium_sized.filename.should == "/images/#{@image.id}/medium_sized.#{ext}"
|
120
|
+
@medium_sized.file_path.should == File.join(@thumbnail.base_path, "/images/#{@image.id}/medium_sized.#{ext}")
|
121
|
+
@medium_sized.public_path.should == File.join(@thumbnail.public_base_path, "/images/#{@image.id}/medium_sized.#{ext}")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "AttachmentSan::Variant, concerning a default variant without extra options" do
|
127
|
+
before do
|
128
|
+
@upload = rails_icon
|
129
|
+
@document = Document.new
|
130
|
+
@document.build_watermark :uploaded_file => @upload
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should use a custom variant for an `original'" do
|
134
|
+
@document.watermark.original.should.be.instance_of AttachmentSan::Variant::Original
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should simply copy the file to the file_path" do
|
138
|
+
Attachment.reset!
|
139
|
+
|
140
|
+
file_path = @document.watermark.original.file_path
|
141
|
+
File.should.not.exist file_path
|
142
|
+
@document.save!
|
143
|
+
|
144
|
+
File.should.exist file_path
|
145
|
+
File.read(file_path).should == File.read(@upload.path)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "AttachmentSan::Variant, concerning a default variant with extra options" do
|
150
|
+
before do
|
151
|
+
@upload = rails_icon
|
152
|
+
@document = Document.new
|
153
|
+
@variant = @document.misc_files.build(:uploaded_file => @upload).original
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should return the filename_scheme value that was specified" do
|
157
|
+
@variant.filename_scheme.should == :keep_original end
|
158
|
+
|
159
|
+
it "should return the original file's name" do
|
160
|
+
@variant.filename.should == @variant.record.filename
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should call the specified process proc" do
|
164
|
+
result = @variant.process!
|
165
|
+
result.should == :from_process_proc
|
166
|
+
end
|
167
|
+
end
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: attachment-san
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eloy Duran
|
8
|
+
- Manfred Stienstra
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-11-19 00:00:00 +01:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Rails plugin for easy and rich attachment manipulation.
|
18
|
+
email:
|
19
|
+
- eloy@fngtps.com
|
20
|
+
- manfred@fngtps.com
|
21
|
+
executables: []
|
22
|
+
|
23
|
+
extensions: []
|
24
|
+
|
25
|
+
extra_rdoc_files:
|
26
|
+
- LICENSE
|
27
|
+
- README
|
28
|
+
files:
|
29
|
+
- LICENSE
|
30
|
+
- README
|
31
|
+
- Rakefile
|
32
|
+
- TODO
|
33
|
+
- VERSION
|
34
|
+
- attachment-san.gemspec
|
35
|
+
- examples/photo.rb
|
36
|
+
- examples/photos_controller.rb
|
37
|
+
- examples/post.rb
|
38
|
+
- lib/attachment_san.rb
|
39
|
+
- lib/attachment_san/has.rb
|
40
|
+
- lib/attachment_san/test/helper.rb
|
41
|
+
- lib/attachment_san/test/rails.png
|
42
|
+
- lib/attachment_san/variant.rb
|
43
|
+
- napkin.rb
|
44
|
+
- rails/init.rb
|
45
|
+
- test/attachment_san_test.rb
|
46
|
+
- test/fixtures/models/attachment.rb
|
47
|
+
- test/fixtures/models/document.rb
|
48
|
+
- test/fixtures/models/options_stub.rb
|
49
|
+
- test/has_test.rb
|
50
|
+
- test/lib/schema.rb
|
51
|
+
- test/test_helper.rb
|
52
|
+
- test/variant_test.rb
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://github.com/Fingertips/attachment-san
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options:
|
59
|
+
- --charset=UTF-8
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.3.5
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Rails plugin for easy and rich attachment manipulation.
|
81
|
+
test_files:
|
82
|
+
- test/attachment_san_test.rb
|
83
|
+
- test/fixtures/models/attachment.rb
|
84
|
+
- test/fixtures/models/document.rb
|
85
|
+
- test/fixtures/models/options_stub.rb
|
86
|
+
- test/has_test.rb
|
87
|
+
- test/lib/schema.rb
|
88
|
+
- test/test_helper.rb
|
89
|
+
- test/variant_test.rb
|
90
|
+
- examples/photo.rb
|
91
|
+
- examples/photos_controller.rb
|
92
|
+
- examples/post.rb
|