Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/wantedly/computed_model
Batch loader with dependency resolution and computed fields
https://github.com/wantedly/computed_model
activerecord batch-loader batch-loading microservices ruby
Last synced: about 1 month ago
JSON representation
Batch loader with dependency resolution and computed fields
- Host: GitHub
- URL: https://github.com/wantedly/computed_model
- Owner: wantedly
- Created: 2020-03-16T02:48:24.000Z (almost 5 years ago)
- Default Branch: master
- Last Pushed: 2022-12-26T20:01:10.000Z (almost 2 years ago)
- Last Synced: 2024-10-31T14:12:45.424Z (about 2 months ago)
- Topics: activerecord, batch-loader, batch-loading, microservices, ruby
- Language: Ruby
- Homepage:
- Size: 149 KB
- Stars: 25
- Watchers: 34
- Forks: 0
- Open Issues: 4
-
Metadata Files:
- Readme: README.ja.md
- Changelog: CHANGELOG.md
Awesome Lists containing this project
README
# ComputedModel
ComputedModelは依存解決アルゴリズムを備えた普遍的なバッチローダーです。
- 依存解決アルゴリズムの恩恵により、抽象化を損なわずに以下の3つを両立させることができます。
- ActiveRecord等から読み込んだデータを加工して提供する。
- ActiveRecord等からのデータの読み込みを一括で行うことでN+1問題を防ぐ。
- 必要なデータだけを読み込む。
- 複数のデータソースからの読み込みにも対応。
- データソースに依存しない普遍的な設計。HTTPで取得した情報とActiveRecordから取得した情報の両方を使う、といったこともできます。[English version](README.md)
## 解決したい問題
モデルが複雑化してくると、単にデータベースから取得した値を返すだけではなく、加工した値を返したくなることがあります。
```ruby
class User < ApplicationRecord
has_one :preference
has_one :profiledef display_name
"#{preference.title} #{profile.name}"
end
end
```ところがこれをそのまま使うと N+1 問題が発生することがあります。
```ruby
# N+1 問題!
User.where(id: friend_ids).map(&:display_name)
```N+1問題を解決するには、 `#display_name` が何に依存していたかを調べ、それをpreloadしておく必要があります。
```ruby
User.where(id: friend_ids).preload(:preference, :profile).map(&:display_name)
# ^^^^^^^^^^^^^^^^^^^^^ display_name の抽象化が漏れてしまう
```これではせっかく `#display_name` を抽象化した意味が半減してしまいます。
ComputedModelは依存解決アルゴリズムをバッチローダーに接続することでこの問題を解消します。
```ruby
class User
define_primary_loader :raw_user do ... end
define_loader :preference do ... end
define_loader :profile do ... enddependency :preference, :profile
computed def display_name
"#{preference.title} #{profile.name}"
end
end
```## インストール
Gemfileに以下の行を追加:
```ruby
gem 'computed_model', '~> 0.3.0'
```その後、以下を実行:
$ bundle
または直接インストール:
$ gem install computed_model
## 動かせるサンプルコード
```ruby
require 'computed_model'# この2つを外部から取得したデータとみなす (ActiveRecordやHTTPで取得したリソース)
RawUser = Struct.new(:id, :name, :title)
Preference = Struct.new(:user_id, :name_public)class User
include ComputedModel::Modelattr_reader :id
def initialize(raw_user)
@id = raw_user.id
@raw_user = raw_user
enddef self.list(ids, with:)
bulk_load_and_compute(Array(with), ids: ids)
enddefine_primary_loader :raw_user do |_subfields, ids:, **|
# ActiveRecordの場合:
# raw_users = RawUser.where(id: ids).to_a
raw_users = [
RawUser.new(1, "Tanaka Taro", "Mr. "),
RawUser.new(2, "Yamada Hanako", "Dr. "),
].filter { |u| ids.include?(u.id) }
raw_users.map { |u| User.new(u) }
enddefine_loader :preference, key: -> { id } do |user_ids, _subfields, **|
# ActiveRecordの場合:
# Preference.where(user_id: user_ids).index_by(&:user_id)
{
1 => Preference.new(1, true),
2 => Preference.new(2, false),
}.filter { |k, _v| user_ids.include?(k) }
enddelegate_dependency :name, to: :raw_user
delegate_dependency :title, to: :raw_user
delegate_dependency :name_public, to: :preferencedependency :name, :name_public
computed def public_name
name_public ? name : "Anonymous"
enddependency :public_name, :title
computed def public_name_with_title
"#{title}#{public_name}"
end
end# あらかじめ要求したフィールドにだけアクセス可能
users = User.list([1, 2], with: [:public_name_with_title])
users.map(&:public_name_with_title) # => ["Mr. Tanaka Taro", "Dr. Anonymous"]
users.map(&:public_name) # => error (ForbiddenDependency)users = User.list([1, 2], with: [:public_name_with_title, :public_name])
users.map(&:public_name_with_title) # => ["Mr. Tanaka Taro", "Dr. Anonymous"]
users.map(&:public_name) # => ["Tanaka Taro", "Anonymous"]# 次のような場合は preference は読み込まれない。
users = User.list([1, 2], with: [:title])
users.map(&:title) # => ["Mr. ", "Dr. "]
```## 次に読むもの
- [基本概念と機能](CONCEPTS.ja.md)
## License
This library is distributed under MIT license.
Copyright (c) 2020 Masaki Hara
Copyright (c) 2020 Masayuki Izumi
Copyright (c) 2020 Wantedly, Inc.
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/wantedly/computed_model.