voltron-upload 0.2.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.
- checksums.yaml +7 -0
 - data/.gitignore +13 -0
 - data/.rspec +2 -0
 - data/.travis.yml +8 -0
 - data/CODE_OF_CONDUCT.md +49 -0
 - data/Gemfile +12 -0
 - data/LICENSE.txt +21 -0
 - data/README.md +166 -0
 - data/Rakefile +6 -0
 - data/app/assets/javascripts/dropzone.js +1769 -0
 - data/app/assets/javascripts/voltron-upload.js +264 -0
 - data/app/assets/stylesheets/dropzone.scss +595 -0
 - data/app/assets/stylesheets/voltron-upload.scss +446 -0
 - data/app/views/voltron/upload/preview/_horizontal_tile.html.erb +18 -0
 - data/app/views/voltron/upload/preview/_progress.html.erb +8 -0
 - data/app/views/voltron/upload/preview/_vertical_tile.html.erb +18 -0
 - data/bin/console +14 -0
 - data/bin/setup +8 -0
 - data/lib/generators/voltron/upload/install/assets_generator.rb +25 -0
 - data/lib/generators/voltron/upload/install/views_generator.rb +21 -0
 - data/lib/generators/voltron/upload/install_generator.rb +57 -0
 - data/lib/voltron/config/upload.rb +18 -0
 - data/lib/voltron/upload/action_dispatch/routes.rb +17 -0
 - data/lib/voltron/upload/action_view/field.rb +152 -0
 - data/lib/voltron/upload/active_record/base.rb +87 -0
 - data/lib/voltron/upload/carrierwave/uploader/base.rb +66 -0
 - data/lib/voltron/upload/engine.rb +20 -0
 - data/lib/voltron/upload/error.rb +20 -0
 - data/lib/voltron/upload/version.rb +5 -0
 - data/lib/voltron/upload.rb +51 -0
 - data/lib/voltron/uploader.rb +58 -0
 - data/voltron-upload.gemspec +34 -0
 - metadata +243 -0
 
| 
         @@ -0,0 +1,152 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Voltron
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Upload
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Field
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  include ActionView::Helpers::TagHelper
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  def file_field(method, options={})
         
     | 
| 
      
 8 
     | 
    
         
            +
                    if Voltron.config.upload.enabled && !options[:default]
         
     | 
| 
      
 9 
     | 
    
         
            +
                      template = instance_variable_get('@template')
         
     | 
| 
      
 10 
     | 
    
         
            +
                      field = UploadField.new(@object, method, template, options)
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                      # +merge+ is because options is a hash with_indifferent_access, and will therefore have an 'object' attribute when converted to html
         
     | 
| 
      
 13 
     | 
    
         
            +
                      #super method, {}.merge(field.options)
         
     | 
| 
      
 14 
     | 
    
         
            +
                      content_tag 'v-upload', nil, field.options
         
     | 
| 
      
 15 
     | 
    
         
            +
                    else
         
     | 
| 
      
 16 
     | 
    
         
            +
                      options.delete(:default)
         
     | 
| 
      
 17 
     | 
    
         
            +
                      super method, options
         
     | 
| 
      
 18 
     | 
    
         
            +
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  class UploadField
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    include ::ActionDispatch::Routing::PolymorphicRoutes
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                    include ::Rails.application.routes.url_helpers
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    include ::ActionView::Helpers::TextHelper
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                    attr_reader :options, :template
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                    def initialize(model, method, template, options)
         
     | 
| 
      
 32 
     | 
    
         
            +
                      @model = model
         
     | 
| 
      
 33 
     | 
    
         
            +
                      @method = method.to_sym
         
     | 
| 
      
 34 
     | 
    
         
            +
                      @template = template
         
     | 
| 
      
 35 
     | 
    
         
            +
                      @options = options.with_indifferent_access
         
     | 
| 
      
 36 
     | 
    
         
            +
                      prepare
         
     | 
| 
      
 37 
     | 
    
         
            +
                    end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    def prepare
         
     | 
