6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ROM(Ruby Object Mapper)4.0でcross-database associationを設定する

Posted at

概要

ROM(Ruby Object Mapper)というORM(Object Relational Mapper)があります。
Railsだとデフォルトで ActiveRecord が組み込まれているので基本的に使うことはないと思いますが、HanamiではROMをラップした Hanami::Model がデフォルトのORMになっています。
ROM自体はデータソースがRDB以外でも使うことのできる汎用的なORMで、単なるHashやArrayでも扱うことができます。

今回はROM 4.0のassociationでcross-database associationを設定する書き方をメモっておきます。

想定用途

  • 複数のデータベースを運用しており、データベース間でassociationを設定したいとき
    • アカウント情報を保存するDBと、個別のデータを保存するDBが別々の場合等

例えば以下のスキーマを定義して users テーブルと posts テーブルを別々のDBで管理していた場合、SQLでJOINすることができません。

schema.sql
CREATE DATABASE `db1`;
USE `db1`;
CREATE TABLE `users`(
  `id` INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  `uuid` VARCHAR(36) NOT NULL,
  `name` VARCHAR(20) NOT NULL
);

CREATE DATABASE `db2`;
USE `db2`;
CREATE TABLE `posts`(
  `id` INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  `user_uuid` VARCHAR(36) UNSIGNED NOT NULL,
  `title` VARCHAR(20) NOT NULL
);

この場合ActiveRecordではassociationを設定できず、 UserPost のインスタンスを別々に作成するしかありません。
一方ROMのassociationでは、別々のDBからのデータを内部で結合することができ、JOINした場合と同じ結果を得ることができます。

# ActiveRecordでの例
# SwitchPoint等のgemを使って複数DBへの接続を管理している前提です
user = User.find_by(id: 1) #=> #<User id=1 uuid="uuid" name="name">
post = Post.find_by(user_uuid: user.uuid) #=> #<Post id=1 user_uuid="uuid" title="title">

ROMで書く

データベース接続の設定

初期設定としてデータベースの接続先を指定します。
ROMでは接続情報を内包した Container を作成し、これを Repository のコンストラクタに渡すことでRepositoryに接続先を教えます。

ROM::Configuration のコンストラクタには、接続先が一つの場合はそのURLを、複数の場合は { db_alias: 'db_url' } の形式のHashを渡します。

container.rb
container = ROM.container(
  ROM::Configuration.new(
    default: 'mysql2://root@localhost/db1',
    db2: 'mysql2://root@localhost/db2'
  )
)

Relation, Repositoryの定義

ROMを使うにあたって、Relation(RDBでいうテーブル定義)とRepository(データソースとやり取りするためのロジックをまとめた層)を定義する必要があります。

relations.rb
class Users < ROM::Relation[:sql]
  schema(infer: true) do
    # :view, :override, :combine_keys を指定するのがポイントです
    has_many :posts, foreign_key: :uuid, view: :for_user, override: true, combine_keys: { uuid: :user_uuid }
  end
end

class Posts < ROM::Relation[:sql]
  gateway :db2 # gateway :alias の書式でどの接続先を使うか指定します。省略すると :default につながります
  
  schema(infer: true)

  def for_user(_assoc, user) # データを結合するときの条件をviewとして指定します
    where(user_uuid: user.pluck(:uuid))
  end
end
user_repo.rb
class UserRepo < ROM::Repository[:users]
  def find_with_posts_by(uuid:)
    users.combine(:posts).where(uuid: uuid).one # usersメソッドで Relation のインスタンスが取れます
  end
end

使う

定義は済んでいるので、あとは使うだけです

user_repo = UserRepo.new(container)
user = user_repo.find_with_posts_by(uuid: 'uuid')
#=> #<ROM::Struct::User id=1 uuid="uuid" name="name" posts=[#<ROM::Struct::Post id=1 user_uuid="uuid" title="title">]>

という感じで、複数DBにまたがるテーブル間でもちゃんとassociationを定義した状態でレコードを取得することができました。

6
4
0

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
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?