effective_assets 0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +341 -72
  4. data/app/assets/javascripts/effective/snippets/effective_asset.js.coffee +53 -0
  5. data/app/assets/javascripts/effective_assets.js +4 -0
  6. data/app/assets/javascripts/effective_assets/asset_box.js.coffee +36 -0
  7. data/app/assets/javascripts/effective_assets/asset_box_dialog.js.coffee +49 -0
  8. data/app/assets/javascripts/effective_assets/asset_box_drag_and_drop.js.coffee +2 -0
  9. data/app/assets/javascripts/effective_assets/asset_box_filtering.js.coffee +16 -0
  10. data/app/assets/javascripts/effective_assets/asset_box_sorting.js.coffee +8 -0
  11. data/app/assets/javascripts/effective_assets/jquery_ui_sortable.js +813 -585
  12. data/app/assets/javascripts/effective_assets/s3_uploader.js.coffee +268 -0
  13. data/app/assets/javascripts/effective_assets_iframe.js.coffee +28 -0
  14. data/app/assets/stylesheets/active_admin/effective_assets.css.scss +57 -0
  15. data/app/assets/stylesheets/effective_assets/_asset_box_input.scss +54 -175
  16. data/app/assets/stylesheets/effective_assets/_iframe_bootstrap.scss +1714 -0
  17. data/app/assets/stylesheets/effective_assets/_input_bootstrap.scss +508 -0
  18. data/app/assets/stylesheets/effective_assets_iframe.css.scss +23 -0
  19. data/app/controllers/effective/assets_controller.rb +41 -0
  20. data/app/controllers/effective/s3_uploads_controller.rb +58 -74
  21. data/app/helpers/effective_assets_helper.rb +30 -14
  22. data/app/helpers/effective_assets_s3_helper.rb +69 -0
  23. data/app/models/concerns/acts_as_asset_box.rb +75 -11
  24. data/app/models/effective/access_denied.rb +17 -0
  25. data/app/models/effective/asset.rb +130 -93
  26. data/app/models/effective/attachment.rb +2 -7
  27. data/app/models/effective/delayed_job.rb +33 -78
  28. data/app/models/effective/snippets/effective_asset.rb +19 -0
  29. data/app/models/effective/user_uploads.rb +19 -0
  30. data/app/models/inputs/asset_box.rb +154 -0
  31. data/app/models/inputs/asset_box_form_input.rb +7 -0
  32. data/app/models/inputs/asset_box_formtastic_input.rb +9 -0
  33. data/app/models/inputs/asset_box_input.rb +13 -82
  34. data/app/models/inputs/asset_box_simple_form_input.rb +7 -0
  35. data/app/uploaders/effective_assets_uploader.rb +14 -2
  36. data/app/uploaders/{asset_uploader.rb → test_asset_uploader.rb} +1 -1
  37. data/app/views/active_admin/effective_assets/_edit.html.haml +3 -3
  38. data/app/views/active_admin/effective_assets/_new.html.haml +2 -1
  39. data/app/views/asset_box_input/_attachment_as_list.html.haml +17 -0
  40. data/app/views/asset_box_input/_attachment_as_table.html.haml +32 -0
  41. data/app/views/asset_box_input/_attachment_as_thumbnail.html.haml +20 -0
  42. data/app/views/asset_box_input/_dialog.html.haml +18 -0
  43. data/app/views/asset_box_input/_progress_bar_template.html.haml +8 -0
  44. data/app/views/asset_box_input/_uploader.html.haml +21 -115
  45. data/app/views/effective/assets/iframe.html.haml +17 -0
  46. data/app/views/effective/snippets/_effective_asset.html.haml +8 -0
  47. data/config/routes.rb +8 -2
  48. data/db/migrate/01_create_effective_assets.rb.erb +3 -0
  49. data/lib/effective_assets.rb +34 -3
  50. data/lib/effective_assets/engine.rb +11 -8
  51. data/lib/effective_assets/version.rb +1 -1
  52. data/lib/generators/templates/asset_uploader.rb +35 -0
  53. data/lib/generators/templates/effective_assets.rb +31 -4
  54. data/lib/tasks/effective_assets_tasks.rake +115 -4
  55. data/spec/internal/config/database.yml +3 -0
  56. data/spec/internal/config/initializers/effective_assets.rb +18 -0
  57. data/spec/internal/config/routes.rb +3 -0
  58. data/spec/internal/db/combustion_test.sqlite +0 -0
  59. data/spec/internal/db/schema.rb +52 -0
  60. data/spec/internal/log/test.log +793 -0
  61. data/spec/{dummy → internal}/public/favicon.ico +0 -0
  62. data/spec/internal/public/sprites.png +0 -0
  63. data/spec/models/asset_spec.rb +119 -35
  64. data/spec/spec_helper.rb +9 -3
  65. metadata +95 -234
  66. data/app/assets/images/effective_assets/s3_down_button.gif +0 -0
  67. data/app/assets/images/effective_assets/s3_over_button.gif +0 -0
  68. data/app/assets/images/effective_assets/s3_up_button.gif +0 -0
  69. data/app/assets/images/effective_assets/s3_upload.swf +0 -0
  70. data/app/assets/javascripts/effective_assets/asset_box_input.js.coffee +0 -71
  71. data/app/assets/javascripts/effective_assets/asset_box_uploader.js +0 -122
  72. data/app/assets/javascripts/effective_assets/asset_box_uploader_customization.js +0 -166
  73. data/app/controllers/effective/attachments_controller.rb +0 -14
  74. data/app/views/asset_box_input/_attachment_fields.html.haml +0 -14
  75. data/spec/dummy/README.rdoc +0 -261
  76. data/spec/dummy/Rakefile +0 -7
  77. data/spec/dummy/app/assets/javascripts/application.js +0 -15
  78. data/spec/dummy/app/assets/stylesheets/application.css +0 -13
  79. data/spec/dummy/app/controllers/application_controller.rb +0 -3
  80. data/spec/dummy/app/helpers/application_helper.rb +0 -2
  81. data/spec/dummy/app/views/layouts/application.html.erb +0 -14
  82. data/spec/dummy/config.ru +0 -4
  83. data/spec/dummy/config/application.rb +0 -65
  84. data/spec/dummy/config/boot.rb +0 -10
  85. data/spec/dummy/config/database.yml +0 -25
  86. data/spec/dummy/config/environment.rb +0 -5
  87. data/spec/dummy/config/environments/development.rb +0 -37
  88. data/spec/dummy/config/environments/production.rb +0 -67
  89. data/spec/dummy/config/environments/test.rb +0 -37
  90. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
  91. data/spec/dummy/config/initializers/inflections.rb +0 -15
  92. data/spec/dummy/config/initializers/mime_types.rb +0 -5
  93. data/spec/dummy/config/initializers/secret_token.rb +0 -7
  94. data/spec/dummy/config/initializers/session_store.rb +0 -8
  95. data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
  96. data/spec/dummy/config/locales/en.yml +0 -5
  97. data/spec/dummy/config/routes.rb +0 -58
  98. data/spec/dummy/db/development.sqlite3 +0 -0
  99. data/spec/dummy/db/schema.rb +0 -16
  100. data/spec/dummy/db/test.sqlite3 +0 -0
  101. data/spec/dummy/log/development.log +0 -71
  102. data/spec/dummy/log/test.log +0 -33
  103. data/spec/dummy/public/404.html +0 -26
  104. data/spec/dummy/public/422.html +0 -26
  105. data/spec/dummy/public/500.html +0 -25
  106. data/spec/dummy/script/rails +0 -6
  107. data/spec/dummy/spec_link +0 -3
