はじめに
ActiveRecordっぽくFirestoreを操作できるgemを作りました。
概要
こんなモデルを定義します。
class Book
include ActAsFireRecordBeta
# 属性の名前と型を定義
firestore_attribute :title, :string
firestore_attribute :published_on, :date
firestore_attribute :page, :integer
# 必要に応じてバリデーションも追加
validates :title, presence: true
end
CRUDする例です。
# Create
book = Book.new(title: 'An Awesome Book', published_on: '2022-12-01'.to_date, page: 200)
book.save
bool.id #=> IdG0ZWT0I5DucEQuRgoP
# Read
id = 'IdG0ZWT0I5DucEQuRgoP'
book = Book.find(id)
# Update
book.update(page: 210)
# Delete
book.destroy
データを検索する例です。
book = Book.find_by(title: 'An Awesome Book')
books = Book.all
books = Book.order(:title)
books = Book.order(:title, :desc)
books = Book.where(:page, :>=, 200)
books = Book.where(:page, :>=, 200).order(:page)
実際にデータの登録や検索を実行しているのは google-cloud-firestore というgemです。
order(:title, :desc)
やwhere(:page, :>=, 200)
みたいな書き方はActiveRecordと少し異なりますが、これはgoogle-cloud-firestoreのAPIをそのまま利用しているためです。
また、FirestoreはいわゆるNoSQLであって、RDBMSではありません。そのため、検索にも独特の制約があります。Firestoreが持っている制約はこのgemを使った場合にもそのまま当てはまります。
# 検索の制約に引っかかってエラーが発生する例
Book.where(:page, :>=, 200).order(:title).first
#=> GRPC::InvalidArgument: 3:inequality filter property and first sort order must be the same: page and title.
APIについてもっと詳しく
ドキュメントをまだちゃんと整備していないので、このgemが提供しているAPIを知りたい場合は以下のテストコードをチェックしてみてください。
とはいえ、このgemが実装しているActiveRecord風のAPIは、オリジナルのActiveRecordのごく一部です。。
このgemを作った動機
HerokuのPostgreSQLが有料化されたので、個人開発していたRailsアプリの運用コストを下げるためにFirestoreを使おうと思いました。そのあたりの思考過程は以下のブログにまとめています。
Firestoreなら1日当たりのオペレーションが以下の上限を超えなければ無料で使えます(2022年12月現在の料金設定)。
- 読み取り = 5万件
- 書き込み = 2万件
- 削除 = 2万件
個人で細々と運用しているRailsアプリであれば無料枠で十分まかなえるはずです。
Firestoreを操作するには前述のgoogle-cloud-firestore gemを使えばいいのですが、記述スタイルがActiveRecordとかなり異なります。
# 引用元 https://github.com/googleapis/google-cloud-ruby/blob/main/google-cloud-firestore/README.md
city = firestore.col("cities").doc("SF")
city.set({ name: "San Francisco",
state: "CA",
country: "USA",
capital: false,
population: 860000 })
firestore.transaction do |tx|
new_population = tx.get(city).data[:population] + 1
tx.update(city, { population: new_population })
end
そこでコードの修正量を必要最小限にするため、なるべくActiveRecordのインターフェースに近づけた形でFirestoreを操作できるようにしたいと思ってこのgemを作りました。
セットアップ
このgemはRailsアプリケーションでの使用を前提としています。
Gemfileに以下の行を追加してbundle install
してください。
gem "act_as_fire_record_beta"
Firebase Emulatorを使う場合 (development or test)
- Firebase Emulatorをインストールします。
-
firebase init
でプロジェクト用のFirestoreをセットアップします。 -
このWikiページを参考にして
.gitignore
の設定を追加します。 - 環境変数
FIRESTORE_EMULATOR_HOST
にEmulatorのhost情報を設定します。(export FIRESTORE_EMULATOR_HOST=127.0.0.1:8080
など) -
firebase emulator:start
でEmulatorを起動し、それからRailsを起動します。-
firebase emulators:start --import=./firebase-local --export-on-exit
のように--import
オプションと--export-on-exit
オプションを付けると、作成したデータをローカルに保存できます。 - データを保存するディレクトリは
.gitignore
に追加しておきましょう。
-
-
firebase emulators:exec 'bundle exec rspec' --only firestore
のようなコマンドを入力すると、Emulatorの起動、テストの実行、Emulatorの終了が一度に行えます。
本物のFirestoreを使う場合
- Firebaseプロジェクトを作成し、 プロジェクト内にFirestoreを追加します。
- 「https://console.cloud.google.com/ > 左サイドメニュー > APIとサービス > 認証情報 > サービスアカウント > 名前=App Engine default service account のアカウントの鉛筆マークをクリック > キータブ > 鍵を追加 > 新しい鍵を作成 > JSON形式」でCredentialをダウンロードします。
- CredentialをRailsプロジェクト内の任意のディレクトリに配置します。
- CredentialがGitHubにpushされないよう、必ず
.gitignore
にCredentialのパスを追加してください。
- CredentialがGitHubにpushされないよう、必ず
- (ローカルで実行する場合)環境変数
GOOGLE_APPLICATION_CREDENTIALS
にCredentialへのパスを追加します。(export GOOGLE_APPLICATION_CREDENTIALS=path/to/your-key.json
など) - (サーバーで実行する場合)環境変数
FIRESTORE_CREDENTIALS
にCredentialの内容(JSON文字列)を書き込みます。 -
config/initializers/firestore.rb
にproject_id
を設定します。
require "google/cloud/firestore"
Google::Cloud::Firestore.configure do |config|
config.project_id = "your-awesome-project"
end
- 作成したデータ(ドキュメント)は「(モデル名)-(実行環境)」という名前のコレクションに保存されます(例:
books-development
、blog_comments-production
)
実際の利用例
妻のパン屋のホームページで表示している"INFORMATION"や"Instagram"の情報はFirestoreに保存したデータを取得しています。
制限事項等
- このgemはあくまでActiveRecord風のAPIを提供しているだけであり、ActiveRecordと完全な互換性があるわけではありません。そのため、ActiveStorageやDevise、Kaminari、Ransackなど、ActiveRecordの使用を前提としたgemやフレームワークをFirestoreに置き換えることはできません。
- FirestoreはいわゆるNoSQLであるため、RDBMSとは根本的に異なるデータ設計を考える必要があります。このgemを使ってActiveRecord風のAPIを手に入れたとしても、根本的なデータ設計についてはNoSQLのベストプラクティスに従う必要があります。
- gem名に"Beta"を入れているように、まだ出来たてホヤホヤのgemです。足りない機能はたくさんありますし、今後APIがガラッと変わる可能性もあるので注意してください。バージョンごとの変更点はCHANGELOG.mdにまとめてあります。
まとめ
というわけで、本記事ではActAsFireRecordBetaというActiveRecordっぽくFirestoreを操作できるgemを紹介しました。
本格的なRailsアプリを作る場合は全然役不足だと思いますが、「たったこれだけしかデータを保存しないのに、毎月5ドル(約670円)を毎月Herokuに払うのもな〜」という規模であればこのgemがうまくフィットするかもしれません。興味がある方は使ってみてください!