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.
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