@@ -0,0 +1,23 @@
1
+ @import "effective_assets/asset_box_input";
2
+
3
+ body {
4
+ padding: 10px;
5
+ }
6
+
7
+ .form-control.filter-attachments {
8
+ display: inline;
9
+ font-weight: normal;
10
+ width: auto;
11
+ min-width: 50%;
12
+ margin-left: 10px;
13
+ }
14
+
15
+ .asset-box-input.dragin {
16
+ border: 1px solid blue;
17
+ border-radius: 4px;
18
+ border-color: #66afe9;
19
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(102, 175, 233, 0.6);
20
+ outline: 0 none;
21
+ }
22
+
23
+ @import "effective_assets/iframe_bootstrap";
@@ -0,0 +1,41 @@
1
+ # This is basically for the CkEditor Pictures functionality
2
+
3
+ module Effective
4
+ class AssetsController < ApplicationController
5
+ layout false
6
+
7
+ def index # This is the Modal dialog that is read by CKEDITOR
8
+ EffectiveAssets.authorized?(self, :index, Effective::Asset.new(:user_id => current_user.try(:id)))
9
+
10
+ @assets = Effective::Asset.where(:user_id => current_user.try(:id))
11
+ @aws_acl = EffectiveAssets.aws_acl.presence || 'public-read'
12
+
13
+ if params[:only] == 'images'
14
+ @assets = @assets.images
15
+ @file_types = [:jpg, :gif, :png, :bmp, :ico]
16
+ @aws_acl = 'public-read' # The CKEditor Insert Image functionality needs a public-read image here
17
+ elsif params[:only] == 'nonimages'
18
+ @assets = @assets.nonimages
19
+ @file_types = [:pdf, :zip, :doc, :docx, :xls, :xlsx, :txt, :avi, :m4v, :m2v, :mov, :mp3, :mp4]
20
+ end
21
+
22
+ @user_uploads = UserUploads.new(@assets)
23
+
24
+ render :file => 'effective/assets/iframe'
25
+ end
26
+
27
+ def destroy
28
+ @asset = Effective::Asset.find(params[:id])
29
+ EffectiveAssets.authorized?(self, :destroy, @asset)
30
+
31
+ if @asset.destroy
32
+ flash[:success] = 'Successfully deleted asset'
33
+ else
34
+ flash[:danger] = 'Unable to delete asset'
35
+ end
36
+
37
+ redirect_to(:back) rescue redirect_to(effective_assets.effective_assets_path)
38
+ end
39
+
40
+ end
41
+ end
@@ -1,98 +1,82 @@
1
- require 's3_swf_upload'
2
- require 'base64'
3
-
4
1
  module Effective
