dragonfly 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of dragonfly might be problematic. Click here for more details.

Files changed (157) hide show
  1. data/.gitignore +2 -0
  2. data/.specopts +2 -0
  3. data/.yardopts +11 -5
  4. data/Gemfile +22 -0
  5. data/Gemfile.rails.2.3.5 +13 -0
  6. data/History.md +49 -0
  7. data/README.md +18 -28
  8. data/Rakefile +24 -36
  9. data/VERSION +1 -1
  10. data/config.ru +4 -1
  11. data/dragonfly.gemspec +85 -99
  12. data/extra_docs/Analysers.md +66 -30
  13. data/extra_docs/Caching.md +22 -0
  14. data/extra_docs/Configuration.md +116 -0
  15. data/extra_docs/DataStorage.md +114 -14
  16. data/extra_docs/Encoding.md +62 -37
  17. data/extra_docs/GeneralUsage.md +118 -0
  18. data/extra_docs/Generators.md +92 -0
  19. data/extra_docs/Heroku.md +51 -0
  20. data/extra_docs/Index.md +8 -9
  21. data/extra_docs/MimeTypes.md +18 -17
  22. data/extra_docs/Models.md +251 -0
  23. data/extra_docs/Processing.md +94 -70
  24. data/extra_docs/Rack.md +53 -0
  25. data/extra_docs/Rails2.md +44 -0
  26. data/extra_docs/Rails3.md +51 -0
  27. data/extra_docs/Sinatra.md +21 -0
  28. data/extra_docs/URLs.md +114 -0
  29. data/features/images.feature +6 -7
  30. data/features/no_processing.feature +0 -6
  31. data/features/rails_2.3.5.feature +1 -1
  32. data/features/rails_3.0.0.rc.feature +8 -0
  33. data/features/steps/dragonfly_steps.rb +14 -12
  34. data/features/steps/rails_steps.rb +20 -9
  35. data/features/support/env.rb +10 -11
  36. data/fixtures/files/app/views/albums/new.html.erb +4 -4
  37. data/fixtures/files/app/views/albums/show.html.erb +1 -1
  38. data/fixtures/files/features/manage_album_images.feature +1 -1
  39. data/fixtures/files/features/step_definitions/{album_steps.rb → image_steps.rb} +4 -3
  40. data/fixtures/files/features/support/paths.rb +2 -0
  41. data/fixtures/files/features/text_images.feature +7 -0
  42. data/fixtures/rails_3.0.0.rc/template.rb +21 -0
  43. data/irbrc.rb +2 -1
  44. data/lib/dragonfly.rb +4 -16
  45. data/lib/dragonfly/{active_record_extensions.rb → active_model_extensions.rb} +1 -1
  46. data/lib/dragonfly/active_model_extensions/attachment.rb +146 -0
  47. data/lib/dragonfly/{active_record_extensions → active_model_extensions}/class_methods.rb +5 -6
  48. data/lib/dragonfly/{active_record_extensions → active_model_extensions}/instance_methods.rb +1 -1
  49. data/lib/dragonfly/{active_record_extensions → active_model_extensions}/validations.rb +5 -9
  50. data/lib/dragonfly/analyser.rb +59 -0
  51. data/lib/dragonfly/analysis/file_command_analyser.rb +1 -1
  52. data/lib/dragonfly/analysis/r_magick_analyser.rb +46 -31
  53. data/lib/dragonfly/app.rb +138 -173
  54. data/lib/dragonfly/config/heroku.rb +19 -0
  55. data/lib/dragonfly/config/r_magick.rb +37 -0
  56. data/lib/dragonfly/config/{rails_defaults.rb → rails.rb} +6 -7
  57. data/lib/dragonfly/configurable.rb +30 -27
  58. data/lib/dragonfly/core_ext/object.rb +1 -1
  59. data/lib/dragonfly/data_storage/file_data_store.rb +59 -26
  60. data/lib/dragonfly/data_storage/mongo_data_store.rb +65 -0
  61. data/lib/dragonfly/data_storage/s3data_store.rb +31 -12
  62. data/lib/dragonfly/encoder.rb +13 -0
  63. data/lib/dragonfly/encoding/r_magick_encoder.rb +10 -19
  64. data/lib/dragonfly/endpoint.rb +43 -0
  65. data/lib/dragonfly/function_manager.rb +65 -0
  66. data/lib/dragonfly/{processing/r_magick_text_processor.rb → generation/r_magick_generator.rb} +25 -11
  67. data/lib/dragonfly/generator.rb +9 -0
  68. data/lib/dragonfly/job.rb +290 -0
  69. data/lib/dragonfly/job_builder.rb +39 -0
  70. data/lib/dragonfly/job_definitions.rb +26 -0
  71. data/lib/dragonfly/job_endpoint.rb +17 -0
  72. data/lib/dragonfly/loggable.rb +28 -0
  73. data/lib/dragonfly/middleware.rb +21 -14
  74. data/lib/dragonfly/processing/r_magick_processor.rb +71 -48
  75. data/lib/dragonfly/processor.rb +9 -0
  76. data/lib/dragonfly/r_magick_utils.rb +24 -0
  77. data/lib/dragonfly/rails/images.rb +10 -7
  78. data/lib/dragonfly/routed_endpoint.rb +42 -0
  79. data/lib/dragonfly/serializer.rb +32 -0
  80. data/lib/dragonfly/simple_cache.rb +23 -0
  81. data/lib/dragonfly/simple_endpoint.rb +64 -0
  82. data/lib/dragonfly/temp_object.rb +77 -45
  83. data/spec/argument_matchers.rb +7 -17
  84. data/spec/dragonfly/active_model_extensions/active_model_setup.rb +97 -0
  85. data/spec/dragonfly/active_model_extensions/active_record_setup.rb +85 -0
  86. data/spec/dragonfly/{active_record_extensions → active_model_extensions}/model_spec.rb +282 -244
  87. data/spec/dragonfly/active_model_extensions/spec_helper.rb +11 -0
  88. data/spec/dragonfly/analyser_spec.rb +123 -0
  89. data/spec/dragonfly/analysis/file_command_analyser_spec.rb +2 -2
  90. data/spec/dragonfly/analysis/r_magick_analyser_spec.rb +10 -1
  91. data/spec/dragonfly/app_spec.rb +175 -69
  92. data/spec/dragonfly/configurable_spec.rb +14 -0
  93. data/spec/dragonfly/data_storage/data_store_spec.rb +36 -9
  94. data/spec/dragonfly/data_storage/file_data_store_spec.rb +61 -38
  95. data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +18 -0
  96. data/spec/dragonfly/data_storage/s3_data_store_spec.rb +34 -39
  97. data/spec/dragonfly/deprecation_spec.rb +20 -0
  98. data/spec/dragonfly/function_manager_spec.rb +154 -0
  99. data/spec/dragonfly/generation/r_magick_generator_spec.rb +119 -0
  100. data/spec/dragonfly/job_builder_spec.rb +37 -0
  101. data/spec/dragonfly/job_definitions_spec.rb +35 -0
  102. data/spec/dragonfly/job_endpoint_spec.rb +66 -0
  103. data/spec/dragonfly/job_spec.rb +605 -0
  104. data/spec/dragonfly/loggable_spec.rb +80 -0
  105. data/spec/dragonfly/middleware_spec.rb +37 -17
  106. data/spec/dragonfly/processing/r_magick_processor_spec.rb +182 -166
  107. data/spec/dragonfly/routed_endpoint_spec.rb +48 -0
  108. data/spec/dragonfly/serializer_spec.rb +61 -0
  109. data/spec/dragonfly/simple_cache_spec.rb +27 -0
  110. data/spec/dragonfly/simple_endpoint_spec.rb +78 -0
  111. data/spec/dragonfly/temp_object_spec.rb +154 -119
  112. data/spec/simple_matchers.rb +22 -0
  113. data/spec/spec_helper.rb +28 -4
  114. data/yard/templates/default/layout/html/layout.erb +18 -11
  115. metadata +89 -190
  116. data/config.rb +0 -5
  117. data/extra_docs/ActiveRecord.md +0 -196
  118. data/extra_docs/ExampleUseCases.md +0 -189
  119. data/extra_docs/GettingStarted.md +0 -114
  120. data/extra_docs/Shortcuts.md +0 -118
  121. data/extra_docs/UsingWithRails.md +0 -81
  122. data/features/rails_3.0.0.beta3.feature +0 -7
  123. data/fixtures/rails_3.0.0.beta3/template.rb +0 -16
  124. data/lib/dragonfly/active_record_extensions/attachment.rb +0 -170
  125. data/lib/dragonfly/analyser_list.rb +0 -9
  126. data/lib/dragonfly/analysis/base.rb +0 -10
  127. data/lib/dragonfly/belongs_to_app.rb +0 -24
  128. data/lib/dragonfly/config/heroku_rails_images.rb +0 -23
  129. data/lib/dragonfly/config/r_magick_images.rb +0 -69
  130. data/lib/dragonfly/config/r_magick_text.rb +0 -25
  131. data/lib/dragonfly/config/rails_images.rb +0 -13
  132. data/lib/dragonfly/data_storage/base.rb +0 -21
  133. data/lib/dragonfly/data_storage/base64_data_store.rb +0 -23
  134. data/lib/dragonfly/data_storage/transparent_data_store.rb +0 -21
  135. data/lib/dragonfly/delegatable.rb +0 -14
  136. data/lib/dragonfly/delegator.rb +0 -62
  137. data/lib/dragonfly/encoder_list.rb +0 -9
  138. data/lib/dragonfly/encoding/base.rb +0 -14
  139. data/lib/dragonfly/encoding/transparent_encoder.rb +0 -14
  140. data/lib/dragonfly/extended_temp_object.rb +0 -120
  141. data/lib/dragonfly/parameters.rb +0 -163
  142. data/lib/dragonfly/processing/base.rb +0 -10
  143. data/lib/dragonfly/processor_list.rb +0 -9
  144. data/lib/dragonfly/url_handler.rb +0 -147
  145. data/spec/dragonfly/active_record_extensions/attachment_spec.rb +0 -8
  146. data/spec/dragonfly/active_record_extensions/migration.rb +0 -42
  147. data/spec/dragonfly/active_record_extensions/models.rb +0 -6
  148. data/spec/dragonfly/active_record_extensions/spec_helper.rb +0 -24
  149. data/spec/dragonfly/belongs_to_app_spec.rb +0 -55
  150. data/spec/dragonfly/delegatable_spec.rb +0 -32
  151. data/spec/dragonfly/delegator_spec.rb +0 -145
  152. data/spec/dragonfly/extended_temp_object_spec.rb +0 -71
  153. data/spec/dragonfly/parameters_spec.rb +0 -298
  154. data/spec/dragonfly/processing/r_magick_text_processor_spec.rb +0 -84
  155. data/spec/dragonfly/url_handler_spec.rb +0 -247
  156. data/spec/dragonfly_spec.rb +0 -16
  157. data/spec/ginger_scenarios.rb +0 -13
