GCSのsigned policy documentsでフォルダごとアップロードする

webアプリケーションでファイルアップロード機能をよく提供すると思います。 ほんの数ファイルのアップロードであれば、署名付きURLを使うことが多いですが、フォルダのようにファイル数が読めないような場合は署名付きポリシードキュメントを使うと便利です。 署名付きURLの特徴 ここではフォルダアップロードする際に困る点だけ説明します。 その他の特徴については公式ドキュメント1を参照してください。 署名付きURLは主に次のようなフォーマットになっており、パスにオブジェクト名まで含まれています。 https://storage.googleapis.com/example-bucket/cat.jpeg?X-Goog-Signature=... これはつまり、アップロードできるオブジェクト名が1つに限定されてしまうということです。 複数ファイルをアップロードしたい場合、ファイルごとに発行する必要があり、計算量はO(n)となってしまいます。 署名付きポリシードキュメントの特徴 前述の計算量がO(n)がネックになるケースでは、署名付きポリシードキュメントを使用することで解決できます。 https://cloud.google.com/storage/docs/authentication/signatures?hl=ja#policy-document ポリシードキュメントには次のような特徴があります。 アップロード先のバケットを指定できる ファイル名のプレフィックスを限定できる 特定のフォルダ以下に対しての操作を許可できる それ以外のフォルダにはアップロードできない Content-TypeやContent-Lengthなどの条件を指定できる POSTのみ可能でGETやDELETEは不可 特にフォルダ配下に対してアップロードする権限を付与できるので、フォルダ内のファイルを全てアップロードするようなケースでは、署名の発行の計算量がO(1)となり、大幅に効率化できます。 実装してみる apiにrails、フロントエンドにreactを使用したアプリケーションで、GCSの署名付きポリシードキュメントを発行する例を示します。 まずapiは、google-cloud-storage gemを使用して、署名付きポリシードキュメントを発行し、返します。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 require 'bundler/inline' gemfile do source 'https://rubygems.org' gem "google-cloud-storage" end class Storage # @param prefix [String] The prefix for the objects to be uploaded # @param max_file_size [Integer] The maximum allowed file size for uploads # @return [Hash] A hash containing the URL and form fields for the signed policy document def self.generate_policy_document_for_prefix(prefix:, max_file_size: 100 * 1024 * 1024) post_object = bucket.generate_signed_post_policy_v4( "#{prefix}${filename}", expires: 3600, conditions: [ [ "starts-with", "$key", prefix ], [ "content-length-range", 0, max_file_size ] ] ) { url: post_object.url, fields: post_object.fields } end private def self.bucket @bucket ||= storage_client.bucket({bucket_name}) end def self.storage_client @storage_client ||= begin Google::Cloud::Storage.new( credentials: load_service_account_key ) end end def self.load_service_account_key keyfile_path = Rails.root.join({service_account.json}).to_s JSON.parse(File.read(keyfile_path)) end end 次にフロントエンドで、発行されたポリシードキュメントを使用して、フォルダ内のファイルをアップロードします。 ...

2025-11-15 · 5 min · ksaito

carrierwave with fog-googleで静的ファイルを扱ってみる

carrierwaveとfog-googleでGCSに静的ファイルをアップロードしたり、ローカルと本番でどう切り替えていくかについて、試してみたことを書き連ねていきます。 ※特に目新しいことはしておらず、既に出回っている記事以上のことは書いていないと思います ローカルストレージでcarrierwaveを試してみる carrierwaveのREADME の手順に沿って、環境構築していきます。 uploaderクラスをこれで生成します。 1 bin/rails g uploader Avatar Userテーブルにnameとavatarを保存するカラムを用意します。 1 2 bin/rails g model User name:string avatar:string bin/rails db:migrate 次に公式に従って以下を定義します。 1 2 3 class User < ApplicationRecord mount_uploader :avatar, AvatarUploader end ここまで公式の手順に従ってやってきました。 これでuserテーブルにavatarのパスを保存できる準備が整います。 rails consoleで試してみます。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 bin/rails c u = User.new u.name = 'tanaka' file = File.open('/Users/tanaka/Desktop/hoge.png') u.avatar = file u.save! u.avatar => #<AvatarUploader:0x00000001105974d0 @cache_id=nil, @cache_storage=#<CarrierWave::Storage::File:0x00000001106db170 @cache_called=nil, @uploader=#<AvatarUploader:0x00000001105974d0 ...>>, @deduplication_index=nil, @file= #<CarrierWave::SanitizedFile:0x00000001102dadc8 @content=nil, @content_type=nil, @declared_content_type=nil, @file="/Users/tanaka/work/project/examples/carrierwave-cdn-gcs-signedurl/public/uploads/user/avatar/1/hoge.png", @original_filename=nil>, @filename="hoge.png", @identifier="hoge.png", @model=#<User:0x000000010f1af698 id: 1, name: "tanaka", avatar: "hoge.png", created_at: Sun, 26 Nov 2023 03:16:54.005319000 UTC +00:00, updated_at: Sun, 26 Nov 2023 03:16:54.005319000 UTC +00:00>, @mounted_as=:avatar, @original_filename=nil, @staged=false, @storage=#<CarrierWave::Storage::File:0x0000000110678688 @cache_called=nil, @uploader=#<AvatarUploader:0x00000001105974d0 ...>>, @versions={}> u.avatar.url => "/uploads/user/avatar/1/hoge.png" u.avatar.curent_path => "/Users/tanaka/work/project/examples/carrierwave-cdn-gcs-signedurl/public/uploads/user/avatar/1/hoge.png" こんな感じでavatar.{url,current_path}でpathを引っ張ってくることができます。 ...

2023-11-27 · 2 min · ksaito

Railsのmigrationとschema_versionsの関係について

Railsのmigrationのバージョン管理がどうやって行われていて、エラーが発生したときにどう対処すればいいか、調べてみたのでまとめていきます。 すでにRailsに関する記事はweb上に山程あって、この記事に目新しい内容は書かれないですが個人の学習記録として残してます。 新しい技術をちゃんとキャッチアップするには、アウトプットが重要なので。 環境 1 2 3 ruby 3.0.6 rails 7.0.6 postgres 15 バージョン管理について railsのmigrationファイルは、/app/db/migrate/に<日時create_model.rb>のような形式でファイルが作られます。 bin/rails db:migrateを実行したときに、どこまでマイグレーションが実行されているかの管理をデータベースのschema_migrationsのほうで記録して、管理しています。 ですので、schema_migrationsに記録されているバージョンと db/migrate/ に存在するmigrationファイルの整合性が合わなくなると、マイグレーションコマンドを実行するときに予期せぬエラーが発生します。 試してみる 以下のコマンドでモデル(必要ないけど)とマイグレーションファイルを作ります。 1 bin/rails g model user そうすると、 20230806103311_create_users.rbが生成されます。 この状態で実行すると、dbのschema_versionsに以下のようにバージョンが記録されます。 1 2 3 version ---------------- 20230806103311 この状態でマイグレーションファイルを削除するなりして、 bin/rails db:rollbackを実行すると、 1 2 3 4 5 6 7 rails aborted! ActiveRecord::UnknownMigrationVersionError: No migration with version number 20230806103311. Tasks: TOP => db:rollback (See full trace by running task with --trace) マイグレーションバージョンに記録されたファイルがありませんとエラーを吐きます。 ...

2023-08-07 · 2 min · ksaito