5
2
  class S3UploadsController < ApplicationController
6
- skip_authorize_resource if defined?(CanCan)
7
-
8
- respond_to :js, :xml
9
-
10
- include S3SwfUpload::Signature
11
-
12
- def index
13
- # This is actually a 'new' action. Not an index. The s3 flash uploader is hardcoded to check this url as an xml request
14
-
15
- EffectiveAssets.authorized?(self, :create, Effective::Asset)
3
+ skip_authorization_check if defined?(CanCan)
4
+ skip_before_filter :verify_authenticity_token
16
5
 
17
- bucket = EffectiveAssets.aws_bucket
18
- access_key_id = EffectiveAssets.aws_access_key_id
19
- acl = EffectiveAssets.aws_acl
20
- secret_key = EffectiveAssets.aws_secret_access_key
21
- key = params[:key]
22
- content_type = params[:content_type]
23
- https = 'false'
24
- error_message = ''
25
- expiration_date = 1.hours.from_now.utc.strftime('%Y-%m-%dT%H:%M:%S.000Z')
6
+ # When we create an Asset, we're effectively reserving the ID
7
+ # But the Asset itself isn't really there or uploaded yet.
8
+ # But we want to start uploading to the final s3 path
26
9
 
27
- policy = Base64.encode64(
28
- "{
29
- 'expiration': '#{expiration_date}',
30
- 'conditions': [
31
- {'bucket': '#{bucket}'},
32
- {'key': '#{key}'},
33
- {'acl': '#{acl}'},
34
- {'Content-Type': '#{content_type}'},
35
- {'Content-Disposition': 'attachment'},
36
- ['starts-with', '$Filename', ''],
37
- ['eq', '$success_action_status', '201']
38
- ]
39
- }").gsub(/\n|\r/, '')
10
+ def create
11
+ # Here we initialize an empty placeholder Asset, so we can reserve the ID
12
+ @asset = Effective::Asset.new(:user_id => ((current_user.try(:id) || 1) rescue 1), :upload_file => 'placeholder')
13
+ @asset.extra = params[:extra] if params[:extra].kind_of?(Hash)
40
14
 