@@ -0,0 +1,13 @@
1
+ module Dragonfly
2
+ class Encoder < FunctionManager
3
+
4
+ def add(name=:encode, callable_obj=nil, &block)
5
+ super(name, callable_obj, &block)
6
+ end
7
+
8
+ def encode(temp_object, *args)
9
+ call_last(:encode, temp_object, *args)
10
+ end
11
+
12
+ end
13
+ end
@@ -2,10 +2,10 @@ require 'RMagick'
2
2
 
3
3
  module Dragonfly
4
4
  module Encoding
5
-
6
- class RMagickEncoder < Base
5
+ class RMagickEncoder
7
6
 
8
7
  include Configurable
8
+ include RMagickUtils
9
9
 
10
10
  configurable_attr :supported_formats, [
11
11
  :ai,
@@ -42,28 +42,19 @@ module Dragonfly
42
42
  :xwd
43
43
  ]
44
44
 
45
- def encode(image, format, encoding={})
45
+ def encode(temp_object, format, encoding={})
46
46
  format = format.to_s.downcase
47
47
  throw :unable_to_handle unless supported_formats.include?(format.to_sym)
48
- encoded_image = rmagick_image(image)
49
- if encoded_image.format.downcase == format
50
- image # do nothing
51
- else
52
- encoded_image.format = format
53
- encoded_image.to_blob
48
+ rmagick_image(temp_object) do |image|
49
+ if image.format.downcase == format
50
+ temp_object # do nothing
51
+ else
52
+ image.format = format
53
+ image
54
+ end
54
55
  end
