railscart 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. data/History.txt +8 -0
  2. data/Manifest.txt +103 -33
  3. data/bin/railscart +4 -1
  4. data/lib/railscart/version.rb +1 -1
  5. data/starter_app/app/controllers/application.rb +1 -1
  6. data/starter_app/config/database.yml +21 -7
  7. data/starter_app/config/environment.rb +9 -2
  8. data/starter_app/db/migrate/{005_create_users.rb → 001_create_users.rb} +0 -0
  9. data/starter_app/db/migrate/{006_create_roles.rb → 002_create_roles.rb} +0 -0
  10. data/starter_app/db/migrate/{002_create_tags_and_taggings.rb → 003_create_tags_and_taggings.rb} +0 -0
  11. data/starter_app/db/migrate/{003_railscart_to_version_19.rb → 004_railscart_to_version_19.rb} +2 -2
  12. data/starter_app/db/sample/images.yml +252 -0
  13. data/starter_app/lib/tasks/rc_sample_data.rake +12 -10
  14. data/starter_app/lib/tasks/sample/products/0000/0001/ror_tote.jpeg +0 -0
  15. data/starter_app/lib/tasks/sample/products/0000/0001/ror_tote_mini.jpeg +0 -0
  16. data/starter_app/lib/tasks/sample/products/0000/0001/ror_tote_product.jpeg +0 -0
  17. data/starter_app/lib/tasks/sample/products/0000/0001/ror_tote_small.jpeg +0 -0
  18. data/starter_app/lib/tasks/sample/products/0000/0002/ror_tote_back.jpeg +0 -0
  19. data/starter_app/lib/tasks/sample/products/0000/0002/ror_tote_back_mini.jpeg +0 -0
  20. data/starter_app/lib/tasks/sample/products/0000/0002/ror_tote_back_product.jpeg +0 -0
  21. data/starter_app/lib/tasks/sample/products/0000/0002/ror_tote_back_small.jpeg +0 -0
  22. data/starter_app/lib/tasks/sample/products/0000/0003/ror_bag.jpeg +0 -0
  23. data/starter_app/lib/tasks/sample/products/0000/0003/ror_bag_mini.jpeg +0 -0
  24. data/starter_app/lib/tasks/sample/products/0000/0003/ror_bag_product.jpeg +0 -0
  25. data/starter_app/lib/tasks/sample/products/0000/0003/ror_bag_small.jpeg +0 -0
  26. data/starter_app/lib/tasks/sample/products/0000/0004/ror_baseball.jpeg +0 -0
  27. data/starter_app/lib/tasks/sample/products/0000/0004/ror_baseball_mini.jpeg +0 -0
  28. data/starter_app/lib/tasks/sample/products/0000/0004/ror_baseball_product.jpeg +0 -0
  29. data/starter_app/lib/tasks/sample/products/0000/0004/ror_baseball_small.jpeg +0 -0
  30. data/starter_app/lib/tasks/sample/products/0000/0005/ror_baseball_back.jpeg +0 -0
  31. data/starter_app/lib/tasks/sample/products/0000/0005/ror_baseball_back_mini.jpeg +0 -0
  32. data/starter_app/lib/tasks/sample/products/0000/0005/ror_baseball_back_product.jpeg +0 -0
  33. data/starter_app/lib/tasks/sample/products/0000/0005/ror_baseball_back_small.jpeg +0 -0
  34. data/starter_app/lib/tasks/sample/products/0000/0006/ror_jr_spaghetti.jpeg +0 -0
  35. data/starter_app/lib/tasks/sample/products/0000/0006/ror_jr_spaghetti_mini.jpeg +0 -0
  36. data/starter_app/lib/tasks/sample/products/0000/0006/ror_jr_spaghetti_product.jpeg +0 -0
  37. data/starter_app/lib/tasks/sample/products/0000/0006/ror_jr_spaghetti_small.jpeg +0 -0
  38. data/starter_app/lib/tasks/sample/products/0000/0007/ror_mug.jpeg +0 -0
  39. data/starter_app/lib/tasks/sample/products/0000/0007/ror_mug_mini.jpeg +0 -0
  40. data/starter_app/lib/tasks/sample/products/0000/0007/ror_mug_product.jpeg +0 -0
  41. data/starter_app/lib/tasks/sample/products/0000/0007/ror_mug_small.jpeg +0 -0
  42. data/starter_app/lib/tasks/sample/products/0000/0008/ror_mug_back.jpeg +0 -0
  43. data/starter_app/lib/tasks/sample/products/0000/0008/ror_mug_back_mini.jpeg +0 -0
  44. data/starter_app/lib/tasks/sample/products/0000/0008/ror_mug_back_product.jpeg +0 -0
  45. data/starter_app/lib/tasks/sample/products/0000/0008/ror_mug_back_small.jpeg +0 -0
  46. data/starter_app/lib/tasks/sample/products/0000/0009/ror_ringer.jpeg +0 -0
  47. data/starter_app/lib/tasks/sample/products/0000/0009/ror_ringer_mini.jpeg +0 -0
  48. data/starter_app/lib/tasks/sample/products/0000/0009/ror_ringer_product.jpeg +0 -0
  49. data/starter_app/lib/tasks/sample/products/0000/0009/ror_ringer_small.jpeg +0 -0
  50. data/starter_app/lib/tasks/sample/products/0000/0010/ror_ringer_back.jpeg +0 -0
  51. data/starter_app/lib/tasks/sample/products/0000/0010/ror_ringer_back_mini.jpeg +0 -0
  52. data/starter_app/lib/tasks/sample/products/0000/0010/ror_ringer_back_product.jpeg +0 -0
  53. data/starter_app/lib/tasks/sample/products/0000/0010/ror_ringer_back_small.jpeg +0 -0
  54. data/starter_app/lib/tasks/sample/products/0000/0011/ror_stein.jpeg +0 -0
  55. data/starter_app/lib/tasks/sample/products/0000/0011/ror_stein_mini.jpeg +0 -0
  56. data/starter_app/lib/tasks/sample/products/0000/0011/ror_stein_product.jpeg +0 -0
  57. data/starter_app/lib/tasks/sample/products/0000/0011/ror_stein_small.jpeg +0 -0
  58. data/starter_app/lib/tasks/sample/products/0000/0012/ror_stein_back.jpeg +0 -0
  59. data/starter_app/lib/tasks/sample/products/0000/0012/ror_stein_back_mini.jpeg +0 -0
  60. data/starter_app/lib/tasks/sample/products/0000/0012/ror_stein_back_product.jpeg +0 -0
  61. data/starter_app/lib/tasks/sample/products/0000/0012/ror_stein_back_small.jpeg +0 -0
  62. data/starter_app/vendor/plugins/attachment_fu/CHANGELOG +24 -0
  63. data/starter_app/vendor/plugins/attachment_fu/README +162 -0
  64. data/starter_app/vendor/plugins/attachment_fu/Rakefile +22 -0
  65. data/starter_app/vendor/plugins/attachment_fu/amazon_s3.yml.tpl +14 -0
  66. data/starter_app/vendor/plugins/attachment_fu/init.rb +14 -0
  67. data/starter_app/vendor/plugins/attachment_fu/install.rb +5 -0
  68. data/starter_app/vendor/plugins/attachment_fu/lib/geometry.rb +93 -0
  69. data/starter_app/vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu.rb +410 -0
  70. data/starter_app/vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/db_file_backend.rb +39 -0
  71. data/starter_app/vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/file_system_backend.rb +97 -0
  72. data/starter_app/vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/s3_backend.rb +309 -0
  73. data/starter_app/vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/image_science_processor.rb +61 -0
  74. data/starter_app/vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb +56 -0
  75. data/starter_app/vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/rmagick_processor.rb +53 -0
  76. data/starter_app/vendor/plugins/attachment_fu/test/amazon_s3.yml +6 -0
  77. data/starter_app/vendor/plugins/attachment_fu/test/backends/db_file_test.rb +16 -0
  78. data/starter_app/vendor/plugins/attachment_fu/test/backends/file_system_test.rb +80 -0
  79. data/starter_app/vendor/plugins/attachment_fu/test/backends/remote/s3_test.rb +103 -0
  80. data/starter_app/vendor/plugins/attachment_fu/test/base_attachment_tests.rb +57 -0
  81. data/starter_app/vendor/plugins/attachment_fu/test/basic_test.rb +64 -0
  82. data/starter_app/vendor/plugins/attachment_fu/test/database.yml +18 -0
  83. data/starter_app/vendor/plugins/attachment_fu/test/extra_attachment_test.rb +57 -0
  84. data/starter_app/vendor/plugins/attachment_fu/test/fixtures/attachment.rb +127 -0
  85. data/starter_app/vendor/plugins/attachment_fu/test/fixtures/files/fake/rails.png +0 -0
  86. data/starter_app/vendor/plugins/attachment_fu/test/fixtures/files/foo.txt +1 -0
  87. data/starter_app/vendor/plugins/attachment_fu/test/fixtures/files/rails.png +0 -0
  88. data/starter_app/vendor/plugins/attachment_fu/test/geometry_test.rb +101 -0
  89. data/starter_app/vendor/plugins/attachment_fu/test/processors/image_science_test.rb +31 -0
  90. data/starter_app/vendor/plugins/attachment_fu/test/processors/mini_magick_test.rb +31 -0
  91. data/starter_app/vendor/plugins/attachment_fu/test/processors/rmagick_test.rb +241 -0
  92. data/starter_app/vendor/plugins/attachment_fu/test/schema.rb +86 -0
  93. data/starter_app/vendor/plugins/attachment_fu/test/test_helper.rb +142 -0
  94. data/starter_app/vendor/plugins/attachment_fu/test/validation_test.rb +55 -0
  95. data/starter_app/vendor/plugins/paginating_find/CHANGELOG +3 -0
  96. data/starter_app/vendor/plugins/paginating_find/lib/paginating_find.rb +5 -2
  97. data/starter_app/vendor/plugins/railscart/app/controllers/admin/categories_controller.rb +8 -8
  98. data/starter_app/vendor/plugins/railscart/app/controllers/admin/images_controller.rb +44 -0
  99. data/starter_app/vendor/plugins/railscart/app/controllers/admin/products_controller.rb +4 -0
  100. data/starter_app/vendor/plugins/railscart/app/helpers/rails_cart/base_helper.rb +25 -0
  101. data/starter_app/vendor/plugins/railscart/app/models/image.rb +12 -0
  102. data/starter_app/vendor/plugins/railscart/app/models/product.rb +15 -16
  103. data/starter_app/vendor/plugins/railscart/app/views/admin/categories/_form.rhtml +0 -3
  104. data/starter_app/vendor/plugins/railscart/app/views/admin/images/new.html.erb +10 -0
  105. data/starter_app/vendor/plugins/railscart/app/views/admin/products/_form.rhtml +3 -3
  106. data/starter_app/vendor/plugins/railscart/app/views/admin/products/_option_types.rhtml +0 -1
  107. data/starter_app/vendor/plugins/railscart/app/views/admin/products/_variations.rhtml +1 -2
  108. data/starter_app/vendor/plugins/railscart/app/views/admin/products/index.rhtml +1 -1
  109. data/starter_app/vendor/plugins/railscart/app/views/admin/products/new.rhtml +1 -3
  110. data/starter_app/vendor/plugins/railscart/app/views/cart/index.rhtml +1 -1
  111. data/starter_app/vendor/plugins/railscart/app/views/shared/_images.html.erb +43 -0
  112. data/starter_app/vendor/plugins/railscart/app/views/store/list.rhtml +1 -1
  113. data/starter_app/vendor/plugins/railscart/app/views/store/show.rhtml +1 -1
  114. data/starter_app/vendor/plugins/railscart/db/migrate/{008_create_option_values.rb → 007_create_option_values.rb} +0 -0
  115. data/starter_app/vendor/plugins/railscart/db/migrate/{010_create_orders.rb → 008_create_orders.rb} +0 -0
  116. data/starter_app/vendor/plugins/railscart/db/migrate/{011_create_products.rb → 009_create_products.rb} +2 -2
  117. data/starter_app/vendor/plugins/railscart/db/migrate/{013_create_txns.rb → 010_create_txns.rb} +0 -0
  118. data/starter_app/vendor/plugins/railscart/db/migrate/{015_create_variations.rb → 011_create_variations.rb} +0 -0
  119. data/starter_app/vendor/plugins/railscart/db/migrate/{016_create_tax_treatments.rb → 012_create_tax_treatments.rb} +0 -0
  120. data/starter_app/vendor/plugins/railscart/db/migrate/{017_create_skus.rb → 013_create_skus.rb} +0 -0
  121. data/starter_app/vendor/plugins/railscart/db/migrate/{018_create_countries.rb → 014_create_countries.rb} +0 -0
  122. data/starter_app/vendor/plugins/railscart/db/migrate/{019_create_states.rb → 015_create_states.rb} +0 -0
  123. data/starter_app/vendor/plugins/railscart/db/migrate/{021_create_option_types.rb → 016_create_option_types.rb} +0 -0
  124. data/starter_app/vendor/plugins/railscart/db/migrate/{022_create_product_option_types.rb → 017_create_product_option_types.rb} +0 -0
  125. data/starter_app/vendor/plugins/railscart/db/migrate/{023_create_option_values_variations.rb → 018_create_option_values_variations.rb} +0 -0
  126. data/starter_app/vendor/plugins/railscart/db/migrate/019_create_images.rb +19 -0
  127. metadata +106 -35
  128. data/starter_app/config/database.master.yml +0 -23
  129. data/starter_app/db/migrate/001_add_sessions.rb +0 -16
  130. data/starter_app/db/migrate/004_railscart_to_version_20.rb +0 -9
  131. data/starter_app/db/migrate/007_railscart_to_version_23.rb +0 -9
  132. data/starter_app/lib/tasks/sample/images/ror_bag.jpg +0 -0
  133. data/starter_app/lib/tasks/sample/images/ror_baseball_jersey.jpg +0 -0
  134. data/starter_app/lib/tasks/sample/images/ror_jr_spaghetti.jpg +0 -0
  135. data/starter_app/lib/tasks/sample/images/ror_mug.jpg +0 -0
  136. data/starter_app/lib/tasks/sample/images/ror_ringer_tshirt.jpg +0 -0
  137. data/starter_app/lib/tasks/sample/images/ror_stein.jpg +0 -0
  138. data/starter_app/lib/tasks/sample/images/ror_tote.jpg +0 -0
  139. data/starter_app/vendor/plugins/railscart/app/models/good.rb +0 -4
  140. data/starter_app/vendor/plugins/railscart/db/migrate/007_create_option_groups.rb +0 -12
  141. data/starter_app/vendor/plugins/railscart/db/migrate/009_create_options.rb +0 -11
  142. data/starter_app/vendor/plugins/railscart/db/migrate/012_ignore_users.rb +0 -9
  143. data/starter_app/vendor/plugins/railscart/db/migrate/014_ignore_roles.rb +0 -9
  144. data/starter_app/vendor/plugins/railscart/db/migrate/020_modify_users.rb +0 -9