41
- signature = b64_hmac_sha1(secret_key, policy)
15
+ EffectiveAssets.authorized?(self, :create, @asset)
42
16
 
43
- respond_to do |format|
44
- format.xml {
45
- render :xml => {
46
- :policy => policy,
47
- :signature => signature,
48
- :bucket => bucket,
49
- :accesskeyid => access_key_id,
50
- :acl => acl,
51
- :expirationdate => expiration_date,
52
- :https => https,
53
- :errorMessage => error_message.to_s,
54
- }.to_xml
55
- }
17
+ if @asset.save
18
+ render(:text => {:id => @asset.id, :s3_key => asset_s3_key(@asset)}.to_json, :status => 200)
19
+ else
20
+ render(:text => '', :status => 400)
56
21
  end
57
22
  end
58
23
 
59
- def create
60
- EffectiveAssets.authorized?(self, :create, Effective::Asset)
24
+ def update
25
+ @asset = Effective::Asset.find(params[:id])
61
26
 
62
- # If we're passed Asset information, then create an Asset
63
- if params[:file_path].present?
64
- asset = Asset.create_from_s3_uploader(params[:file_path],
65
- {
66
- :title => params[:title],
67
- :description => params[:description],
68
- :tags => params[:tags],
69
- :content_type => params[:content_type],
70
- :data_size => params[:file_size],
71
- :user_id => current_user.try(:id)
72
- }
73
- )
74
- else
75
- asset = (Asset.find(params[:asset_id].to_i) rescue false)
76
- end
27
+ EffectiveAssets.authorized?(self, :update, @asset)
77
28
 
78
- unless asset
79
- render :text => '', :status => :unprocessable_entity
80
- return
29
+ unless params[:skip_update] # This is useful for the acts_as_asset_box Attach action
30
+ if update_placeholder_asset(@asset, params) == false
31
+ render :text => '', :status => :unprocessable_entity
32
+ return
33
+ end
81
34
  end
82
35
 
83
36
  # If the attachment information is present, then our input needs some attachment HTML
84
- if params.key?(:attachable_type)
85
- attachment = Attachment.new
37
+ if params.key?(:attachable_object_name)
38
+ attachment = Effective::Attachment.new
86
39
  attachment.attachable_type = params[:attachable_type].try(:classify)
87
- attachment.attachable_id = params[:attachable_id].try(:to_i) if params[:attachable_id].present? # attachable_id can be nil if we're on a New action
88
- attachment.asset_id = asset.try(:id)
40
+ attachment.attachable_id = params[:attachable_id].try(:to_i)
41
+ attachment.asset_id = @asset.try(:id)
89
42
  attachment.box = params[:box]
90
43
  attachment.position = 0
44
+ attachable_object_name = params[:attachable_object_name].to_s
45
+ attachment_actions = params[:attachment_actions]
46
+
47
+ attachment_partial =
48
+ case params[:attachment_style].to_s
49
+ when 'table'
50
+ 'attachment_as_table'
51
+ when 'list'
52
+ 'attachment_as_list'
53
+ else # :thumbnail, nil, or anything
54
+ 'attachment_as_thumbnail'
55
+ end
91
56
 
92
- render :partial => 'asset_box_input/attachment_fields', :locals => {:attachment => attachment}, :status => 200, :content_type => 'text/html'
57
+ render :partial => "asset_box_input/#{attachment_partial}", :locals => {:attachment => attachment, :attachable_object_name => attachable_object_name, :attachment_actions => attachment_actions}, :status => 200, :content_type => 'text/html'
93
58
  else
94
59
  render :text => '', :status => 200
95
60
  end
96
61
  end
62
+
63
+ private
64
+
65
+ def update_placeholder_asset(asset, opts)
66
+ asset.upload_file = opts[:upload_file]
67
+ asset.data_size = opts[:data_size]
68
+ asset.content_type = opts[:content_type]
69
+ asset.title = opts[:title]
70
+ asset.aws_acl = opts[:aws_acl]
71
+ asset[:data] = opts[:title]
72
+
73
+ # If our S3 Uploader has any issue uploading/saving the asset, destroy the placeholder empty one
74
+ asset.save ? true : (asset.try(:destroy) and false)
75
+ end
76
+
77
+ def asset_s3_key(asset)
78
+ "#{EffectiveAssets.aws_path.chomp('/')}/#{asset.id.to_i}/${filename}"
79
+ end
80
+
97
81
  end
