LoginSignup
19

posted at

updated at

ActiveRecordっぽくFirestoreを操作できるgemを作りました(ActAsFireRecordBeta)

はじめに

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してください。

Gemfile
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を追加します。

Screen Shot 2022-12-28 at 15.21.10.png

  • https://console.cloud.google.com/ > 左サイドメニュー > APIとサービス > 認証情報 > サービスアカウント > 名前=App Engine default service account のアカウントの鉛筆マークをクリック > キータブ > 鍵を追加 > 新しい鍵を作成 > JSON形式」でCredentialをダウンロードします。

key.png

  • CredentialをRailsプロジェクト内の任意のディレクトリに配置します。
    • CredentialがGitHubにpushされないよう、必ず.gitignoreにCredentialのパスを追加してください。
  • (ローカルで実行する場合)環境変数GOOGLE_APPLICATION_CREDENTIALSにCredentialへのパスを追加します。(export GOOGLE_APPLICATION_CREDENTIALS=path/to/your-key.jsonなど)
  • (サーバーで実行する場合)環境変数FIRESTORE_CREDENTIALSにCredentialの内容(JSON文字列)を書き込みます。
  • config/initializers/firestore.rbproject_idを設定します。
require "google/cloud/firestore"

Google::Cloud::Firestore.configure do |config|
  config.project_id = "your-awesome-project"
end
  • 作成したデータ(ドキュメント)は「(モデル名)-(実行環境)」という名前のコレクションに保存されます(例:books-developmentblog_comments-production

sample.png

実際の利用例

妻のパン屋のホームページで表示している"INFORMATION"や"Instagram"の情報はFirestoreに保存したデータを取得しています。

Screen Shot 2022-12-28 at 11.01.24.png

制限事項等

  • この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がうまくフィットするかもしれません。興味がある方は使ってみてください!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
19