oahu-dragonfly 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.rspec +1 -0
- data/.yardopts +24 -0
- data/Gemfile +30 -0
- data/History.md +323 -0
- data/LICENSE +20 -0
- data/README.md +88 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/config.ru +14 -0
- data/docs.watchr +1 -0
- data/dragonfly.gemspec +297 -0
- data/extra_docs/Analysers.md +66 -0
- data/extra_docs/Caching.md +23 -0
- data/extra_docs/Configuration.md +124 -0
- data/extra_docs/Couch.md +49 -0
- data/extra_docs/DataStorage.md +153 -0
- data/extra_docs/Encoding.md +67 -0
- data/extra_docs/GeneralUsage.md +121 -0
- data/extra_docs/Generators.md +60 -0
- data/extra_docs/Heroku.md +50 -0
- data/extra_docs/ImageMagick.md +125 -0
- data/extra_docs/Index.md +33 -0
- data/extra_docs/MimeTypes.md +40 -0
- data/extra_docs/Models.md +272 -0
- data/extra_docs/Mongo.md +45 -0
- data/extra_docs/Processing.md +77 -0
- data/extra_docs/Rack.md +52 -0
- data/extra_docs/Rails2.md +57 -0
- data/extra_docs/Rails3.md +62 -0
- data/extra_docs/Sinatra.md +25 -0
- data/extra_docs/URLs.md +169 -0
- data/features/images.feature +47 -0
- data/features/no_processing.feature +14 -0
- data/features/rails_3.0.5.feature +8 -0
- data/features/steps/common_steps.rb +8 -0
- data/features/steps/dragonfly_steps.rb +66 -0
- data/features/steps/rails_steps.rb +28 -0
- data/features/support/env.rb +13 -0
- data/features/support/setup.rb +32 -0
- data/fixtures/rails_3.0.5/files/app/models/album.rb +7 -0
- data/fixtures/rails_3.0.5/files/app/views/albums/new.html.erb +7 -0
- data/fixtures/rails_3.0.5/files/app/views/albums/show.html.erb +6 -0
- data/fixtures/rails_3.0.5/files/config/initializers/dragonfly.rb +4 -0
- data/fixtures/rails_3.0.5/files/features/manage_album_images.feature +38 -0
- data/fixtures/rails_3.0.5/files/features/step_definitions/helper_steps.rb +7 -0
- data/fixtures/rails_3.0.5/files/features/step_definitions/image_steps.rb +25 -0
- data/fixtures/rails_3.0.5/files/features/support/paths.rb +17 -0
- data/fixtures/rails_3.0.5/files/features/text_images.feature +7 -0
- data/fixtures/rails_3.0.5/template.rb +20 -0
- data/irbrc.rb +18 -0
- data/lib/dragonfly.rb +55 -0
- data/lib/dragonfly/active_model_extensions.rb +13 -0
- data/lib/dragonfly/active_model_extensions/attachment.rb +250 -0
- data/lib/dragonfly/active_model_extensions/attachment_class_methods.rb +148 -0
- data/lib/dragonfly/active_model_extensions/class_methods.rb +95 -0
- data/lib/dragonfly/active_model_extensions/instance_methods.rb +28 -0
- data/lib/dragonfly/active_model_extensions/validations.rb +41 -0
- data/lib/dragonfly/analyser.rb +58 -0
- data/lib/dragonfly/analysis/file_command_analyser.rb +32 -0
- data/lib/dragonfly/analysis/image_magick_analyser.rb +6 -0
- data/lib/dragonfly/app.rb +172 -0
- data/lib/dragonfly/config/heroku.rb +19 -0
- data/lib/dragonfly/config/image_magick.rb +6 -0
- data/lib/dragonfly/config/rails.rb +20 -0
- data/lib/dragonfly/configurable.rb +207 -0
- data/lib/dragonfly/core_ext/array.rb +7 -0
- data/lib/dragonfly/core_ext/hash.rb +7 -0
- data/lib/dragonfly/core_ext/object.rb +12 -0
- data/lib/dragonfly/core_ext/string.rb +9 -0
- data/lib/dragonfly/core_ext/symbol.rb +9 -0
- data/lib/dragonfly/data_storage.rb +9 -0
- data/lib/dragonfly/data_storage/couch_data_store.rb +64 -0
- data/lib/dragonfly/data_storage/file_data_store.rb +141 -0
- data/lib/dragonfly/data_storage/mongo_data_store.rb +86 -0
- data/lib/dragonfly/data_storage/s3data_store.rb +145 -0
- data/lib/dragonfly/encoder.rb +13 -0
- data/lib/dragonfly/encoding/image_magick_encoder.rb +6 -0
- data/lib/dragonfly/function_manager.rb +71 -0
- data/lib/dragonfly/generation/image_magick_generator.rb +6 -0
- data/lib/dragonfly/generator.rb +9 -0
- data/lib/dragonfly/hash_with_css_style_keys.rb +21 -0
- data/lib/dragonfly/image_magick/analyser.rb +51 -0
- data/lib/dragonfly/image_magick/config.rb +41 -0
- data/lib/dragonfly/image_magick/encoder.rb +57 -0
- data/lib/dragonfly/image_magick/generator.rb +145 -0
- data/lib/dragonfly/image_magick/processor.rb +99 -0
- data/lib/dragonfly/image_magick/utils.rb +72 -0
- data/lib/dragonfly/image_magick_utils.rb +4 -0
- data/lib/dragonfly/job.rb +451 -0
- data/lib/dragonfly/job_builder.rb +39 -0
- data/lib/dragonfly/job_definitions.rb +26 -0
- data/lib/dragonfly/job_endpoint.rb +15 -0
- data/lib/dragonfly/loggable.rb +28 -0
- data/lib/dragonfly/middleware.rb +20 -0
- data/lib/dragonfly/processing/image_magick_processor.rb +6 -0
- data/lib/dragonfly/processor.rb +9 -0
- data/lib/dragonfly/rails/images.rb +27 -0
- data/lib/dragonfly/response.rb +97 -0
- data/lib/dragonfly/routed_endpoint.rb +40 -0
- data/lib/dragonfly/serializer.rb +32 -0
- data/lib/dragonfly/server.rb +113 -0
- data/lib/dragonfly/simple_cache.rb +23 -0
- data/lib/dragonfly/temp_object.rb +175 -0
- data/lib/dragonfly/url_mapper.rb +78 -0
- data/samples/beach.png +0 -0
- data/samples/egg.png +0 -0
- data/samples/round.gif +0 -0
- data/samples/sample.docx +0 -0
- data/samples/taj.jpg +0 -0
- data/spec/dragonfly/active_model_extensions/model_spec.rb +1426 -0
- data/spec/dragonfly/active_model_extensions/spec_helper.rb +91 -0
- data/spec/dragonfly/analyser_spec.rb +123 -0
- data/spec/dragonfly/analysis/file_command_analyser_spec.rb +48 -0
- data/spec/dragonfly/app_spec.rb +135 -0
- data/spec/dragonfly/configurable_spec.rb +461 -0
- data/spec/dragonfly/core_ext/array_spec.rb +19 -0
- data/spec/dragonfly/core_ext/hash_spec.rb +19 -0
- data/spec/dragonfly/core_ext/string_spec.rb +17 -0
- data/spec/dragonfly/core_ext/symbol_spec.rb +17 -0
- data/spec/dragonfly/data_storage/couch_data_store_spec.rb +76 -0
- data/spec/dragonfly/data_storage/file_data_store_spec.rb +296 -0
- data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +57 -0
- data/spec/dragonfly/data_storage/s3_data_store_spec.rb +258 -0
- data/spec/dragonfly/data_storage/shared_data_store_examples.rb +77 -0
- data/spec/dragonfly/function_manager_spec.rb +154 -0
- data/spec/dragonfly/hash_with_css_style_keys_spec.rb +24 -0
- data/spec/dragonfly/image_magick/analyser_spec.rb +64 -0
- data/spec/dragonfly/image_magick/encoder_spec.rb +41 -0
- data/spec/dragonfly/image_magick/generator_spec.rb +172 -0
- data/spec/dragonfly/image_magick/processor_spec.rb +233 -0
- data/spec/dragonfly/image_magick/utils_spec.rb +18 -0
- data/spec/dragonfly/job_builder_spec.rb +37 -0
- data/spec/dragonfly/job_definitions_spec.rb +35 -0
- data/spec/dragonfly/job_endpoint_spec.rb +173 -0
- data/spec/dragonfly/job_spec.rb +1046 -0
- data/spec/dragonfly/loggable_spec.rb +80 -0
- data/spec/dragonfly/middleware_spec.rb +47 -0
- data/spec/dragonfly/routed_endpoint_spec.rb +48 -0
- data/spec/dragonfly/serializer_spec.rb +61 -0
- data/spec/dragonfly/server_spec.rb +278 -0
- data/spec/dragonfly/simple_cache_spec.rb +27 -0
- data/spec/dragonfly/temp_object_spec.rb +306 -0
- data/spec/dragonfly/url_mapper_spec.rb +126 -0
- data/spec/functional/deprecations_spec.rb +51 -0
- data/spec/functional/image_magick_app_spec.rb +27 -0
- data/spec/functional/model_urls_spec.rb +85 -0
- data/spec/functional/remote_on_the_fly_spec.rb +51 -0
- data/spec/functional/to_response_spec.rb +31 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/support/argument_matchers.rb +19 -0
- data/spec/support/image_matchers.rb +47 -0
- data/spec/support/simple_matchers.rb +53 -0
- data/yard/handlers/configurable_attr_handler.rb +38 -0
- data/yard/setup.rb +15 -0
- data/yard/templates/default/fulldoc/html/css/common.css +107 -0
- data/yard/templates/default/layout/html/layout.erb +89 -0
- data/yard/templates/default/module/html/configuration_summary.erb +31 -0
- data/yard/templates/default/module/setup.rb +17 -0
- metadata +544 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Dragonfly
|
|
2
|
+
class SimpleCache < Hash
|
|
3
|
+
|
|
4
|
+
def initialize(max_size)
|
|
5
|
+
@max_size = max_size
|
|
6
|
+
@keys = []
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
attr_reader :max_size
|
|
10
|
+
|
|
11
|
+
def []=(key, value)
|
|
12
|
+
if !has_key?(key)
|
|
13
|
+
@keys << key
|
|
14
|
+
if size == max_size
|
|
15
|
+
key_to_purge = @keys.shift
|
|
16
|
+
delete(key_to_purge)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
require 'stringio'
|
|
2
|
+
require 'tempfile'
|
|
3
|
+
require 'pathname'
|
|
4
|
+
|
|
5
|
+
module Dragonfly
|
|
6
|
+
|
|
7
|
+
# A TempObject is used for HOLDING DATA.
|
|
8
|
+
# It's the thing that is passed between the datastore, the processor and the encoder, and is useful
|
|
9
|
+
# for separating how the data was created and how it is later accessed.
|
|
10
|
+
#
|
|
11
|
+
# You can initialize it various ways:
|
|
12
|
+
#
|
|
13
|
+
# temp_object = Dragonfly::TempObject.new('this is the content') # with a String
|
|
14
|
+
# temp_object = Dragonfly::TempObject.new(Pathname.new('path/to/content')) # with a Pathname
|
|
15
|
+
# temp_object = Dragonfly::TempObject.new(File.new('path/to/content')) # with a File
|
|
16
|
+
# temp_object = Dragonfly::TempObject.new(some_tempfile) # with a Tempfile
|
|
17
|
+
# temp_object = Dragonfly::TempObject.new(some_other_temp_object) # with another TempObject
|
|
18
|
+
#
|
|
19
|
+
# However, no matter how it was initialized, you can always access the data a number of ways:
|
|
20
|
+
#
|
|
21
|
+
# temp_object.data # returns a data string
|
|
22
|
+
# temp_object.file # returns a file object holding the data
|
|
23
|
+
# temp_object.path # returns a path for the file
|
|
24
|
+
#
|
|
25
|
+
# The data/file are created lazily, something which you may wish to take advantage of.
|
|
26
|
+
#
|
|
27
|
+
# For example, if a TempObject is initialized with a file, and temp_object.data is never called, then
|
|
28
|
+
# the data string will never be loaded into memory.
|
|
29
|
+
#
|
|
30
|
+
# Conversely, if the TempObject is initialized with a data string, and neither temp_object.file nor temp_object.path
|
|
31
|
+
# are ever called, then the filesystem will never be hit.
|
|
32
|
+
#
|
|
33
|
+
class TempObject
|
|
34
|
+
|
|
35
|
+
# Class configuration
|
|
36
|
+
class << self
|
|
37
|
+
|
|
38
|
+
include Configurable
|
|
39
|
+
configurable_attr :block_size, 8192
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Instance Methods
|
|
44
|
+
|
|
45
|
+
def initialize(obj)
|
|
46
|
+
if obj.is_a? TempObject
|
|
47
|
+
@data = obj.get_data
|
|
48
|
+
@tempfile = obj.get_tempfile
|
|
49
|
+
@pathname = obj.get_pathname
|
|
50
|
+
elsif obj.is_a? String
|
|
51
|
+
@data = obj
|
|
52
|
+
elsif obj.is_a? Tempfile
|
|
53
|
+
@tempfile = obj
|
|
54
|
+
elsif obj.is_a? File
|
|
55
|
+
@pathname = Pathname.new(obj.path)
|
|
56
|
+
@original_filename = @pathname.basename.to_s
|
|
57
|
+
elsif obj.is_a? Pathname
|
|
58
|
+
@pathname = obj
|
|
59
|
+
@original_filename = @pathname.basename.to_s
|
|
60
|
+
elsif obj.respond_to?(:tempfile)
|
|
61
|
+
@tempfile = obj.tempfile
|
|
62
|
+
else
|
|
63
|
+
raise ArgumentError, "#{self.class.name} must be initialized with a String, a Pathname, a File, a Tempfile, another TempObject, or something that responds to .tempfile"
|
|
64
|
+
end
|
|
65
|
+
@tempfile.close if @tempfile
|
|
66
|
+
@original_filename = obj.original_filename if obj.respond_to?(:original_filename)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
attr_reader :original_filename
|
|
70
|
+
|
|
71
|
+
def data
|
|
72
|
+
@data ||= file{|f| f.read }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def tempfile
|
|
76
|
+
@tempfile ||= begin
|
|
77
|
+
case
|
|
78
|
+
when @data
|
|
79
|
+
@tempfile = new_tempfile(@data)
|
|
80
|
+
when @pathname
|
|
81
|
+
@tempfile = copy_to_tempfile(@pathname.expand_path)
|
|
82
|
+
end
|
|
83
|
+
@tempfile
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def file(&block)
|
|
88
|
+
f = tempfile.open
|
|
89
|
+
tempfile.binmode
|
|
90
|
+
if block_given?
|
|
91
|
+
ret = yield f
|
|
92
|
+
tempfile.close
|
|
93
|
+
else
|
|
94
|
+
ret = f
|
|
95
|
+
end
|
|
96
|
+
ret
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def path
|
|
100
|
+
@pathname ? @pathname.expand_path.to_s : tempfile.path
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def size
|
|
104
|
+
@data ? @data.bytesize : File.size(path)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def each(&block)
|
|
108
|
+
to_io do |io|
|
|
109
|
+
while part = io.read(block_size)
|
|
110
|
+
yield part
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def to_file(path)
|
|
116
|
+
if @data
|
|
117
|
+
File.open(path, 'wb'){|f| f.write(@data) }
|
|
118
|
+
else
|
|
119
|
+
FileUtils.cp(self.path, path)
|
|
120
|
+
end
|
|
121
|
+
File.new(path, 'rb')
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def to_io(&block)
|
|
125
|
+
@data ? StringIO.open(@data, 'rb', &block) : file(&block)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def inspect
|
|
129
|
+
content_string = case
|
|
130
|
+
when @data
|
|
131
|
+
data_string = size > 20 ? "#{@data[0..20]}..." : @data
|
|
132
|
+
"data=#{data_string.inspect}"
|
|
133
|
+
when @pathname then "pathname=#{@pathname.inspect}"
|
|
134
|
+
when @tempfile then "tempfile=#{@tempfile.inspect}"
|
|
135
|
+
end
|
|
136
|
+
to_s.sub(/>$/, " #{content_string} >")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
protected
|
|
140
|
+
|
|
141
|
+
# We don't use normal accessors here because #data etc. do more than just return the instance var
|
|
142
|
+
def get_data
|
|
143
|
+
@data
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def get_pathname
|
|
147
|
+
@pathname
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def get_tempfile
|
|
151
|
+
@tempfile
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
def block_size
|
|
157
|
+
self.class.block_size
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def copy_to_tempfile(path)
|
|
161
|
+
tempfile = new_tempfile
|
|
162
|
+
FileUtils.cp path, tempfile.path
|
|
163
|
+
tempfile
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def new_tempfile(content=nil)
|
|
167
|
+
tempfile = Tempfile.new('dragonfly')
|
|
168
|
+
tempfile.binmode
|
|
169
|
+
tempfile.write(content) if content
|
|
170
|
+
tempfile.close
|
|
171
|
+
tempfile
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
end
|
|
175
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
module Dragonfly
|
|
2
|
+
class UrlMapper
|
|
3
|
+
|
|
4
|
+
# Exceptions
|
|
5
|
+
class BadUrlFormat < StandardError; end
|
|
6
|
+
|
|
7
|
+
class Segment < Struct.new(:param, :seperator, :pattern)
|
|
8
|
+
|
|
9
|
+
def regexp_string
|
|
10
|
+
@regexp_string ||= "(#{Regexp.escape(seperator)}#{pattern}+?)?"
|
|
11
|
+
end
|
|
12
|
+
#
|
|
13
|
+
# def regexp
|
|
14
|
+
# @regexp ||= Regexp.new(regexp_string)
|
|
15
|
+
# end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(url_format, patterns={})
|
|
20
|
+
@url_format = url_format
|
|
21
|
+
raise BadUrlFormat, "bad url format #{url_format}" if url_format[/[\w_]:[\w_]/]
|
|
22
|
+
init_segments(patterns)
|
|
23
|
+
init_url_regexp
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr_reader :url_format, :url_regexp, :segments
|
|
27
|
+
|
|
28
|
+
def params_for(path, query=nil)
|
|
29
|
+
if path and md = path.match(url_regexp)
|
|
30
|
+
params = Rack::Utils.parse_query(query)
|
|
31
|
+
params_in_url.each_with_index do |var, i|
|
|
32
|
+
value = md[i+1][1..-1] if md[i+1]
|
|
33
|
+
params[var] = value
|
|
34
|
+
end
|
|
35
|
+
params
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def params_in_url
|
|
40
|
+
@params_in_url ||= url_format.scan(/\:[\w_]+/).map{|f| f.tr(':','') }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def url_for(params)
|
|
44
|
+
params = params.dup
|
|
45
|
+
url = url_format.dup
|
|
46
|
+
segments.each do |seg|
|
|
47
|
+
value = params[seg.param]
|
|
48
|
+
value ? url.sub!(/:[\w_]+/, value) : url.sub!(/.:[\w_]+/, '')
|
|
49
|
+
params.delete(seg.param)
|
|
50
|
+
end
|
|
51
|
+
url << "?#{Rack::Utils.build_query(params)}" if params.any?
|
|
52
|
+
url
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def init_segments(patterns)
|
|
58
|
+
@segments = []
|
|
59
|
+
url_format.scan(/([^\w_]):([\w_]+)/).each do |seperator, param|
|
|
60
|
+
segments << Segment.new(
|
|
61
|
+
param,
|
|
62
|
+
seperator,
|
|
63
|
+
patterns[param.to_sym] || '[^\/\-\.]'
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def init_url_regexp
|
|
69
|
+
i = -1
|
|
70
|
+
regexp_string = url_format.gsub(/[^\w_]:[\w_]+/) do
|
|
71
|
+
i += 1
|
|
72
|
+
segments[i].regexp_string
|
|
73
|
+
end
|
|
74
|
+
@url_regexp = Regexp.new('^' + regexp_string + '$')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end
|
|
78
|
+
end
|
data/samples/beach.png
ADDED
|
Binary file
|
data/samples/egg.png
ADDED
|
Binary file
|
data/samples/round.gif
ADDED
|
Binary file
|
data/samples/sample.docx
ADDED
|
Binary file
|
data/samples/taj.jpg
ADDED
|
Binary file
|
|
@@ -0,0 +1,1426 @@
|
|
|
1
|
+
require 'dragonfly/active_model_extensions/spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Item do
|
|
4
|
+
|
|
5
|
+
def set_up_item_class(app=test_app)
|
|
6
|
+
app.define_macro(MyModel, :image_accessor)
|
|
7
|
+
Item.class_eval do
|
|
8
|
+
image_accessor :preview_image
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# See extra setup in models / initializer files
|
|
13
|
+
|
|
14
|
+
describe "defining accessors" do
|
|
15
|
+
|
|
16
|
+
let(:app1){ Dragonfly[:img] }
|
|
17
|
+
let(:app2){ Dragonfly[:vid] }
|
|
18
|
+
|
|
19
|
+
describe "attachment classes" do
|
|
20
|
+
before(:each) do
|
|
21
|
+
app1.define_macro(MyModel, :image_accessor)
|
|
22
|
+
app2.define_macro(MyModel, :video_accessor)
|
|
23
|
+
Item.class_eval do
|
|
24
|
+
image_accessor :preview_image
|
|
25
|
+
video_accessor :trailer_video
|
|
26
|
+
end
|
|
27
|
+
@classes = Item.dragonfly_attachment_classes
|
|
28
|
+
@class1, @class2 = @classes
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "should return the attachment classes" do
|
|
32
|
+
@class1.superclass.should == Dragonfly::ActiveModelExtensions::Attachment
|
|
33
|
+
@class2.superclass.should == Dragonfly::ActiveModelExtensions::Attachment
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "should associate the correct app with each class" do
|
|
37
|
+
@class1.app.should == app1
|
|
38
|
+
@class2.app.should == app2
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "should associate the correct attribute with each class" do
|
|
42
|
+
@class1.attribute.should == :preview_image
|
|
43
|
+
@class2.attribute.should == :trailer_video
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "should associate the correct model class with each class" do
|
|
47
|
+
@class1.model_class.should == Item
|
|
48
|
+
@class2.model_class.should == Item
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "should work for included modules (e.g. Mongoid::Document)" do
|
|
53
|
+
mongoid_document = Module.new
|
|
54
|
+
app1.define_macro_on_include(mongoid_document, :dog_accessor)
|
|
55
|
+
model_class = Class.new do
|
|
56
|
+
def self.before_save(*args); end
|
|
57
|
+
def self.before_destroy(*args); end
|
|
58
|
+
include mongoid_document
|
|
59
|
+
dog_accessor :doogie
|
|
60
|
+
end
|
|
61
|
+
klass = model_class.dragonfly_attachment_classes.first
|
|
62
|
+
klass.app.should == app1
|
|
63
|
+
klass.attribute.should == :doogie
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe "correctly defined" do
|
|
69
|
+
|
|
70
|
+
before(:each) do
|
|
71
|
+
@app = test_app
|
|
72
|
+
@app.define_macro(MyModel, :image_accessor)
|
|
73
|
+
Item.class_eval do
|
|
74
|
+
image_accessor :preview_image
|
|
75
|
+
end
|
|
76
|
+
@item = Item.new
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "should provide a reader" do
|
|
80
|
+
@item.should respond_to(:preview_image)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "should provide a writer" do
|
|
84
|
+
@item.should respond_to(:preview_image=)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "when there has been nothing assigned" do
|
|
88
|
+
it "the reader should return nil" do
|
|
89
|
+
@item.preview_image.should be_nil
|
|
90
|
+
end
|
|
91
|
+
it "the uid should be nil" do
|
|
92
|
+
@item.preview_image_uid.should be_nil
|
|
93
|
+
end
|
|
94
|
+
it "should not try to store anything on save" do
|
|
95
|
+
@app.datastore.should_not_receive(:store)
|
|
96
|
+
@item.save!
|
|
97
|
+
end
|
|
98
|
+
it "should not try to destroy anything on save" do
|
|
99
|
+
@app.datastore.should_not_receive(:destroy)
|
|
100
|
+
@item.save!
|
|
101
|
+
end
|
|
102
|
+
it "should not try to destroy anything on destroy" do
|
|
103
|
+
@app.datastore.should_not_receive(:destroy)
|
|
104
|
+
@item.destroy
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
describe "when the uid is set manually" do
|
|
109
|
+
before(:each) do
|
|
110
|
+
@item.preview_image_uid = 'some_known_uid'
|
|
111
|
+
end
|
|
112
|
+
it "should not try to retrieve any data" do
|
|
113
|
+
@app.datastore.should_not_receive(:retrieve)
|
|
114
|
+
@item.save!
|
|
115
|
+
end
|
|
116
|
+
it "should not try to destroy any data" do
|
|
117
|
+
@app.datastore.should_not_receive(:destroy)
|
|
118
|
+
@item.save!
|
|
119
|
+
end
|
|
120
|
+
it "should not try to store any data" do
|
|
121
|
+
@app.datastore.should_not_receive(:store)
|
|
122
|
+
@item.save!
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe "when there has been some thing assigned but not saved" do
|
|
127
|
+
before(:each) do
|
|
128
|
+
@item.preview_image = "DATASTRING"
|
|
129
|
+
end
|
|
130
|
+
it "the reader should return an attachment" do
|
|
131
|
+
@item.preview_image.should be_a(Dragonfly::ActiveModelExtensions::Attachment)
|
|
132
|
+
end
|
|
133
|
+
it "the uid should be nil" do
|
|
134
|
+
@item.preview_image_uid.should be_nil
|
|
135
|
+
end
|
|
136
|
+
it "should store the image when saved" do
|
|
137
|
+
@app.datastore.should_receive(:store).with(a_temp_object_with_data("DATASTRING"), hash_including)
|
|
138
|
+
@item.save!
|
|
139
|
+
end
|
|
140
|
+
it "should not try to destroy anything on destroy" do
|
|
141
|
+
@app.datastore.should_not_receive(:destroy)
|
|
142
|
+
@item.destroy
|
|
143
|
+
end
|
|
144
|
+
it "should return nil for the url" do
|
|
145
|
+
@item.preview_image.url.should be_nil
|
|
146
|
+
end
|
|
147
|
+
describe "when the uid is set manually" do
|
|
148
|
+
before(:each) do
|
|
149
|
+
@item.preview_image_uid = 'some_known_uid'
|
|
150
|
+
end
|
|
151
|
+
it "should not try to retrieve any data" do
|
|
152
|
+
@app.datastore.should_not_receive(:retrieve)
|
|
153
|
+
@item.save!
|
|
154
|
+
end
|
|
155
|
+
it "should not try to destroy any data" do
|
|
156
|
+
@app.datastore.should_not_receive(:destroy)
|
|
157
|
+
@item.save!
|
|
158
|
+
end
|
|
159
|
+
it "should not try to store any data" do
|
|
160
|
+
@app.datastore.should_not_receive(:store)
|
|
161
|
+
@item.save!
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe "when something has been assigned and saved" do
|
|
168
|
+
|
|
169
|
+
before(:each) do
|
|
170
|
+
@item.preview_image = "DATASTRING"
|
|
171
|
+
@app.datastore.should_receive(:store).with(a_temp_object_with_data("DATASTRING"), hash_including).once.and_return('some_uid')
|
|
172
|
+
@item.save!
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it "should have the correct uid" do
|
|
176
|
+
@item.preview_image_uid.should == 'some_uid'
|
|
177
|
+
end
|
|
178
|
+
it "should not try to store anything if saved again" do
|
|
179
|
+
@app.datastore.should_not_receive(:store)
|
|
180
|
+
@item.save!
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it "should not try to destroy anything if saved again" do
|
|
184
|
+
@app.datastore.should_not_receive(:destroy)
|
|
185
|
+
@item.save!
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it "should destroy the data on destroy" do
|
|
189
|
+
@app.datastore.should_receive(:destroy).with('some_uid')
|
|
190
|
+
@item.destroy
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it "should return the url for the data" do
|
|
194
|
+
@item.preview_image.url.should =~ %r<^/\w+$>
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it "should destroy the old data when the uid is set manually" do
|
|
198
|
+
@app.datastore.should_receive(:destroy).with('some_uid')
|
|
199
|
+
@item.preview_image_uid = 'some_known_uid'
|
|
200
|
+
@item.save!
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
describe "when accessed by a new model object" do
|
|
204
|
+
before(:each) do
|
|
205
|
+
@item = Item.find(@item.id)
|
|
206
|
+
end
|
|
207
|
+
it "should destroy the data on destroy" do
|
|
208
|
+
@app.datastore.should_receive(:destroy).with(@item.preview_image_uid)
|
|
209
|
+
@item.destroy
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
describe "when something new is assigned" do
|
|
214
|
+
before(:each) do
|
|
215
|
+
@item.preview_image = "ANEWDATASTRING"
|
|
216
|
+
@app.datastore.stub!(:store).and_return('some_uid')
|
|
217
|
+
end
|
|
218
|
+
it "should set the uid to nil" do
|
|
219
|
+
@item.preview_image_uid.should be_nil
|
|
220
|
+
end
|
|
221
|
+
it "should destroy the old data when saved" do
|
|
222
|
+
@app.datastore.should_receive(:destroy).with('some_uid')
|
|
223
|
+
@item.save!
|
|
224
|
+
end
|
|
225
|
+
it "should not try to destroy the old data if saved again" do
|
|
226
|
+
@app.datastore.should_receive(:destroy).with('some_uid')
|
|
227
|
+
@item.save!
|
|
228
|
+
@app.datastore.should_not_receive(:destroy).with('some_uid')
|
|
229
|
+
@item.save!
|
|
230
|
+
end
|
|
231
|
+
it "should destroy the old data when saved, even if yet another thing is assigned" do
|
|
232
|
+
@item.preview_image = "YET ANOTHER DATA STRING"
|
|
233
|
+
@app.datastore.should_receive(:destroy).with('some_uid')
|
|
234
|
+
@item.save!
|
|
235
|
+
end
|
|
236
|
+
it "should store the new data when saved" do
|
|
237
|
+
@app.datastore.should_receive(:store).with(a_temp_object_with_data("ANEWDATASTRING"), hash_including)
|
|
238
|
+
@item.save!
|
|
239
|
+
end
|
|
240
|
+
it "should destroy the old data on destroy" do
|
|
241
|
+
@app.datastore.should_receive(:destroy).with('some_uid')
|
|
242
|
+
@item.destroy
|
|
243
|
+
end
|
|
244
|
+
it "should destroy the old data on destroy, even if yet another thing is assigned" do
|
|
245
|
+
@item.preview_image = "YET ANOTHER DATA STRING"
|
|
246
|
+
@app.datastore.should_receive(:destroy).with('some_uid')
|
|
247
|
+
@item.destroy
|
|
248
|
+
end
|
|
249
|
+
it "should destroy the old data when the uid has been set manually" do
|
|
250
|
+
@app.datastore.should_receive(:destroy).with('some_uid')
|
|
251
|
+
@item.preview_image_uid = 'some_known_uid'
|
|
252
|
+
@item.save!
|
|
253
|
+
end
|
|
254
|
+
it "should return the new size" do
|
|
255
|
+
@item.preview_image.size.should == 14
|
|
256
|
+
end
|
|
257
|
+
it "should return the new data" do
|
|
258
|
+
@item.preview_image.data.should == 'ANEWDATASTRING'
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
describe "when it is set to nil" do
|
|
263
|
+
before(:each) do
|
|
264
|
+
@item.preview_image = nil
|
|
265
|
+
end
|
|
266
|
+
it "should set the uid to nil" do
|
|
267
|
+
@item.preview_image_uid.should be_nil
|
|
268
|
+
end
|
|
269
|
+
it "should return the attribute as nil" do
|
|
270
|
+
@item.preview_image.should be_nil
|
|
271
|
+
end
|
|
272
|
+
it "should destroy the data on save" do
|
|
273
|
+
@app.datastore.should_receive(:destroy).with('some_uid')
|
|
274
|
+
@item.save!
|
|
275
|
+
@item.preview_image.should be_nil
|
|
276
|
+
end
|
|
277
|
+
it "should destroy the old data on destroy" do
|
|
278
|
+
@app.datastore.should_receive(:destroy).with('some_uid')
|
|
279
|
+
@item.destroy
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
describe "when the data can't be found" do
|
|
284
|
+
it "should log a warning if the data wasn't found on destroy" do
|
|
285
|
+
@app.datastore.should_receive(:destroy).with('some_uid').and_raise(Dragonfly::DataStorage::DataNotFound)
|
|
286
|
+
@app.log.should_receive(:warn)
|
|
287
|
+
@item.destroy
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
describe "other types of assignment" do
|
|
294
|
+
before(:each) do
|
|
295
|
+
@app.generator.add :egg do
|
|
296
|
+
"Gungedin"
|
|
297
|
+
end
|
|
298
|
+
@app.processor.add :doogie do |temp_object|
|
|
299
|
+
temp_object.data.upcase
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
describe "assigning with a job" do
|
|
304
|
+
before(:each) do
|
|
305
|
+
@job = @app.generate(:egg)
|
|
306
|
+
@item.preview_image = @job
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
it "should work" do
|
|
310
|
+
@item.preview_image.data.should == 'Gungedin'
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it "should not be affected by subsequent changes to the job" do
|
|
314
|
+
@job.process!(:doogie)
|
|
315
|
+
@item.preview_image.data.should == 'Gungedin'
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
describe "assigning with another attachment" do
|
|
320
|
+
before(:each) do
|
|
321
|
+
Item.class_eval do
|
|
322
|
+
image_accessor :other_image
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
it "should work like assigning the job" do
|
|
326
|
+
@item.preview_image = 'eggheads'
|
|
327
|
+
@item.other_image = @item.preview_image
|
|
328
|
+
@item.preview_image = 'dogchin'
|
|
329
|
+
@item.other_image.data.should == 'eggheads'
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
describe "assigning by means of a bang method" do
|
|
334
|
+
before(:each) do
|
|
335
|
+
@app.processor.add :double do |temp_object|
|
|
336
|
+
temp_object.data * 2
|
|
337
|
+
end
|
|
338
|
+
@app.encoder.add do |temp_object, format|
|
|
339
|
+
temp_object.data.downcase + format.to_s
|
|
340
|
+
end
|
|
341
|
+
@item.preview_image = "HELLO"
|
|
342
|
+
end
|
|
343
|
+
it "should modify as if being assigned again" do
|
|
344
|
+
@item.preview_image.process!(:double)
|
|
345
|
+
@item.preview_image.data.should == 'HELLOHELLO'
|
|
346
|
+
end
|
|
347
|
+
it "should update the magic attributes" do
|
|
348
|
+
@item.preview_image.process!(:double)
|
|
349
|
+
@item.preview_image_size.should == 10
|
|
350
|
+
end
|
|
351
|
+
it "should work for encode" do
|
|
352
|
+
@item.preview_image.encode!(:egg)
|
|
353
|
+
@item.preview_image.data.should == 'helloegg'
|
|
354
|
+
end
|
|
355
|
+
it "should work repeatedly" do
|
|
356
|
+
@item.preview_image.process!(:double).encode!(:egg)
|
|
357
|
+
@item.preview_image.data.should == 'hellohelloegg'
|
|
358
|
+
@item.preview_image_size.should == 13
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
describe "remote_url" do
|
|
364
|
+
it "should give the remote url if the uid is set" do
|
|
365
|
+
@item.preview_image_uid = 'some/uid'
|
|
366
|
+
@app.should_receive(:remote_url_for).with('some/uid', :some => 'param').and_return('http://egg.nog')
|
|
367
|
+
@item.preview_image.remote_url(:some => 'param').should == 'http://egg.nog'
|
|
368
|
+
end
|
|
369
|
+
it "should return nil if the content is not yet saved" do
|
|
370
|
+
@item.preview_image = "hello"
|
|
371
|
+
@item.preview_image.remote_url(:some => 'param').should be_nil
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
describe "validations" do
|
|
378
|
+
|
|
379
|
+
before(:all) do
|
|
380
|
+
@app = test_app
|
|
381
|
+
@app.define_macro(MyModel, :image_accessor)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
describe "validates_presence_of" do
|
|
385
|
+
|
|
386
|
+
before(:all) do
|
|
387
|
+
Item.class_eval do
|
|
388
|
+
image_accessor :preview_image
|
|
389
|
+
validates_presence_of :preview_image
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
it "should be valid if set" do
|
|
394
|
+
Item.new(:preview_image => "1234567890").should be_valid
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
it "should be invalid if not set" do
|
|
398
|
+
Item.new.should_not be_valid
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
describe "validates_size_of" do
|
|
404
|
+
|
|
405
|
+
before(:all) do
|
|
406
|
+
Item.class_eval do
|
|
407
|
+
image_accessor :preview_image
|
|
408
|
+
validates_size_of :preview_image, :within => (6..10)
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
it "should be valid if ok" do
|
|
413
|
+
Item.new(:preview_image => "1234567890").should be_valid
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
it "should be invalid if too small" do
|
|
417
|
+
Item.new(:preview_image => "12345").should_not be_valid
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
describe "validates_property" do
|
|
423
|
+
|
|
424
|
+
before(:each) do
|
|
425
|
+
@item = Item.new(:preview_image => "1234567890")
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
before(:all) do
|
|
429
|
+
custom_analyser = Class.new do
|
|
430
|
+
def mime_type(temp_object)
|
|
431
|
+
case temp_object.data
|
|
432
|
+
when "WRONG TYPE" then 'wrong/type'
|
|
433
|
+
when "OTHER TYPE" then nil
|
|
434
|
+
else 'how/special'
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def number_of_Gs(temp_object)
|
|
439
|
+
temp_object.data.count('G')
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
@app.analyser.register(custom_analyser)
|
|
443
|
+
|
|
444
|
+
Item.class_eval do
|
|
445
|
+
validates_property :mime_type, :of => :preview_image, :in => ['how/special', 'how/crazy'], :if => :its_friday
|
|
446
|
+
validates_property :mime_type, :of => [:other_image, :yet_another_image], :as => 'how/special'
|
|
447
|
+
validates_property :number_of_Gs, :of => :preview_image, :in => (0..2)
|
|
448
|
+
validates_property :mime_type, :of => :otra_imagen, :in => ['que/pasa', 'illo/tio'], :message => "tipo de contenido incorrecto. Que chungo tio"
|
|
449
|
+
|
|
450
|
+
image_accessor :preview_image
|
|
451
|
+
image_accessor :other_image
|
|
452
|
+
image_accessor :yet_another_image
|
|
453
|
+
image_accessor :otra_imagen
|
|
454
|
+
|
|
455
|
+
def its_friday
|
|
456
|
+
true
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
it "should be valid if nil, if not validated on presence (even with validates_property)" do
|
|
463
|
+
@item.other_image = nil
|
|
464
|
+
@item.should be_valid
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
it "should be invalid if the property is nil" do
|
|
468
|
+
@item.preview_image = "OTHER TYPE"
|
|
469
|
+
@item.should_not be_valid
|
|
470
|
+
@item.errors[:preview_image].should == ["mime type is incorrect. It needs to be one of 'how/special', 'how/crazy', but was 'application/octet-stream'"]
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
it "should be invalid if the property is wrong" do
|
|
474
|
+
@item.preview_image = "WRONG TYPE"
|
|
475
|
+
@item.should_not be_valid
|
|
476
|
+
@item.errors[:preview_image].should == ["mime type is incorrect. It needs to be one of 'how/special', 'how/crazy', but was 'wrong/type'"]
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
it "should work for a range" do
|
|
480
|
+
@item.preview_image = "GOOGLE GUM"
|
|
481
|
+
@item.should_not be_valid
|
|
482
|
+
@item.errors[:preview_image].should == ["number of gs is incorrect. It needs to be between 0 and 2, but was '3'"]
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
it "should validate individually" do
|
|
486
|
+
@item.other_image = "1234567"
|
|
487
|
+
@item.yet_another_image = "WRONG TYPE"
|
|
488
|
+
@item.should_not be_valid
|
|
489
|
+
@item.errors[:other_image].should == []
|
|
490
|
+
@item.errors[:yet_another_image].should == ["mime type is incorrect. It needs to be 'how/special', but was 'wrong/type'"]
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
it "should include standard extra options like 'if' on mime type validation" do
|
|
494
|
+
@item.should_receive(:its_friday).and_return(false)
|
|
495
|
+
@item.preview_image = "WRONG TYPE"
|
|
496
|
+
@item.should be_valid
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
it "should require either :as or :in as an argument" do
|
|
500
|
+
lambda{
|
|
501
|
+
Item.class_eval do
|
|
502
|
+
validates_property :mime_type, :of => :preview_image
|
|
503
|
+
end
|
|
504
|
+
}.should raise_error(ArgumentError)
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
it "should require :of as an argument" do
|
|
508
|
+
lambda{
|
|
509
|
+
Item.class_eval do
|
|
510
|
+
validates_property :mime_type, :as => 'hi/there'
|
|
511
|
+
end
|
|
512
|
+
}.should raise_error(ArgumentError)
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
it "should allow for custom messages" do
|
|
516
|
+
@item.otra_imagen = "WRONG TYPE"
|
|
517
|
+
@item.should_not be_valid
|
|
518
|
+
@item.errors[:otra_imagen].should == ["tipo de contenido incorrecto. Que chungo tio"]
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
it "should allow for custom messages including access to the property name and expected/allowed values" do
|
|
522
|
+
@item.should_receive(:its_friday).and_return(false) # hack to get rid of other validation
|
|
523
|
+
Item.class_eval do
|
|
524
|
+
validates_property :mime_type, :of => :preview_image, :as => 'one/thing',
|
|
525
|
+
:message => proc{|actual, model| "Unlucky #{model.title}! Was #{actual}" }
|
|
526
|
+
end
|
|
527
|
+
@item.title = 'scubby'
|
|
528
|
+
@item.preview_image = "WRONG TYPE"
|
|
529
|
+
@item.should_not be_valid
|
|
530
|
+
@item.errors[:preview_image].should == ["Unlucky scubby! Was wrong/type"]
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
describe "extra properties" do
|
|
538
|
+
|
|
539
|
+
before(:each) do
|
|
540
|
+
@app = test_app
|
|
541
|
+
custom_analyser = Class.new do
|
|
542
|
+
def some_analyser_method(temp_object)
|
|
543
|
+
"abc" + temp_object.data[0..0]
|
|
544
|
+
end
|
|
545
|
+
def number_of_As(temp_object); temp_object.data.count('A'); end
|
|
546
|
+
end
|
|
547
|
+
@app.analyser.register(custom_analyser)
|
|
548
|
+
@app.define_macro(MyModel, :image_accessor)
|
|
549
|
+
Item.class_eval do
|
|
550
|
+
image_accessor :preview_image
|
|
551
|
+
end
|
|
552
|
+
@item = Item.new
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
describe "magic attributes" do
|
|
556
|
+
|
|
557
|
+
it "should default the magic attribute as nil" do
|
|
558
|
+
@item.preview_image_some_analyser_method.should be_nil
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
it "should set the magic attribute when assigned" do
|
|
562
|
+
@item.preview_image = '123'
|
|
563
|
+
@item.preview_image_some_analyser_method.should == 'abc1'
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
it "should not set non-magic attributes with the same prefix when assigned" do
|
|
567
|
+
@item.preview_image_blah_blah = 'wassup'
|
|
568
|
+
@item.preview_image = '123'
|
|
569
|
+
@item.preview_image_blah_blah.should == 'wassup'
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
it "should update the magic attribute when something else is assigned" do
|
|
573
|
+
@item.preview_image = '123'
|
|
574
|
+
@item.preview_image = '456'
|
|
575
|
+
@item.preview_image_some_analyser_method.should == 'abc4'
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
it "should not reset non-magic attributes with the same prefix when set to nil" do
|
|
579
|
+
@item.preview_image_blah_blah = 'wassup'
|
|
580
|
+
@item.preview_image = '123'
|
|
581
|
+
@item.preview_image = nil
|
|
582
|
+
@item.preview_image_blah_blah.should == 'wassup'
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
it "should work for size too" do
|
|
586
|
+
@item.preview_image = '123'
|
|
587
|
+
@item.preview_image_size.should == 3
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
it "should store the original file name if it exists" do
|
|
591
|
+
data = 'jasdlkf sadjl'
|
|
592
|
+
data.stub!(:original_filename).and_return('hello.png')
|
|
593
|
+
@item.preview_image = data
|
|
594
|
+
@item.preview_image_name.should == 'hello.png'
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
describe "meta from magic attributes" do
|
|
600
|
+
|
|
601
|
+
it "should set the meta for the magic attribute when assigned" do
|
|
602
|
+
@item.preview_image = '123'
|
|
603
|
+
@item.preview_image.meta[:some_analyser_method].should == 'abc1'
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
it "should not set meta for non-magic attributes with the same prefix when assigned" do
|
|
607
|
+
@item.preview_image = '123'
|
|
608
|
+
@item.preview_image.meta[:blah_blah].should be_nil
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
it "should update the meta for the magic attribute when something else is assigned" do
|
|
612
|
+
@item.preview_image = '123'
|
|
613
|
+
@item.preview_image = '456'
|
|
614
|
+
@item.preview_image.meta[:some_analyser_method].should == 'abc4'
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
it "should include the meta for size too" do
|
|
618
|
+
@item.preview_image = '123'
|
|
619
|
+
@item.preview_image.meta[:size].should == 3
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
it "should store the meta for the original file name if it exists" do
|
|
623
|
+
data = 'jasdlkf sadjl'
|
|
624
|
+
data.stub!(:original_filename).and_return('hello.png')
|
|
625
|
+
@item.preview_image = data
|
|
626
|
+
@item.preview_image.meta[:name].should == 'hello.png'
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
it "should still have the meta after reload" do
|
|
630
|
+
@item.preview_image = '123'
|
|
631
|
+
@item.save!
|
|
632
|
+
item = Item.find(@item.id)
|
|
633
|
+
item.preview_image.meta[:some_analyser_method].should == 'abc1'
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
describe "delegating methods to the job" do
|
|
639
|
+
before(:each) do
|
|
640
|
+
@item.preview_image = "DATASTRING"
|
|
641
|
+
end
|
|
642
|
+
it "should have properties from the analyser" do
|
|
643
|
+
@item.preview_image.number_of_As.should == 2
|
|
644
|
+
end
|
|
645
|
+
it "should report that it responds to analyser methods" do
|
|
646
|
+
@item.preview_image.respond_to?(:number_of_As).should be_true
|
|
647
|
+
end
|
|
648
|
+
it "should include analyser methods in methods" do
|
|
649
|
+
@item.preview_image.methods.include?('number_of_As'.to_method_name).should be_true
|
|
650
|
+
end
|
|
651
|
+
it "should include analyser methods in public_methods" do
|
|
652
|
+
@item.preview_image.public_methods.include?('number_of_As'.to_method_name).should be_true
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
it "should update when something new is assigned" do
|
|
656
|
+
@item.preview_image = 'ANEWDATASTRING'
|
|
657
|
+
@item.preview_image.number_of_As.should == 3
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
describe "from a new model object" do
|
|
661
|
+
before(:each) do
|
|
662
|
+
@app.datastore.stub!(:store).and_return('my_uid')
|
|
663
|
+
item = Item.create!(:preview_image => 'DATASTRING')
|
|
664
|
+
@item = Item.find(item.id)
|
|
665
|
+
end
|
|
666
|
+
it "should load the content then delegate the method" do
|
|
667
|
+
@app.datastore.should_receive(:retrieve).with('my_uid').and_return(['DATASTRING', {}])
|
|
668
|
+
@item.preview_image.number_of_As.should == 2
|
|
669
|
+
end
|
|
670
|
+
it "should use the magic attribute if there is one, and not load the content" do
|
|
671
|
+
@app.datastore.should_not_receive(:retrieve)
|
|
672
|
+
@item.should_receive(:preview_image_some_analyser_method).at_least(:once).and_return('result yo')
|
|
673
|
+
@item.preview_image.some_analyser_method.should == 'result yo'
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
it "should use the magic attribute for size if there is one, and not the job object" do
|
|
677
|
+
@item.preview_image.send(:job).should_not_receive(:size)
|
|
678
|
+
@item.should_receive("preview_image_size").and_return(17)
|
|
679
|
+
@item.preview_image.size.should == 17
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
it "should delegate 'size' to the job object if there is no magic attribute for it" do
|
|
683
|
+
@item.other_image = 'blahdata'
|
|
684
|
+
@item.other_image.send(:job).should_receive(:size).and_return 54
|
|
685
|
+
@item.other_image.size.should == 54
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
it "should not raise an error if a non-existent method is called" do
|
|
691
|
+
# Just checking method missing works ok
|
|
692
|
+
lambda{
|
|
693
|
+
@item.preview_image.eggbert
|
|
694
|
+
}.should raise_error(NoMethodError)
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
describe "job shortcuts" do
|
|
699
|
+
before(:each) do
|
|
700
|
+
@app.job :bacon do
|
|
701
|
+
process :breakfast
|
|
702
|
+
end
|
|
703
|
+
@item = Item.new :preview_image => 'gurg'
|
|
704
|
+
end
|
|
705
|
+
it "should add job shortcuts for that app" do
|
|
706
|
+
job = @item.preview_image.bacon
|
|
707
|
+
job.steps.first.should be_a(Dragonfly::Job::Process)
|
|
708
|
+
end
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
describe "setting things on the attachment" do
|
|
712
|
+
|
|
713
|
+
before(:each) do
|
|
714
|
+
@item = Item.new
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
describe "name" do
|
|
718
|
+
before(:each) do
|
|
719
|
+
@item.preview_image = "Hello"
|
|
720
|
+
@item.preview_image.name = 'hello.there'
|
|
721
|
+
end
|
|
722
|
+
it "should allow for setting the name" do
|
|
723
|
+
@item.preview_image.name.should == 'hello.there'
|
|
724
|
+
end
|
|
725
|
+
it "should update the magic attribute" do
|
|
726
|
+
@item.preview_image_name.should == 'hello.there'
|
|
727
|
+
end
|
|
728
|
+
it "should return the name" do
|
|
729
|
+
(@item.preview_image.name = 'no.silly').should == 'no.silly'
|
|
730
|
+
end
|
|
731
|
+
it "should update the ext too" do
|
|
732
|
+
@item.preview_image.ext.should == 'there'
|
|
733
|
+
end
|
|
734
|
+
end
|
|
735
|
+
|
|
736
|
+
describe "meta" do
|
|
737
|
+
before(:each) do
|
|
738
|
+
@item.preview_image = "Hello all"
|
|
739
|
+
@item.preview_image.meta = {:slime => 'balls'}
|
|
740
|
+
end
|
|
741
|
+
it "should allow for setting the meta" do
|
|
742
|
+
@item.preview_image.meta.should == {:slime => 'balls'}
|
|
743
|
+
end
|
|
744
|
+
it "should allow for updating the meta" do
|
|
745
|
+
@item.preview_image.meta[:numb] = 'nuts'
|
|
746
|
+
@item.preview_image.meta.should == {:slime => 'balls', :numb => 'nuts'}
|
|
747
|
+
end
|
|
748
|
+
it "should return the meta" do
|
|
749
|
+
(@item.preview_image.meta = {:doogs => 'boogs'}).should == {:doogs => 'boogs'}
|
|
750
|
+
end
|
|
751
|
+
it "should save it correctly" do
|
|
752
|
+
@item.save!
|
|
753
|
+
item = Item.find(@item.id)
|
|
754
|
+
item.preview_image.apply.meta.should include_hash(:slime => 'balls')
|
|
755
|
+
end
|
|
756
|
+
it "should include meta info about the model" do
|
|
757
|
+
@item.save!
|
|
758
|
+
item = Item.find(@item.id)
|
|
759
|
+
item.preview_image.meta.should include_hash(:model_class => 'Item', :model_attachment => :preview_image)
|
|
760
|
+
end
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
describe "inheritance" do
|
|
768
|
+
|
|
769
|
+
before(:all) do
|
|
770
|
+
@app = test_app
|
|
771
|
+
@app2 = test_app
|
|
772
|
+
@app.define_macro(MyModel, :image_accessor)
|
|
773
|
+
@app2.define_macro(MyModel, :egg_accessor)
|
|
774
|
+
Car.class_eval do
|
|
775
|
+
image_accessor :image
|
|
776
|
+
end
|
|
777
|
+
Photo.class_eval do
|
|
778
|
+
egg_accessor :image
|
|
779
|
+
end
|
|
780
|
+
|
|
781
|
+
@base_class = Car
|
|
782
|
+
class ReliantRobin < Car; image_accessor :reliant_image; end
|
|
783
|
+
@subclass = ReliantRobin
|
|
784
|
+
class ReliantRobinWithModule < Car
|
|
785
|
+
include Module.new
|
|
786
|
+
image_accessor :reliant_image
|
|
787
|
+
end
|
|
788
|
+
@subclass_with_module = ReliantRobinWithModule
|
|
789
|
+
@unrelated_class = Photo
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
it "should allow assigning base class accessors" do
|
|
793
|
+
@base_class.create! :image => 'blah'
|
|
794
|
+
end
|
|
795
|
+
it "should not allow assigning subclass accessors in the base class" do
|
|
796
|
+
@base_class.new.should_not respond_to(:reliant_image=)
|
|
797
|
+
end
|
|
798
|
+
it "should allow assigning base class accessors in the subclass" do
|
|
799
|
+
@subclass.create! :image => 'blah'
|
|
800
|
+
end
|
|
801
|
+
it "should allow assigning subclass accessors in the subclass" do
|
|
802
|
+
@subclass.create! :reliant_image => 'blah'
|
|
803
|
+
end
|
|
804
|
+
it "should allow assigning base class accessors in the subclass, even if it has mixins" do
|
|
805
|
+
@subclass_with_module.create! :image => 'blah'
|
|
806
|
+
end
|
|
807
|
+
it "should allow assigning subclass accessors in the subclass, even if it has mixins" do
|
|
808
|
+
@subclass_with_module.create! :reliant_image => 'blah'
|
|
809
|
+
end
|
|
810
|
+
it "should return the correct attachment classes for the base class" do
|
|
811
|
+
@base_class.dragonfly_attachment_classes.should match_attachment_classes([[Car, :image, @app]])
|
|
812
|
+
end
|
|
813
|
+
it "should return the correct attachment classes for the subclass" do
|
|
814
|
+
@subclass.dragonfly_attachment_classes.should match_attachment_classes([[ReliantRobin, :image, @app], [ReliantRobin, :reliant_image, @app]])
|
|
815
|
+
end
|
|
816
|
+
it "should return the correct attachment classes for the subclass with module" do
|
|
817
|
+
@subclass_with_module.dragonfly_attachment_classes.should match_attachment_classes([[ReliantRobinWithModule, :image, @app], [ReliantRobinWithModule, :reliant_image, @app]])
|
|
818
|
+
end
|
|
819
|
+
it "should return the correct attachment classes for a class from a different hierarchy" do
|
|
820
|
+
@unrelated_class.dragonfly_attachment_classes.should match_attachment_classes([[Photo, :image, @app2]])
|
|
821
|
+
end
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
describe "setting the url" do
|
|
825
|
+
before(:each) do
|
|
826
|
+
set_up_item_class
|
|
827
|
+
@item = Item.new
|
|
828
|
+
stub_request(:get, "http://some.url/yo.png").to_return(:body => "aaaaayo")
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
it "should allow setting the url" do
|
|
832
|
+
@item.preview_image_url = 'http://some.url/yo.png'
|
|
833
|
+
@item.preview_image.data.should == 'aaaaayo'
|
|
834
|
+
end
|
|
835
|
+
it "should return nil always for the reader" do
|
|
836
|
+
@item.preview_image_url = 'http://some.url/yo.png'
|
|
837
|
+
@item.preview_image_url.should be_nil
|
|
838
|
+
end
|
|
839
|
+
it "should have set the name" do
|
|
840
|
+
@item.preview_image_url = 'http://some.url/yo.png'
|
|
841
|
+
@item.preview_image_name.should == 'yo.png'
|
|
842
|
+
@item.preview_image.meta[:name].should == 'yo.png'
|
|
843
|
+
end
|
|
844
|
+
[nil, ""].each do |value|
|
|
845
|
+
it "should do nothing if set with #{value.inspect}" do
|
|
846
|
+
@item.preview_image_url = value
|
|
847
|
+
@item.preview_image.should be_nil
|
|
848
|
+
end
|
|
849
|
+
end
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
describe "removing the accessor with e.g. a form" do
|
|
853
|
+
before(:each) do
|
|
854
|
+
set_up_item_class
|
|
855
|
+
@item = Item.new
|
|
856
|
+
@item.preview_image = "something"
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
[
|
|
860
|
+
1,
|
|
861
|
+
"1",
|
|
862
|
+
true,
|
|
863
|
+
"true",
|
|
864
|
+
"blahblah"
|
|
865
|
+
].each do |value|
|
|
866
|
+
it "should remove the accessor if passed #{value.inspect}" do
|
|
867
|
+
@item.remove_preview_image = value
|
|
868
|
+
@item.preview_image.should be_nil
|
|
869
|
+
end
|
|
870
|
+
|
|
871
|
+
it "should return true when called if set with #{value.inspect}" do
|
|
872
|
+
@item.remove_preview_image = value
|
|
873
|
+
@item.remove_preview_image.should be_true
|
|
874
|
+
end
|
|
875
|
+
end
|
|
876
|
+
|
|
877
|
+
[
|
|
878
|
+
0,
|
|
879
|
+
"0",
|
|
880
|
+
false,
|
|
881
|
+
"false",
|
|
882
|
+
"",
|
|
883
|
+
nil
|
|
884
|
+
].each do |value|
|
|
885
|
+
it "should not remove the accessor if passed #{value.inspect}" do
|
|
886
|
+
@item.remove_preview_image = value
|
|
887
|
+
@item.preview_image.should_not be_nil
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
it "should return false when called if set with #{value.inspect}" do
|
|
891
|
+
@item.remove_preview_image = value
|
|
892
|
+
@item.remove_preview_image.should be_false
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
it "should return false by default for the getter" do
|
|
897
|
+
@item.remove_preview_image.should be_false
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
describe "callbacks" do
|
|
903
|
+
|
|
904
|
+
describe "after_assign" do
|
|
905
|
+
|
|
906
|
+
before(:each) do
|
|
907
|
+
@app = test_app
|
|
908
|
+
@app.define_macro(MyModel, :image_accessor)
|
|
909
|
+
end
|
|
910
|
+
|
|
911
|
+
describe "as a block" do
|
|
912
|
+
|
|
913
|
+
def set_after_assign(*args, &block)
|
|
914
|
+
Item.class_eval do
|
|
915
|
+
image_accessor :preview_image do
|
|
916
|
+
after_assign(*args, &block)
|
|
917
|
+
end
|
|
918
|
+
end
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
it "should call it after assign" do
|
|
922
|
+
x = nil
|
|
923
|
+
set_after_assign{ x = 3 }
|
|
924
|
+
Item.new.preview_image = "hello"
|
|
925
|
+
x.should == 3
|
|
926
|
+
end
|
|
927
|
+
|
|
928
|
+
it "should not call it after unassign" do
|
|
929
|
+
x = nil
|
|
930
|
+
set_after_assign{ x = 3 }
|
|
931
|
+
Item.new.preview_image = nil
|
|
932
|
+
x.should be_nil
|
|
933
|
+
end
|
|
934
|
+
|
|
935
|
+
it "should yield the attachment" do
|
|
936
|
+
x = nil
|
|
937
|
+
set_after_assign{|a| x = a.data }
|
|
938
|
+
Item.new.preview_image = "discussion"
|
|
939
|
+
x.should == "discussion"
|
|
940
|
+
end
|
|
941
|
+
|
|
942
|
+
it "should evaluate in the model context" do
|
|
943
|
+
x = nil
|
|
944
|
+
set_after_assign{ x = title.upcase }
|
|
945
|
+
item = Item.new
|
|
946
|
+
item.title = "big"
|
|
947
|
+
item.preview_image = "jobs"
|
|
948
|
+
x.should == "BIG"
|
|
949
|
+
end
|
|
950
|
+
|
|
951
|
+
it "should allow passing a symbol for calling a model method" do
|
|
952
|
+
set_after_assign :set_title
|
|
953
|
+
item = Item.new
|
|
954
|
+
def item.set_title; self.title = 'duggen'; end
|
|
955
|
+
item.preview_image = "jobs"
|
|
956
|
+
item.title.should == "duggen"
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
it "should allow passing multiple symbols" do
|
|
960
|
+
set_after_assign :set_title, :upcase_title
|
|
961
|
+
item = Item.new
|
|
962
|
+
def item.set_title; self.title = 'doobie'; end
|
|
963
|
+
def item.upcase_title; self.title.upcase!; end
|
|
964
|
+
item.preview_image = "jobs"
|
|
965
|
+
item.title.should == "DOOBIE"
|
|
966
|
+
end
|
|
967
|
+
|
|
968
|
+
it "should not re-trigger callbacks (causing an infinite loop)" do
|
|
969
|
+
set_after_assign{|a| self.preview_image = 'dogman' }
|
|
970
|
+
item = Item.new
|
|
971
|
+
item.preview_image = "hello"
|
|
972
|
+
end
|
|
973
|
+
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
end
|
|
977
|
+
|
|
978
|
+
describe "after_unassign" do
|
|
979
|
+
before(:each) do
|
|
980
|
+
@app = test_app
|
|
981
|
+
@app.define_macro(MyModel, :image_accessor)
|
|
982
|
+
Item.class_eval do
|
|
983
|
+
image_accessor :preview_image do
|
|
984
|
+
after_unassign{ self.title = 'unassigned' }
|
|
985
|
+
end
|
|
986
|
+
end
|
|
987
|
+
@item = Item.new :title => 'yo'
|
|
988
|
+
end
|
|
989
|
+
|
|
990
|
+
it "should not call it after assign" do
|
|
991
|
+
@item.preview_image = 'suggs'
|
|
992
|
+
@item.title.should == 'yo'
|
|
993
|
+
end
|
|
994
|
+
|
|
995
|
+
it "should call it after unassign" do
|
|
996
|
+
@item.preview_image = nil
|
|
997
|
+
@item.title.should == 'unassigned'
|
|
998
|
+
end
|
|
999
|
+
end
|
|
1000
|
+
|
|
1001
|
+
describe "copy_to" do
|
|
1002
|
+
before(:each) do
|
|
1003
|
+
@app = test_app
|
|
1004
|
+
@app.define_macro(MyModel, :image_accessor)
|
|
1005
|
+
@app.processor.add(:append) do |temp_object, string|
|
|
1006
|
+
temp_object.data + string
|
|
1007
|
+
end
|
|
1008
|
+
Item.class_eval do
|
|
1009
|
+
image_accessor :preview_image do
|
|
1010
|
+
copy_to(:other_image){|a| a.process(:append, title) }
|
|
1011
|
+
copy_to(:yet_another_image)
|
|
1012
|
+
end
|
|
1013
|
+
image_accessor :other_image
|
|
1014
|
+
image_accessor :yet_another_image
|
|
1015
|
+
end
|
|
1016
|
+
@item = Item.new :title => 'yo'
|
|
1017
|
+
end
|
|
1018
|
+
|
|
1019
|
+
it "should copy to the other image when assigned" do
|
|
1020
|
+
@item.preview_image = 'hello bear'
|
|
1021
|
+
@item.other_image.data.should == 'hello bearyo'
|
|
1022
|
+
end
|
|
1023
|
+
|
|
1024
|
+
it "should remove the other image when unassigned" do
|
|
1025
|
+
@item.preview_image = 'hello bear'
|
|
1026
|
+
@item.preview_image = nil
|
|
1027
|
+
@item.other_image.should be_nil
|
|
1028
|
+
end
|
|
1029
|
+
|
|
1030
|
+
it "should allow simply copying over without args" do
|
|
1031
|
+
@item.preview_image = 'hello bear'
|
|
1032
|
+
@item.yet_another_image.data.should == 'hello bear'
|
|
1033
|
+
end
|
|
1034
|
+
|
|
1035
|
+
end
|
|
1036
|
+
|
|
1037
|
+
end
|
|
1038
|
+
|
|
1039
|
+
describe "storage_opts" do
|
|
1040
|
+
|
|
1041
|
+
def set_storage_opts(*args, &block)
|
|
1042
|
+
Item.class_eval do
|
|
1043
|
+
image_accessor :preview_image do
|
|
1044
|
+
storage_opts(*args, &block)
|
|
1045
|
+
end
|
|
1046
|
+
end
|
|
1047
|
+
end
|
|
1048
|
+
|
|
1049
|
+
before(:each) do
|
|
1050
|
+
@app = test_app
|
|
1051
|
+
@app.define_macro(MyModel, :image_accessor)
|
|
1052
|
+
end
|
|
1053
|
+
|
|
1054
|
+
it "should send the specified options to the datastore on store" do
|
|
1055
|
+
set_storage_opts :egg => 'head'
|
|
1056
|
+
item = Item.new :preview_image => 'hello'
|
|
1057
|
+
@app.datastore.should_receive(:store).with(anything, hash_including(:egg => 'head'))
|
|
1058
|
+
item.save!
|
|
1059
|
+
end
|
|
1060
|
+
|
|
1061
|
+
it "should allow putting in a proc" do
|
|
1062
|
+
set_storage_opts{ {:egg => 'numb'} }
|
|
1063
|
+
item = Item.new :preview_image => 'hello'
|
|
1064
|
+
@app.datastore.should_receive(:store).with(anything, hash_including(:egg => 'numb'))
|
|
1065
|
+
item.save!
|
|
1066
|
+
end
|
|
1067
|
+
|
|
1068
|
+
it "should yield the attachment and exec in model context" do
|
|
1069
|
+
set_storage_opts{|a| {:egg => (a.data + title)} }
|
|
1070
|
+
item = Item.new :title => 'lump', :preview_image => 'hello'
|
|
1071
|
+
@app.datastore.should_receive(:store).with(anything, hash_including(:egg => 'hellolump'))
|
|
1072
|
+
item.save!
|
|
1073
|
+
end
|
|
1074
|
+
|
|
1075
|
+
it "should allow giving it a method symbol" do
|
|
1076
|
+
set_storage_opts :special_ops
|
|
1077
|
+
item = Item.new :preview_image => 'hello'
|
|
1078
|
+
def item.special_ops; {:a => 1}; end
|
|
1079
|
+
@app.datastore.should_receive(:store).with(anything, hash_including(:a => 1))
|
|
1080
|
+
item.save!
|
|
1081
|
+
end
|
|
1082
|
+
|
|
1083
|
+
it "should allow setting more than once" do
|
|
1084
|
+
Item.class_eval do
|
|
1085
|
+
image_accessor :preview_image do
|
|
1086
|
+
storage_opts{{ :a => title, :b => 'dumple' }}
|
|
1087
|
+
storage_opts{{ :b => title.upcase, :c => 'digby' }}
|
|
1088
|
+
end
|
|
1089
|
+
end
|
|
1090
|
+
item = Item.new :title => 'lump', :preview_image => 'hello'
|
|
1091
|
+
@app.datastore.should_receive(:store).with(anything, hash_including(
|
|
1092
|
+
:a => 'lump', :b => 'LUMP', :c => 'digby'
|
|
1093
|
+
))
|
|
1094
|
+
item.save!
|
|
1095
|
+
end
|
|
1096
|
+
end
|
|
1097
|
+
|
|
1098
|
+
describe "storage_path, etc." do
|
|
1099
|
+
|
|
1100
|
+
def set_storage_path(path=nil, &block)
|
|
1101
|
+
Item.class_eval do
|
|
1102
|
+
image_accessor :preview_image do
|
|
1103
|
+
storage_path(path, &block)
|
|
1104
|
+
end
|
|
1105
|
+
def monkey
|
|
1106
|
+
"mr/#{title}/monkey"
|
|
1107
|
+
end
|
|
1108
|
+
end
|
|
1109
|
+
end
|
|
1110
|
+
|
|
1111
|
+
before(:each) do
|
|
1112
|
+
@app = test_app
|
|
1113
|
+
@app.define_macro(MyModel, :image_accessor)
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1116
|
+
it "should allow setting as a string" do
|
|
1117
|
+
set_storage_path 'always/the/same'
|
|
1118
|
+
item = Item.new :preview_image => 'bilbo'
|
|
1119
|
+
@app.datastore.should_receive(:store).with(anything, hash_including(
|
|
1120
|
+
:path => 'always/the/same'
|
|
1121
|
+
))
|
|
1122
|
+
item.save!
|
|
1123
|
+
end
|
|
1124
|
+
|
|
1125
|
+
it "should allow setting as a symbol" do
|
|
1126
|
+
set_storage_path :monkey
|
|
1127
|
+
item = Item.new :title => 'billy'
|
|
1128
|
+
item.preview_image = 'bilbo'
|
|
1129
|
+
@app.datastore.should_receive(:store).with(anything, hash_including(
|
|
1130
|
+
:path => 'mr/billy/monkey'
|
|
1131
|
+
))
|
|
1132
|
+
item.save!
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
it "should allow setting as a block" do
|
|
1136
|
+
set_storage_path{|a| "#{a.data}/megs/#{title}" }
|
|
1137
|
+
item = Item.new :title => 'billy'
|
|
1138
|
+
item.preview_image = 'bilbo'
|
|
1139
|
+
@app.datastore.should_receive(:store).with(anything, hash_including(
|
|
1140
|
+
:path => 'bilbo/megs/billy'
|
|
1141
|
+
))
|
|
1142
|
+
item.save!
|
|
1143
|
+
end
|
|
1144
|
+
|
|
1145
|
+
it "should work for other storage_xxx declarations" do
|
|
1146
|
+
Item.class_eval do
|
|
1147
|
+
image_accessor :preview_image do
|
|
1148
|
+
storage_eggs 23
|
|
1149
|
+
end
|
|
1150
|
+
end
|
|
1151
|
+
item = Item.new :preview_image => 'bilbo'
|
|
1152
|
+
@app.datastore.should_receive(:store).with(anything, hash_including(
|
|
1153
|
+
:eggs => 23
|
|
1154
|
+
))
|
|
1155
|
+
item.save!
|
|
1156
|
+
end
|
|
1157
|
+
end
|
|
1158
|
+
|
|
1159
|
+
describe "unknown config method" do
|
|
1160
|
+
it "should raise an error" do
|
|
1161
|
+
lambda{
|
|
1162
|
+
Item.class_eval do
|
|
1163
|
+
image_accessor :preview_image do
|
|
1164
|
+
what :now?
|
|
1165
|
+
end
|
|
1166
|
+
end
|
|
1167
|
+
}.should raise_error(NoMethodError)
|
|
1168
|
+
end
|
|
1169
|
+
end
|
|
1170
|
+
|
|
1171
|
+
describe "changed?" do
|
|
1172
|
+
before(:each) do
|
|
1173
|
+
set_up_item_class
|
|
1174
|
+
@item = Item.new
|
|
1175
|
+
end
|
|
1176
|
+
|
|
1177
|
+
it "should be changed when assigned" do
|
|
1178
|
+
@item.preview_image = 'ggg'
|
|
1179
|
+
@item.preview_image.should be_changed
|
|
1180
|
+
end
|
|
1181
|
+
|
|
1182
|
+
it "should not be changed when saved" do
|
|
1183
|
+
@item.preview_image = 'ggg'
|
|
1184
|
+
@item.save!
|
|
1185
|
+
@item.preview_image.should_not be_changed
|
|
1186
|
+
end
|
|
1187
|
+
|
|
1188
|
+
it "should not be changed when reloaded" do
|
|
1189
|
+
@item.preview_image = 'ggg'
|
|
1190
|
+
@item.save!
|
|
1191
|
+
item = Item.find(@item.id)
|
|
1192
|
+
item.preview_image.should_not be_changed
|
|
1193
|
+
end
|
|
1194
|
+
end
|
|
1195
|
+
|
|
1196
|
+
describe "retain and pending" do
|
|
1197
|
+
before(:each) do
|
|
1198
|
+
set_up_item_class(@app=test_app)
|
|
1199
|
+
@app.analyser.add :some_analyser_method do |temp_object|
|
|
1200
|
+
temp_object.data.upcase
|
|
1201
|
+
end
|
|
1202
|
+
@item = Item.new
|
|
1203
|
+
end
|
|
1204
|
+
|
|
1205
|
+
it "should return nil if there are no changes" do
|
|
1206
|
+
@item.retained_preview_image.should be_nil
|
|
1207
|
+
end
|
|
1208
|
+
|
|
1209
|
+
it "should return nil if assigned but not saved" do
|
|
1210
|
+
@item.preview_image = 'hello'
|
|
1211
|
+
@item.retained_preview_image.should be_nil
|
|
1212
|
+
end
|
|
1213
|
+
|
|
1214
|
+
it "should return nil if assigned and saved" do
|
|
1215
|
+
@item.preview_image = 'hello'
|
|
1216
|
+
@item.save!
|
|
1217
|
+
@item.retained_preview_image.should be_nil
|
|
1218
|
+
end
|
|
1219
|
+
|
|
1220
|
+
it "should return the saved stuff if assigned and retained" do
|
|
1221
|
+
@item.preview_image = 'hello'
|
|
1222
|
+
@item.preview_image.name = 'dog.biscuit'
|
|
1223
|
+
@app.datastore.should_receive(:store).with(a_temp_object_with_data('hello'), anything).and_return('new/uid')
|
|
1224
|
+
@item.preview_image.retain!
|
|
1225
|
+
Dragonfly::Serializer.marshal_decode(@item.retained_preview_image).should == {
|
|
1226
|
+
:uid => 'new/uid',
|
|
1227
|
+
:some_analyser_method => 'HELLO',
|
|
1228
|
+
:size => 5,
|
|
1229
|
+
:name => 'dog.biscuit'
|
|
1230
|
+
}
|
|
1231
|
+
end
|
|
1232
|
+
|
|
1233
|
+
it "should return nil if assigned, retained and saved" do
|
|
1234
|
+
@item.preview_image = 'hello'
|
|
1235
|
+
@item.preview_image.retain!
|
|
1236
|
+
@item.save!
|
|
1237
|
+
@item.retained_preview_image.should be_nil
|
|
1238
|
+
end
|
|
1239
|
+
|
|
1240
|
+
it "should return nil if assigned, saved and retained" do
|
|
1241
|
+
@item.preview_image = 'hello'
|
|
1242
|
+
@item.save!
|
|
1243
|
+
@item.preview_image.retain!
|
|
1244
|
+
@item.retained_preview_image.should be_nil
|
|
1245
|
+
end
|
|
1246
|
+
|
|
1247
|
+
it "should return nil if no changes have been made" do
|
|
1248
|
+
@item.preview_image = 'hello'
|
|
1249
|
+
@item.save!
|
|
1250
|
+
item = Item.find(@item.id)
|
|
1251
|
+
item.preview_image.retain!
|
|
1252
|
+
item.retained_preview_image.should be_nil
|
|
1253
|
+
end
|
|
1254
|
+
end
|
|
1255
|
+
|
|
1256
|
+
describe "assigning from a pending state" do
|
|
1257
|
+
before(:each) do
|
|
1258
|
+
set_up_item_class(@app=test_app)
|
|
1259
|
+
@app.analyser.add :some_analyser_method do |temp_object|
|
|
1260
|
+
temp_object.data.upcase
|
|
1261
|
+
end
|
|
1262
|
+
@pending_string = Dragonfly::Serializer.marshal_encode(
|
|
1263
|
+
:uid => 'new/uid',
|
|
1264
|
+
:some_analyser_method => 'HELLO',
|
|
1265
|
+
:size => 5,
|
|
1266
|
+
:name => 'dog.biscuit'
|
|
1267
|
+
)
|
|
1268
|
+
@item = Item.new
|
|
1269
|
+
end
|
|
1270
|
+
|
|
1271
|
+
it "should update the attributes" do
|
|
1272
|
+
@item.retained_preview_image = @pending_string
|
|
1273
|
+
@item.preview_image_uid.should == 'new/uid'
|
|
1274
|
+
@item.preview_image_some_analyser_method.should == 'HELLO'
|
|
1275
|
+
@item.preview_image_size.should == 5
|
|
1276
|
+
@item.preview_image_name.should == 'dog.biscuit'
|
|
1277
|
+
end
|
|
1278
|
+
|
|
1279
|
+
it "should update the attachment meta" do
|
|
1280
|
+
@item.retained_preview_image = @pending_string
|
|
1281
|
+
@item.preview_image.meta[:some_analyser_method].should == 'HELLO'
|
|
1282
|
+
@item.preview_image.meta[:size].should == 5
|
|
1283
|
+
@item.preview_image.meta[:name].should == 'dog.biscuit'
|
|
1284
|
+
end
|
|
1285
|
+
|
|
1286
|
+
it "should be a normal fetch job" do
|
|
1287
|
+
@item.retained_preview_image = @pending_string
|
|
1288
|
+
@app.datastore.should_receive(:retrieve).with('new/uid').and_return(Dragonfly::TempObject.new('retrieved yo'))
|
|
1289
|
+
@item.preview_image.data.should == 'retrieved yo'
|
|
1290
|
+
end
|
|
1291
|
+
|
|
1292
|
+
it "should give the correct url" do
|
|
1293
|
+
@item.retained_preview_image = @pending_string
|
|
1294
|
+
@item.preview_image.url.should =~ %r{^/\w+/dog.biscuit$}
|
|
1295
|
+
end
|
|
1296
|
+
|
|
1297
|
+
it "should raise an error if the pending string contains a non-magic attr method" do
|
|
1298
|
+
pending_string = Dragonfly::Serializer.marshal_encode(
|
|
1299
|
+
:uid => 'new/uid',
|
|
1300
|
+
:some_analyser_method => 'HELLO',
|
|
1301
|
+
:size => 5,
|
|
1302
|
+
:name => 'dog.biscuit',
|
|
1303
|
+
:something => 'else'
|
|
1304
|
+
)
|
|
1305
|
+
item = @item
|
|
1306
|
+
lambda{
|
|
1307
|
+
item.retained_preview_image = pending_string
|
|
1308
|
+
}.should raise_error(Dragonfly::ActiveModelExtensions::Attachment::BadAssignmentKey)
|
|
1309
|
+
end
|
|
1310
|
+
|
|
1311
|
+
[nil, "", "asdfsad"].each do |value|
|
|
1312
|
+
it "should do nothing if assigned with #{value}" do
|
|
1313
|
+
@item.retained_preview_image = value
|
|
1314
|
+
@item.preview_image_uid.should be_nil
|
|
1315
|
+
end
|
|
1316
|
+
end
|
|
1317
|
+
|
|
1318
|
+
it "should return the pending string again" do
|
|
1319
|
+
@item.retained_preview_image = @pending_string
|
|
1320
|
+
Dragonfly::Serializer.marshal_decode(@item.retained_preview_image).should ==
|
|
1321
|
+
Dragonfly::Serializer.marshal_decode(@pending_string)
|
|
1322
|
+
end
|
|
1323
|
+
|
|
1324
|
+
it "should destroy the old one on save" do
|
|
1325
|
+
@item.preview_image = 'oldone'
|
|
1326
|
+
@app.datastore.should_receive(:store).with(a_temp_object_with_data('oldone'), anything).and_return('old/uid')
|
|
1327
|
+
@item.save!
|
|
1328
|
+
item = Item.find(@item.id)
|
|
1329
|
+
item.retained_preview_image = @pending_string
|
|
1330
|
+
@app.datastore.should_receive(:destroy).with('old/uid')
|
|
1331
|
+
item.save!
|
|
1332
|
+
end
|
|
1333
|
+
|
|
1334
|
+
describe "combinations of assignment" do
|
|
1335
|
+
before(:each) do
|
|
1336
|
+
end
|
|
1337
|
+
it "should destroy the previously retained one if something new is then assigned" do
|
|
1338
|
+
@item.retained_preview_image = @pending_string
|
|
1339
|
+
@app.datastore.should_receive(:destroy).with('new/uid')
|
|
1340
|
+
@item.preview_image = 'yet another new thing'
|
|
1341
|
+
end
|
|
1342
|
+
|
|
1343
|
+
it "should destroy the previously retained one if something new is already assigned" do
|
|
1344
|
+
@item.preview_image = 'yet another new thing'
|
|
1345
|
+
@app.datastore.should_receive(:destroy).with('new/uid')
|
|
1346
|
+
@item.retained_preview_image = @pending_string
|
|
1347
|
+
end
|
|
1348
|
+
|
|
1349
|
+
it "should destroy the previously retained one if nil is then assigned" do
|
|
1350
|
+
@item.retained_preview_image = @pending_string
|
|
1351
|
+
@app.datastore.should_receive(:destroy).with('new/uid')
|
|
1352
|
+
@item.preview_image = nil
|
|
1353
|
+
end
|
|
1354
|
+
|
|
1355
|
+
it "should destroy the previously retained one if nil is already assigned" do
|
|
1356
|
+
@item.preview_image = nil
|
|
1357
|
+
@app.datastore.should_receive(:destroy).with('new/uid')
|
|
1358
|
+
@item.retained_preview_image = @pending_string
|
|
1359
|
+
end
|
|
1360
|
+
end
|
|
1361
|
+
|
|
1362
|
+
end
|
|
1363
|
+
|
|
1364
|
+
describe "retain macro" do
|
|
1365
|
+
|
|
1366
|
+
before(:each) do
|
|
1367
|
+
@app = test_app
|
|
1368
|
+
@app.define_macro(MyModel, :image_accessor)
|
|
1369
|
+
Item.class_eval do
|
|
1370
|
+
image_accessor :preview_image do
|
|
1371
|
+
retain
|
|
1372
|
+
end
|
|
1373
|
+
end
|
|
1374
|
+
@item = Item.new
|
|
1375
|
+
end
|
|
1376
|
+
|
|
1377
|
+
it "should retain if specified" do
|
|
1378
|
+
@app.datastore.should_receive(:store).with(a_temp_object_with_data('boo'), anything).and_return('uidyo')
|
|
1379
|
+
@item.preview_image = 'boo'
|
|
1380
|
+
@item.retained_preview_image.should =~ /^\w+$/
|
|
1381
|
+
end
|
|
1382
|
+
end
|
|
1383
|
+
|
|
1384
|
+
describe "format and mime type" do
|
|
1385
|
+
before(:each) do
|
|
1386
|
+
@app = test_app
|
|
1387
|
+
@app.analyser.add :mime_type do |temp_object|
|
|
1388
|
+
'some/type'
|
|
1389
|
+
end
|
|
1390
|
+
set_up_item_class(@app)
|
|
1391
|
+
@item = Item.new
|
|
1392
|
+
@content = "doggo"
|
|
1393
|
+
@content.stub!(:original_filename).and_return('egg.png')
|
|
1394
|
+
end
|
|
1395
|
+
it "should trust the file extension with format if configured to" do
|
|
1396
|
+
@item.preview_image = @content
|
|
1397
|
+
@item.preview_image.format.should == :png
|
|
1398
|
+
end
|
|
1399
|
+
it "should trust the file extension with mime_type if configured to" do
|
|
1400
|
+
@item.preview_image = @content
|
|
1401
|
+
@item.preview_image.mime_type.should == 'image/png'
|
|
1402
|
+
end
|
|
1403
|
+
it "should not trust the file extension with format if configured not to" do
|
|
1404
|
+
@app.trust_file_extensions = false
|
|
1405
|
+
@item.preview_image = @content
|
|
1406
|
+
@item.preview_image.format.should == nil
|
|
1407
|
+
end
|
|
1408
|
+
it "should not trust the file extension with mime_type if configured not to" do
|
|
1409
|
+
@app.trust_file_extensions = false
|
|
1410
|
+
@item.preview_image = @content
|
|
1411
|
+
@item.preview_image.mime_type.should == 'some/type'
|
|
1412
|
+
end
|
|
1413
|
+
end
|
|
1414
|
+
|
|
1415
|
+
describe "inspect" do
|
|
1416
|
+
before(:each) do
|
|
1417
|
+
set_up_item_class
|
|
1418
|
+
@item = Item.new :preview_image => 'blug'
|
|
1419
|
+
@item.save!
|
|
1420
|
+
end
|
|
1421
|
+
it "should be awesome" do
|
|
1422
|
+
@item.preview_image.inspect.should =~ %r{^<Dragonfly Attachment uid="[^"]+".*>$}
|
|
1423
|
+
end
|
|
1424
|
+
end
|
|
1425
|
+
|
|
1426
|
+
end
|