asset_cloud 2.7.0 → 2.7.2
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 +4 -4
- data/.github/workflows/ci.yml +42 -0
- data/.github/workflows/cla.yml +22 -0
- data/.rubocop.yml +3 -1
- data/Gemfile +5 -3
- data/History.md +8 -0
- data/README.rdoc +3 -4
- data/Rakefile +18 -16
- data/asset_cloud.gemspec +19 -18
- data/dev.yml +1 -1
- data/lib/asset_cloud/asset.rb +17 -13
- data/lib/asset_cloud/asset_extension.rb +27 -15
- data/lib/asset_cloud/base.rb +77 -72
- data/lib/asset_cloud/bucket.rb +5 -2
- data/lib/asset_cloud/buckets/active_record_bucket.rb +16 -14
- data/lib/asset_cloud/buckets/blackhole_bucket.rb +2 -0
- data/lib/asset_cloud/buckets/bucket_chain.rb +38 -31
- data/lib/asset_cloud/buckets/file_system_bucket.rb +14 -15
- data/lib/asset_cloud/buckets/gcs_bucket.rb +6 -8
- data/lib/asset_cloud/buckets/invalid_bucket.rb +9 -6
- data/lib/asset_cloud/buckets/memory_bucket.rb +7 -4
- data/lib/asset_cloud/buckets/s3_bucket.rb +11 -8
- data/lib/asset_cloud/buckets/versioned_memory_bucket.rb +4 -2
- data/lib/asset_cloud/callbacks.rb +9 -5
- data/lib/asset_cloud/free_key_locator.rb +6 -6
- data/lib/asset_cloud/metadata.rb +11 -7
- data/lib/asset_cloud/validations.rb +9 -5
- data/lib/asset_cloud.rb +23 -21
- data/spec/active_record_bucket_spec.rb +27 -26
- data/spec/asset_cloud/metadata_spec.rb +4 -2
- data/spec/asset_extension_spec.rb +17 -16
- data/spec/asset_spec.rb +27 -21
- data/spec/base_spec.rb +93 -92
- data/spec/blackhole_bucket_spec.rb +12 -11
- data/spec/bucket_chain_spec.rb +61 -56
- data/spec/bucket_spec.rb +6 -5
- data/spec/callbacks_spec.rb +52 -32
- data/spec/file_system_spec.rb +25 -24
- data/spec/find_free_key_spec.rb +16 -17
- data/spec/gcs_bucket_remote_spec.rb +23 -22
- data/spec/gcs_bucket_spec.rb +48 -60
- data/spec/memory_bucket_spec.rb +12 -11
- data/spec/mock_s3_interface.rb +17 -6
- data/spec/remote_s3_bucket_spec.rb +31 -28
- data/spec/s3_bucket_spec.rb +19 -17
- data/spec/spec_helper.rb +8 -7
- data/spec/validations_spec.rb +13 -12
- data/spec/versioned_memory_bucket_spec.rb +11 -10
- metadata +10 -33
- data/.github/probots.yml +0 -2
- data/.rubocop_todo.yml +0 -326
- data/.travis.yml +0 -12
    
        data/lib/asset_cloud/base.rb
    CHANGED
    
    | @@ -1,42 +1,44 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 3 | 
            +
            require "uri/rfc2396_parser"
         | 
| 4 4 |  | 
| 5 | 
            +
            module AssetCloud
         | 
| 5 6 | 
             
              class IllegalPath < StandardError
         | 
| 6 7 | 
             
              end
         | 
| 7 8 |  | 
| 8 9 | 
             
              class Base
         | 
| 9 10 | 
             
                cattr_accessor :logger
         | 
| 10 11 |  | 
| 11 | 
            -
                VALID_PATHS =  | 
| 12 | 
            +
                VALID_PATHS = %r{\A
         | 
| 12 13 | 
             
                  (
         | 
| 13 | 
            -
                    (\w) | 
| 14 | 
            -
                    | | 
| 14 | 
            +
                    (\w)                        # Filename can be a single letter or underscore
         | 
| 15 | 
            +
                    |                           # OR it is many and follows the below rules
         | 
| 15 16 | 
             
                    (
         | 
| 16 | 
            -
                      (\.?[\w\[\]\(\)\-\@]) | 
| 17 | 
            +
                      (\.?[\w\[\]\(\)\-\@])     # It can start with a dot but it must have a following character
         | 
| 17 18 | 
             
                      (
         | 
| 18 | 
            -
                        [\w\[\]\(\)\-\@] | 
| 19 | 
            +
                        [\w\[\]\(\)\-\@]        # You can have a letter without any following conditions
         | 
| 19 20 | 
             
                        |
         | 
| 20 | 
            -
                        [\ ][\w\[\]\(\)\-\@\.] | 
| 21 | 
            +
                        [\ ][\w\[\]\(\)\-\@\.]  # If there is a space you need to have a normal letter afterward or a dot
         | 
| 21 22 | 
             
                        |
         | 
| 22 | 
            -
                        [ | 
| 23 | 
            +
                        [/][\w\[\]\(\)\-\@]     # If there is a slash you need to have a normal letter afterward
         | 
| 23 24 | 
             
                        |
         | 
| 24 | 
            -
                        [ | 
| 25 | 
            +
                        [/][\.][\w\[\]\(\)\-\@] # Though a slash could be followed by a dot
         | 
| 26 | 
            +
                                                # so long as there is a normal letter afterward
         | 
| 25 27 | 
             
                        |
         | 
| 26 | 
            -
                        [\.]+[\w\[\]\(\)\-\@]+ | 
| 27 | 
            -
                      )* | 
| 28 | 
            +
                        [\.]+[\w\[\]\(\)\-\@]+  # One or more dots must be followed by one (or more) normal letters
         | 
| 29 | 
            +
                      )*                        # Zero to many of these combinations.
         | 
| 28 30 | 
             
                    )
         | 
| 29 | 
            -
                  )\z | 
| 30 | 
            -
                MATCH_BUCKET =  | 
| 31 | 
            +
                  )\z}x
         | 