| 
      
 40 
     | 
    
         
            +
                      #add_preview_class if has_preview_template?
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                      options.merge!({
         
     | 
| 
      
 43 
     | 
    
         
            +
                        ':multiple'   => multiple?,
         
     | 
| 
      
 44 
     | 
    
         
            +
                        ':files'      => files.to_json,
         
     | 
| 
      
 45 
     | 
    
         
            +
                        ':cached'     => caches.to_json,
         
     | 
| 
      
 46 
     | 
    
         
            +
                        ':removed'    => removals.to_json,
         
     | 
| 
      
 47 
     | 
    
         
            +
                        ':options'    => preview_options.to_json,
         
     | 
| 
      
 48 
     | 
    
         
            +
                        'accept'      => accept,
         
     | 
| 
      
 49 
     | 
    
         
            +
                        'preview'     => preview_name,
         
     | 
| 
      
 50 
     | 
    
         
            +
                        'param'       => input_name,
         
     | 
| 
      
 51 
     | 
    
         
            +
                        'url'         => polymorphic_path(@model.class, action: :upload)
         
     | 
| 
      
 52 
     | 
    
         
            +
                      })
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                      #options[:data] ||= {}
         
     | 
| 
      
 55 
     | 
    
         
            +
                      #options[:data].merge!({
         
     | 
| 
      
 56 
     | 
    
         
            +
                      #  upload_files: files,
         
     | 
| 
      
 57 
     | 
    
         
            +
                      #  upload_cache: caches,
         
     | 
| 
      
 58 
     | 
    
         
            +
                      #  upload_remove: removals,
         
     | 
| 
      
 59 
     | 
    
         
            +
                      #  upload_options: preview_options
         
     | 
| 
      
 60 
     | 
    
         
            +
                      #})
         
     | 
| 
      
 61 
     | 
    
         
            +
                    end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                    def preview_options
         
     | 
| 
      
 64 
     | 
    
         
            +
                      previews = Voltron.config.upload.previews || {}
         
     | 
| 
      
 65 
     | 
    
         
            +
                      opts = previews.with_indifferent_access.try(:[], preview_name) || {}
         
     | 
| 
      
 66 
     | 
    
         
            +
                      opts.merge!({
         
     | 
| 
      
 67 
     | 
    
         
            +
                        preview_template: preview_markup,
         
     | 
| 
      
 68 
     | 
    
         
            +
                      })
         
     | 
| 
      
 69 
     | 
    
         
            +
                      opts.merge!(options.delete(:options) || {})
         
     | 
| 
      
 70 
     | 
    
         
            +
                      opts.map { |k,v| { k.to_s.camelize(:lower) => v } }.reduce(Hash.new, :merge).compact
         
     | 
| 
      
 71 
     | 
    
         
            +
                    end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                    def preview_markup
         
     | 
| 
      
 74 
     | 
    
         
            +
                      if has_preview_template?
         
     | 
| 
      
 75 
     | 
    
         
            +
                        # Fetch the html found in the partial provided
         
     | 
| 
      
 76 
     | 
    
         
            +
                        ActionController::Base.new.render_to_string(partial: "voltron/upload/preview/#{preview_name}").squish
         
     | 
| 
      
 77 
     | 
    
         
            +
                      elsif has_preview_markup?
         
     | 
| 
      
 78 
     | 
    
         
            +
                        # If not blank, value of +preview+ is likely (should be) raw html, in which case, just return that markup
         
     | 
| 
      
 79 
     | 
    
         
            +
                        preview.squish
         
     | 
| 
      
 80 
     | 
    
         
            +
                      end
         
     | 
| 
      
 81 
     | 
    
         
            +
                    end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                    # Strip tags, they cause problems in the lookup_context +exists?+ and +render_to_string+
         
     | 
| 
      
 84 
     | 
    
         
            +
                    def preview_name
         
     | 
| 
      
 85 
     | 
    
         
            +
                      strip_tags(preview)
         
     | 
| 
      
 86 
     | 
    
         
            +
                    end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                    def preview
         
     | 
| 
      
 89 
     | 
    
         
            +
                      @preview ||= options.delete(:preview).to_s
         
     | 
| 
      
 90 
     | 
    
         
            +
                    end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                    def accept
         
     | 
| 
      
 93 
     | 
    
         
            +
                      @accept ||= options.delete(:accept).to_s
         
     | 
| 
      
 94 
     | 
    
         
            +
                    end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                    def has_preview_template?
         
     | 
| 
      
 97 
     | 
    
         
            +
                      preview_name.present? && template.lookup_context.exists?(preview_name, 'voltron/upload/preview', true)
         
     | 
| 
      
 98 
     | 
    
         
            +
                    end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                    # Eventually, consider utilizing Nokogiri to detect whether content also is actually HTML markup
         
     | 