55
56
  end
56
57
 
57
- private
58
-
59
- def rmagick_image(temp_object)
60
- Magick::Image.from_blob(temp_object.data).first
61
- rescue Magick::ImageMagickError => e
62
- log.warn("Unable to handle content in #{self.class} - got:\n#{e}")
63
- throw :unable_to_handle
64
- end
65
-
66
58
  end
67
-
68
59
  end
69
60
  end
@@ -0,0 +1,43 @@
1
+ module Dragonfly
2
+ module Endpoint
3
+
4
+ class EmptyJob < StandardError; end
5
+
6
+ private
7
+
8
+ def response_for_job(job, env)
9
+ if etag_matches?(job, env)
10
+ [304, cache_headers(job), []]
11
+ else
12
+ [200, success_headers(job), job.result] # Successful response
13
+ end
14
+ rescue DataStorage::DataNotFound => e
15
+ [404, {"Content-Type" => 'text/plain'}, [e.message]]
16
+ end
17
+
18
+ def cache_headers(job)
19
+ {
20
+ "Cache-Control" => "public, max-age=#{job.app.cache_duration}",
21
+ "ETag" => %("#{job.unique_signature}")
22
+ }
23
+ end
24
+
25
+ def etag_matches?(job, env)
26
+ if_none_match = env['HTTP_IF_NONE_MATCH']
27
+ if if_none_match
28
+ if_none_match.tr!('"','')
29
+ if_none_match.split(',').include?(job.unique_signature) || if_none_match == '*'
30
+ else
31
+ false
32
+ end
33
+ end
34
+
35
+ def success_headers(job)
36
+ {
37
+ "Content-Type" => job.app.resolve_mime_type(job.result),
38
+ "Content-Length" => job.size.to_s,
39
+ }.merge(cache_headers(job))
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,65 @@
1
+ module Dragonfly
2
+ class FunctionManager
3
+
4
+ # Exceptions
5
+ class NotDefined < NoMethodError; end
6
+ class UnableToHandle < NotImplementedError; end
7
+
8
+ include Loggable
9
+
10
+ def initialize
11
+ @functions = {}
12
+ @objects = []
13
+ end
14
+
15
+ def add(name, callable_obj=nil, &block)
16
+ functions[name] ||= []
17
+ functions[name] << (callable_obj || block)
18
+ end
19
+
20
+ attr_reader :functions, :objects
21
+
22
+ def register(klass, *args, &block)
23
+ obj = klass.new(*args)
24
+ obj.configure(&block) if block
25
+ obj.use_same_log_as(self) if obj.is_a?(Loggable)
26
+ methods_to_add(obj).each do |meth|
27
+ add meth.to_sym, obj.method(meth)
28
+ end
29
+ objects << obj
30
+ obj
31
+ end
32
+
33
+ def call_last(meth, *args)
34
+ if functions[meth.to_sym]
35
+ functions[meth.to_sym].reverse.each do |function|
36
+ catch :unable_to_handle do
37
+ return function.call(*args)
38
+ end
39
+ end
40
+ # If the code gets here, then none of the registered functions were able to handle the method call
41
+ raise UnableToHandle, "None of the functions registered with #{self} were able to deal with the method call " +
42
+ "#{meth}(#{args.map{|a| a.inspect[0..100]}.join(',')}). You may need to register one that can."
43
+ else
44
+ raise NotDefined, "function #{meth} not registered with #{self}"
45
+ end
46
+ end
47
+
48
+ def inspect
49
+ to_s.sub(/>$/, " with functions: #{functions.keys.map{|k| k.to_s }.sort.join(', ')} >")
50
+ end
51
+
52
+ private
53
+
54
+ def methods_to_add(obj)
55
+ if obj.is_a?(Configurable)
56
+ obj.public_methods(false) -
57
+ obj.configuration_methods.map{|meth| meth.to_method_name} -
58
+ [:configuration_methods.to_method_name]
59
+ else
60
+ obj.public_methods(false)
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -1,9 +1,8 @@
1
1
  require 'RMagick'