| 32 | 
            +
                MATCH_BUCKET = %r{^(\w+)(/|$)}
         | 
| 31 33 |  | 
| 32 34 | 
             
                URI_PARSER = URI::RFC2396_Parser.new
         | 
| 33 35 |  | 
| 34 36 | 
             
                attr_accessor :url, :root
         | 
| 35 37 |  | 
| 36 38 | 
             
                class_attribute :root_bucket_class
         | 
| 37 | 
            -
                self.root_bucket_class =  | 
| 39 | 
            +
                self.root_bucket_class = "AssetCloud::FileSystemBucket"
         | 
| 38 40 | 
             
                class_attribute :root_asset_class
         | 
| 39 | 
            -
                self.root_asset_class | 
| 41 | 
            +
                self.root_asset_class = "AssetCloud::Asset"
         | 
| 40 42 |  | 
| 41 43 | 
             
                class_attribute :bucket_classes
         | 
| 42 44 | 
             
                self.bucket_classes = {}.freeze
         | 
| @@ -45,56 +47,68 @@ module AssetCloud | |
| 45 47 | 
             
                class_attribute :asset_extension_classes
         | 
| 46 48 | 
             
                self.asset_extension_classes = {}.freeze
         | 
| 47 49 |  | 
| 48 | 
            -
                 | 
| 49 | 
            -
                   | 
| 50 | 
            -
                     | 
| 51 | 
            -
             | 
| 50 | 
            +
                class << self
         | 
| 51 | 
            +
                  def bucket(*args)
         | 
| 52 | 
            +
                    asset_class = if args.last.is_a?(Hash)
         | 
| 53 | 
            +
                      convert_to_class_name_if_possible(args.pop[:asset_class])
         | 
| 54 | 
            +
                    end
         | 
| 52 55 |  | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 56 | 
            +
                    bucket_class = if args.last.is_a?(Class)
         | 
| 57 | 
            +
                      convert_to_class_name_if_possible(args.pop)
         | 
| 58 | 
            +
                    else
         | 
| 59 | 
            +
                      raise ArgumentError, "requires a bucket class"
         | 
| 60 | 
            +
                    end
         | 
| 58 61 |  | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 62 | 
            +
                    if (bucket_name = args.first)
         | 
| 63 | 
            +
                      self.bucket_classes = bucket_classes.merge(bucket_name.to_sym => bucket_class).freeze
         | 
| 64 | 
            +
                      self.asset_classes = asset_classes.merge(bucket_name.to_sym => asset_class).freeze if asset_class
         | 
| 65 | 
            +
                    else
         | 
| 66 | 
            +
                      self.root_bucket_class = bucket_class
         | 
| 67 | 
            +
                      if asset_class
         | 
| 68 | 
            +
                        raise ArgumentError, "asset_class on the root bucket cannot be a proc" if asset_class.is_a?(Proc)
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                        self.root_asset_class = asset_class
         | 
| 71 | 
            +
                      end
         | 
| 67 72 | 
             
                    end
         | 
| 68 73 | 
             
                  end
         | 
| 69 | 
            -
                end
         | 
| 70 74 |  | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            +
                  def asset_extensions(*args)
         | 
| 76 | 
            +
                    opts = args.last.is_a?(Hash) ? args.pop.slice(:only, :except) : {}
         | 
| 77 | 
            +
                    opts.each do |k, v|
         | 
| 78 | 
            +
                      opts[k] = [v].flatten.map(&:to_sym)
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    args.each do |klass|
         | 
| 82 | 
            +
                      klass = convert_to_class_name_if_possible(klass)
         | 
| 83 | 
            +
                      self.asset_extension_classes = asset_extension_classes.merge(klass => opts).freeze
         | 
| 84 | 
            +
                    end
         | 
| 75 85 | 
             
                  end
         | 
| 76 86 |  | 
| 77 | 
            -
                   | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 87 | 
            +
                  private
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                  def convert_to_class_name_if_possible(klass)
         | 
| 90 | 
            +
                    if klass.is_a?(Class) && klass.name.present?
         | 