| 
      
 101 
     | 
    
         
            +
                    # Right now the overhead and frustration of that gem is not worth it
         
     | 
| 
      
 102 
     | 
    
         
            +
                    def has_preview_markup?
         
     | 
| 
      
 103 
     | 
    
         
            +
                      preview.present?
         
     | 
| 
      
 104 
     | 
    
         
            +
                    end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                    #def add_preview_class
         
     | 
| 
      
 107 
     | 
    
         
            +
                    #  options[:class] ||= ''
         
     | 
| 
      
 108 
     | 
    
         
            +
                    #  classes = options[:class].split(/\s+/)
         
     | 
| 
      
 109 
     | 
    
         
            +
                    #  classes << "dz-layout-#{preview_name}"
         
     | 
| 
      
 110 
     | 
    
         
            +
                    #  options[:class] = classes.join(' ')
         
     | 
| 
      
 111 
     | 
    
         
            +
                    #end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                    def input_name
         
     | 
| 
      
 114 
     | 
    
         
            +
                      ActionView::Helpers::Tags::Base.new(ActiveModel::Naming.param_key(@model), @method, nil).send(:tag_name) + (multiple? ? '[]' : '')
         
     | 
| 
      
 115 
     | 
    
         
            +
                    end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                    def multiple?
         
     | 
| 
      
 118 
     | 
    
         
            +
                      @model.respond_to?("#{@method}_urls")
         
     | 
| 
      
 119 
     | 
    
         
            +
                    end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                    def files
         
     | 
| 
      
 122 
     | 
    
         
            +
                      # If set to not preserve files, return an empty array so nothing is shown
         
     | 
| 
      
 123 
     | 
    
         
            +
                      return [] if options[:preserve] === false
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                      # Return an array of files' json data
         
     | 
| 
      
 126 
     | 
    
         
            +
                      uploads = Array.wrap(@model.send(@method)).map(&:to_upload_json).reject(&:blank?)
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                      # Remove all uploads from the array that have been flagged for removal
         
     | 
| 
      
 129 
     | 
    
         
            +
                      removals.each do |removal|
         
     | 
| 
      
 130 
     | 
    
         
            +
                        uploads.reject! { |upload| upload[:id] == removal }
         
     | 
| 
      
 131 
     | 
    
         
            +
                      end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                      uploads
         
     | 
| 
      
 134 
     | 
    
         
            +
                    end
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                    def caches
         
     | 
| 
      
 137 
     | 
    
         
            +
                      if multiple?
         
     | 
| 
      
 138 
     | 
    
         
            +
                        cache = @model.send("cache_#{@method}") rescue []
         
     | 
| 
      
 139 
     | 
    
         
            +
                        Array.wrap(cache)
         
     | 
| 
      
 140 
     | 
    
         
            +
                      else
         
     | 
| 
      
 141 
     | 
    
         
            +
                        Array.wrap(@model.send("cache_#{@method}"))
         
     | 
| 
      
 142 
     | 
    
         
            +
                      end
         
     | 
| 
      
 143 
     | 
    
         
            +
                    end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                    def removals
         
     | 
| 
      
 146 
     | 
    
         
            +
                      Array.wrap(@model.send("remove_#{@method}")).compact.reject { |i| !i }
         
     | 
| 
      
 147 
     | 
    
         
            +
                    end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                  end
         
     | 
| 
      
 150 
     | 
    
         
            +
                end
         
     | 
| 
      
 151 
     | 
    
         
            +
              end
         
     | 
| 
      
 152 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,87 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Voltron
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Upload
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Base
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  extend ActiveSupport::Concern
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  module ClassMethods
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                    def mount_uploader(*args)
         
     | 
| 
      
 10 
     | 
    
         
            +
                      super *args
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                      column = args.first.to_sym
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                      attr_accessor "cache_#{column}"
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                      before_validation do
         
     | 
| 
      
 17 
     | 
    
         
            +
                        uploader = self.class.uploaders[column]
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 20 
     | 
    
         
            +
                          cache_id = send("cache_#{column}")
         
     | 
| 
      
 21 
     | 
    
         
            +
                          send(column).retrieve_from_cache!(cache_id) if cache_id.present?
         
     | 
| 
      
 22 
     | 
    
         
            +
                        rescue ::CarrierWave::InvalidParameter => e
         
     | 