@@ -0,0 +1,410 @@
1
+ module Technoweenie # :nodoc:
2
+ module AttachmentFu # :nodoc:
3
+ @@default_processors = %w(ImageScience Rmagick MiniMagick)
4
+ @@tempfile_path = File.join(RAILS_ROOT, 'tmp', 'attachment_fu')
5
+ @@content_types = ['image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/x-png', 'image/jpg']
6
+ mattr_reader :content_types, :tempfile_path, :default_processors
7
+ mattr_writer :tempfile_path
8
+
9
+ class ThumbnailError < StandardError; end
10
+ class AttachmentError < StandardError; end
11
+
12
+ module ActMethods
13
+ # Options:
14
+ # * <tt>:content_type</tt> - Allowed content types. Allows all by default. Use :image to allow all standard image types.
15
+ # * <tt>:min_size</tt> - Minimum size allowed. 1 byte is the default.
16
+ # * <tt>:max_size</tt> - Maximum size allowed. 1.megabyte is the default.
17
+ # * <tt>:size</tt> - Range of sizes allowed. (1..1.megabyte) is the default. This overrides the :min_size and :max_size options.
18
+ # * <tt>:resize_to</tt> - Used by RMagick to resize images. Pass either an array of width/height, or a geometry string.
19
+ # * <tt>:thumbnails</tt> - Specifies a set of thumbnails to generate. This accepts a hash of filename suffixes and RMagick resizing options.
20
+ # * <tt>:thumbnail_class</tt> - Set what class to use for thumbnails. This attachment class is used by default.
21
+ # * <tt>:path_prefix</tt> - path to store the uploaded files. Uses public/#{table_name} by default for the filesystem, and just #{table_name}
22
+ # for the S3 backend. Setting this sets the :storage to :file_system.
23
+ # * <tt>:storage</tt> - Use :file_system to specify the attachment data is stored with the file system. Defaults to :db_system.
24
+ #
25
+ # Examples:
26
+ # has_attachment :max_size => 1.kilobyte
27
+ # has_attachment :size => 1.megabyte..2.megabytes
28
+ # has_attachment :content_type => 'application/pdf'
29
+ # has_attachment :content_type => ['application/pdf', 'application/msword', 'text/plain']
30
+ # has_attachment :content_type => :image, :resize_to => [50,50]
31
+ # has_attachment :content_type => ['application/pdf', :image], :resize_to => 'x50'
32
+ # has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
33
+ # has_attachment :storage => :file_system, :path_prefix => 'public/files'
34
+ # has_attachment :storage => :file_system, :path_prefix => 'public/files',
35
+ # :content_type => :image, :resize_to => [50,50]
36
+ # has_attachment :storage => :file_system, :path_prefix => 'public/files',
37
+ # :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
38
+ # has_attachment :storage => :s3
39
+ def has_attachment(options = {})
40
+ # this allows you to redefine the acts' options for each subclass, however
41
+ options[:min_size] ||= 1
42
+ options[:max_size] ||= 1.megabyte
43
+ options[:size] ||= (options[:min_size]..options[:max_size])
44
+ options[:thumbnails] ||= {}
45
+ options[:thumbnail_class] ||= self
46
+ options[:s3_access] ||= :public_read
47
+ options[:content_type] = [options[:content_type]].flatten.collect! { |t| t == :image ? Technoweenie::AttachmentFu.content_types : t }.flatten unless options[:content_type].nil?
48
+
49
+ unless options[:thumbnails].is_a?(Hash)
50
+ raise ArgumentError, ":thumbnails option should be a hash: e.g. :thumbnails => { :foo => '50x50' }"
51
+ end
52
+
53
+ # doing these shenanigans so that #attachment_options is available to processors and backends
54
+ class_inheritable_accessor :attachment_options
55
+ self.attachment_options = options
56
+
57
+ # only need to define these once on a class
58
+ unless included_modules.include?(InstanceMethods)
59
+ attr_accessor :thumbnail_resize_options
60
+
61
+ attachment_options[:storage] ||= (attachment_options[:file_system_path] || attachment_options[:path_prefix]) ? :file_system : :db_file
62
+ attachment_options[:path_prefix] ||= attachment_options[:file_system_path]
63
+ if attachment_options[:path_prefix].nil?
64
+ attachment_options[:path_prefix] = attachment_options[:storage] == :s3 ? table_name : File.join("public", table_name)
65
+ end
66
+ attachment_options[:path_prefix] = attachment_options[:path_prefix][1..-1] if options[:path_prefix].first == '/'
67
+
68
+ with_options :foreign_key => 'parent_id' do |m|
69
+ m.has_many :thumbnails, :class_name => attachment_options[:thumbnail_class].to_s
70
+ m.belongs_to :parent, :class_name => base_class.to_s
71
+ end
72
+ before_destroy :destroy_thumbnails
73
+
74
+ before_validation :set_size_from_temp_path
75
+ after_save :after_process_attachment
76
+ after_destroy :destroy_file
77
+ extend ClassMethods
78
+ include InstanceMethods
79
+ include Technoweenie::AttachmentFu::Backends.const_get("#{options[:storage].to_s.classify}Backend")
80
+ case attachment_options[:processor]
81
+ when :none
82
+ when nil
83
+ processors = Technoweenie::AttachmentFu.default_processors.dup
84
+ begin
85
+ if processors.any?
86
+ attachment_options[:processor] = "#{processors.first}Processor"
87
+ include Technoweenie::AttachmentFu::Processors.const_get(attachment_options[:processor])
88
+ end
89
+ rescue LoadError, MissingSourceFile
90
+ processors.shift
91
+ retry
92
+ end
93
+ else
94
+ begin
95
+ include Technoweenie::AttachmentFu::Processors.const_get("#{options[:processor].to_s.classify}Processor")
96
+ rescue LoadError, MissingSourceFile
97
+ puts "Problems loading #{options[:processor]}Processor: #{$!}"
98
+ end
99
+ end
100
+ after_validation :process_attachment
101
+ end
102
+ end
103
+ end
104
+
105
+ module ClassMethods
106
+ delegate :content_types, :to => Technoweenie::AttachmentFu
107
+
108
+ # Performs common validations for attachment models.
109
+ def validates_as_attachment
110
+ validates_presence_of :size, :content_type, :filename
111
+ validate :attachment_attributes_valid?
112
+ end
113
+
114
+ # Returns true or false if the given content type is recognized as an image.
115
+ def image?(content_type)
116
+ content_types.include?(content_type)
117
+ end
118
+
119
+ # Callback after an image has been resized.
120
+ #
121
+ # class Foo < ActiveRecord::Base
122
+ # acts_as_attachment
123
+ # after_resize do |record, img|
124
+ # record.aspect_ratio = img.columns.to_f / img.rows.to_f
125
+ # end
126
+ # end
127
+ def after_resize(&block)
128
+ write_inheritable_array(:after_resize, [block])
129
+ end
130
+
131
+ # Callback after an attachment has been saved either to the file system or the DB.
132
+ # Only called if the file has been changed, not necessarily if the record is updated.
133
+ #
134
+ # class Foo < ActiveRecord::Base
135
+ # acts_as_attachment
136
+ # after_attachment_saved do |record|
137
+ # ...
138
+ # end
139
+ # end
140
+ def after_attachment_saved(&block)
141
+ write_inheritable_array(:after_attachment_saved, [block])
142
+ end
143
+
144
+ # Callback before a thumbnail is saved. Use this to pass any necessary extra attributes that may be required.
145
+ #
146
+ # class Foo < ActiveRecord::Base
147
+ # acts_as_attachment
148
+ # before_thumbnail_saved do |record, thumbnail|
149
+ # ...
150
+ # end
151
+ # end
152
+ def before_thumbnail_saved(&block)
153
+ write_inheritable_array(:before_thumbnail_saved, [block])
154
+ end
155
+
156
+ # Get the thumbnail class, which is the current attachment class by default.
157
+ # Configure this with the :thumbnail_class option.
158
+ def thumbnail_class
159
+ attachment_options[:thumbnail_class] = attachment_options[:thumbnail_class].constantize unless attachment_options[:thumbnail_class].is_a?(Class)
160
+ attachment_options[:thumbnail_class]
161
+ end
162
+
163
+ # Copies the given file path to a new tempfile, returning the closed tempfile.
164
+ def copy_to_temp_file(file, temp_base_name)
165
+ returning Tempfile.new(temp_base_name, Technoweenie::AttachmentFu.tempfile_path) do |tmp|
166
+ tmp.close
167
+ FileUtils.cp file, tmp.path
168
+ end
169
+ end
170
+
171
+ # Writes the given data to a new tempfile, returning the closed tempfile.
172
+ def write_to_temp_file(data, temp_base_name)
173
+ returning Tempfile.new(temp_base_name, Technoweenie::AttachmentFu.tempfile_path) do |tmp|
174
+ tmp.binmode
175
+ tmp.write data
176
+ tmp.close
177
+ end
178
+ end
179
+ end
180
+
181
+ module InstanceMethods
182
+ # Checks whether the attachment's content type is an image content type
183
+ def image?
184
+ self.class.image?(content_type)
185
+ end
186
+
187
+ # Returns true/false if an attachment is thumbnailable. A thumbnailable attachment has an image content type and the parent_id attribute.
188
+ def thumbnailable?
189
+ image? && respond_to?(:parent_id) && parent_id.nil?
190
+ end
191
+
192
+ # Returns the class used to create new thumbnails for this attachment.
193
+ def thumbnail_class
194
+ self.class.thumbnail_class
195
+ end
196
+
197
+ # Gets the thumbnail name for a filename. 'foo.jpg' becomes 'foo_thumbnail.jpg'
198
+ def thumbnail_name_for(thumbnail = nil)
199
+ return filename if thumbnail.blank?
200
+ ext = nil
201
+ basename = filename.gsub /\.\w+$/ do |s|
202
+ ext = s; ''
203
+ end
204
+ # ImageScience doesn't create gif thumbnails, only pngs
205
+ ext.sub!(/gif$/, 'png') if attachment_options[:processor] == "ImageScienceProcessor"
206
+ "#{basename}_#{thumbnail}#{ext}"
207
+ end
208
+
209
+ # Creates or updates the thumbnail for the current attachment.
210
+ def create_or_update_thumbnail(temp_file, file_name_suffix, *size)
211
+ thumbnailable? || raise(ThumbnailError.new("Can't create a thumbnail if the content type is not an image or there is no parent_id column"))
212
+ returning find_or_initialize_thumbnail(file_name_suffix) do |thumb|
213
+ thumb.attributes = {
214
+ :content_type => content_type,
215
+ :filename => thumbnail_name_for(file_name_suffix),
216
+ :temp_path => temp_file,
217
+ :thumbnail_resize_options => size
218
+ }
219
+ callback_with_args :before_thumbnail_saved, thumb
220
+ thumb.save!
221
+ end
222
+ end
223
+
224
+ # Sets the content type.
225
+ def content_type=(new_type)
226
+ write_attribute :content_type, new_type.to_s.strip
227
+ end
228
+
229
+ # Sanitizes a filename.
230
+ def filename=(new_name)
231
+ write_attribute :filename, sanitize_filename(new_name)
232
+ end
233
+
234
+ # Returns the width/height in a suitable format for the image_tag helper: (100x100)
235
+ def image_size
236
+ [width.to_s, height.to_s] * 'x'
237
+ end
238
+
239
+ # Returns true if the attachment data will be written to the storage system on the next save
240
+ def save_attachment?
241
+ File.file?(temp_path.to_s)
242
+ end
243
+
244
+ # nil placeholder in case this field is used in a form.
245
+ def uploaded_data() nil; end
246
+
247
+ # This method handles the uploaded file object. If you set the field name to uploaded_data, you don't need
248
+ # any special code in your controller.
249
+ #
250
+ # <% form_for :attachment, :html => { :multipart => true } do |f| -%>
251
+ # <p><%= f.file_field :uploaded_data %></p>
252
+ # <p><%= submit_tag :Save %>
253
+ # <% end -%>
254
+ #
255
+ # @attachment = Attachment.create! params[:attachment]
256
+ #
257
+ # TODO: Allow it to work with Merb tempfiles too.
258
+ def uploaded_data=(file_data)
259
+ return nil if file_data.nil? || file_data.size == 0
260
+ self.content_type = file_data.content_type
261
+ self.filename = file_data.original_filename if respond_to?(:filename)
262
+ if file_data.is_a?(StringIO)
263
+ file_data.rewind
264
+ self.temp_data = file_data.read
265
+ else
266
+ self.temp_path = file_data.path
267
+ end
268
+ end
269
+
270
+ # Gets the latest temp path from the collection of temp paths. While working with an attachment,
271
+ # multiple Tempfile objects may be created for various processing purposes (resizing, for example).
272
+ # An array of all the tempfile objects is stored so that the Tempfile instance is held on to until
273
+ # it's not needed anymore. The collection is cleared after saving the attachment.
274
+ def temp_path
275
+ p = temp_paths.first
276
+ p.respond_to?(:path) ? p.path : p.to_s
277
+ end
278
+
279
+ # Gets an array of the currently used temp paths. Defaults to a copy of #full_filename.
280
+ def temp_paths
281
+ @temp_paths ||= (new_record? || !File.exist?(full_filename)) ? [] : [copy_to_temp_file(full_filename)]
282
+ end
283
+
284
+ # Adds a new temp_path to the array. This should take a string or a Tempfile. This class makes no
285
+ # attempt to remove the files, so Tempfiles should be used. Tempfiles remove themselves when they go out of scope.
286
+ # You can also use string paths for temporary files, such as those used for uploaded files in a web server.
287
+ def temp_path=(value)
288
+ temp_paths.unshift value
289
+ temp_path
290
+ end
291
+
292
+ # Gets the data from the latest temp file. This will read the file into memory.
293
+ def temp_data
294
+ save_attachment? ? File.read(temp_path) : nil
295
+ end
296
+
297
+ # Writes the given data to a Tempfile and adds it to the collection of temp files.
298
+ def temp_data=(data)
299
+ self.temp_path = write_to_temp_file data unless data.nil?
300
+ end
301
+
302
+ # Copies the given file to a randomly named Tempfile.
303
+ def copy_to_temp_file(file)
304
+ self.class.copy_to_temp_file file, random_tempfile_filename
305
+ end
306
+
307
+ # Writes the given file to a randomly named Tempfile.
308
+ def write_to_temp_file(data)
309
+ self.class.write_to_temp_file data, random_tempfile_filename
310
+ end
311
+
312
+ # Stub for creating a temp file from the attachment data. This should be defined in the backend module.
313
+ def create_temp_file() end
314
+
315
+ # Allows you to work with a processed representation (RMagick, ImageScience, etc) of the attachment in a block.
316
+ #
317
+ # @attachment.with_image do |img|
318
+ # self.data = img.thumbnail(100, 100).to_blob
319
+ # end
320
+ #
321
+ def with_image(&block)
322
+ self.class.with_image(temp_path, &block)
323
+ end
324
+
325
+ protected
326
+ # Generates a unique filename for a Tempfile.
327
+ def random_tempfile_filename
328
+ "#{rand Time.now.to_i}#{filename || 'attachment'}"
329
+ end
330
+
331
+ def sanitize_filename(filename)
332
+ returning filename.strip do |name|
333
+ # NOTE: File.basename doesn't work right with Windows paths on Unix
334
+ # get only the filename, not the whole path
335
+ name.gsub! /^.*(\\|\/)/, ''
336
+
337
+ # Finally, replace all non alphanumeric, underscore or periods with underscore
338
+ name.gsub! /[^\w\.\-]/, '_'
339
+ end
340
+ end
341
+
342
+ # before_validation callback.
343
+ def set_size_from_temp_path
344
+ self.size = File.size(temp_path) if save_attachment?
345
+ end
346
+
347
+ # validates the size and content_type attributes according to the current model's options
348
+ def attachment_attributes_valid?
349
+ [:size, :content_type].each do |attr_name|
350
+ enum = attachment_options[attr_name]
351
+ errors.add attr_name, ActiveRecord::Errors.default_error_messages[:inclusion] unless enum.nil? || enum.include?(send(attr_name))
352
+ end
353
+ end
354
+
355
+ # Initializes a new thumbnail with the given suffix.
356
+ def find_or_initialize_thumbnail(file_name_suffix)
357
+ respond_to?(:parent_id) ?
358
+ thumbnail_class.find_or_initialize_by_thumbnail_and_parent_id(file_name_suffix.to_s, id) :
359
+ thumbnail_class.find_or_initialize_by_thumbnail(file_name_suffix.to_s)
360
+ end
361
+
362
+ # Stub for a #process_attachment method in a processor
363
+ def process_attachment
364
+ @saved_attachment = save_attachment?
365
+ end
366
+
367
+ # Cleans up after processing. Thumbnails are created, the attachment is stored to the backend, and the temp_paths are cleared.
368
+ def after_process_attachment
369
+ if @saved_attachment
370
+ if respond_to?(:process_attachment_with_processing) && thumbnailable? && !attachment_options[:thumbnails].blank? && parent_id.nil?
371
+ temp_file = temp_path || create_temp_file
372
+ attachment_options[:thumbnails].each { |suffix, size| create_or_update_thumbnail(temp_file, suffix, *size) }
373
+ end
374
+ save_to_storage
375
+ @temp_paths.clear
376
+ @saved_attachment = nil
377
+ callback :after_attachment_saved
378
+ end
379
+ end
380
+
381
+ # Resizes the given processed img object with either the attachment resize options or the thumbnail resize options.
382
+ def resize_image_or_thumbnail!(img)
383
+ if (!respond_to?(:parent_id) || parent_id.nil?) && attachment_options[:resize_to] # parent image
384
+ resize_image(img, attachment_options[:resize_to])
385
+ elsif thumbnail_resize_options # thumbnail
386
+ resize_image(img, thumbnail_resize_options)
387
+ end
388
+ end
389
+
390
+ # Yanked from ActiveRecord::Callbacks, modified so I can pass args to the callbacks besides self.
391
+ # Only accept blocks, however
392
+ def callback_with_args(method, arg = self)
393
+ notify(method)
394
+
395
+ result = nil
396
+ callbacks_for(method).each do |callback|
397
+ result = callback.call(self, arg)
398
+ return false if result == false
399
+ end
400
+
401
+ return result
402
+ end
403
+
404
+ # Removes the thumbnails for the attachment, if it has any
405
+ def destroy_thumbnails
406
+ self.thumbnails.each { |thumbnail| thumbnail.destroy } if thumbnailable?
407
+ end
408
+ end
409
+ end
410
+ end
@@ -0,0 +1,39 @@
1
+ module Technoweenie # :nodoc:
2
+ module AttachmentFu # :nodoc:
3
+ module Backends
4
+ # Methods for DB backed attachments
5
+ module DbFileBackend
6
+ def self.included(base) #:nodoc:
7
+ Object.const_set(:DbFile, Class.new(ActiveRecord::Base)) unless Object.const_defined?(:DbFile)
8
+ base.belongs_to :db_file, :class_name => '::DbFile', :foreign_key => 'db_file_id'
9
+ end
10
+
11
+ # Creates a temp file with the current db data.
12
+ def create_temp_file
13
+ write_to_temp_file current_data
14
+ end
15
+
16
+ # Gets the current data from the database
17
+ def current_data
18
+ db_file.data
19
+ end
20
+
21
+ protected
22
+ # Destroys the file. Called in the after_destroy callback
23
+ def destroy_file
24
+ db_file.destroy if db_file
25
+ end
26
+
27
+ # Saves the data to the DbFile model
28
+ def save_to_storage
29
+ if save_attachment?
30
+ (db_file || build_db_file).data = temp_data
31
+ db_file.save!
32
+ self.class.update_all ['db_file_id = ?', self.db_file_id = db_file.id], ['id = ?', id]
33
+ end
34
+ true
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,97 @@
1
+ require 'ftools'
2
+ module Technoweenie # :nodoc:
3
+ module AttachmentFu # :nodoc:
4
+ module Backends
5
+ # Methods for file system backed attachments
6
+ module FileSystemBackend
7
+ def self.included(base) #:nodoc:
8
+ base.before_update :rename_file
9
+ end
10
+
11
+ # Gets the full path to the filename in this format:
12
+ #
13
+ # # This assumes a model name like MyModel
14
+ # # public/#{table_name} is the default filesystem path
15
+ # RAILS_ROOT/public/my_models/5/blah.jpg
16
+ #
17
+ # Overwrite this method in your model to customize the filename.
18
+ # The optional thumbnail argument will output the thumbnail's filename.
19
+ def full_filename(thumbnail = nil)
20
+ file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s
21
+ File.join(RAILS_ROOT, file_system_path, *partitioned_path(thumbnail_name_for(thumbnail)))
22
+ end
23
+
24
+ # Used as the base path that #public_filename strips off full_filename to create the public path
25
+ def base_path
26
+ @base_path ||= File.join(RAILS_ROOT, 'public')
27
+ end
28
+
29
+ # The attachment ID used in the full path of a file
30
+ def attachment_path_id
31
+ ((respond_to?(:parent_id) && parent_id) || id).to_i
32
+ end
33
+
34
+ # overrwrite this to do your own app-specific partitioning.
35
+ # you can thank Jamis Buck for this: http://www.37signals.com/svn/archives2/id_partitioning.php
36
+ def partitioned_path(*args)
37
+ ("%08d" % attachment_path_id).scan(/..../) + args
38
+ end
39
+
40
+ # Gets the public path to the file
41
+ # The optional thumbnail argument will output the thumbnail's filename.
42
+ def public_filename(thumbnail = nil)
43
+ full_filename(thumbnail).gsub %r(^#{Regexp.escape(base_path)}), ''
44
+ end
45
+
46
+ def filename=(value)
47
+ @old_filename = full_filename unless filename.nil? || @old_filename
48
+ write_attribute :filename, sanitize_filename(value)
49
+ end
50
+
51
+ # Creates a temp file from the currently saved file.
52
+ def create_temp_file
53
+ copy_to_temp_file full_filename
54
+ end
55
+
56
+ protected
57
+ # Destroys the file. Called in the after_destroy callback
58
+ def destroy_file
59
+ FileUtils.rm full_filename
60
+ # remove directory also if it is now empty
61
+ Dir.rmdir(File.dirname(full_filename)) if (Dir.entries(File.dirname(full_filename))-['.','..']).empty?
62
+ rescue
63
+ logger.info "Exception destroying #{full_filename.inspect}: [#{$!.class.name}] #{$1.to_s}"
64
+ logger.warn $!.backtrace.collect { |b| " > #{b}" }.join("\n")
65
+ end
66
+
67
+ # Renames the given file before saving
68
+ def rename_file
69
+ return unless @old_filename && @old_filename != full_filename
70
+ if save_attachment? && File.exists?(@old_filename)
71
+ FileUtils.rm @old_filename
72
+ elsif File.exists?(@old_filename)
73
+ FileUtils.mv @old_filename, full_filename
74
+ end
75
+ @old_filename = nil
76
+ true
77
+ end
78
+
79
+ # Saves the file to the file system
80
+ def save_to_storage
81
+ if save_attachment?
82
+ # TODO: This overwrites the file if it exists, maybe have an allow_overwrite option?
83
+ FileUtils.mkdir_p(File.dirname(full_filename))
84
+ File.cp(temp_path, full_filename)
85
+ File.chmod(attachment_options[:chmod] || 0644, full_filename)
86
+ end
87
+ @old_filename = nil
88
+ true
89
+ end
90
+
91
+ def current_data
92
+ File.file?(full_filename) ? File.read(full_filename) : nil
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end