98
82
  end
@@ -14,42 +14,58 @@ module EffectiveAssetsHelper
14
14
  end
15
15
 
16
16
  opts = opts.merge({:alt => asset.description || asset.title || asset.file_name}).merge(options)
17
- image_tag(_effective_asset_image_url(asset, version), opts).gsub('"', "'").html_safe # we need all ' quotes or it breaks Insert as functionality
17
+
18
+ content_tag(:img, nil, opts.merge(:src => _effective_asset_image_url(asset, version))).gsub('"', "'").html_safe
18
19
  end
19
20
 
20
21
  def effective_asset_link_to(asset, version = nil, options = {})
21
- link_title = asset.title || asset.file_name || asset.description || "Asset ##{asset.id}"
22
+ options_title = options.delete(:title)
23
+ link_title = options_title || asset.title || asset.file_name || asset.description || "Asset ##{asset.id}"
22
24
 
23
- link_to(link_title, asset.url).gsub('"', "'").html_safe # we need all ' quotes or it breaks Insert as functionality
25
+ if asset.image?
26
+ link_to(link_title, _effective_asset_image_url(asset, version), options).gsub('"', "'").html_safe # we need all ' quotes or it breaks Insert as functionality
27
+ else
28
+ link_to(link_title, asset.url, options).gsub('"', "'").html_safe # we need all ' quotes or it breaks Insert as functionality
29
+ end
24
30
  end
25
31
 
26
32
  def effective_asset_video_tag(asset)
27
33
  render(:partial => 'assets/video', :locals => { :asset => asset }).gsub('"', "'").html_safe # we need all ' quotes or it breaks Insert as functionality
28
34
  end
29
35
 
36
+ def effective_asset_title(asset)
37
+ [
38
+ asset.title,
39
+ asset.description,
40
+ asset.tags,
41
+ "Size: #{number_to_human_size(asset.data_size)}",
42
+ "Content Type: #{asset.content_type}"
43
+ ].compact.join("\n")
44
+ end
45
+
30
46
  def _effective_asset_image_url(asset, version = nil)
31
- return '/assets/mime-types/file.png' if !asset.content_type.present? or asset.content_type == 'unknown'
47
+ # asset_url and image_url will work in Rails4
48
+
49
+ return image_path('mime-types/file.png') if !asset.content_type.present? or asset.content_type == 'unknown'
32
50
 
33
51
  if asset.icon?
34
52
  asset.url
35
53
  elsif asset.image?
36
- (version == nil or asset.still_processing?) ? asset.url : asset.url.insert(asset.url.rindex('/')+1, "#{version.to_s}_")
54
+ asset.url(version)
37
55
  elsif asset.audio?
38
- '/assets/mime-types/mp3.png'
56
+ image_path('mime-types/mp3.png')
39
57
  elsif asset.video?
40
- '/assets/mime-types/video.png'
58
+ image_path('mime-types/video.png')
41
59
  elsif asset.content_type.include? 'msword'
42
- '/assets/mime-types/word.jpg'
60
+ image_path('mime-types/word.jpg')
43
61
  elsif asset.content_type.include? 'excel'
44
- '/assets/mime-types/excel.jpg'
62
+ image_path('mime-types/excel.png')
45
63
  elsif asset.content_type.include? 'application/pdf'
46
- '/assets/mime-types/pdf.png'
64
+ image_path('mime-types/pdf.png')
47
65
  elsif asset.content_type.include? 'application/zip'
48
- '/assets/mime-types/zip.png'
66
+ image_path('mime-types/zip.png')
49
67
  else
50
- '/assets/mime-types/file.png'
68
+ image_path('mime-types/file.png')
51
69
  end
52
70
  end
53
-
54
-
55
71
  end