| 91 | 
            +
                      klass.name
         | 
| 92 | 
            +
                    else
         | 
| 93 | 
            +
                      klass
         | 
| 94 | 
            +
                    end
         | 
| 80 95 | 
             
                  end
         | 
| 81 96 | 
             
                end
         | 
| 82 97 |  | 
| 83 98 | 
             
                def buckets
         | 
| 84 99 | 
             
                  @buckets ||= Hash.new do |hash, key|
         | 
| 85 | 
            -
                    if klass = self.class.bucket_classes[key]
         | 
| 86 | 
            -
                       | 
| 87 | 
            -
                    else
         | 
| 88 | 
            -
                      hash[key] = nil
         | 
| 100 | 
            +
                    hash[key] = if (klass = self.class.bucket_classes[key])
         | 
| 101 | 
            +
                      constantize_if_necessary(klass).new(self, key)
         | 
| 89 102 | 
             
                    end
         | 
| 90 103 | 
             
                  end
         | 
| 91 104 | 
             
                end
         | 
| 92 105 |  | 
| 93 | 
            -
                def initialize(root, url =  | 
| 94 | 
            -
                  @root | 
| 106 | 
            +
                def initialize(root, url = "/")
         | 
| 107 | 
            +
                  @root = root
         | 
| 108 | 
            +
                  @url = url
         | 
| 95 109 | 
             
                end
         | 
| 96 110 |  | 
| 97 | 
            -
                def url_for(key, options={})
         | 
| 111 | 
            +
                def url_for(key, options = {})
         | 
| 98 112 | 
             
                  File.join(@url, URI_PARSER.escape(key))
         | 
| 99 113 | 
             
                end
         | 
| 100 114 |  | 
| @@ -140,13 +154,13 @@ module AssetCloud | |
| 140 154 | 
             
                end
         | 
| 141 155 |  | 
| 142 156 | 
             
                def build(key, value = nil, &block)
         | 
| 143 | 
            -
                  logger | 
| 157 | 
            +
                  logger&.info { "  [#{self.class.name}] Building asset #{key}" }
         | 
| 144 158 | 
             
                  asset_class_for(key).new(self, key, value, Metadata.non_existing, &block)
         | 
| 145 159 | 
             
                end
         | 
| 146 160 |  | 
| 147 161 | 
             
                def write(key, value)
         | 
| 148 162 | 
             
                  check_key_for_errors(key)
         | 
| 149 | 
            -
                  logger | 
| 163 | 
            +
                  logger&.info { "  [#{self.class.name}] Writing #{value.size} bytes to #{key}" }
         | 
| 150 164 |  | 
| 151 165 | 
             
                  bucket_for(key).write(key, value)
         | 
| 152 166 | 
             
                end
         | 
| @@ -158,25 +172,25 @@ module AssetCloud | |
| 158 172 | 
             
                end
         | 
| 159 173 |  | 
| 160 174 | 
             
                def read(key)
         | 
| 161 | 
            -
                  logger | 
| 175 | 
            +
                  logger&.info { "  [#{self.class.name}] Reading from #{key}" }
         | 
| 162 176 |  | 
| 163 177 | 
             
                  bucket_for(key).read(key)
         | 
| 164 178 | 
             
                end
         | 
| 165 179 |  | 
| 166 180 | 
             
                def stat(key)
         | 
| 167 | 
            -
                  logger | 
| 181 | 
            +
                  logger&.info { "  [#{self.class.name}] Statting #{key}" }
         | 
| 168 182 |  | 
| 169 183 | 
             
                  bucket_for(key).stat(key)
         | 
| 170 184 | 
             
                end
         | 
| 171 185 |  | 
| 172 186 | 
             
                def ls(key)
         | 
| 173 | 
            -
                  logger | 
| 187 | 
            +
                  logger&.info { "  [#{self.class.name}] Listing objects in #{key}" }
         | 
| 174 188 |  | 
| 175 189 | 
             
                  bucket_for(key).ls(key)
         | 
| 176 190 | 
             
                end
         | 
| 177 191 |  | 
| 178 192 | 
             
                def exist?(key)
         | 
| 179 | 
            -
                  if fp = stat(key)
         | 
| 193 | 
            +
                  if (fp = stat(key))
         | 
| 180 194 | 
             
                    fp.exist?
         | 
| 181 195 | 
             
                  else
         | 
| 182 196 | 
             
                    false
         | 
| @@ -188,7 +202,7 @@ module AssetCloud | |
| 188 202 | 
             
                end
         | 
| 189 203 |  | 
| 190 204 | 
             
                def delete(key)
         | 
| 191 | 
            -
                  logger | 
| 205 | 
            +
                  logger&.info { "  [#{self.class.name}] Deleting #{key}" }
         | 
| 192 206 |  | 
| 193 207 | 
             
                  bucket_for(key).delete(key)
         | 
| 194 208 | 
             
                end
         | 
| @@ -211,17 +225,17 @@ module AssetCloud | |
| 211 225 | 
             
                # versioning
         | 
| 212 226 |  | 
| 213 227 | 
             
                def read_version(key, version)
         | 
| 214 | 
            -
                  logger | 
| 228 | 
            +
                  logger&.info { "  [#{self.class.name}] Reading from #{key} at version #{version}" }
         | 
| 215 229 | 
             
                  bucket_for(key).read_version(key, version)
         | 
| 216 230 | 
             
                end
         | 
| 217 231 |  | 
| 218 232 | 
             
                def versions(key)
         | 
| 219 | 
            -
                  logger | 
| 233 | 
            +
                  logger&.info { "  [#{self.class.name}] Getting all versions for #{key}" }
         | 
| 220 234 | 
             
                  bucket_for(key).versions(key)
         | 
| 221 235 | 
             
                end
         | 
| 222 236 |  | 
| 223 237 | 
             
                def version_details(key)
         | 
| 224 | 
            -
                  logger | 
| 238 | 
            +
                  logger&.info { "  [#{self.class.name}] Getting all version details for #{key}" }
         | 
| 225 239 | 
             
                  bucket_for(key).version_details(key)
         | 
| 226 240 | 
             
                end
         | 
| 227 241 |  | 
| @@ -239,40 +253,31 @@ module AssetCloud | |
| 239 253 | 
             
                  klasses = extensions.keys.select do |ext|
         | 
| 240 254 | 
             
                    opts = extensions[ext]
         | 
| 241 255 | 
             
                    (opts.key?(:only) ? opts[:only].include?(bucket) : true) &&
         | 
| 242 | 
            -
             | 
| 256 | 
            +
                      (opts.key?(:except) ? !opts[:except].include?(bucket) : true)
         | 
| 243 257 | 
             
                  end
         | 
| 244 | 
            -
                  klasses.map {|klass| constantize_if_necessary(klass)}
         | 
| 258 | 
            +
                  klasses.map { |klass| constantize_if_necessary(klass) }
         | 
| 245 259 | 
             
                end
         | 
| 246 260 |  | 
| 247 261 | 
             
                protected
         | 
| 248 262 |  | 
| 249 263 | 
             
                def bucket_symbol_for_key(key)
         | 
| 250 | 
            -
                   | 
| 264 | 
            +
                  Regexp.last_match(1).to_sym if key =~ MATCH_BUCKET
         | 
| 251 265 | 
             
                end
         | 
| 252 266 |  | 
| 253 267 | 
             
                def root_bucket
         | 
| 254 | 
            -
                  @default_bucket ||= constantize_if_necessary(self.class.root_bucket_class).new(self,  | 
| 268 | 
            +
                  @default_bucket ||= constantize_if_necessary(self.class.root_bucket_class).new(self, "")
         | 
| 255 269 | 
             
                end
         | 
| 256 270 |  | 
| 257 271 | 
             
                def constantize_if_necessary(klass)
         | 
| 258 272 | 
             
                  klass.is_a?(Class) ? klass : klass.constantize
         | 
| 259 273 | 
             
                end
         | 
| 260 274 |  | 
| 261 | 
            -
                def self.convert_to_class_name_if_possible(klass)
         | 
| 262 | 
            -
                  if klass.is_a?(Class) && klass.name.present?
         | 
| 263 | 
            -
                    klass.name
         | 
| 264 | 
            -
                  else
         | 
| 265 | 
            -
                    klass
         | 
| 266 | 
            -
                  end
         | 
| 267 | 
            -
                end
         | 
| 268 | 
            -
             | 
| 269 275 | 
             
                def check_key_for_errors(key)
         | 
| 270 276 | 
             
                  raise IllegalPath, "key cannot be empty" if key.blank?
         | 
| 271 277 | 
             
                  raise IllegalPath, "#{key.inspect} contains illegal characters" unless supports?(key)
         | 
| 272 278 | 
             
                rescue => e
         | 
| 273 | 
            -
                  logger | 
| 279 | 
            +
                  logger&.info { "  [#{self.class.name}]   bad key #{e.message}" }
         | 
| 274 280 | 
             
                  raise
         | 
| 275 281 | 
             
                end
         | 
| 276 | 
            -
             | 
| 277 282 | 
             
              end
         | 
| 278 283 | 
             
            end
         | 
    
        data/lib/asset_cloud/bucket.rb
    CHANGED
    
    | @@ -1,6 +1,8 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module AssetCloud
         | 
| 2 4 | 
             
              class AssetNotFoundError < StandardError
         | 
| 3 | 
            -
                def initialize(key, version=nil)
         | 
| 5 | 
            +
                def initialize(key, version = nil)
         | 
| 4 6 | 
             
                  super(version ? "Could not find version #{version} of asset #{key}" : "Could not find asset #{key}")
         | 
| 5 7 | 
             
                end
         | 
| 6 8 | 
             
              end
         | 
| @@ -10,7 +12,8 @@ module AssetCloud | |
| 10 12 | 
             
                attr_accessor :cloud
         | 
| 11 13 |  | 
| 12 14 | 
             
                def initialize(cloud, name)
         | 
| 13 | 
            -
                  @cloud | 
| 15 | 
            +
                  @cloud = cloud
         | 
| 16 | 
            +
                  @name = name
         | 
| 14 17 | 
             
                end
         | 
| 15 18 |  | 
| 16 19 | 
             
                def ls(key = nil)
         | 
| @@ -1,35 +1,37 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module AssetCloud
         | 
| 2 4 | 
             
              class ActiveRecordBucket < AssetCloud::Bucket
         | 
| 3 5 | 
             
                class_attribute :key_attribute, :value_attribute
         | 
| 4 | 
            -
                self.key_attribute =  | 
| 5 | 
            -
                self.value_attribute =  | 
| 6 | 
            +
                self.key_attribute = "key"
         | 
| 7 | 
            +
                self.value_attribute = "value"
         | 
| 6 8 |  | 
| 7 | 
            -
                def ls(key=name)
         | 
| 8 | 
            -
                  col = records.connection.quote_column_name( | 
| 9 | 
            -
                  records.all(: | 
| 10 | 
            -
                    cloud[r.send( | 
| 9 | 
            +
                def ls(key = name)
         | 
| 10 | 
            +
                  col = records.connection.quote_column_name(key_attribute)
         | 
| 11 | 
            +
                  records.all(conditions: ["#{col} LIKE ?", "#{key}%"]).map do |r|
         | 
| 12 | 
            +
                    cloud[r.send(key_attribute)]
         | 
| 11 13 | 
             
                  end
         | 
| 12 14 | 
             
                end
         | 
| 13 15 |  | 
| 14 16 | 
             
                def read(key)
         | 
| 15 | 
            -
                  find_record!(key).send( | 
| 17 | 
            +
                  find_record!(key).send(value_attribute)
         | 
| 16 18 | 
             
                end
         | 
| 17 19 |  | 
| 18 20 | 
             
                def write(key, value)
         | 
| 19 | 
            -
                  record = records.send("find_or_initialize_by_#{ | 
| 20 | 
            -
                  record.send("#{ | 
| 21 | 
            +
                  record = records.send("find_or_initialize_by_#{key_attribute}", key.to_s)
         | 
| 22 | 
            +
                  record.send("#{value_attribute}=", value)
         | 
| 21 23 | 
             
                  record.save!
         | 
| 22 24 | 
             
                end
         | 
| 23 25 |  | 
| 24 26 | 
             
                def delete(key)
         | 
| 25 | 
            -
                  if record = find_record(key)
         | 
| 27 | 
            +
                  if (record = find_record(key))
         | 
| 26 28 | 
             
                    record.destroy
         | 
| 27 29 | 
             
                  end
         | 
| 28 30 | 
             
                end
         | 
| 29 31 |  | 
| 30 32 | 
             
                def stat(key)
         | 
| 31 | 
            -
                  if record = find_record(key)
         | 
| 32 | 
            -
                    AssetCloud::Metadata.new(true, record.send( | 
| 33 | 
            +
                  if (record = find_record(key))
         | 
| 34 | 
            +
                    AssetCloud::Metadata.new(true, record.send(value_attribute).size, record.created_at, record.updated_at)
         | 
| 33 35 | 
             
                  else
         | 
| 34 36 | 
             
                    AssetCloud::Metadata.new(false)
         | 
| 35 37 | 
             
                  end
         | 
| @@ -47,11 +49,11 @@ module AssetCloud | |
| 47 49 | 
             
                end
         | 
| 48 50 |  | 
| 49 51 | 
             
                def find_record(key)
         | 
| 50 | 
            -
                  records.first(: | 
| 52 | 
            +
                  records.first(conditions: { key_attribute => key.to_s })
         | 
| 51 53 | 
             
                end
         | 
| 52 54 |  | 
| 53 55 | 
             
                def find_record!(key)
         | 
| 54 | 
            -
                  find_record(key)  | 
| 56 | 
            +
                  find_record(key) || raise(AssetCloud::AssetNotFoundError, key)
         | 
| 55 57 | 
             
                end
         | 
| 56 58 | 
             
              end
         | 
| 57 59 | 
             
            end
         | 
| @@ -1,61 +1,70 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module AssetCloud
         | 
| 2 4 | 
             
              class BucketChain < Bucket
         | 
| 3 | 
            -
                 | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
                   | 
| 7 | 
            -
                     | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
                       | 
| 5 | 
            +
                class << self
         | 
| 6 | 
            +
                  # returns a new Bucket class which writes to each given Bucket
         | 
| 7 | 
            +
                  # but only uses the first one for reading
         | 
| 8 | 
            +
                  def chain(*klasses)
         | 
| 9 | 
            +
                    Class.new(self) do
         | 
| 10 | 
            +
                      attr_reader :chained_buckets
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                      define_method "initialize" do |cloud, name|
         | 
| 13 | 
            +
                        super(cloud, name)
         | 
| 14 | 
            +
                        @chained_buckets = klasses.map { |klass| klass.new(cloud, name) }
         | 
| 15 | 
            +
                      end
         | 
| 11 16 | 
             
                    end
         | 
| 12 17 | 
             
                  end
         | 
| 13 18 | 
             
                end
         | 
| 14 19 |  | 
| 15 | 
            -
                def ls(key=nil)
         | 
| 16 | 
            -
                  first_possible_bucket {|b| b.ls(key)}
         | 
| 20 | 
            +
                def ls(key = nil)
         | 
| 21 | 
            +
                  first_possible_bucket { |b| b.ls(key) }
         | 
| 17 22 | 
             
                end
         | 
| 23 | 
            +
             | 
| 18 24 | 
             
                def read(key)
         | 
| 19 | 
            -
                  first_possible_bucket {|b| b.read(key)}
         | 
| 25 | 
            +
                  first_possible_bucket { |b| b.read(key) }
         | 
| 20 26 | 
             
                end
         | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 27 | 
            +
             | 
| 28 | 
            +
                def stat(key = nil)
         | 
| 29 | 
            +
                  first_possible_bucket { |b| b.stat(key) }
         | 
| 23 30 | 
             
                end
         | 
| 31 | 
            +
             | 
| 24 32 | 
             
                def read_version(key, version)
         | 
| 25 | 
            -
                  first_possible_bucket {|b| b.read_version(key, version)}
         | 
| 33 | 
            +
                  first_possible_bucket { |b| b.read_version(key, version) }
         | 
| 26 34 | 
             
                end
         | 
| 35 | 
            +
             | 
| 27 36 | 
             
                def versions(key)
         | 
| 28 | 
            -
                  first_possible_bucket {|b| b.versions(key)}
         | 
| 37 | 
            +
                  first_possible_bucket { |b| b.versions(key) }
         | 
| 29 38 | 
             
                end
         | 
| 30 39 |  | 
| 31 40 | 
             
                def write(key, data)
         | 
| 32 | 
            -
                  every_bucket_with_transaction_on_key(key) {|b| b.write(key, data)}
         | 
| 41 | 
            +
                  every_bucket_with_transaction_on_key(key) { |b| b.write(key, data) }
         | 
| 33 42 | 
             
                end
         | 
| 43 | 
            +
             | 
| 34 44 | 
             
                def delete(key)
         | 
| 35 | 
            -
                  every_bucket_with_transaction_on_key(key) {|b| b.delete(key)}
         | 
| 45 | 
            +
                  every_bucket_with_transaction_on_key(key) { |b| b.delete(key) }
         | 
| 36 46 | 
             
                end
         | 
| 37 47 |  | 
| 38 | 
            -
                def  | 
| 39 | 
            -
                  @chained_buckets.any? {|b| b.respond_to?(sym)}
         | 
| 48 | 
            +
                def respond_to_missing?(sym, *)
         | 
| 49 | 
            +
                  @chained_buckets.any? { |b| b.respond_to?(sym) }
         | 
| 40 50 | 
             
                end
         | 
| 51 | 
            +
             | 
| 41 52 | 
             
                def method_missing(sym, *args)
         | 
| 42 | 
            -
                  first_possible_bucket {|b| b.send(sym, *args)}
         | 
| 53 | 
            +
                  first_possible_bucket { |b| b.send(sym, *args) }
         | 
| 43 54 | 
             
                end
         | 
| 44 55 |  | 
| 45 56 | 
             
                private
         | 
| 46 57 |  | 
| 47 58 | 
             
                def first_possible_bucket(&block)
         | 
| 48 59 | 
             
                  @chained_buckets.each do |bucket|
         | 
| 49 | 
            -
                     | 
| 50 | 
            -
             | 
| 51 | 
            -
                     | 
| 52 | 
            -
                      nil
         | 
| 53 | 
            -
                    end
         | 
| 60 | 
            +
                    return yield(bucket)
         | 
| 61 | 
            +
                  rescue NoMethodError, NotImplementedError
         | 
| 62 | 
            +
                    nil
         | 
| 54 63 | 
             
                  end
         | 
| 55 64 | 
             
                end
         | 
| 56 65 |  | 
| 57 | 
            -
                def every_bucket_with_transaction_on_key(key, i=0, &block)
         | 
| 58 | 
            -
                  return unless bucket = @chained_buckets[i]
         | 
| 66 | 
            +
                def every_bucket_with_transaction_on_key(key, i = 0, &block)
         | 
| 67 | 
            +
                  return unless (bucket = @chained_buckets[i])
         | 
| 59 68 |  | 
| 60 69 | 
             
                  old_value = begin
         | 
| 61 70 | 
             
                    bucket.read(key)
         | 
| @@ -65,8 +74,8 @@ module AssetCloud | |
| 65 74 | 
             
                  result = yield(bucket)
         | 
| 66 75 |  | 
| 67 76 | 
             
                  begin
         | 
| 68 | 
            -
                    every_bucket_with_transaction_on_key(key, i+1, &block)
         | 
| 69 | 
            -
                     | 
| 77 | 
            +
                    every_bucket_with_transaction_on_key(key, i + 1, &block)
         | 
| 78 | 
            +
                    result
         | 
| 70 79 | 
             
                  rescue StandardError => e
         | 
| 71 80 | 
             
                    if old_value
         | 
| 72 81 | 
             
                      bucket.write(key, old_value)
         | 
| @@ -76,7 +85,5 @@ module AssetCloud | |
| 76 85 | 
             
                    raise e
         | 
| 77 86 | 
             
                  end
         | 
| 78 87 | 
             
                end
         | 
| 79 | 
            -
             | 
| 80 88 | 
             
              end
         | 
| 81 | 
            -
             | 
| 82 89 | 
             
            end
         | 
| @@ -1,27 +1,29 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            +
            module AssetCloud
         | 
| 3 4 | 
             
              class FileSystemBucket < Bucket
         | 
| 4 | 
            -
             | 
| 5 5 | 
             
                def ls(key = nil)
         | 
| 6 6 | 
             
                  objects = []
         | 
| 7 | 
            -
                  base_path = File.join(path_for(key),  | 
| 7 | 
            +
                  base_path = File.join(path_for(key), "*")
         | 
| 8 8 |  | 
| 9 9 | 
             
                  Dir.glob(base_path).each do |f|
         | 
| 10 10 | 
             
                    next unless File.file?(f)
         | 
| 11 | 
            -
             | 
| 11 | 
            +
             | 
| 12 | 
            +
                    objects.push(cloud[relative_path_for(f)])
         | 
| 12 13 | 
             
                  end
         | 
| 13 14 | 
             
                  objects
         | 
| 14 15 | 
             
                end
         | 
| 15 16 |  | 
| 16 17 | 
             
                def read(key)
         | 
| 17 18 | 
             
                  File.read(path_for(key))
         | 
| 18 | 
            -
                rescue Errno::ENOENT | 
| 19 | 
            +
                rescue Errno::ENOENT
         | 
| 19 20 | 
             
                  raise AssetCloud::AssetNotFoundError, key
         | 
| 20 21 | 
             
                end
         | 
| 21 22 |  | 
| 22 23 | 
             
                def delete(key)
         | 
| 23 24 | 
             
                  File.delete(path_for(key))
         | 
| 24 25 | 
             
                rescue Errno::ENOENT
         | 
| 26 | 
            +
                  nil
         | 
| 25 27 | 
             
                end
         | 
| 26 28 |  | 
| 27 29 | 
             
                def write(key, data)
         | 
| @@ -32,12 +34,10 @@ module AssetCloud | |
| 32 34 | 
             
                end
         | 
| 33 35 |  | 
| 34 36 | 
             
                def stat(key)
         | 
| 35 | 
            -
                   | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
                   | 
| 39 | 
            -
                    Metadata.new(false)
         | 
| 40 | 
            -
                  end
         | 
| 37 | 
            +
                  stat = File.stat(path_for(key))
         | 
| 38 | 
            +
                  Metadata.new(true, stat.size, stat.ctime, stat.mtime)
         | 
| 39 | 
            +
                rescue Errno::ENOENT
         | 
| 40 | 
            +
                  Metadata.new(false)
         | 
| 41 41 | 
             
                end
         | 
| 42 42 |  | 
| 43 43 | 
             
                protected
         | 
| @@ -53,11 +53,11 @@ module AssetCloud | |
| 53 53 | 
             
                private
         | 
| 54 54 |  | 
| 55 55 | 
             
                def remove_full_path_regexp
         | 
| 56 | 
            -
                  @regexp ||=  | 
| 56 | 
            +
                  @regexp ||= %r{^#{path}/}
         | 
| 57 57 | 
             
                end
         | 
| 58 58 |  | 
| 59 59 | 
             
                def relative_path_for(f)
         | 
| 60 | 
            -
                  f.sub(remove_full_path_regexp,  | 
| 60 | 
            +
                  f.sub(remove_full_path_regexp, "")
         | 
| 61 61 | 
             
                end
         | 
| 62 62 |  | 
| 63 63 | 
             
                def execute_in_full_path(key, &block)
         | 
| @@ -71,10 +71,9 @@ module AssetCloud | |
| 71 71 |  | 
| 72 72 | 
             
                  begin
         | 
| 73 73 | 
             
                    yield(path)
         | 
| 74 | 
            -
                  rescue Errno::ENOENT | 
| 74 | 
            +
                  rescue Errno::ENOENT
         | 
| 75 75 | 
             
                    raise if retried
         | 
| 76 76 |  | 
| 77 | 
            -
                    directory = File.dirname(path)
         | 
| 78 77 | 
             
                    FileUtils.mkdir_p(File.dirname(path))
         | 
| 79 78 | 
             
                    retried = true
         | 
| 80 79 | 
             
                    retry
         | 
| @@ -31,12 +31,10 @@ module AssetCloud | |
| 31 31 | 
             
                end
         | 
| 32 32 |  | 
| 33 33 | 
             
                def stat(key)
         | 
| 34 | 
            -
                   | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
                   | 
| 38 | 
            -
                    Metadata.new(false)
         | 
| 39 | 
            -
                  end
         | 
| 34 | 
            +
                  file = find_by_key!(key)
         | 
| 35 | 
            +
                  Metadata.new(true, file.size, file.created_at, file.updated_at)
         | 
| 36 | 
            +
                rescue AssetCloud::AssetNotFoundError
         | 
| 37 | 
            +
                  Metadata.new(false)
         | 
| 40 38 | 
             
                end
         | 
| 41 39 |  | 
| 42 40 | 
             
                private
         | 
| @@ -47,11 +45,11 @@ module AssetCloud | |
| 47 45 |  | 
| 48 46 | 
             
                def absolute_key(key = nil)
         | 
| 49 47 | 
             
                  if key.to_s.starts_with?(path_prefix)
         | 
| 50 | 
            -
                     | 
| 48 | 
            +
                    key
         | 
| 51 49 | 
             
                  else
         | 
| 52 50 | 
             
                    args = [path_prefix]
         | 
| 53 51 | 
             
                    args << key.to_s if key
         | 
| 54 | 
            -
                    args.join( | 
| 52 | 
            +
                    args.join("/")
         | 
| 55 53 | 
             
                  end
         | 
| 56 54 | 
             
                end
         | 
| 57 55 |  | 
| @@ -1,28 +1,31 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module AssetCloud
         | 
| 2 4 | 
             
              class InvalidBucketError < StandardError
         | 
| 3 5 | 
             
              end
         | 
| 4 6 |  | 
| 5 7 | 
             
              class InvalidBucket < Bucket
         | 
| 6 | 
            -
                 | 
| 8 | 
            +
                ERROR = "No such namespace: %s"
         | 
| 9 | 
            +
                private_constant :ERROR
         | 
| 7 10 |  | 
| 8 11 | 
             
                def ls(namespace)
         | 
| 9 | 
            -
                  raise InvalidBucketError,  | 
| 12 | 
            +
                  raise InvalidBucketError, ERROR % namespace
         | 
| 10 13 | 
             
                end
         | 
| 11 14 |  | 
| 12 15 | 
             
                def read(key)
         | 
| 13 | 
            -
                  raise InvalidBucketError,  | 
| 16 | 
            +
                  raise InvalidBucketError, ERROR % key
         | 
| 14 17 | 
             
                end
         | 
| 15 18 |  | 
| 16 19 | 
             
                def write(key, data)
         | 
| 17 | 
            -
                  raise InvalidBucketError,  | 
| 20 | 
            +
                  raise InvalidBucketError, ERROR % key
         | 
| 18 21 | 
             
                end
         | 
| 19 22 |  | 
| 20 23 | 
             
                def delete(key)
         | 
| 21 | 
            -
                  raise InvalidBucketError,  | 
| 24 | 
            +
                  raise InvalidBucketError, ERROR % key
         | 
| 22 25 | 
             
                end
         | 
| 23 26 |  | 
| 24 27 | 
             
                def stat(key)
         | 
| 25 | 
            -
                  raise InvalidBucketError,  | 
| 28 | 
            +
                  raise InvalidBucketError, ERROR % key
         | 
| 26 29 | 
             
                end
         | 
| 27 30 | 
             
              end
         | 
| 28 31 | 
             
            end
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module AssetCloud
         | 
| 2 4 | 
             
              class MemoryBucket < Bucket
         | 
| 3 5 | 
             
                def initialize(*args)
         | 
| @@ -5,16 +7,17 @@ module AssetCloud | |
| 5 7 | 
             
                  @memory = {}
         | 
| 6 8 | 
             
                end
         | 
| 7 9 |  | 
| 8 | 
            -
                def ls(prefix=nil)
         | 
| 10 | 
            +
                def ls(prefix = nil)
         | 
| 9 11 | 
             
                  results = []
         | 
| 10 | 
            -
                  @memory.each do |k, | 
| 12 | 
            +
                  @memory.each do |k, _v|
         | 
| 11 13 | 
             
                    results.push(cloud[k]) if prefix.nil? || k.starts_with?(prefix)
         | 
| 12 14 | 
             
                  end
         | 
| 13 15 | 
             
                  results
         | 
| 14 16 | 
             
                end
         | 
| 15 17 |  | 
| 16 18 | 
             
                def read(key)
         | 
| 17 | 
            -
                  raise AssetCloud::AssetNotFoundError, key unless @memory. | 
| 19 | 
            +
                  raise AssetCloud::AssetNotFoundError, key unless @memory.key?(key)
         | 
| 20 | 
            +
             | 
| 18 21 | 
             
                  @memory[key]
         | 
| 19 22 | 
             
                end
         | 
| 20 23 |  | 
| @@ -29,7 +32,7 @@ module AssetCloud | |
| 29 32 | 
             
                end
         | 
| 30 33 |  | 
| 31 34 | 
             
                def stat(key)
         | 
| 32 | 
            -
                  return Metadata.non_existing unless @memory. | 
| 35 | 
            +
                  return Metadata.non_existing unless @memory.key?(key)
         | 
| 33 36 |  | 
| 34 37 | 
             
                  Metadata.new(true, read(key).size)
         | 
| 35 38 | 
             
                end
         |