| 
      
 23 
     | 
    
         
            +
                          # Invalid cache id, we don't need to do anything but skip it
         
     | 
| 
      
 24 
     | 
    
         
            +
                        end
         
     | 
| 
      
 25 
     | 
    
         
            +
                      end
         
     | 
| 
      
 26 
     | 
    
         
            +
                    end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                    def mount_uploaders(*args)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      super *args
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                      column = args.first.to_sym
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                      attr_accessor "cache_#{column}"
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                      before_validation do
         
     | 
| 
      
 36 
     | 
    
         
            +
                        uploader = self.class.uploaders[column]
         
     | 
| 
      
 37 
     | 
    
         
            +
                        cache_ids = (send("cache_#{column}") rescue []) || []
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                        # Store the existing files
         
     | 
| 
      
 40 
     | 
    
         
            +
                        files = send(column)
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                        cache_ids.each do |cache_id|
         
     | 
| 
      
 43 
     | 
    
         
            +
                          begin
         
     | 
| 
      
 44 
     | 
    
         
            +
                            # Retrieve files from the cache and add them to the list of files
         
     | 
| 
      
 45 
     | 
    
         
            +
                            file = uploader.new(self, column)
         
     | 
| 
      
 46 
     | 
    
         
            +
                            file.retrieve_from_cache!(cache_id)
         
     | 
| 
      
 47 
     | 
    
         
            +
                            files << file
         
     | 
| 
      
 48 
     | 
    
         
            +
                          rescue ::CarrierWave::InvalidParameter => e
         
     | 
| 
      
 49 
     | 
    
         
            +
                            # Invalid cache id, we don't need to do anything but skip it
         
     | 
| 
      
 50 
     | 
    
         
            +
                          end
         
     | 
| 
      
 51 
     | 
    
         
            +
                        end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                        # Set the files
         
     | 
| 
      
 54 
     | 
    
         
            +
                        send("#{column}=", files)
         
     | 
| 
      
 55 
     | 
    
         
            +
                      end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                      # Only required for multiple uploads. Since Carrierwave does not have any way to remove individual files
         
     | 
| 
      
 58 
     | 
    
         
            +
                      # we must identify each file to be removed individually, remove it from the array of existing files,
         
     | 
| 
      
 59 
     | 
    
         
            +
                      # then reset the value of our mounted files
         
     | 
| 
      
 60 
     | 
    
         
            +
                      after_validation do
         
     | 
| 
      
 61 
     | 
    
         
            +
                        # Only attempt to remove files if there are no validation errors
         
     | 
| 
      
 62 
     | 
    
         
            +
                        if errors.empty?
         
     | 
| 
      
 63 
     | 
    
         
            +
                          # Merge any new uploads with the pre-existing uploads (so we can "add" new files, instead of overwriting)
         
     | 
| 
      
 64 
     | 
    
         
            +
                          uploads = Array.wrap(send(column))
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                          # Get the ids of uploads we want to remove
         
     | 
| 
      
 67 
     | 
    
         
            +
                          removals = Array.wrap(send("remove_#{column}"))
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                          removals.each do |removal|
         
     | 
| 
      
 70 
     | 
    
         
            +
                            uploads.reject! { |upload| upload.id == removal }
         
     | 
| 
      
 71 
     | 
    
         
            +
                          end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                          # Initially ensure carrierwave DOESN'T think we want to remove ALL files, we're just going to change the files (i.e. - remove some, not all)
         
     | 
| 
      
 74 
     | 
    
         
            +
                          send("remove_#{column}=", false)
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                          # Ensure that nil is assigned as the value if we indeed have no more files
         
     | 
| 
      
 77 
     | 
    
         
            +
                          send("remove_#{column}!") if uploads.empty?
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                          assign_attributes(column => uploads)
         
     | 
| 
      
 80 
     | 
    
         
            +
                        end
         
     | 
| 
      
 81 
     | 
    
         
            +
                      end
         
     | 
| 
      
 82 
     | 
    
         
            +
                    end
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                end
         
     | 
| 
      
 86 
     | 
    
         
            +
              end
         
     | 
| 
      
 87 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,66 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Voltron
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Upload
         
     | 
| 
      
 3 
     | 
    
         
            +
                module CarrierWave
         
     | 
| 
      
 4 
     | 
    
         
            +
                  module Uploader
         
     | 
