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,60 @@
1
+ Generators
2
+ ==========
3
+
4
+ Unlike processors and encoders, generators create content out of nothing, rather than modifying already existing content, for example text image generation.
5
+
6
+ You can register as many generators as you like.
7
+
8
+ Given a Dragonfly app
9
+
10
+ app = Dragonfly[:images]
11
+
12
+ we can get generated content using
13
+
14
+ image = app.generate(:some_method, :some => :args)
15
+
16
+ where `:some_method` is added by the configured generators.
17
+
18
+ ImageMagick Generator
19
+ ---------------------
20
+ See {file:Imagemagick}.
21
+
22
+ Custom Generators
23
+ -----------------
24
+ To register a single custom generator:
25
+
26
+ app.generator.add :blank_image do |colour|
27
+ SomeLibrary.create_blank_image(colour) # return a String, Pathname, File or Tempfile
28
+ end
29
+
30
+ app.generate(:blank_image, 'red') # => 'Job' object which we can get data, etc.
31
+
32
+
33
+ Or create a class like the ImageMagick one above, in which case all public methods will be counted as generator methods.
34
+
35
+ class RoundedCornerGenerator
36
+
37
+ def top_left_corner(opts={})
38
+ SomeLib.tlc(opts)
39
+ end
40
+
41
+ def bottom_right_corner(opts={})
42
+ tempfile = Tempfile.new('brc')
43
+ `some_command -c #{opts[:colour]} -o #{tempfile.path}`
44
+ tempfile
45
+ end
46
+
47
+ # ...
48
+
49
+ private
50
+
51
+ def my_helper_method
52
+ # do stuff
53
+ end
54
+
55
+ end
56
+
57
+ app.generator.register(RoundedCornerGenerator)
58
+
59
+ app.generate(:top_left_corner, :colour => 'green')
60
+ app.generate(:bottom_right_corner, :colour => 'mauve')
@@ -0,0 +1,50 @@
1
+ Heroku
2
+ ======
3
+
4
+ The default configuration won't work out of the box for Heroku, because
5
+
6
+ - Heroku doesn't allow saving files to the filesystem (although it does use tempfiles)
7
+ - We won't need {http://tomayko.com/src/rack-cache/ Rack::Cache} on Heroku because it already uses the caching proxy {http://varnish.projects.linpro.no/ Varnish}, which we can make use of
8
+
9
+ Instead of the normal {Dragonfly::DataStorage::FileDataStore FileDataStore}, we can use the {Dragonfly::DataStorage::S3DataStore S3DataStore}.
10
+
11
+ Assuming you have an S3 account set up...
12
+
13
+ Gem dependencies:
14
+
15
+ - aws-s3 (require as aws/s3)
16
+ - dragonfly
17
+
18
+ Initializer (e.g. config/initializers/dragonfly.rb):
19
+
20
+ require 'dragonfly'
21
+ app = Dragonfly[:images]
22
+
23
+ app.configure_with(:imagemagick)
24
+ app.configure_with(:rails)
25
+ app.configure_with(:heroku, 'my_bucket_name') if Rails.env.production?
26
+
27
+ app.define_macro(ActiveRecord::Base, :image_accessor)
28
+
29
+ The datastore remains as the {Dragonfly::DataStorage::FileDataStore FileDataStore} for non-production environments.
30
+
31
+ environment.rb (application.rb in Rails 3):
32
+
33
+ # make sure the last arg is the same as the app's configured prefix
34
+ config.middleware.insert_before 'Rack::Lock', 'Dragonfly::Middleware', :images, '/media'
35
+
36
+ We don't store the S3 access key and secret in the repository, rather we use Heroku's
37
+ {http://docs.heroku.com/config-vars config variables} using the command line (we only have to do this once).
38
+
39
+ From your app's directory:
40
+
41
+ heroku config:add S3_KEY=XXXXXXXXX S3_SECRET=XXXXXXXXXX
42
+
43
+ Obviously replace 'XXXXXXXXX' with your access key and secret.
44
+
45
+ Now you can benefit from super-fast images served straight from Heroku's cache!
46
+
47
+ NOTE: HEROKU'S CACHE IS CLEARED EVERY TIME YOU DEPLOY!!!
48
+
49
+ If this is an issue, you may want to look into using something like a Memcached add-on, or maybe an after-deploy hook for hitting specific Dragonfly urls you want to cache, etc.
50
+ It won't be a problem for most sites though.
@@ -0,0 +1,125 @@
1
+ ImageMagick
2
+ ===========
3
+ Either `require 'dragonfly/rails/images'` or `Dragonfly[:images].configure_with(:imagemagick)`
4
+ gives us an ImageMagick {Dragonfly::ImageMagick::Processor Processor}, {Dragonfly::ImageMagick::Encoder Encoder},
5
+ {Dragonfly::ImageMagick::Analyser Analyser} and {Dragonfly::ImageMagick::Generator Generator}.
6
+
7
+ Given a {Dragonfly::Job Job} object
8
+
9
+ image = app.fetch('some/uid')
10
+
11
+ ...OR a Dragonfly model accessor...
12
+
13
+ image = @album.cover_image
14
+
15
+ we have the following:
16
+
17
+ Shortcuts
18
+ ---------
19
+ image.thumb('40x30') # same as image.process(:thumb, '40x30')
20
+ image.jpg # same as image.encode(:jpg)
21
+ image.png # same as image.encode(:png)
22
+ image.gif # same as image.encode(:gif)
23
+ image.convert('-scale 30x30') # same as image.process(:convert, '-scale 30x30')
24
+
25
+ `thumb` and `convert` can optionally take a format (e.g. :gif) as the second argument.
26
+ Bang methods like `image.thumb!('40x30')`, `image.png!` etc. will operate on `self`.
27
+
28
+ Below are some examples of geometry strings for `thumb`:
29
+
30
+ '400x300' # resize, maintain aspect ratio
31
+ '400x300!' # force resize, don't maintain aspect ratio
32
+ '400x' # resize width, maintain aspect ratio
33
+ 'x300' # resize height, maintain aspect ratio
34
+ '400x300>' # resize only if the image is larger than this
35
+ '400x300<' # resize only if the image is smaller than this
36
+ '50x50%' # resize width and height to 50%
37
+ '400x300^' # resize width, height to minimum 400,300, maintain aspect ratio
38
+ '2000@' # resize so max area in pixels is 2000
39
+ '400x300#' # resize, crop if necessary to maintain aspect ratio (centre gravity)
40
+ '400x300#ne' # as above, north-east gravity
41
+ '400x300se' # crop, with south-east gravity
42
+ '400x300+50+100' # crop from the point 50,100 with width, height 400,300
43
+
44
+ Processor
45
+ ---------
46
+
47
+ image.process(:crop, :width => 40, :height => 50, :x => 20, :y => 30)
48
+ image.process(:crop, :width => 40, :height => 50, :gravity => 'ne')
49
+
50
+ image.process(:flip) # flips it vertically
51
+ image.process(:flop) # flips it horizontally
52
+
53
+ image.process(:greyscale, :depth => 128) # default depth 256
54
+
55
+ image.process(:resize, '40x40')
56
+ image.process(:resize_and_crop, :width => 40, :height=> 50, :gravity => 'ne')
57
+
58
+ image.process(:rotate, 45, :background_colour => 'transparent') # default bg black
59
+
60
+ The method `thumb` takes a geometry string and calls `resize`, `resize_and_crop` or `crop` accordingly.
61
+
62
+ image.process(:thumb, '400x300') # calls resize
63
+
64
+ Encoder
65
+ -------
66
+ The {Dragonfly::ImageMagick::Encoder ImageMagick Encoder} gives us:
67
+
68
+ image.encode(:jpg)
69
+ image.encode(:gif)
70
+ image.encode(:png)
71
+ image.encode(:tiff)
72
+
73
+ and various other formats (see {Dragonfly::ImageMagick::Encoder ImageMagick Encoder}).
74
+
75
+ You can also pass additional options to the imagemagick command line:
76
+
77
+ image.encode(:jpg, '-quality 10')
78
+
79
+ Analyser
80
+ --------
81
+ The {Dragonfly::ImageMagick::Analyser ImageMagick Analyser} gives us these methods:
82
+
83
+ image.width # => 280
84
+ image.height # => 355
85
+ image.aspect_ratio # => 0.788732394366197
86
+ image.portrait? # => true
87
+ image.landscape? # => false
88
+ image.depth # => 8
89
+ image.number_of_colours # => 34703
90
+ image.format # => :png
91
+ image.image? # => true - will return true or false for any content
92
+
93
+ Generator
94
+ ---------
95
+ The {Dragonfly::ImageMagick::Generator ImageMagick Generator} gives us these methods:
96
+
97
+ image = app.generate(:plain, 600, 400, 'rgba(40,200,30,0.5)')
98
+ image = app.generate(:plain, 600, 400, '#ccc', :format => :gif)
99
+ # generate a 600x400 plain image
100
+ # any css-style colour should work
101
+
102
+ image = app.generate(:plasma, 600, 400, :gif) # generate a 600x400 plasma image
103
+ # last arg defaults to :png
104
+
105
+ image = app.generate(:text, "Hello there") # an image of the text "Hello there"
106
+
107
+ image = app.generate(:text, "Hello there",
108
+ :font_size => 30, # defaults to 12
109
+ :font_family => 'Monaco',
110
+ :stroke_color => '#ddd',
111
+ :color => 'red',
112
+ :font_style => 'italic',
113
+ :font_stretch => 'expanded',
114
+ :font_weight => 'bold',
115
+ :padding => '30 20 10',
116
+ :background_color => '#efefef', # defaults to transparent
117
+ :format => :gif # defaults to png
118
+ )
119
+
120
+ Note that the text generation options are meant to resemble css as much as possible. You can also use, for example, `'font-family'` instead of `:font_family`.
121
+
122
+ You can use `padding-top`, `padding-left`, etc., as well as the standard css shortcuts for `padding` (it assumes unit is px).
123
+
124
+ An alternative for `:font_family` is `:font` (see {http://www.imagemagick.org/script/command-line-options.php#font}), which could be a complete filename.
125
+ Available fonts are those available on your system.
@@ -0,0 +1,33 @@
1
+ Dragonfly is a {http://rack.rubyforge.org Rack} framework for on-the-fly image handling in Ruby.
2
+
3
+ It is suitable for using with web frameworks such as Rails(2.3 and 3), Sinatra, etc.
4
+
5
+ I actually lied about image handling - it can be used for any type of content.
6
+
7
+ See the links on the right for more info.
8
+
9
+ Installation
10
+ ------------
11
+
12
+ gem install dragonfly
13
+
14
+ Add-ons
15
+ -------
16
+ For third-party add-ons, see [the Add-ons wiki](http://github.com/markevans/dragonfly/wiki/Dragonfly-add-ons)
17
+
18
+ Issues
19
+ ------
20
+ Please use the <a href="http://github.com/markevans/dragonfly/issues">github issue tracker</a>.
21
+
22
+ Suggestions/Questions
23
+ ---------------------
24
+ {http://groups.google.com/group/dragonfly-users}
25
+
26
+ Credits
27
+ -------
28
+ - [Mark Evans](http://github.com/markevans) (author)
29
+ - Loads of helpful comments, issues, questions, suggestions and insults from others - you know who you are!
30
+
31
+ Copyright
32
+ ---------
33
+ Copyright (c) 2009-2010 Mark Evans. See LICENSE for details.
@@ -0,0 +1,40 @@
1
+ Mime Types
2
+ ==========
3
+
4
+ Responses from the Dragonfly app have the HTTP 'Content-Type' header set.
5
+
6
+ This is decided by the first found from:
7
+
8
+ 1. The requested format (e.g. when encode is specifically called)
9
+ 2. The original file extension (you can configure it to ignore this if you wish)
10
+ 3. Analyse the content using the analyser's 'mime_type' method (if exists)
11
+ 4. Analyse the content using the analyser's 'format' method (if exists)
12
+ 5. Use the fallback mime-type (default 'application/octet-stream')
13
+
14
+ Note that 'format' means 'jpg', 'png', etc. whereas mime-type would be 'image/jpeg', image/png', etc.
15
+ Formats are mapped to mime-types using the app's registered list of mime-types.
16
+
17
+ Registered mime-types
18
+ ---------------------
19
+ Registered mime-types default to the list given by Rack (see {http://rack.rubyforge.org/doc/Rack/Mime.html#MIME_TYPES Rack mime-types}).
20
+
21
+ To register a mime-type for the format 'egg':
22
+
23
+ Dragonfly[:my_app].register_mime_type(:egg, 'fried/egg')
24
+
25
+ You can also do this inside a configuration block.
26
+
27
+ Analysers
28
+ ---------
29
+ The {Dragonfly::Analysis::FileCommandAnalyser FileCommandAnalyser} has a `mime_type` method and the
30
+ {Dragonfly::ImageMagick::Analyser ImageMagick Analyser} has a `format` method.
31
+
32
+ These are both registered by default when you use the preconfigured 'dragonfly/rails/images' file.
33
+
34
+ Fallback mime-type
35
+ ------------------
36
+ By default this is 'application/octet-stream', but it can be changed using
37
+
38
+ Dragonfly[:my_app].fallback_mime_type = 'meaty/beef'
39
+
40
+ This can also be done inside a configuration block.
@@ -0,0 +1,272 @@
1
+ Using with Models
2
+ =================
3
+
4
+ You can extend ActiveModel-compatible models to make working with content such as images
5
+ as easy as working with strings or numbers!
6
+
7
+ The examples below assume an initialized Dragonfly app, e.g.
8
+
9
+ app = Dragonfly[:images]
10
+
11
+ ActiveRecord
12
+ ------------
13
+ If you've required 'dragonfly/rails/images', then the following step will be already done for you.
14
+ Otherwise:
15
+
16
+ app.define_macro(ActiveRecord::Base, :image_accessor)
17
+
18
+ defines the macro `image_accessor` on any ActiveRecord models.
19
+
20
+ Mongoid
21
+ -------
22
+
23
+ app.define_macro_on_include(Mongoid::Document, :image_accessor)
24
+
25
+ defines the macro `image_accessor` on any models that include `Mongoid::Document`
26
+
27
+ CouchRest::Model
28
+ -------
29
+ app.define_macro(CouchRest::Model::Base, :image_accessor)
30
+
31
+ defines the macro `image_accessor` on any models that include `CouchRest::Model::Base`
32
+
33
+ Adding accessors
34
+ ----------------
35
+ Now we have the method `image_accessor` available in our model classes, which we can use as many times as we like
36
+
37
+ class Album
38
+ image_accessor :cover_image
39
+ image_accessor :band_photo # Note: this is a different image altogether, not a thumbnail of cover_image
40
+ end
41
+
42
+ Each accessor (e.g. `cover_image`) depends on a string field to actually hold the datastore uid,
43
+ named by appending the suffix `_uid` (e.g. `cover_image_uid`).
44
+
45
+ For example, ActiveRecord models need a migration such as:
46
+
47
+ class MyMigration < ActiveRecord::Migration
48
+
49
+ def self.up
50
+ add_column :albums, :cover_image_uid, :string
51
+ add_column :albums, :band_photo_uid, :string
52
+ end
53
+
54
+ def self.down
55
+ remove_column :albums, :cover_image_uid
56
+ remove_column :albums, :band_photo_uid
57
+ end
58
+
59
+ end
60
+
61
+ Using the accessors
62
+ -------------------
63
+
64
+ We can use the attribute much like other other model attributes:
65
+
66
+ @album = Album.new
67
+
68
+ @album.cover_image = "\377???JFIF\000\..." # can assign as a string...
69
+ @album.cover_image = File.new('path/to/my_image.png') # ... or as a file...
70
+ @album.cover_image = some_tempfile # ... or as a tempfile...
71
+ @album.cover_image = @album.band_photo # ... or as another Dragonfly attachment
72
+
73
+ @album.cover_image # => #<Dragonfly::ActiveModelExtensions::Attachment:0x103ef6128...
74
+
75
+ @album.cover_image = nil
76
+ @album.cover_image # => nil
77
+
78
+ We can inspect properties of the attribute
79
+
80
+ @album.cover_image.width # => 280
81
+ @album.cover_image.height # => 140
82
+ @album.cover_image.number_of_colours # => 34703
83
+ @album.cover_image.mime_type # => 'image/png'
84
+
85
+ The properties available (i.e. 'width', etc.) come from the app's registered analysers - see {file:Analysers.md Analysers}.
86
+
87
+ We can play around with the data
88
+
89
+ @album.cover_image.data # => "\377???JFIF\000\..."
90
+ @album.cover_image.to_file('out.png') # writes to file 'out.png' and returns a readable file object
91
+ @album.cover_image.tempfile # => #<File:/var/folders/st/strHv74sH044JPabSiODz... a closed Tempfile object
92
+ @album.cover_image.file # => #<File:/var/folders/st/strHv74sH044JPabSiODz... a readable (open) File object
93
+ @album.cover_image.file do |f| # Yields an open file object, returns the return value of
94
+ data = f.read(256) # the block, and closes the file object
95
+ end
96
+ @album.cover_image.path # => '/var/folders/st/strHv74sH044JPabSiODz...' i.e. the path of the tempfile
97
+ @album.cover_image.size # => 134507 (size in bytes)
98
+
99
+ We can process the data
100
+
101
+ image = @album.cover_image.process(:thumb, '20x20') # returns a 'Job' object, with similar properties
102
+ image.width # => 20
103
+ @album.cover_image.width # => 280 (no change)
104
+
105
+ The available processing methods available (i.e. 'thumb', etc.) come from the {Dragonfly} app's registered processors - see {file:Processing.md Processing}
106
+
107
+ We can encode the data
108
+
109
+ image = @album.cover_image.encode(:gif) # returns a 'Job' object, with similar properties
110
+ image.format # => :gif
111
+ @album.cover_image.format # => :png (no change)
112
+
113
+ The encoding is implemented by the {Dragonfly} app's registered encoders (which will usually just be one) - see {file:Encoding.md Encoding}
114
+
115
+ We can use configured shortcuts for processing/encoding, and chain them:
116
+
117
+ @album.cover_image.thumb('300x200#ne') # => returns a 'Job' object, with similar properties
118
+
119
+ We can chain all these things much like ActiveRecord scopes:
120
+
121
+ @album.cover_image.png.thumb('300x200#ne').process(:greyscale).encode(:tiff)
122
+
123
+ Because the processing/encoding methods are lazy, no actual processing or encoding is done until a method like `data`, `file`, `to_file`, `width`, etc. is called.
124
+ You can force the processing to be done if you must by then calling `apply`.
125
+
126
+ @album.cover_image.process(:greyscale).apply
127
+
128
+ Persisting
129
+ ----------
130
+ When the model is saved, a before_save callback persists the data to the {Dragonfly::App App}'s configured datastore (see {file:DataStorage.md DataStorage})
131
+ The uid column is then filled in.
132
+
133
+ @album = Album.new
134
+
135
+ @album.cover_image_uid # => nil
136
+
137
+ @album.cover_image = File.new('path/to/my_image.png')
138
+ @album.cover_image_uid # => nil
139
+
140
+ @album.save
141
+ @album.cover_image_uid # => '2009/12/05/file.png' (some unique uid, used by the datastore)
142
+
143
+ URLs
144
+ ----
145
+ Once the model is saved, we can get a url for the image (which is served by the Dragonfly {Dragonfly::App App} itself), and for its processed/encoded versions:
146
+
147
+ @album.cover_image.url # => '/media/BAhbBlsHOgZmIhgy...'
148
+ @album.cover_image.thumb('300x200#nw').url # => '/media/BAhbB1sYusgZhgyM...'
149
+ @album.cover_image.process(:greyscale).jpg.url # => '/media/BnA6CnRodW1iIg8z...'
150
+
151
+ Because the processing/encoding methods (including shortcuts like `thumb` and `jpg`) are lazy, no processing or encoding is actually done.
152
+
153
+ Validations
154
+ -----------
155
+ `validates_presence_of` and `validates_size_of` work out of the box, and Dragonfly also provides `validates_property`.
156
+
157
+ class Album
158
+
159
+ validates_presence_of :cover_image
160
+ validates_size_of :cover_image, :maximum => 500.kilobytes
161
+
162
+ validates_property :format, :of => :cover_image, :in => [:jpeg, :png, :gif]
163
+ # ..or..
164
+ validates_property :mime_type, :of => :cover_image, :in => %w(image/jpeg image/png image/gif)
165
+
166
+ validates_property :width, :of => :cover_image, :in => (0..400), :message => "é demais cara!"
167
+
168
+ # ...
169
+ end
170
+
171
+ The property argument of `validates_property` will generally be one of the registered analyser properties as described in {file:Analysers.md Analysers}.
172
+ However it would actually work for arbitrary properties, including those of non-dragonfly model attributes.
173
+
174
+ Name and extension
175
+ ------------------
176
+ If the object assigned is a file, or responds to `original_filename` (as is the case with file uploads in Rails, etc.), then `name` will be set.
177
+
178
+ @album.cover_image = File.new('path/to/my_image.png')
179
+
180
+ @album.cover_image.name # => 'my_image.png'
181
+ @album.cover_image.ext # => 'png'
182
+
183
+ Meta data
184
+ ---------
185
+ You can store metadata along with the content data of your attachment:
186
+
187
+ @album.cover_image = File.new('path/to/my_image.png')
188
+ @album.cover_image.meta = {:taken => Date.yesterday}
189
+ @album.save!
190
+
191
+ @album.cover_image.meta # => {:model_class=>"Album",
192
+ # :model_attachment=>:cover_image,
193
+ # :taken=>Sat, 11 Sep 2010}
194
+
195
+ As you can see, a couple of things are added by the model. You can also access this directly on the {Dragonfly::Job Job} object.
196
+
197
+ app.fetch(@album.cover_image_uid).meta # => {:model_class=>"Album", ...}
198
+
199
+ "Magic" Attributes
200
+ ------------------
201
+ An accessor like `cover_image` only relies on the accessor `cover_image_uid` to work.
202
+ However, in some cases you may want to record some other properties, whether it be for using in queries, or
203
+ for caching an attribute for performance reasons, etc.
204
+
205
+ For the properties `name`, `ext`, `size` and any of the registered analysis methods (e.g. `width`, etc. in the examples above),
206
+ this is done automatically for you, if the corresponding accessor exists.
207
+
208
+ For example - with ActiveRecord, given the migration:
209
+
210
+ add_column :albums, :cover_image_width, :integer
211
+
212
+ This will automatically be set when assigned:
213
+
214
+ @album.cover_image = File.new('path/to/my_image.png')
215
+
216
+ @album.cover_image_width # => 280
217
+
218
+ They can be used to avoid retrieving data from the datastore for analysis
219
+
220
+ @album = Album.first
221
+
222
+ @album.cover_image.width # => 280 - no need to retrieve data - takes it from `cover_image_width`
223
+ @album.cover_image.size # => 134507 - but this needs to retrieve data from the data store, then analyse
224
+
225
+
226
+ Custom Model
227
+ ------------
228
+ The accessors only require that your model class implements `before_save`, `before_destroy` and `validates_each`
229
+ (if using validations), as well as of course the `..._uid` field for storing the datastore uid.
230
+
231
+ Here is an example of a minimal ActiveModel `Album` model:
232
+
233
+ class CustomModel::Base
234
+
235
+ extend ActiveModel::Callbacks
236
+ define_model_callbacks :save, :destroy
237
+
238
+ include ActiveModel::Validations # if needed
239
+
240
+ def save
241
+ _run_save_callbacks {
242
+ # do some saving!
243
+ }
244
+ end
245
+
246
+ def destroy
247
+ _run_destroy_callbacks {
248
+ # do some destroying!
249
+ }
250
+ end
251
+
252
+ end
253
+
254
+ Define our `image_accessor` macro...
255
+
256
+ app.define_macro(CustomModel::Base, :image_accessor)
257
+
258
+ ...which is used by `Album`:
259
+
260
+ class Album < CustomModel::Base
261
+
262
+ def cover_image_uid=
263
+ # ...
264
+ end
265
+
266
+ def cover_image_uid
267
+ # ...
268
+ end
269
+
270
+ image_accessor :cover_image
271
+
272
+ end