@@ -0,0 +1,69 @@
1
+ module EffectiveAssetsS3Helper
2
+
3
+ def s3_uploader_fields(options = {})
4
+ S3Uploader.new(options).fields.map do |name, value|
5
+ hidden_field_tag(name, value, :disabled => 'disabled')
6
+ end.join.html_safe
7
+ end
8
+
9
+ def s3_uploader_url
10
+ Effective::Asset.s3_base_path.chomp('/') + '/'
11
+ end
12
+
13
+ # Copied and modified from https://github.com/waynehoover/s3_direct_upload/blob/master/lib/s3_direct_upload/form_helper.rb
14
+ class S3Uploader
15
+ def initialize(options)
16
+ @options = options.reverse_merge(
17
+ aws_access_key_id: EffectiveAssets.aws_access_key_id,
18
+ aws_secret_access_key: EffectiveAssets.aws_secret_access_key,
19
+ bucket: EffectiveAssets.aws_bucket,
20
+ aws_acl: EffectiveAssets.aws_acl,
21
+ expiration: 10.hours.from_now.utc.iso8601,
22
+ max_file_size: 1000.megabytes,
23
+ key_starts_with: "#{EffectiveAssets.aws_path.chomp('/')}/",
24
+ key: '${filename}' # We use an AJAX request to update this key to a useful value
25
+ )
26
+ end
27
+
28
+ def fields
29
+ {
30
+ :key => @options[:key],
31
+ :acl => @options[:aws_acl],
32
+ 'AWSAccessKeyId' => @options[:aws_access_key_id],
33
+ :policy => policy,
34
+ :signature => signature,
35
+ :success_action_status => '201',
36
+ 'X-Requested-With' => 'xhr'
37
+ }
38
+ end
39
+
40
+ def policy
41
+ Base64.encode64(policy_data.to_json).gsub("\n", "")
42
+ end
43
+
44
+ def policy_data
45
+ {
46
+ expiration: @options[:expiration],
47
+ conditions: [
48
+ ['starts-with', '$key', @options[:key_starts_with]],
49
+ ['starts-with', '$x-requested-with', ''],
50
+ ['content-length-range', 0, @options[:max_file_size]],
51
+ ['starts-with','$content-type', @options[:content_type_starts_with] || ''],
52
+ {:bucket => @options[:bucket]},
53
+ {:acl => @options[:aws_acl]},
54
+ {:success_action_status => '201'}
55
+ ] + (@options[:conditions] || [])
56
+ }
57
+ end
58
+
59
+ def signature
60
+ Base64.encode64(
61
+ OpenSSL::HMAC.digest(
62
+ OpenSSL::Digest::SHA1.new(),
63
+ @options[:aws_secret_access_key], policy
64
+ )
65
+ ).gsub("\n", "")
66
+ end
67
+ end
68
+
69
+ end
@@ -37,7 +37,6 @@ module ActsAsAssetBox
37
37
  has_many :attachments, :as => :attachable, :class_name => "Effective::Attachment", :dependent => :delete_all
38
38
  has_many :assets, :through => :attachments, :class_name => "Effective::Asset"
39
39
 
40
- #attr_accessible :attachments_attributes
41
40
  accepts_nested_attributes_for :attachments, :reject_if => :all_blank, :allow_destroy => true
42
41
 
43
42
  # Setup validations
@@ -45,9 +44,9 @@ module ActsAsAssetBox
45
44
  if boxes.first.kind_of?(Hash) # We were passed some validation requirements
46
45
  boxes = boxes.first
47
46
 
48
- @@acts_as_asset_box_boxes = boxes.keys
49
-
50
47
  boxes.each do |box, validation|
48
+ self.send(:define_method, box) { assets(box) }
49
+
51
50
  if validation == true
52
51
  validates box, :asset_box_presence => true
53
52
  elsif validation.kind_of?(Integer) or validation.kind_of?(Range)
@@ -55,11 +54,15 @@ module ActsAsAssetBox
55
54
  end
56
55
  end
57
56
  else
58
- @@acts_as_asset_box_boxes = boxes
57
+ boxes.each do |key|
58
+ self.send(:define_method, key) { assets(key) }
59
+ end
59
60
  end
60
61
 
61
62
  class_eval do
62
63
  def attachments_attributes=(atts)
64
+ @last_atts == atts ? return : @last_atts = atts
65
+
63
66
  current_box = ''; position = 0
64
67
 