| 
      
 5 
     | 
    
         
            +
                    module Base
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                      def initialize(*args)
         
     | 
| 
      
 8 
     | 
    
         
            +
                        self.class.send(:before, :store, :save_timestamp)
         
     | 
| 
      
 9 
     | 
    
         
            +
                        self.class.send(:after, :store, :apply_timestamp)
         
     | 
| 
      
 10 
     | 
    
         
            +
                        super(*args)
         
     | 
| 
      
 11 
     | 
    
         
            +
                      end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                      def to_upload_json
         
     | 
| 
      
 14 
     | 
    
         
            +
                        if present?
         
     | 
| 
      
 15 
     | 
    
         
            +
                          {
         
     | 
| 
      
 16 
     | 
    
         
            +
                            id: id,
         
     | 
| 
      
 17 
     | 
    
         
            +
                            url: url,
         
     | 
| 
      
 18 
     | 
    
         
            +
                            name: file.original_filename,
         
     | 
| 
      
 19 
     | 
    
         
            +
                            size: file.size,
         
     | 
| 
      
 20 
     | 
    
         
            +
                            type: file.content_type
         
     | 
| 
      
 21 
     | 
    
         
            +
                          }
         
     | 
| 
      
 22 
     | 
    
         
            +
                        else
         
     | 
| 
      
 23 
     | 
    
         
            +
                          {}
         
     | 
| 
      
 24 
     | 
    
         
            +
                        end
         
     | 
| 
      
 25 
     | 
    
         
            +
                      end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                      def id
         
     | 
| 
      
 28 
     | 
    
         
            +
                        if stored?
         
     | 
| 
      
 29 
     | 
    
         
            +
                          [File.mtime(full_store_path).to_i, file.original_filename].join('/')
         
     | 
| 
      
 30 
     | 
    
         
            +
                        elsif cached? && File.exists?(Rails.root.join('public', cache_path))
         
     | 
| 
      
 31 
     | 
    
         
            +
                          [cached?, file.original_filename].join('/')
         
     | 
| 
      
 32 
     | 
    
         
            +
                        else
         
     | 
| 
      
 33 
     | 
    
         
            +
                          file.original_filename
         
     | 
| 
      
 34 
     | 
    
         
            +
                        end
         
     | 
| 
      
 35 
     | 
    
         
            +
                      end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                      def stored?
         
     | 
| 
      
 38 
     | 
    
         
            +
                        File.exists?(full_store_path)
         
     | 
| 
      
 39 
     | 
    
         
            +
                      end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                      def full_store_path
         
     | 
| 
      
 42 
     | 
    
         
            +
                        Rails.root.join('public', store_path(file.filename))
         
     | 
| 
      
 43 
     | 
    
         
            +
                      end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                      private
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                        # Before we store the file for good, grab the offset number
         
     | 
| 
      
 48 
     | 
    
         
            +
                        # so it can be used to create a unique timestamp after storing
         
     | 
| 
      
 49 
     | 
    
         
            +
                        def save_timestamp(*args)
         
     | 
| 
      
 50 
     | 
    
         
            +
                          id_components = File.basename(File.expand_path('..', file.path)).split('-')
         
     | 
| 
      
 51 
     | 
    
         
            +
                          @offset = id_components[2].to_i + 1000
         
     | 
| 
      
 52 
     | 
    
         
            +
                        end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                        # Update the modified time of the file to a unique timestamp
         
     | 
| 
      
 55 
     | 
    
         
            +
                        # This timestamp will later be used to help identify the file,
         
     | 
| 
      
 56 
     | 
    
         
            +
                        # as it will be part of the generated id
         
     | 
| 
      
 57 
     | 
    
         
            +
                        def apply_timestamp(*args)
         
     | 
| 
      
 58 
     | 
    
         
            +
                          @offset ||= rand(1..1000)
         
     | 
| 
      
 59 
     | 
    
         
            +
                          FileUtils.touch file.path, mtime: Time.now + @offset.seconds
         
     | 
| 
      
 60 
     | 
    
         
            +
                        end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    end
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
              end
         
     | 
| 
      
 66 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Voltron
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Upload
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Engine < Rails::Engine
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  isolate_namespace Voltron
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  initializer 'voltron.upload.initialize' do
         
     | 
| 
      
 8 
     | 
    
         
            +
                    ::ActionDispatch::Routing::Mapper.send :include, ::Voltron::Upload::Routes
         
     | 