2
2
 
3
3
  module Dragonfly
4
- module Processing
5
-
6
- class RMagickTextProcessor < Base
4
+ module Generation
5
+ class RMagickGenerator
7
6
 
8
7
  FONT_STYLES = {
9
8
  'normal' => Magick::NormalStyle,
@@ -56,8 +55,19 @@ module Dragonfly
56
55
  )
57
56
  end
58
57
  end
59
-
60
- def text(temp_object, opts={})
58
+
59
+ def plasma(width, height, format='png')
60
+ image = Magick::Image.read("plasma:fractal"){self.size = "#{width}x#{height}"}.first
61
+ image.format = format.to_s
62
+ data = image.to_blob
63
+ image.destroy!
64
+ [
65
+ data,
66
+ {:format => format.to_sym, :name => "plasma.#{format}"}
67
+ ]
68
+ end
69
+
70
+ def text(text_string, opts={})
61
71
  opts = HashWithCssStyleKeys[opts]
62
72
 
63
73
  draw = Magick::Draw.new
@@ -89,10 +99,8 @@ module Dragonfly
89
99
  padding_bottom = (opts[:padding_bottom] || pb || 0) * s
90
100
  padding_left = (opts[:padding_left] || pl || 0) * s
91
101
 
92
- text = temp_object.data
93
-
94
102
  # Calculate (scaled up) dimensions