65
68
  atts.each do |k, v|
@@ -67,7 +70,7 @@ module ActsAsAssetBox
67
70
  atts[k]['position'] = (position += 1) if atts[k]['_destroy'] != '1'
68
71
  end
69
72
 
70
- assign_nested_attributes_for_collection_association(:attachments, atts, {})
73
+ assign_nested_attributes_for_collection_association(:attachments, atts)
71
74
  end
72
75
  end
73
76
  end
@@ -77,11 +80,12 @@ module ActsAsAssetBox
77
80
 
78
81
  def assets(box = nil)
79
82
  box = (box.present? ? box.to_s : 'assets')
83
+ boxes = box.pluralize
80
84
 
81
- if box == box.pluralize
82
- attachments.select { |attachment| attachment.box == box }.map { |attachment| attachment.asset }
85
+ if box == boxes
86
+ attachments.map { |attachment| attachment.asset if attachment.box == boxes }.compact
83
87
  else
84
- attachments.to_a.find { |attachment| attachment.box == box.pluralize }.try(:asset)
88
+ attachments.to_a.find { |attachment| attachment.box == boxes }.try(:asset)
85
89
  end
86
90
  end
87
91
 
@@ -89,9 +93,69 @@ module ActsAsAssetBox
89
93
  assets(:asset)
90
94
  end
91
95
 
92
- # This handles all the boxes type function calls
93
- def method_missing(method_name, *args, &block)
94
- (@@acts_as_asset_box_boxes || []).include?(method_name.to_sym) ? assets(method_name) : super
96
+ def add_to_asset_box(box, *items)
97
+ box = (box.present? ? box.to_s : 'assets')
98
+ boxes = box.pluralize
99
+ items = [items].flatten.compact
100
+
101
+ if items.present? && items.any? { |obj| !obj.kind_of?(Effective::Asset) }
102
+ raise ArgumentError.new('add_to_asset_box expects one or more Effective::Assets, or an Array of Effective::Assets')
103
+ end
104
+
105
+ if box == boxes # If we're adding to a pluralized box, we want to add our attachment onto the end
106
+ pos = attachments.select { |attachment| attachment.box == boxes }.last.try(:position).to_i + 1
107
+ else # If we're adding to a singular box, we want our attachments to be on the front
108
+ pos = attachments.to_a.find { |attachment| attachment.box == boxes }.try(:position).to_i
109
+
110
+ # Push all the attachments forward
111
+ attachments.each { |att| att.position = (att.position + items.length) if att.box == boxes }
112
+ end
113
+
114
+ # Build the attachments
115
+ items.each_with_index do |item, x|
116
+ attachment = self.attachments.build(:position => (pos+x), :box => boxes)
117
+
118
+ attachment.attachable = self
119
+ attachment.asset = item
120
+ end
121
+
122
+ attachments.to_a.sort_by!(&:position)
123
+
124
+ true
125
+ end
126
+
127
+ def add_to_asset_box!(box, *items)
128
+ add_to_asset_box(box, items) && save!
129
+ end
130
+
131
+ # The idea here is that if you want to replace an Asset with Another one
132
+ # the Attachment should keep the same order, and only the asset should change
133
+
134
+ def replace_in_asset_box(box, original, overwrite)
135
+ box = (box.present? ? box.to_s : 'assets')
136
+ boxes = box.pluralize
137
+
138
+ unless original.present? && original.kind_of?(Effective::Asset)
139
+ raise ArgumentError.new("second parameter 'original' must be a single Effective::Asset")
140
+ end
141
+
142
+ unless overwrite.present? && overwrite.kind_of?(Effective::Asset)
143
+ raise ArgumentError.new("third parameter 'overwrite' must be a single Effective::Asset")
144
+ end
145
+
146
+ attachment = attachments.to_a.find { |attachment| attachment.box == boxes && attachment.asset == original }
147
+
148
+ if attachment.present?
149
+ attachment.asset = overwrite
150
+ true
151
+ else
152
+ false
153
+ end
154
+ end
155
+
156
+ def replace_in_asset_box!(box, original, overwrite)
157
+ replace_in_asset_box(box, original, overwrite) && save!
95
158
  end
159
+
96
160
  end
97
161