| 
      
 9 
     | 
    
         
            +
                    ::ActionController::Base.send :extend, ::Voltron::Upload
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    ActiveSupport.on_load :active_record do
         
     | 
| 
      
 12 
     | 
    
         
            +
                      require 'voltron/upload/action_view/field'
         
     | 
| 
      
 13 
     | 
    
         
            +
                      ::ActionView::Helpers::FormBuilder.send :prepend, ::Voltron::Upload::Field
         
     | 
| 
      
 14 
     | 
    
         
            +
                      ::CarrierWave::Uploader::Base.send :prepend, ::Voltron::Upload::CarrierWave::Uploader::Base
         
     | 
| 
      
 15 
     | 
    
         
            +
                      ::ActiveRecord::Base.send :include, ::Voltron::Upload::Base
         
     | 
| 
      
 16 
     | 
    
         
            +
                    end
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Voltron
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Upload
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Error < StandardError
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  attr_accessor :messages
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  def initialize(*messages)
         
     | 
| 
      
 8 
     | 
    
         
            +
                    @messages = messages.flatten
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def response
         
     | 
| 
      
 12 
     | 
    
         
            +
                    { success: false, error: @messages }
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def status
         
     | 
| 
      
 16 
     | 
    
         
            +
                    :not_acceptable
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,51 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'voltron'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'carrierwave'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'voltron/upload/version'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'voltron/config/upload'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'voltron/upload/error'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'voltron/uploader'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'voltron/upload/carrierwave/uploader/base'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require 'voltron/upload/active_record/base'
         
     | 
| 
      
 9 
     | 
    
         
            +
            require 'voltron/upload/action_dispatch/routes'
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            module Voltron
         
     | 
| 
      
 12 
     | 
    
         
            +
              module Upload
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                LOG_COLOR = :light_cyan
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def uploadable(resource = nil)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  include ControllerMethods
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  resource ||= controller_name
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @uploader ||= Voltron::Uploader.new(resource)
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  rescue_from ActionController::InvalidAuthenticityToken do |e|
         
     | 
| 
      
 23 
     | 
    
         
            +
                    raise unless action_name == 'upload'
         
     | 
| 
      
 24 
     | 
    
         
            +
                    render json: { success: false, error: 'Invalid authenticity token provided' }, status: :unauthorized
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                module ControllerMethods
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def upload
         
     | 
| 
      
 31 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 32 
     | 
    
         
            +
                      render json: uploader.process!(upload_params), status: :created
         
     | 
| 
      
 33 
     | 
    
         
            +
                    rescue Voltron::Upload::Error => e
         
     | 
| 
      
 34 
     | 
    
         
            +
                      render json: e.response, status: e.status
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  def uploader
         
     | 
| 
      
 39 
     | 
    
         
            +
                    self.class.instance_variable_get('@uploader')
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  def upload_params
         
     | 
| 
      
 43 
     | 
    
         
            +
                    request.parameters[uploader.resource_name].slice(*uploader.permitted_params)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
              end
         
     | 
| 
      
 49 
     | 
    
         
            +
            end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
            require "voltron/upload/engine" if defined?(Rails)
         
     | 
| 
         @@ -0,0 +1,58 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Voltron
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Uploader
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                attr_accessor :resource
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(resource)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @resource = resource.to_s.classify.safe_constantize
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # Resource name as it would appear in the params hash
         
     | 
| 
      
 11 
     | 
    
         
            +
                def resource_name
         
     | 
| 
      
 12 
     | 
    
         
            +
                  resource.name.singularize.underscore.to_sym
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                # List of permitted parameters needed for upload action
         
     | 
| 
      
 16 
     | 
    
         
            +
                def permitted_params
         
     | 
| 
      
 17 
     | 
    
         
            +
                  columns.keys.map(&:to_sym)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  #.map { |name, multiple| multiple ? { name => [] } : name }
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def process!(params)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  params = params.map { |column, value| { column => multiple?(column) && value.is_a?(Array) ? value.map(&:values).flatten : value } }.reduce(Hash.new, :merge)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  model = resource.new(params)
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  # Test the validity, get the errors if any
         
     | 
| 
      
 26 
     | 
    
         
            +
                  model.valid?
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  # Remove all errors that were not related to an uploader, they're expected in this case
         
     | 
