{"id":20434400,"url":"https://github.com/wantedly/computed_model","last_synced_at":"2026-03-10T13:05:11.446Z","repository":{"id":42440771,"uuid":"247596158","full_name":"wantedly/computed_model","owner":"wantedly","description":"Batch loader with dependency resolution and computed fields","archived":false,"fork":false,"pushed_at":"2022-12-26T20:01:10.000Z","size":153,"stargazers_count":26,"open_issues_count":4,"forks_count":0,"subscribers_count":34,"default_branch":"master","last_synced_at":"2026-01-31T15:50:20.333Z","etag":null,"topics":["activerecord","batch-loader","batch-loading","microservices","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wantedly.png","metadata":{"files":{"readme":"README.ja.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-03-16T02:48:24.000Z","updated_at":"2025-08-07T12:43:54.000Z","dependencies_parsed_at":"2023-01-31T01:01:06.780Z","dependency_job_id":null,"html_url":"https://github.com/wantedly/computed_model","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/wantedly/computed_model","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wantedly%2Fcomputed_model","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wantedly%2Fcomputed_model/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wantedly%2Fcomputed_model/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wantedly%2Fcomputed_model/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wantedly","download_url":"https://codeload.github.com/wantedly/computed_model/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wantedly%2Fcomputed_model/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30334412,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T12:41:07.687Z","status":"ssl_error","status_checked_at":"2026-03-10T12:41:06.728Z","response_time":106,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","batch-loader","batch-loading","microservices","ruby"],"created_at":"2024-11-15T08:26:29.413Z","updated_at":"2026-03-10T13:05:11.401Z","avatar_url":"https://github.com/wantedly.png","language":"Ruby","readme":"# ComputedModel\n\nComputedModelは依存解決アルゴリズムを備えた普遍的なバッチローダーです。\n\n- 依存解決アルゴリズムの恩恵により、抽象化を損なわずに以下の3つを両立させることができます。\n  - ActiveRecord等から読み込んだデータを加工して提供する。\n  - ActiveRecord等からのデータの読み込みを一括で行うことでN+1問題を防ぐ。\n  - 必要なデータだけを読み込む。\n- 複数のデータソースからの読み込みにも対応。\n- データソースに依存しない普遍的な設計。HTTPで取得した情報とActiveRecordから取得した情報の両方を使う、といったこともできます。\n\n[English version](README.md)\n\n## 解決したい問題\n\nモデルが複雑化してくると、単にデータベースから取得した値を返すだけではなく、加工した値を返したくなることがあります。\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_one :preference\n  has_one :profile\n\n  def display_name\n    \"#{preference.title} #{profile.name}\"\n  end\nend\n```\n\nところがこれをそのまま使うと N+1 問題が発生することがあります。\n\n```ruby\n# N+1 問題!\nUser.where(id: friend_ids).map(\u0026:display_name)\n```\n\nN+1問題を解決するには、 `#display_name` が何に依存していたかを調べ、それをpreloadしておく必要があります。\n\n```ruby\nUser.where(id: friend_ids).preload(:preference, :profile).map(\u0026:display_name)\n#                                  ^^^^^^^^^^^^^^^^^^^^^ display_name の抽象化が漏れてしまう\n```\n\nこれではせっかく `#display_name` を抽象化した意味が半減してしまいます。\n\nComputedModelは依存解決アルゴリズムをバッチローダーに接続することでこの問題を解消します。\n\n```ruby\nclass User\n  define_primary_loader :raw_user do ... end\n  define_loader :preference do ... end\n  define_loader :profile do ... end\n\n  dependency :preference, :profile\n  computed def display_name\n    \"#{preference.title} #{profile.name}\"\n  end\nend\n```\n\n## インストール\n\nGemfileに以下の行を追加:\n\n```ruby\ngem 'computed_model', '~\u003e 0.3.0'\n```\n\nその後、以下を実行:\n\n    $ bundle\n\nまたは直接インストール:\n\n    $ gem install computed_model\n\n## 動かせるサンプルコード\n\n```ruby\nrequire 'computed_model'\n\n# この2つを外部から取得したデータとみなす (ActiveRecordやHTTPで取得したリソース)\nRawUser = Struct.new(:id, :name, :title)\nPreference = Struct.new(:user_id, :name_public)\n\nclass User\n  include ComputedModel::Model\n\n  attr_reader :id\n  def initialize(raw_user)\n    @id = raw_user.id\n    @raw_user = raw_user\n  end\n\n  def self.list(ids, with:)\n    bulk_load_and_compute(Array(with), ids: ids)\n  end\n\n  define_primary_loader :raw_user do |_subfields, ids:, **|\n    # ActiveRecordの場合:\n    # raw_users = RawUser.where(id: ids).to_a\n    raw_users = [\n      RawUser.new(1, \"Tanaka Taro\", \"Mr. \"),\n      RawUser.new(2, \"Yamada Hanako\", \"Dr. \"),\n    ].filter { |u| ids.include?(u.id) }\n    raw_users.map { |u| User.new(u) }\n  end\n\n  define_loader :preference, key: -\u003e { id } do |user_ids, _subfields, **|\n    # ActiveRecordの場合:\n    # Preference.where(user_id: user_ids).index_by(\u0026:user_id)\n    {\n      1 =\u003e Preference.new(1, true),\n      2 =\u003e Preference.new(2, false),\n    }.filter { |k, _v| user_ids.include?(k) }\n  end\n\n  delegate_dependency :name, to: :raw_user\n  delegate_dependency :title, to: :raw_user\n  delegate_dependency :name_public, to: :preference\n\n  dependency :name, :name_public\n  computed def public_name\n    name_public ? name : \"Anonymous\"\n  end\n\n  dependency :public_name, :title\n  computed def public_name_with_title\n    \"#{title}#{public_name}\"\n  end\nend\n\n# あらかじめ要求したフィールドにだけアクセス可能\nusers = User.list([1, 2], with: [:public_name_with_title])\nusers.map(\u0026:public_name_with_title) # =\u003e [\"Mr. Tanaka Taro\", \"Dr. Anonymous\"]\nusers.map(\u0026:public_name) # =\u003e error (ForbiddenDependency)\n\nusers = User.list([1, 2], with: [:public_name_with_title, :public_name])\nusers.map(\u0026:public_name_with_title) # =\u003e [\"Mr. Tanaka Taro\", \"Dr. Anonymous\"]\nusers.map(\u0026:public_name) # =\u003e [\"Tanaka Taro\", \"Anonymous\"]\n\n# 次のような場合は preference は読み込まれない。\nusers = User.list([1, 2], with: [:title])\nusers.map(\u0026:title) # =\u003e [\"Mr. \", \"Dr. \"]\n```\n\n## 次に読むもの\n\n- [基本概念と機能](CONCEPTS.ja.md)\n\n## License\n\nThis library is distributed under MIT license.\n\nCopyright (c) 2020 Masaki Hara\n\nCopyright (c) 2020 Masayuki Izumi\n\nCopyright (c) 2020 Wantedly, Inc.\n\n## Development\n\nAfter 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.\n\nTo 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).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/wantedly/computed_model.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwantedly%2Fcomputed_model","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwantedly%2Fcomputed_model","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwantedly%2Fcomputed_model/lists"}