effective_assets 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +228 -0
  3. data/Rakefile +23 -0
  4. data/app/assets/images/effective_assets/icon_close.png +0 -0
  5. data/app/assets/images/effective_assets/s3_down_button.gif +0 -0
  6. data/app/assets/images/effective_assets/s3_over_button.gif +0 -0
  7. data/app/assets/images/effective_assets/s3_up_button.gif +0 -0
  8. data/app/assets/images/effective_assets/s3_upload.swf +0 -0
  9. data/app/assets/images/effective_assets/spinner.gif +0 -0
  10. data/app/assets/images/mime-types/excel.jpg +0 -0
  11. data/app/assets/images/mime-types/file.png +0 -0
  12. data/app/assets/images/mime-types/mp3.png +0 -0
  13. data/app/assets/images/mime-types/pdf.png +0 -0
  14. data/app/assets/images/mime-types/video.png +0 -0
  15. data/app/assets/images/mime-types/word.jpg +0 -0
  16. data/app/assets/images/mime-types/zip.png +0 -0
  17. data/app/assets/javascripts/effective_assets.js +1 -0
  18. data/app/assets/javascripts/effective_assets/asset_box_input.js.coffee +71 -0
  19. data/app/assets/javascripts/effective_assets/asset_box_uploader.js +122 -0
  20. data/app/assets/javascripts/effective_assets/asset_box_uploader_customization.js +166 -0
  21. data/app/assets/javascripts/effective_assets/jquery_ui_sortable.js +2106 -0
  22. data/app/assets/stylesheets/effective_assets.css.scss +1 -0
  23. data/app/assets/stylesheets/effective_assets/_asset_box_input.scss +206 -0
  24. data/app/controllers/effective/attachments_controller.rb +14 -0
  25. data/app/controllers/effective/s3_uploads_controller.rb +98 -0
  26. data/app/helpers/effective_assets_helper.rb +55 -0
  27. data/app/models/concerns/acts_as_asset_box.rb +97 -0
  28. data/app/models/effective/asset.rb +224 -0
  29. data/app/models/effective/attachment.rb +25 -0
  30. data/app/models/effective/delayed_job.rb +99 -0
  31. data/app/models/inputs/asset_box_input.rb +87 -0
  32. data/app/models/validators/asset_box_length_validator.rb +11 -0
  33. data/app/models/validators/asset_box_presence_validator.rb +6 -0
  34. data/app/uploaders/asset_uploader.rb +26 -0
  35. data/app/uploaders/effective_assets_uploader.rb +63 -0
  36. data/app/views/active_admin/effective_assets/_edit.html.haml +35 -0
  37. data/app/views/active_admin/effective_assets/_form.html.haml +4 -0
  38. data/app/views/active_admin/effective_assets/_new.html.haml +3 -0
  39. data/app/views/asset_box_input/_attachment_fields.html.haml +14 -0
  40. data/app/views/asset_box_input/_uploader.html.haml +119 -0
  41. data/app/views/assets/_video.html.erb +4 -0
  42. data/config/routes.rb +6 -0
  43. data/db/migrate/01_create_effective_assets.rb.erb +42 -0
  44. data/lib/effective_assets.rb +31 -0
  45. data/lib/effective_assets/engine.rb +38 -0
  46. data/lib/effective_assets/version.rb +3 -0
  47. data/lib/generators/effective_assets/install_generator.rb +41 -0
  48. data/lib/generators/templates/README +1 -0
  49. data/lib/generators/templates/asset_uploader.rb +25 -0
  50. data/lib/generators/templates/effective_assets.rb +19 -0
  51. data/lib/tasks/effective_assets_tasks.rake +4 -0
  52. data/spec/dummy/README.rdoc +261 -0
  53. data/spec/dummy/Rakefile +7 -0
  54. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  55. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  56. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  57. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  58. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  59. data/spec/dummy/config.ru +4 -0
  60. data/spec/dummy/config/application.rb +65 -0
  61. data/spec/dummy/config/boot.rb +10 -0
  62. data/spec/dummy/config/database.yml +25 -0
  63. data/spec/dummy/config/environment.rb +5 -0
  64. data/spec/dummy/config/environments/development.rb +37 -0
  65. data/spec/dummy/config/environments/production.rb +67 -0
  66. data/spec/dummy/config/environments/test.rb +37 -0
  67. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  68. data/spec/dummy/config/initializers/inflections.rb +15 -0
  69. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  70. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  71. data/spec/dummy/config/initializers/session_store.rb +8 -0
  72. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  73. data/spec/dummy/config/locales/en.yml +5 -0
  74. data/spec/dummy/config/routes.rb +58 -0
  75. data/spec/dummy/db/development.sqlite3 +0 -0
  76. data/spec/dummy/db/schema.rb +16 -0
  77. data/spec/dummy/db/test.sqlite3 +0 -0
  78. data/spec/dummy/log/development.log +71 -0
  79. data/spec/dummy/log/test.log +33 -0
  80. data/spec/dummy/public/404.html +26 -0
  81. data/spec/dummy/public/422.html +26 -0
  82. data/spec/dummy/public/500.html +25 -0
  83. data/spec/dummy/public/favicon.ico +0 -0
  84. data/spec/dummy/script/rails +6 -0
  85. data/spec/dummy/spec_link +3 -0
  86. data/spec/models/asset_spec.rb +46 -0
  87. data/spec/spec_helper.rb +34 -0
  88. data/spec/support/factories.rb +28 -0
  89. metadata +431 -0