| 
      
 29 
     | 
    
         
            +
                  (model.errors.keys - resource.uploaders.keys).each { |k| model.errors.delete k }
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  if model.errors.any?
         
     | 
| 
      
 32 
     | 
    
         
            +
                    # If any errors, return the messages
         
     | 
| 
      
 33 
     | 
    
         
            +
                    raise ::Voltron::Upload::Error.new(model.errors.full_messages)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  else
         
     | 
| 
      
 35 
     | 
    
         
            +
                    { uploads: files_from(model) }
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def files_from(model)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  model.slice(columns.keys).values.flatten.reject(&:blank?).map(&:to_upload_json)
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                # Get a hash of uploader columns and whether or not it accepts multiple uploads
         
     | 
| 
      
 44 
     | 
    
         
            +
                # i.e. - { column => multiple_uploads? }
         
     | 
| 
      
 45 
     | 
    
         
            +
                # i.e. - { avatar: false }
         
     | 
| 
      
 46 
     | 
    
         
            +
                def columns
         
     | 
| 
      
 47 
     | 
    
         
            +
                  @instance ||= resource.new
         
     | 
| 
      
 48 
     | 
    
         
            +
                  uploaders = resource.uploaders.keys.map(&:to_s)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  resource.uploaders.map { |k,v| { k.to_s => multiple?(k) } }.reduce(Hash.new, :merge)
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                def multiple?(column)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  @instance ||= resource.new
         
     | 
| 
      
 54 
     | 
    
         
            +
                  @instance.respond_to?("#{column}_urls")
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
              end
         
     | 
| 
      
 58 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,34 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # coding: utf-8
         
     | 
| 
      
 2 
     | 
    
         
            +
            lib = File.expand_path('../lib', __FILE__)
         
     | 
| 
      
 3 
     | 
    
         
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'voltron/upload/version'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            Gem::Specification.new do |spec|
         
     | 
| 
      
 7 
     | 
    
         
            +
              spec.name          = 'voltron-upload'
         
     | 
| 
      
 8 
     | 
    
         
            +
              spec.version       = Voltron::Upload::VERSION
         
     | 
| 
      
 9 
     | 
    
         
            +
              spec.authors       = ['Eric Hainer']
         
     | 
| 
      
 10 
     | 
    
         
            +
              spec.email         = ['eric@commercekitchen.com']
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              spec.summary       = %q{Adds logic to handle drag and drop file uploads with Dropzone.js}
         
     | 
| 
      
 13 
     | 
    
         
            +
              spec.homepage      = 'https://github.com/ehainer/voltron-upload'
         
     | 
| 
      
 14 
     | 
    
         
            +
              spec.license       = 'GNU GPL v3'
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              spec.files         = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
         
     | 
| 
      
 17 
     | 
    
         
            +
              spec.bindir        = 'exe'
         
     | 
| 
      
 18 
     | 
    
         
            +
              spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
         
     | 
| 
      
 19 
     | 
    
         
            +
              spec.require_paths = ['lib']
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              spec.add_dependency 'rails', '>= 4.2'
         
     | 
| 
      
 22 
     | 
    
         
            +
              spec.add_dependency 'voltron', '>= 0.2.5'
         
     | 
| 
      
 23 
     | 
    
         
            +
              spec.add_dependency 'carrierwave', '~> 1.0'
         
     | 
| 
      
 24 
     | 
    
         
            +
              spec.add_dependency 'sass-rails', '>= 5.0'
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              spec.add_development_dependency 'byebug'
         
     | 
| 
      
 27 
     | 
    
         
            +
              spec.add_development_dependency 'bundler', '~> 1.12'
         
     | 
| 
      
 28 
     | 
    
         
            +
              spec.add_development_dependency 'rake', '~> 11.3'
         
     | 
| 
      
 29 
     | 
    
         
            +
              spec.add_development_dependency 'rspec', '~> 3.0'
         
     | 
| 
      
 30 
     | 
    
         
            +
              spec.add_development_dependency 'rspec-rails', '~> 3.4'
         
     | 
| 
      
 31 
     | 
    
         
            +
              spec.add_development_dependency 'factory_girl_rails', '~> 4.0'
         
     | 
| 
      
 32 
     | 
    
         
            +
              spec.add_development_dependency 'jquery-rails', '~> 4'
         
     | 
| 
      
 33 
     | 
    
         
            +
              spec.add_development_dependency 'sqlite3', '~> 1.2'
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     |