95
- metrics = draw.get_type_metrics(text)
103
+ metrics = draw.get_type_metrics(text_string)
96
104
  width, height = metrics.width, metrics.height
97
105
 
98
106
  scaled_up_width = padding_left + width + padding_right
@@ -103,15 +111,21 @@ module Dragonfly
103
111
  self.background_color = opts[:background_color] || 'transparent'
104
112
  }
105
113
  # Draw the text
106
- draw.annotate(image, width, height, padding_left, padding_top, text)
114
+ draw.annotate(image, width, height, padding_left, padding_top, text_string)
107
115
 
108
116
  # Scale back down again
109
117
  image.scale!(1/s)
110
118
 
111
- image.format = 'png'
119
+ format = opts[:format] || :png
120
+ image.format = format.to_s
112
121
 
113
122
  # Output image as string
114
- image.to_blob
123
+ data = image.to_blob
124
+ image.destroy!
125
+ [
126
+ data,
127
+ {:format => format, :name => "text.#{format}"}
128
+ ]
115
129
  end
116
130
 
117
131
  private
@@ -0,0 +1,9 @@
1
+ module Dragonfly
2
+ class Generator < FunctionManager
3
+
4
+ def generate(method, *args)
5
+ call_last(method, *args)
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,290 @@
1
+ require 'forwardable'
2
+ require 'digest/sha1'
3
+
4
+ module Dragonfly
5
+ class Job
6
+
7
+ # Exceptions
8
+ class AppDoesNotMatch < StandardError; end
9
+ class JobAlreadyApplied < StandardError; end
10
+ class NothingToProcess < StandardError; end
11
+ class NothingToEncode < StandardError; end
12
+ class NothingToAnalyse < StandardError; end
13
+ class InvalidArray < StandardError; end
14
+ class NoSHAGiven < StandardError; end
15
+ class IncorrectSHA < StandardError; end
16
+
17
+ extend Forwardable
18
+ def_delegators :result, :data, :file, :tempfile, :path, :to_file, :size, :ext, :name, :meta, :format, :_format
19
+
20
+ class Step
21
+
22
+ class << self
23
+ # Dragonfly::Job::Fetch -> 'Fetch'
24
+ def basename
25
+ @basename ||= name.split('::').last
26
+ end
27
+ # Dragonfly::Job::Fetch -> :fetch
28
+ def step_name
29
+ @step_name ||= basename.gsub(/[A-Z]/){ "_#{$&.downcase}" }.sub('_','').to_sym
30
+ end
31
+ # Dragonfly::Job::Fetch -> :f
32
+ def abbreviation
33
+ @abbreviation ||= basename.scan(/[A-Z]/).join.downcase.to_sym
34
+ end
35
+ end
36
+
37
+ def initialize(*args)
38
+ @args = args
39
+ end
40
+ attr_reader :args
41
+ def inspect
42
+ "#{self.class.step_name}(#{args.map{|a| a.inspect }.join(', ')})"
43
+ end
44
+ end
45
+
46
+ class Fetch < Step
47
+ def uid
48
+ args.first
49
+ end
50
+ def apply(job)
51
+ content, extra = job.app.datastore.retrieve(uid)
52
+ job.temp_object = TempObject.new(content, (extra || {}))
53
+ end
54
+ end
55
+
56
+ class Process < Step
57
+ def name
58
+ args.first
59
+ end
60
+ def arguments
61
+ args[1..-1]
62
+ end
63
+ def apply(job)
64
+ raise NothingToProcess, "Can't process because temp object has not been initialized. Need to fetch first?" unless job.temp_object
65
+ old = job.temp_object
66
+ job.temp_object = TempObject.new(
67
+ job.app.processor.process(old, name, *arguments),
68
+ old.attributes
69
+ )
70
+ end
71
+ end
72
+
73
+ class Encode < Step
74
+ def format
75
+ args.first
76
+ end
77
+ def arguments
78
+ args[1..-1]
79
+ end
80
+ def apply(job)
81
+ raise NothingToEncode, "Can't encode because temp object has not been initialized. Need to fetch first?" unless job.temp_object
82
+ old = job.temp_object
83
+ job.temp_object = TempObject.new(
84
+ job.app.encoder.encode(old, format, *arguments),
85
+ old.attributes.merge(:format => format)
86
+ )
87
+ end
88
+ end
89
+
90
+ class Generate < Step
91
+ def apply(job)
92
+ content, extra = job.app.generator.generate(*args)
93
+ job.temp_object = TempObject.new(content, (extra || {}))
94
+ end
95
+ end
96
+
97
+ class FetchFile < Step
98
+ def path
99
+ File.expand_path(args.first)
100
+ end
101
+ def apply(job)
102
+ job.temp_object = TempObject.new(File.new(path))
103
+ end
104
+ end
105
+
106
+ STEPS = [
107
+ Fetch,
108
+ Process,
109
+ Encode,
110
+ Generate,
111
+ FetchFile
112
+ ]
113
+
114
+ # Class methods
115
+ class << self
116
+
117
+ def from_a(steps_array, app)
118
+ unless steps_array.is_a?(Array) &&
119
+ steps_array.all?{|s| s.is_a?(Array) && step_abbreviations[s.first] }
120
+ raise InvalidArray, "can't define a job from #{steps_array.inspect}"
121
+ end
122
+ job = app.new_job
123
+ steps_array.each do |step_array|
124
+ step_class = step_abbreviations[step_array.shift]
125
+ job.steps << step_class.new(*step_array)
126
+ end
127
+ job
128
+ end
129
+
130
+ def from_path(path, app)
131
+ path.sub!(app.url_path_prefix, '') if app.url_path_prefix
132
+ path.sub!('/', '')
133
+ deserialize(path, app)
134
+ end
135
+
136
+ def deserialize(string, app)
137
+ from_a(Serializer.marshal_decode(string), app)
138
+ end
139
+
140
+ def step_abbreviations
141
+ @step_abbreviations ||= STEPS.inject({}){|hash, step_class| hash[step_class.abbreviation] = step_class; hash }
142
+ end
143
+
144
+ def step_names
145
+ @step_names ||= STEPS.map{|step_class| step_class.step_name }
146
+ end
147
+
148
+ end
149
+
150
+ # Instance methods
151
+
152
+ def initialize(app, temp_object=nil)
153
+ @app = app
154
+ self.extend app.analyser.analysis_methods
155
+ self.extend app.job_definitions
156
+ @steps = []
157
+ @next_step_index = 0
158
+ @temp_object = temp_object
159
+ end
160
+
161
+ # Used by 'dup' and 'clone'
162
+ def initialize_copy(other)
163
+ self.steps = other.steps.dup
164
+ self.extend app.analyser.analysis_methods
165
+ self.extend app.job_definitions
166
+ end
167
+
168
+ attr_accessor :temp_object
169
+ attr_reader :app, :steps
170
+
171
+ # define fetch(), fetch!(), process(), etc.
172
+ STEPS.each do |step_class|
173
+ class_eval %(
174
+ def #{step_class.step_name}(*args)
175
+ new_job = self.dup
176
+ new_job.steps << #{step_class}.new(*args)
177
+ new_job
178
+ end
179
+
180
+ def #{step_class.step_name}!(*args)
181
+ steps << #{step_class}.new(*args)
182
+ self
183
+ end
184
+ )
185
+ end
186
+
187
+ def analyse(method, *args)
188
+ unless result
189
+ raise NothingToAnalyse, "Can't analyse because temp object has not been initialized. Need to fetch first?"
190
+ end
191
+ # Hacky - wish there was a nicer way to do this without extending with yet another module
192
+ if method == :format
193
+ _format || analyser.analyse(result, method, *args)
194
+ else
195
+ analyser.analyse(result, method, *args)
196
+ end
197
+ end
198
+
199
+ def +(other_job)
200
+ unless app == other_job.app
201
+ raise AppDoesNotMatch, "Cannot add jobs belonging to different apps (#{app} is not #{other_job.app})"
202
+ end
203
+ unless other_job.applied_steps.empty?
204
+ raise JobAlreadyApplied, "Cannot add jobs when the second one has already been applied (#{other_job})"
205
+ end
206
+ new_job = self.class.new(app, temp_object)
207
+ new_job.steps = steps + other_job.steps
208
+ new_job.next_step_index = next_step_index
209
+ new_job
210
+ end
211
+
212
+ def apply
213
+ pending_steps.each{|step| step.apply(self) }
214
+ self.next_step_index = steps.length
215
+ self
216
+ end
217
+
218
+ def result
219
+ apply
220
+ temp_object
221
+ end
222
+
223
+ def applied_steps
224
+ steps[0...next_step_index]
225
+ end
226
+
227
+ def pending_steps
228
+ steps[next_step_index..-1]
229
+ end
230
+
231
+ def to_a
232
+ steps.map{|step|
233
+ [step.class.abbreviation, *step.args]
234
+ }
235
+ end
236
+
237
+ def serialize
238
+ Serializer.marshal_encode(to_a)
239
+ end
240
+ alias unique_signature serialize
241
+
242
+ def sha
243
+ Digest::SHA1.hexdigest("#{serialize}#{app.secret}")[0...8]
244
+ end
245
+
246
+ def validate_sha!(sha)
247
+ case sha
248
+ when nil
249
+ raise NoSHAGiven
250
+ when self.sha
251
+ self
252
+ else
253
+ raise IncorrectSHA, sha
254
+ end
255
+ end
256
+
257
+ def to_app
258
+ JobEndpoint.new(self)
259
+ end
260
+
261
+ def to_response
262
+ to_app.call
263
+ end
264
+
265
+ def url(*args)
266
+ app.url_for(self, *args) unless steps.empty?
267
+ end
268
+
269
+ def to_fetched_job(uid)
270
+ new_job = self.class.new(app, temp_object)
271
+ new_job.fetch!(uid)
272
+ new_job.next_step_index = 1
273
+ new_job
274
+ end
275
+
276
+ def to_path
277
+ "/#{serialize}"
278
+ end
279
+
280
+ def inspect
281
+ to_s.sub(/>$/, " app=#{app}, steps=#{steps.inspect}, temp_object=#{temp_object.inspect}, steps applied:#{applied_steps.length}/#{steps.length} >")
282
+ end
283
+
284
+ protected
285
+
286
+ attr_writer :steps
287
+ attr_accessor :next_step_index
288
+
289
+ end
290
+ end