@@ -0,0 +1,224 @@
1
+ module Effective
2
+ class Asset < ActiveRecord::Base
3
+ self.table_name = EffectiveAssets.assets_table_name.to_s
4
+
5
+ mount_uploader :data, EffectiveAssets.uploader
6
+
7
+ belongs_to :user if defined?(User)
8
+ # This is the user that uploaded the asset.
9
+ # We are using belongs_to because it makes all the permissions 'just work',
10
+ # in letting people create assets.
11
+ #
12
+ # But this doesn't exactly belong to a user.
13
+ # Assets belong to acts_as_asset_box enabled classes through attachments.
14
+ #
15
+ # 1. The S3 uploader makes a post to s3_controller#create
16
+ # 2. The asset is created by the current_user
17
+ # 3. But it doesn't belong to anything yet. It might be attached to a user's asset_boxes, or a page, or a post through attachments
18
+ #
19
+
20
+ has_many :attachments, :dependent => :delete_all
21
+
22
+ structure do
23
+ title :string
24
+ description :text
25
+ tags :string
26
+
27
+ content_type :string, :validates => [:presence]
28
+ upload_file :string # The full url of the file, as originally uploaded
29
+ processed :boolean, :default => false
30
+
31
+ data :string
32
+
33
+ data_size :integer
34
+ height :integer, :validates => [:numericality => { :allow_nil => true }]
35
+ width :integer, :validates => [:numericality => { :allow_nil => true }]
36
+
37
+ versions_info :text # We store a hash of {:thumb => 34567, :medium => 3343434} data sizes
38
+
39
+ timestamps
40
+ end
41
+
42
+ serialize :versions_info, Hash
43
+
44
+ #attr_accessible :title, :description, :tags, :content_type, :data_size, :upload_file, :user, :user_id, :id
45
+ #validates_presence_of :user_id
46
+
47
+ before_save :update_asset_dimensions
48
+
49
+ default_scope order('created_at DESC')
50
+
51
+ scope :images, -> { where('content_type LIKE ?', '%image%') }
52
+ scope :videos, -> { where('content_type LIKE ?', '%video%') }
53
+ scope :audio, -> { where('content_type LIKE ?', '%audio%') }
54
+ scope :files, -> { where('(content_type NOT LIKE ?) AND (content_type NOT LIKE ?) AND (content_type NOT LIKE ?)', '%image%', '%video%', '%audio%') }
55
+
56
+ scope :today, -> { where("created_at >= ?", Date.today.beginning_of_day) }
57
+ scope :this_week, -> { where("created_at >= ?", Date.today.beginning_of_week) }
58
+ scope :this_month, -> { where("created_at >= ?", Date.today.beginning_of_month) }
59
+
60
+ class << self
61
+ def s3_base_path
62
+ "http://#{EffectiveAssets.aws_bucket}.s3.amazonaws.com"
63
+ end
64
+
65
+ def string_base_path
66
+ "string://"
67
+ end
68
+
69
+ # Just call me with Asset.create_from_url('http://somewhere.com/somthing.jpg')
70
+ def create_from_url(url, options = {})
71
+ opts = {:upload_file => url, :user_id => 1}.merge(options)
72
+
73
+ if (asset = Asset.create(opts))
74
+ Effective::DelayedJob.new.process_asset(asset)
75
+ asset
76
+ else
77
+ false
78
+ end
79
+ end
80
+
81
+ # We have just uploaded an asset via our s3 uploader
82
+ # We want this image to be immediately available.
83
+ def create_from_s3_uploader(url, options = {})
84
+ opts = {:upload_file => "#{Asset.s3_base_path}/#{url}", :user_id => 1}.merge(options)
85
+
86
+ asset = false
87
+
88
+ Asset.transaction do
89
+ begin
90
+ asset = Asset.create!(opts)
91
+
92
+ Rails.logger.info "Copying s3 uploaded file to final resting place..."
93
+ storage = Fog::Storage.new(:provider => 'AWS', :aws_access_key_id => EffectiveAssets.aws_access_key_id, :aws_secret_access_key => EffectiveAssets.aws_secret_access_key)
94
+ storage.copy_object(EffectiveAssets.aws_bucket, url, EffectiveAssets.aws_bucket, "#{EffectiveAssets.aws_final_path.chomp('/')}/#{asset.id}/#{asset.file_name}")
95
+ storage.put_object_acl(EffectiveAssets.aws_bucket, "#{EffectiveAssets.aws_final_path.chomp('/')}/#{asset.id}/#{asset.file_name}", EffectiveAssets.aws_acl)
96
+
97
+ Rails.logger.info "Deleting original..."
98
+ directory = storage.directories.get(EffectiveAssets.aws_bucket)
99
+ directory.files.new(:key => url).destroy
100
+
101
+ asset.update_column(:upload_file, asset.url) # This is our upload file as far as CarrierWave is now concerned
102
+
103
+ Effective::DelayedJob.new.process_asset(asset)
104
+ rescue => e
105
+ Rails.logger.info e.message
106
+ Rails.logger.info e.backtrace.join('\n')
107
+ asset = false
108
+ end
109
+
110
+ raise ActiveRecord::Rollback unless asset
111
+ end
112
+
113
+ asset
114
+ end
115
+
116
+ # This loads the raw contents of a string into a file and uploads that file to s3
117
+ # Expect to be passed something like
118
+ # Asset.create_from_string('some binary stuff from a string', :filename => 'icon_close.png', :content_type => 'image/png')
119
+ def create_from_string(str, options = {})
120
+ filename = options.delete(:filename) || "file-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}.txt"
121
+
122
+ opts = {:upload_file => "#{Asset.string_base_path}#{filename}", :user_id => 1}.merge(options)
123
+
124
+ asset = Asset.new(opts)
125
+ asset.data = AssetStringIO.new(filename, str)
126
+
127
+ if asset.save
128
+ Effective::DelayedJob.new.process_asset(asset)
129
+ asset
130
+ end
131
+ end
132
+ end
133
+
134
+ before_validation do
135
+ if !content_type.present? or content_type == 'null' or content_type == 'unknown' or content_type == 'application/octet-stream'
136
+ self.content_type = case url.to_s[-4, 4].downcase
137
+ when '.mp3' ; 'audio/mp3'
138
+ when '.mp4' ; 'video/mp4'
139
+ when '.mov' ; 'video/mov'
140
+ when '.m2v' ; 'video/m2v'
141
+ when '.m4v' ; 'video/m4v'
142
+ when '.3gp' ; 'video/3gp'
143
+ when '.3g2' ; 'video/3g2'
144
+ when '.avi' ; 'video/avi'
145
+ when '.jpg' ; 'image/jpg'
146
+ when '.gif' ; 'image/gif'
147
+ when '.png' ; 'image/png'
148
+ when '.bmp' ; 'image/bmp'
149
+ when '.ico' ; 'image/x-icon'
150
+ else ; 'unknown'
151
+ end
152
+ end
153
+ end
154
+
155
+ def title
156
+ self[:title].present? ? self[:title] : file_name
157
+ end
158
+
159
+ # Return the final location of this asset
160
+ def url
161
+ "#{Asset.s3_base_path}/#{EffectiveAssets.aws_final_path.chomp('/')}/#{self.id.to_i}/#{upload_file.to_s.split('/').last}"
162
+ end
163
+
164
+ def file_name
165
+ url.split('/').last rescue url
166
+ end
167
+
168
+ def video?
169
+ content_type.include? 'video'
170
+ end
171
+
172
+ def image?
173
+ content_type.include? 'image'
174
+ end
175
+
176
+ def icon?
177
+ content_type.include? 'image/x-icon'
178
+ end
179
+
180
+ def audio?
181
+ content_type.include? 'audio'
182
+ end
183
+
184
+ def as_json(options={})
185
+ {:thumbnail => image_tag(:thumb).html_safe}.merge super
186
+ end
187
+
188
+ def still_processing?
189
+ !processed
190
+ end
191
+
192
+ def versions_info
193
+ self[:versions_info] || {}
194
+ end
195
+
196
+ protected
197
+ # Called in the DelayedJob
198
+ def update_asset_dimensions
199
+ if data.present? and data_changed? and image?
200
+ begin
201
+ image = MiniMagick::Image.open(self.data.path)
202
+ self.width = image[:width]
203
+ self.height = image[:height]
204
+ rescue => e
205
+ end
206
+ end
207
+ true
208
+ end
209
+ end
210
+
211
+ class AssetStringIO < StringIO
212
+ attr_accessor :filepath
213
+
214
+ def initialize(*args)
215
+ super(*args[1..-1])
216
+ @filepath = args[0]
217
+ end
218
+
219
+ def original_filename
220
+ File.basename(filepath)
221
+ end
222
+ end
223
+ end
224
+
@@ -0,0 +1,25 @@
1
+ # This is a join table for assets and acts_as_attachable
2
+
3
+ module Effective
4
+ class Attachment < ActiveRecord::Base
5
+ self.table_name = EffectiveAssets.attachments_table_name.to_s
6
+
7
+ belongs_to :asset
8
+ belongs_to :attachable, :polymorphic => true
9
+
10
+ structure do
11
+ position :integer, :validates => [:presence, :numericality]
12
+ box :string, :default => 'assets', :validates => [:presence] # This is essentially a category
13
+ end
14
+
15
+ validates_presence_of :asset_id
16
+
17
+ #attr_accessible :box, :position, :asset_id, :attachable_type, :attachable_id
18
+
19
+ class << self
20
+ def default_scope
21
+ includes(:asset).order(:position)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,99 @@
1
+ #require 'uri'
2
+ # Call with DelayedJob.new.process_asset_images(...)
3
+ # Run jobs locally with "rake jobs:work"
4
+
5
+ module Effective
6
+ class DelayedJob
7
+ def process_asset(asset)
8
+ DelayedJob.configure_carrierwave
9
+
10
+ if asset and !asset.processed? and asset.upload_file.present?
11
+ begin
12
+ puts "Processing an asset ID ##{asset.id}..."
13
+
14
+ if asset.upload_file.include?("#{Asset.s3_base_path}")
15
+ if asset.image?
16
+ puts "Asset is an image in our S3 assets directory. Downloading and processing..."
17
+
18
+ # Carrierwave must download the file, process it, then re-upload it to S3
19
+ asset.remote_data_url = asset.upload_file
20
+ asset.processed = true
21
+ asset.save!
22
+ else
23
+ puts "Asset is a non-image in our S3 uploads directory. Copying to final location..."
24
+
25
+ # We have uploaded a video, or something non-image to our S3 bucket.
26
+ # We do not currently process anything.
27
+
28
+ puts "Marking local asset as processed..."
29
+ asset.update_column(:data, asset.file_name)
30
+ asset.processed = true
31
+ asset.save!
32
+ end
33
+ elsif asset.upload_file.include?(Asset.string_base_path)
34
+ puts "Asset is a string-based asset. Processing..."
35
+
36
+ asset.data.cache_stored_file!
37
+ asset.data.retrieve_from_cache!(asset.data.cache_name)
38
+ asset.data.recreate_versions!
39
+ asset.processed = true
40
+ asset.save!
41
+ else
42
+ puts "Asset is not an s3 uploaded asset. Downloading and processing..."
43
+
44
+ # Carrierwave must download the file, process it, then re-upload it to S3
45
+ asset.remote_data_url = asset.upload_file
46
+ asset.processed = true
47
+ asset.save!
48
+ end
49
+
50
+ puts "Successfully processed the asset."
51
+ rescue => e
52
+ puts "An error occurred while processing an asset:"
53
+ puts e.message
54
+ puts e.backtrace.inspect
55
+ end
56
+ end
57
+ end
58
+ handle_asynchronously :process_asset
59
+
60
+ def reprocess_all_assets
61
+ DelayedJob.configure_carrierwave
62
+
63
+ Asset.all.each do |asset|
64
+ begin
65
+ puts "Processing Asset ID=#{asset.id}..."
66
+ asset.data.cache_stored_file!
67
+ asset.data.retrieve_from_cache!(asset.data.cache_name)
68
+ asset.data.recreate_versions!
69
+ asset.save!
70
+ puts "Successfully processed #{asset.inspect}"
71
+ rescue => e
72
+ puts "ERROR: #{asset.id} -> #{e.to_s}"
73
+ end
74
+ end
75
+ end
76
+ handle_asynchronously :reprocess_all_assets
77
+
78
+ def self.configure_carrierwave
79
+ return if defined? @@configured_carrierwave
80
+
81
+ CarrierWave.configure do |config|
82
+ config.fog_credentials = {
83
+ :provider => 'AWS',
84
+ :aws_access_key_id => EffectiveAssets.aws_access_key_id,
85
+ :aws_secret_access_key => EffectiveAssets.aws_secret_access_key
86
+ }
87
+ config.fog_directory = EffectiveAssets.aws_bucket
88
+ config.fog_public = EffectiveAssets.aws_acl.to_s.include?('public')
89
+ config.fog_attributes = {'Cache-Control'=>'max-age=315576000'}
90
+ config.cache_dir = "#{Rails.root}/tmp/uploads" # For heroku
91
+ end
92
+
93
+ Rails.logger.info "CONFIGURED CARRIERWAVE"
94
+
95
+ @@configured_carrierwave = true
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,87 @@
1
+ require "formtastic"
2
+
3
+ # With formtastic, just use
4
+ #
5
+ # = f.input :pictures, :as => :asset_box
6
+ # = f.input :fav_icon, :as => :asset_box, :limit => 4, :file_types => [:jpg, :gif, :png]
7
+ # = f.input :logo, :as => :asset_box, :uploader => false, :dialog => true
8
+ # = f.input :logo, :as => :asset_box, :uploader => true, :uploader_visible => true
9
+
10
+ class AssetBoxInput
11
+ include ::Formtastic::Inputs::Base
12
+
13
+ def to_html
14
+ @@uid = (@@uid ||= 0) + 1 # We just need a unique number to pass along, incase we have multiple SWF Uploaders per form
15
+
16
+ input_wrapping do
17
+ output = label_html
18
+ output += header_html
19
+ output += "<div class='attachments'>".html_safe
20
+ output += build_values_html
21
+ output += "</div>".html_safe
22
+
23
+ if options[:uploader]
24
+ output += insert_uploader_html
25
+ output += uploader_html
26
+ end
27
+
28
+ if options[:dialog]
29
+ output += insert_dialog_html
30
+ end
31
+
32
+ output += "</div>".html_safe
33
+ end
34
+ end
35
+
36
+ def header_html
37
+ "<div class='asset_box_input #{method.to_s.pluralize}' data-box='#{method.to_s.pluralize}' data-swf='s3_swf_#{@@uid}' data-limit='#{limit}' data-attachable-id='#{attachable_id}' data-attachable-type='#{attachable_type}'>".html_safe
38
+ end
39
+
40
+ def insert_uploader_html
41
+ "<a href='#' class='asset-box-upload'>Upload...</a>".html_safe
42
+ end
43
+
44
+ def uploader_html
45
+ template.render(:partial => 'asset_box_input/uploader', :locals => {:attachable_id => attachable_id, :attachable_type => attachable_type, :box => method.to_s.pluralize, :uid => @@uid, :file_types => options[:file_types], :limit => limit, :uploader_visible => options[:uploader_visible]}).html_safe
46
+ end
47
+
48
+ def insert_dialog_html
49
+ "<a href='#' class='asset-box-dialog'>Attach...</a>".html_safe
50
+ end
51
+
52
+ def build_values_html
53
+ count = 0
54
+ attachments_limit = limit
55
+
56
+ attachments.map do |attachment|
57
+ count += 1 unless attachment.marked_for_destruction?
58
+
59
+ template.render(
60
+ :partial => 'asset_box_input/attachment_fields',
61
+ :locals => {:attachment => attachment, :attachable_type => attachable_type, :hidden => (count > attachments_limit) }
62
+ )
63
+ end.join.html_safe
64
+ end
65
+
66
+ def attachments
67
+ method_name = method.to_s.pluralize
68
+ object.attachments.select { |attachment| attachment.box == method_name }
69
+ end
70
+
71
+ def attachable_type
72
+ options[:attachable_type] || object.class.name.titleize.gsub(" ", "_").gsub('/', '_').downcase
73
+ end
74
+
75
+ def attachable_id
76
+ options[:attachable_id] || (object.try(:id) rescue nil)
77
+ end
78
+
79
+ def limit
80
+ method.to_s == method.to_s.pluralize ? (options[:limit] || 10000) : 1
81
+ end
82
+
83
+ def options
84
+ {:uploader => false, :dialog => false, :uploader_visible => false}.merge(super)
85
+ end
86
+
87
+ end
@@ -0,0 +1,11 @@
1
+ class AssetBoxLengthValidator < ActiveModel::EachValidator
2
+ def validate_each(record, attribute, value)
3
+ num_in_box = (record.attachments || []).select { |attachment| attachment.box == attribute.to_s.pluralize && attachment.marked_for_destruction? == false }.size
4
+
5
+ if options[:with]
6
+ record.errors[attribute] << "requires at least #{options[:with]} #{attribute.to_s.pluralize}" if num_in_box < options[:with]
7
+ elsif options[:in]
8
+ record.errors[attribute] << "requires #{options[:in].min} to #{options[:in].max} #{attribute.to_s.pluralize}" if options[:in].include?(num_in_box) == false
9
+ end
10
+ end
11
+ end