paperclipのストレージとしてWindows Azureのblob storageを利用する

[Paperclip](https://github.com/thoughtbot/paperclip)は6000スター超えの有名gemだが、そのgithubからリンクされてる [paperclip-azure-storage](https://github.com/gmontard/paperclip-azure-storage)は3スターしかついておらず、ほとんどメンテもされていないっぽい。Rails4.1.4, paperclip4.2.0でazureのストレージを利用したときにやったことをまとめる。

今回はpaperclip-azure-storageを利用せず、自分で同様のプログラムを実装した。paperclip-azure-storageではsecrets.ymlからazureのストレージに関するパスワードを読むことができず、自分のニーズに合わなかったため。

##### 1. Gemfileにwas-storageを追加
> gem 'waz-storage'

##### 2. config/initializer/azure-storage.rbというファイルを追加
Railsあんまり詳しくないので、本当におくべき場所がここなのかはよくわからない。

ファイルの内容は以下のような感じ。おおむね https://github.com/gmontard/paperclip-azure-storage/blob/master/storage.rb からのコピペだが、本家であるhttps://github.com/thoughtbot/paperclip/blob/master/lib/paperclip/storage/s3.rb を参考にしたほうが良さそうだと後から気づいた。

```rb:config/initializer/azure-storage.rb
module Paperclip
  module Storage
    module AzureStorage
      def self.extended base
        begin
          require 'waz-blobs'
        rescue LoadError => e
          e.message << " (You may need to install the waz-storage gem)"
          raise e
        end

        base.instance_eval do
          @azure_credentials = @options[:azure_credentials] # parse_credentials(@options[:azure_credentials])
          @container         = @options[:azure_container] || @azure_credentials[:azure_container]
          @account_name      = @azure_credentials[:account_name]
          @azure_host_alias  = @options[:azure_host_alias]
          @url               = ":azure_host_alias"

          WAZ::Storage::Base.establish_connection!(
            account_name: @azure_credentials[:account_name],
              access_key:   @azure_credentials[:access_key])
        end

        Paperclip.interpolates(:azure_path_url) do |attachment, style|
          "https://#{attachment.account_name}.blob.core.windows.net/#{attachment.container_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
        end
        Paperclip.interpolates(:azure_domain_url) do |attachment, style|
          "https://#{attachment.account_name}.blob.core.windows.net/#{attachment.container_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
        end
      end

      def custom_url(style = default_style, ssl = false)
        ssl ? self.url(style).gsub('http', 'https') : self.url(style)
      end

      def account_name
        @account_name
      end

      def container_name
        @container
      end

      def azure_host_alias
        @azure_host_alias
      end

      def parse_credentials creds
        @azure_credentials
      end

      def exists?(style = default_style)
        # Note: コピペ元ではWAZ::Blobs::Container.find(container_name)[path(style)] を利用しているが、これは毎回リクエストを発行してしまうのであまり賢くないのでは？と思う
        # 一方original_filenameだけで判断すると実際にはファイルのアップロードが失敗しているのにtrueを返してしまう可能性がありそう
        # この部分は要改善っぽい
        !!original_filename
      end

      # Returns representation of the data of the file assigned to the given
      # style, in the format most representative of the current storage.
      def to_file style = default_style
        return @queued_for_write[style] if @queued_for_write[style]
        if exists?(style)
          file = Tempfile.new(path(style))
          file.write(WAZ::Blobs::Container.find(container_name)[path(style)].value)
          file.rewind
          file
        end
        return file
      end

      def flush_writes #:nodoc:
        @queued_for_write.each do |style, file|
          begin
            log("saving to Azure #{path(style)}")
            WAZ::Blobs::Container.find(container_name).store(path(style), file.read, instance_read(:content_type), {:x_ms_blob_cache_control=>"max-age=315360000, public"})
          rescue
            log("error saving to Azure #{path(style)}")
            ## If container doesn't exist we create it
            if WAZ::Blobs::Container.find(container_name).blank?
              WAZ::Blobs::Container.create(container_name).public_access = "blob"
              log("retryng saving to Azure #{path(style)}")
              retry
            end
          end
        end
        @queued_for_write = {}
      end

      def flush_deletes #:nodoc:
        @queued_for_delete.each do |path|
          begin
            log("deleting #{path}")
            WAZ::Blobs::Container.find(container_name)[path].destroy!
          rescue
            log("error deleting #{path}")
          end
        end
        @queued_for_delete = []
      end
    end
  end
end
```

##### 3. application.rbに以下を追加して、paperclipのストレージとしてazureストレージを利用する

```rb
    config.paperclip_defaults = {
        storage: :azure_storage,
        path: ":class/:id/:attachment/:style/:filename",
        azure_credentials: {
          account_name: Rails.application.secrets.azure_credentials['account_name'],
          access_key: Rails.application.secrets.azure_credentials['access_key']
        },
        azure_container: "YOUR_STORAGE",
        url: ":azure_domain_url",
    }
```

##### 4. secrets.ymlに以下を追加、azure blobサービスのアカウント名、アカウントキーを設定する

```yaml
  azure_credentials:
    account_name: your_account
    access_key: "your accesskey"
```

これでひとまず、blobサービスへの画像のアップロード、取り出しは一通り動くようになった。ただし、アップロード失敗してblobサービスに画像がないときも, image.url がそれっぽいurlを返してくるので、その辺り治してくれる人をお待ちしている