effective_assets 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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