oahu-dragonfly 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. data/.rspec +1 -0
  2. data/.yardopts +24 -0
  3. data/Gemfile +30 -0
  4. data/History.md +323 -0
  5. data/LICENSE +20 -0
  6. data/README.md +88 -0
  7. data/Rakefile +50 -0
  8. data/VERSION +1 -0
  9. data/config.ru +14 -0
  10. data/docs.watchr +1 -0
  11. data/dragonfly.gemspec +297 -0
  12. data/extra_docs/Analysers.md +66 -0
  13. data/extra_docs/Caching.md +23 -0
  14. data/extra_docs/Configuration.md +124 -0
  15. data/extra_docs/Couch.md +49 -0
  16. data/extra_docs/DataStorage.md +153 -0
  17. data/extra_docs/Encoding.md +67 -0
  18. data/extra_docs/GeneralUsage.md +121 -0
  19. data/extra_docs/Generators.md +60 -0
  20. data/extra_docs/Heroku.md +50 -0
  21. data/extra_docs/ImageMagick.md +125 -0
  22. data/extra_docs/Index.md +33 -0
  23. data/extra_docs/MimeTypes.md +40 -0
  24. data/extra_docs/Models.md +272 -0
  25. data/extra_docs/Mongo.md +45 -0
  26. data/extra_docs/Processing.md +77 -0
  27. data/extra_docs/Rack.md +52 -0
  28. data/extra_docs/Rails2.md +57 -0
  29. data/extra_docs/Rails3.md +62 -0
  30. data/extra_docs/Sinatra.md +25 -0
  31. data/extra_docs/URLs.md +169 -0
  32. data/features/images.feature +47 -0
  33. data/features/no_processing.feature +14 -0
  34. data/features/rails_3.0.5.feature +8 -0
  35. data/features/steps/common_steps.rb +8 -0
  36. data/features/steps/dragonfly_steps.rb +66 -0
  37. data/features/steps/rails_steps.rb +28 -0
  38. data/features/support/env.rb +13 -0
  39. data/features/support/setup.rb +32 -0
  40. data/fixtures/rails_3.0.5/files/app/models/album.rb +7 -0
  41. data/fixtures/rails_3.0.5/files/app/views/albums/new.html.erb +7 -0
  42. data/fixtures/rails_3.0.5/files/app/views/albums/show.html.erb +6 -0
  43. data/fixtures/rails_3.0.5/files/config/initializers/dragonfly.rb +4 -0
  44. data/fixtures/rails_3.0.5/files/features/manage_album_images.feature +38 -0
  45. data/fixtures/rails_3.0.5/files/features/step_definitions/helper_steps.rb +7 -0
  46. data/fixtures/rails_3.0.5/files/features/step_definitions/image_steps.rb +25 -0
  47. data/fixtures/rails_3.0.5/files/features/support/paths.rb +17 -0
  48. data/fixtures/rails_3.0.5/files/features/text_images.feature +7 -0
  49. data/fixtures/rails_3.0.5/template.rb +20 -0
  50. data/irbrc.rb +18 -0
  51. data/lib/dragonfly.rb +55 -0
  52. data/lib/dragonfly/active_model_extensions.rb +13 -0
  53. data/lib/dragonfly/active_model_extensions/attachment.rb +250 -0
  54. data/lib/dragonfly/active_model_extensions/attachment_class_methods.rb +148 -0
  55. data/lib/dragonfly/active_model_extensions/class_methods.rb +95 -0
  56. data/lib/dragonfly/active_model_extensions/instance_methods.rb +28 -0
  57. data/lib/dragonfly/active_model_extensions/validations.rb +41 -0
  58. data/lib/dragonfly/analyser.rb +58 -0
  59. data/lib/dragonfly/analysis/file_command_analyser.rb +32 -0
  60. data/lib/dragonfly/analysis/image_magick_analyser.rb +6 -0
  61. data/lib/dragonfly/app.rb +172 -0
  62. data/lib/dragonfly/config/heroku.rb +19 -0
  63. data/lib/dragonfly/config/image_magick.rb +6 -0
  64. data/lib/dragonfly/config/rails.rb +20 -0
  65. data/lib/dragonfly/configurable.rb +207 -0
  66. data/lib/dragonfly/core_ext/array.rb +7 -0
  67. data/lib/dragonfly/core_ext/hash.rb +7 -0
  68. data/lib/dragonfly/core_ext/object.rb +12 -0
  69. data/lib/dragonfly/core_ext/string.rb +9 -0
  70. data/lib/dragonfly/core_ext/symbol.rb +9 -0
  71. data/lib/dragonfly/data_storage.rb +9 -0
  72. data/lib/dragonfly/data_storage/couch_data_store.rb +64 -0
  73. data/lib/dragonfly/data_storage/file_data_store.rb +141 -0
  74. data/lib/dragonfly/data_storage/mongo_data_store.rb +86 -0
  75. data/lib/dragonfly/data_storage/s3data_store.rb +145 -0
  76. data/lib/dragonfly/encoder.rb +13 -0
  77. data/lib/dragonfly/encoding/image_magick_encoder.rb +6 -0
  78. data/lib/dragonfly/function_manager.rb +71 -0
  79. data/lib/dragonfly/generation/image_magick_generator.rb +6 -0
  80. data/lib/dragonfly/generator.rb +9 -0
  81. data/lib/dragonfly/hash_with_css_style_keys.rb +21 -0
  82. data/lib/dragonfly/image_magick/analyser.rb +51 -0
  83. data/lib/dragonfly/image_magick/config.rb +41 -0
  84. data/lib/dragonfly/image_magick/encoder.rb +57 -0
  85. data/lib/dragonfly/image_magick/generator.rb +145 -0
  86. data/lib/dragonfly/image_magick/processor.rb +99 -0
  87. data/lib/dragonfly/image_magick/utils.rb +72 -0
  88. data/lib/dragonfly/image_magick_utils.rb +4 -0
  89. data/lib/dragonfly/job.rb +451 -0
  90. data/lib/dragonfly/job_builder.rb +39 -0
  91. data/lib/dragonfly/job_definitions.rb +26 -0
  92. data/lib/dragonfly/job_endpoint.rb +15 -0
  93. data/lib/dragonfly/loggable.rb +28 -0
  94. data/lib/dragonfly/middleware.rb +20 -0
  95. data/lib/dragonfly/processing/image_magick_processor.rb +6 -0
  96. data/lib/dragonfly/processor.rb +9 -0
  97. data/lib/dragonfly/rails/images.rb +27 -0
  98. data/lib/dragonfly/response.rb +97 -0
  99. data/lib/dragonfly/routed_endpoint.rb +40 -0
  100. data/lib/dragonfly/serializer.rb +32 -0
  101. data/lib/dragonfly/server.rb +113 -0
  102. data/lib/dragonfly/simple_cache.rb +23 -0
  103. data/lib/dragonfly/temp_object.rb +175 -0
  104. data/lib/dragonfly/url_mapper.rb +78 -0
  105. data/samples/beach.png +0 -0
  106. data/samples/egg.png +0 -0
  107. data/samples/round.gif +0 -0
  108. data/samples/sample.docx +0 -0
  109. data/samples/taj.jpg +0 -0
  110. data/spec/dragonfly/active_model_extensions/model_spec.rb +1426 -0
  111. data/spec/dragonfly/active_model_extensions/spec_helper.rb +91 -0
  112. data/spec/dragonfly/analyser_spec.rb +123 -0
  113. data/spec/dragonfly/analysis/file_command_analyser_spec.rb +48 -0
  114. data/spec/dragonfly/app_spec.rb +135 -0
  115. data/spec/dragonfly/configurable_spec.rb +461 -0
  116. data/spec/dragonfly/core_ext/array_spec.rb +19 -0
  117. data/spec/dragonfly/core_ext/hash_spec.rb +19 -0
  118. data/spec/dragonfly/core_ext/string_spec.rb +17 -0
  119. data/spec/dragonfly/core_ext/symbol_spec.rb +17 -0
  120. data/spec/dragonfly/data_storage/couch_data_store_spec.rb +76 -0
  121. data/spec/dragonfly/data_storage/file_data_store_spec.rb +296 -0
  122. data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +57 -0
  123. data/spec/dragonfly/data_storage/s3_data_store_spec.rb +258 -0
  124. data/spec/dragonfly/data_storage/shared_data_store_examples.rb +77 -0
  125. data/spec/dragonfly/function_manager_spec.rb +154 -0
  126. data/spec/dragonfly/hash_with_css_style_keys_spec.rb +24 -0
  127. data/spec/dragonfly/image_magick/analyser_spec.rb +64 -0
  128. data/spec/dragonfly/image_magick/encoder_spec.rb +41 -0
  129. data/spec/dragonfly/image_magick/generator_spec.rb +172 -0
  130. data/spec/dragonfly/image_magick/processor_spec.rb +233 -0
  131. data/spec/dragonfly/image_magick/utils_spec.rb +18 -0
  132. data/spec/dragonfly/job_builder_spec.rb +37 -0
  133. data/spec/dragonfly/job_definitions_spec.rb +35 -0
  134. data/spec/dragonfly/job_endpoint_spec.rb +173 -0
  135. data/spec/dragonfly/job_spec.rb +1046 -0
  136. data/spec/dragonfly/loggable_spec.rb +80 -0
  137. data/spec/dragonfly/middleware_spec.rb +47 -0
  138. data/spec/dragonfly/routed_endpoint_spec.rb +48 -0
  139. data/spec/dragonfly/serializer_spec.rb +61 -0
  140. data/spec/dragonfly/server_spec.rb +278 -0
  141. data/spec/dragonfly/simple_cache_spec.rb +27 -0
  142. data/spec/dragonfly/temp_object_spec.rb +306 -0
  143. data/spec/dragonfly/url_mapper_spec.rb +126 -0
  144. data/spec/functional/deprecations_spec.rb +51 -0
  145. data/spec/functional/image_magick_app_spec.rb +27 -0
  146. data/spec/functional/model_urls_spec.rb +85 -0
  147. data/spec/functional/remote_on_the_fly_spec.rb +51 -0
  148. data/spec/functional/to_response_spec.rb +31 -0
  149. data/spec/spec_helper.rb +51 -0
  150. data/spec/support/argument_matchers.rb +19 -0
  151. data/spec/support/image_matchers.rb +47 -0
  152. data/spec/support/simple_matchers.rb +53 -0
  153. data/yard/handlers/configurable_attr_handler.rb +38 -0
  154. data/yard/setup.rb +15 -0
  155. data/yard/templates/default/fulldoc/html/css/common.css +107 -0
  156. data/yard/templates/default/layout/html/layout.erb +89 -0
  157. data/yard/templates/default/module/html/configuration_summary.erb +31 -0
  158. data/yard/templates/default/module/setup.rb +17 -0
  159. 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
Binary file
Binary file
Binary file
Binary file
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