computed_model 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +18 -0
- data/.github/workflows/test.yml +24 -0
- data/.yardopts +6 -0
- data/CHANGELOG.md +126 -0
- data/CONCEPTS.ja.md +324 -0
- data/CONCEPTS.md +330 -0
- data/Migration-0.3.md +343 -0
- data/README.ja.md +168 -0
- data/README.md +112 -70
- data/Rakefile +14 -0
- data/computed_model.gemspec +10 -2
- data/lib/computed_model.rb +78 -153
- data/lib/computed_model/dep_graph.rb +245 -0
- data/lib/computed_model/model.rb +447 -0
- data/lib/computed_model/plan.rb +48 -0
- data/lib/computed_model/version.rb +1 -1
- metadata +102 -7
- data/.travis.yml +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: abe05016ded2805bb131703f0ac56757d84b3d341bb79d041184703dd2d61d73
|
4
|
+
data.tar.gz: 81f95364fee17dd9aff3dc37fb1036c679ad59f5006f734cd4b0bcbe79b6a4f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e5cc071ca1645543721eb8ba0c50811930ac00f3ff542b9119b9919c328e3c22a8d127310cd3b8fca04d1ad32122b01a708a7b8210d5afe12d31f7580dbeb023
|
7
|
+
data.tar.gz: 431edf1ed4d6993b75fd67a8367f031e00eb210a57e9c40a87a28b393ecbc0fce54d3a364e80353a440687eabff65bbd7e213830d5b1bdbc7b4e669540f9eaef
|
@@ -0,0 +1,18 @@
|
|
1
|
+
version: 2
|
2
|
+
updates:
|
3
|
+
- package-ecosystem: bundler
|
4
|
+
directory: "/"
|
5
|
+
schedule:
|
6
|
+
interval: daily
|
7
|
+
time: "20:00"
|
8
|
+
open-pull-requests-limit: 10
|
9
|
+
reviewers:
|
10
|
+
- qnighy
|
11
|
+
- package-ecosystem: github-actions
|
12
|
+
directory: "/"
|
13
|
+
schedule:
|
14
|
+
interval: daily
|
15
|
+
time: "20:00"
|
16
|
+
open-pull-requests-limit: 10
|
17
|
+
reviewers:
|
18
|
+
- qnighy
|
@@ -0,0 +1,24 @@
|
|
1
|
+
name: test
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
test:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
|
9
|
+
strategy:
|
10
|
+
matrix:
|
11
|
+
ruby: ["2.5", "2.6", "2.7", "3.0"]
|
12
|
+
|
13
|
+
steps:
|
14
|
+
- uses: actions/checkout@v2.3.4
|
15
|
+
- uses: ruby/setup-ruby@v1
|
16
|
+
with:
|
17
|
+
ruby-version: ${{ matrix.ruby }}
|
18
|
+
- run: bundle install
|
19
|
+
- run: bundle exec rake
|
20
|
+
- name: Upload coverage
|
21
|
+
uses: codecov/codecov-action@v1.5.0
|
22
|
+
with:
|
23
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
24
|
+
files: coverage/coverage.lcov
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,131 @@
|
|
1
1
|
## Unreleased
|
2
2
|
|
3
|
+
## 0.3.0
|
4
|
+
|
5
|
+
computed_model 0.3 comes with a great number of improvements, and a bunch of breaking changes.
|
6
|
+
|
7
|
+
- Breaking changes
|
8
|
+
- `include ComputedModel` is now `include ComputedModel::Model`.
|
9
|
+
- Indirect dependencies are now rejected.
|
10
|
+
- `computed_model_error` was removed.
|
11
|
+
- `dependency` before `define_loader` will be consumed and ignored.
|
12
|
+
- `dependency` before `define_primary_loader` will be an error.
|
13
|
+
- Cyclic dependency is an error even if it is unused.
|
14
|
+
- `nil`, `true`, and `false` in subdeps will be filtered out before passed to a loader.
|
15
|
+
- `ComputedModel.normalized_dependencies` now returns `[true]` instead of `[]` as an empty value.
|
16
|
+
- `include_subdeps` is now `include_subfields`
|
17
|
+
- Notable behavioral changes
|
18
|
+
- The order in which fields are loaded is changed.
|
19
|
+
- `ComputedModel::Model` now uses `ActiveSupport::Concern`.
|
20
|
+
- Changed
|
21
|
+
- Separate `ComputedModel::Model` from `ComputedModel` https://github.com/wantedly/computed_model/pull/17
|
22
|
+
- Remove `computed_model_error` https://github.com/wantedly/computed_model/pull/18
|
23
|
+
- Improve behavior around dependency-field pairing https://github.com/wantedly/computed_model/pull/20
|
24
|
+
- Implement strict field access https://github.com/wantedly/computed_model/pull/23
|
25
|
+
- Preprocess graph with topological sorting https://github.com/wantedly/computed_model/pull/24
|
26
|
+
- Implement conditional dependencies and subdependency mapping/passthrough https://github.com/wantedly/computed_model/pull/25
|
27
|
+
- Use `ActiveSupport::Concern` https://github.com/wantedly/computed_model/pull/26
|
28
|
+
- Rename subdeps as subfields https://github.com/wantedly/computed_model/pull/31
|
29
|
+
- Added
|
30
|
+
- `ComputedModel::Model#verify_dependencies`
|
31
|
+
- Loader dependency https://github.com/wantedly/computed_model/pull/28
|
32
|
+
- Support computed model inheritance https://github.com/wantedly/computed_model/pull/29
|
33
|
+
- Refactored
|
34
|
+
- Extract `DepGraph` from `Model` https://github.com/wantedly/computed_model/pull/19
|
35
|
+
- Define loader as a singleton method https://github.com/wantedly/computed_model/pull/21
|
36
|
+
- Refactor `ComputedModel::Plan` https://github.com/wantedly/computed_model/pull/22
|
37
|
+
- Misc
|
38
|
+
- Collect coverage https://github.com/wantedly/computed_model/pull/12 https://github.com/wantedly/computed_model/pull/16
|
39
|
+
- Refactor tests https://github.com/wantedly/computed_model/pull/10 https://github.com/wantedly/computed_model/pull/15
|
40
|
+
- Add tests https://github.com/wantedly/computed_model/pull/27
|
41
|
+
- Add documentation https://github.com/wantedly/computed_model/pull/30
|
42
|
+
|
43
|
+
See [Migration-0.3.md](Migration-0.3.md) for migration.
|
44
|
+
|
45
|
+
### New feature: dynamic dependencies
|
46
|
+
|
47
|
+
Previously, subdeps are only useful for loaded fields and primary fields. Now computed fields can make use of subdeps!
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
|
51
|
+
class User
|
52
|
+
# Delegate subdeps
|
53
|
+
dependency(
|
54
|
+
blog_articles: -> (subdeps) { subdeps }
|
55
|
+
)
|
56
|
+
computed def filtered_blog_articles
|
57
|
+
if current_subfields.normalized[:image].any?
|
58
|
+
# ...
|
59
|
+
end
|
60
|
+
# ...
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
See [CONCEPTS.md](CONCEPTS.md) for more usages.
|
66
|
+
|
67
|
+
### New feature: loader dependency
|
68
|
+
|
69
|
+
You can specify dependency from a loaded field.
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
class User
|
73
|
+
dependency :raw_user # dependency of :raw_books
|
74
|
+
define_loader :raw_books, key: -> { id } do |subdeps, **|
|
75
|
+
# ...
|
76
|
+
end
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
### New feature: computed model inheritance
|
81
|
+
|
82
|
+
Now you can reuse computed model definitions via inheritance.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
module UserLikeConcern
|
86
|
+
extends ActiveSupport::Concern
|
87
|
+
include ComputedModel::Model
|
88
|
+
|
89
|
+
dependency :preference, :profile
|
90
|
+
computed def display_name
|
91
|
+
"#{preference.title} #{profile.name}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class User
|
96
|
+
include UserLikeConcern
|
97
|
+
|
98
|
+
define_loader :preference, key: -> { id } do ... end
|
99
|
+
define_loader :profile, key: -> { id } do ... end
|
100
|
+
end
|
101
|
+
|
102
|
+
class Admin
|
103
|
+
include UserLikeConcern
|
104
|
+
|
105
|
+
define_loader :preference, key: -> { id } do ... end
|
106
|
+
define_loader :profile, key: -> { id } do ... end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
## 0.2.2
|
111
|
+
|
112
|
+
- [#7](https://github.com/wantedly/computed_model/pull/7) Accept Hash as a `with` parameter
|
113
|
+
|
114
|
+
## 0.2.1
|
115
|
+
|
116
|
+
- Fix problem with `prefix` option in `delegate_dependency` not working
|
117
|
+
|
118
|
+
## 0.2.0
|
119
|
+
|
120
|
+
- **BREAKING CHANGE** Make define_loader more concise interface like GraphQL's DataLoader.
|
121
|
+
- Introduce primary loader through `#define_primary_loader`.
|
122
|
+
- **BREAKING CHANGE** Change `#bulk_load_and_compute` signature to support primary loader.
|
123
|
+
|
124
|
+
## 0.1.1
|
125
|
+
|
126
|
+
- Expand docs.
|
127
|
+
- Add `ComputedModel#computed_model_error` for load cancellation
|
128
|
+
|
3
129
|
## 0.1.0
|
4
130
|
|
5
131
|
Initial release.
|
data/CONCEPTS.ja.md
ADDED
@@ -0,0 +1,324 @@
|
|
1
|
+
# 基本概念と機能
|
2
|
+
|
3
|
+
[English version](CONCEPTS.md)
|
4
|
+
|
5
|
+
## ラッパークラス
|
6
|
+
|
7
|
+
ComputedModelは、ActiveRecordクラスなどに直接includeして使うことを(今のところ)想定していません。
|
8
|
+
その場合ラッパークラスを作成し、元のクラスのオブジェクトは主ローダー (後述) として定義するのがよいでしょう。
|
9
|
+
|
10
|
+
## フィールド
|
11
|
+
|
12
|
+
**フィールド**はComputedModelの管理下にある属性のことで、依存管理の基本単位です。以下の3種類のフィールドがあります。
|
13
|
+
|
14
|
+
- computed field (計算フィールド)
|
15
|
+
- loaded field (読み込みフィールド)
|
16
|
+
- primary field (主フィールド)
|
17
|
+
|
18
|
+
### computed field (計算フィールド)
|
19
|
+
|
20
|
+
別のフィールドの組み合わせで算出されるフィールドです。各レコードごとに独立に計算されます。
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
class User
|
24
|
+
dependency :preference, :profile
|
25
|
+
computed def display_name
|
26
|
+
"#{preference.title} #{profile.name}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
### loaded field (読み込みフィールド)
|
32
|
+
|
33
|
+
複数レコードに対してまとめて読み込むフィールドです。
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class User
|
37
|
+
define_loader :preference, key: -> { id } do |ids, _subfields, **|
|
38
|
+
Preference.where(user_id: ids).index_by(&:user_id)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
### primary field (主フィールド)
|
44
|
+
|
45
|
+
loaded fieldの機能に加えて、レコードの検索・列挙機能を担う特別なフィールドです。
|
46
|
+
|
47
|
+
たとえば `User` の場合、あるidのユーザーが存在するかどうかはどこかのデータソースに問い合わせる必要があるはずです。
|
48
|
+
それがたとえばActiveRecordの `RawUser` クラスである場合、主フィールドは以下のように定義されます。
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
class User
|
52
|
+
def initialize(raw_user)
|
53
|
+
@raw_user = raw_user
|
54
|
+
end
|
55
|
+
|
56
|
+
define_primary_loader :raw_user do |_subfields, ids:, **|
|
57
|
+
# User#initialize 内で @raw_user をセットする必要がある
|
58
|
+
RawUser.where(id: ids).map { |u| User.new(u) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
## 計算タイミング
|
64
|
+
|
65
|
+
ComputedModelの `bulk_load_and_compute` が呼ばれたタイミングで全ての必要なフィールドが計算されます。
|
66
|
+
|
67
|
+
遅延ロードは今のところサポートしていません。
|
68
|
+
|
69
|
+
## 依存関係
|
70
|
+
|
71
|
+
フィールドには依存関係を宣言することができます。
|
72
|
+
ただし、主フィールドは依存関係を持つことができません。 (他のフィールドから主フィールドに依存することはできます。)
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
class User
|
76
|
+
dependency :preference, :profile
|
77
|
+
computed def display_name
|
78
|
+
"#{preference.title} #{profile.name}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
`computed def` 内や `define_loader` のブロック内では、 `dependency` で宣言したフィールドのみ参照できます。
|
84
|
+
間接依存しているフィールドなど、たまたまロードされている場合でもアクセスはブロックされます。
|
85
|
+
|
86
|
+
## `bulk_load_and_compute`
|
87
|
+
|
88
|
+
ComputedModelの読み込みを行うメソッドが `bulk_load_and_compute` です。
|
89
|
+
`bulk_load_and_compute` をそのまま使うのではなく、各モデルでラッパー関数を実装することが推奨されます。
|
90
|
+
(これは後述するバッチロード引数の自由度が高く、そのままでは使い間違いが起きやすいからです)
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
class User
|
94
|
+
# with には [:display_name, :title] のようにフィールド名の配列を指定する
|
95
|
+
def self.list(ids, with:)
|
96
|
+
bulk_load_and_compute(with, ids: ids)
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.get(id, with:)
|
100
|
+
list([id], with: with).first
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.get!(id, with:)
|
104
|
+
get(id, with: with) || (raise User::NotFound)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
単独のレコードを読み込むための専用のメソッドはありません。こちらも `bulk_load_and_compute` のラッパーを実装することで実現してください。
|
110
|
+
もし単独のレコードであることを利用した最適化が必要な場合は、 `define_loader` や `define_primary_loader` で分岐を実装するとよいでしょう。
|
111
|
+
|
112
|
+
## 下位フィールドセレクタ
|
113
|
+
|
114
|
+
下位フィールドセレクタ (subfield selector) または 下位依存 (subdependency) は依存関係につけられる追加の情報です。
|
115
|
+
|
116
|
+
実装上はフィールドから依存先フィールドに任意のメッセージを送ることができる仕組みになっていますが、
|
117
|
+
名前の通りフィールドにぶら下がっている情報の取得に使うことを想定しています。
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
class User
|
121
|
+
define_loader :profile, key: -> { id } do |ids, subfields, **|
|
122
|
+
Profile.preload(subfields).where(user_id: ids).index_by(&:user_id)
|
123
|
+
end
|
124
|
+
|
125
|
+
# profileのローダーに [:contact_phones] が渡される
|
126
|
+
dependency profile: :contact_phones
|
127
|
+
computed def contact_phones
|
128
|
+
profile.contact_phones
|
129
|
+
end
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
計算フィールドでも下位フィールドセレクタを使うことができます。 (「高度な依存関係」で後述)
|
134
|
+
|
135
|
+
## バッチロード引数
|
136
|
+
|
137
|
+
`bulk_load_and_compute` のキーワード引数は、 `define_primary_loader` や `define_loader` のブロックにそのまま渡されます。
|
138
|
+
状況にあわせて色々な使い方が考えられます。
|
139
|
+
|
140
|
+
### id以外での検索
|
141
|
+
|
142
|
+
複数の検索条件を与えることもできます。
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
class User
|
146
|
+
def self.list(ids, with:)
|
147
|
+
bulk_load_and_compute(with, ids: ids, emails: nil)
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.list_by_emails(emails, with:)
|
151
|
+
bulk_load_and_compute(with, ids: nil, emails: emails)
|
152
|
+
end
|
153
|
+
|
154
|
+
define_primary_loader :raw_user do |_subfields, ids:, emails:, **|
|
155
|
+
s = User.all
|
156
|
+
s = s.where(id: ids) if ids
|
157
|
+
s = s.where(email: emails) if emails
|
158
|
+
s.map { |u| User.new(u) }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
```
|
162
|
+
|
163
|
+
### カレントユーザー
|
164
|
+
|
165
|
+
「今どのユーザーでログインしているか」によって情報の見え方が違う、というような状況を考えます。これはカレントユーザー情報をバッチロード引数に含めることで実現可能です。
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
class User
|
169
|
+
def initialize(raw_user, current_user_id)
|
170
|
+
@raw_user = raw_user
|
171
|
+
@current_user_id = current_user_id
|
172
|
+
end
|
173
|
+
|
174
|
+
define_primary_loader :raw_user do |_subfields, current_user_id:, ids:, **|
|
175
|
+
# ...
|
176
|
+
end
|
177
|
+
|
178
|
+
define_loader :profile, key: -> { id } do |ids, _subfields, current_user_id:, **|
|
179
|
+
# ...
|
180
|
+
end
|
181
|
+
end
|
182
|
+
```
|
183
|
+
|
184
|
+
## 高度な依存関係
|
185
|
+
|
186
|
+
下位フィールドセレクタにprocを指定することで、より高度な制御をすることができます。
|
187
|
+
|
188
|
+
### 条件つき依存
|
189
|
+
|
190
|
+
受け取った下位フィールドセレクタの内容にもとづいて、条件つき依存関係を定義することができます。
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
|
194
|
+
class User
|
195
|
+
dependency(
|
196
|
+
:blog_articles,
|
197
|
+
# image 下位フィールドセレクタがあるときのみ image_permissions フィールド を読み込む
|
198
|
+
image_permissions: -> (subfields) { subfields.normalized[:image].any? }
|
199
|
+
)
|
200
|
+
computed def filtered_blog_articles
|
201
|
+
if current_subfields.normalized[:image].any?
|
202
|
+
# ...
|
203
|
+
end
|
204
|
+
# ...
|
205
|
+
end
|
206
|
+
end
|
207
|
+
```
|
208
|
+
|
209
|
+
### 下位フィールドセレクタのパススルー
|
210
|
+
|
211
|
+
下位フィールドセレクタを別のフィールドにそのまま流すことができます。
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
|
215
|
+
class User
|
216
|
+
dependency(
|
217
|
+
blog_articles: -> (subfields) { subfields }
|
218
|
+
)
|
219
|
+
computed def filtered_blog_articles
|
220
|
+
if current_subfields.normalized[:image].any?
|
221
|
+
# ...
|
222
|
+
end
|
223
|
+
# ...
|
224
|
+
end
|
225
|
+
end
|
226
|
+
```
|
227
|
+
|
228
|
+
### 下位フィールドセレクタのマッピング
|
229
|
+
|
230
|
+
下位フィールドセレクタを加工して別のフィールドに流すこともできます。
|
231
|
+
|
232
|
+
```ruby
|
233
|
+
class User
|
234
|
+
dependency(
|
235
|
+
# blog_articles を必ずロードするが、
|
236
|
+
# 特に下位フィールドセレクタが blog_articles キーを持つ場合はそれを blog_articles の下位フィールドセレクタとして流す
|
237
|
+
blog_articles: [true, -> (subfields) { subfields.normalized[:blog_articles] }],
|
238
|
+
# wiki_articles を必ずロードするが、
|
239
|
+
# 特に下位フィールドセレクタが wiki_articles キーを持つ場合はそれを wiki_articles の下位フィールドセレクタとして流す
|
240
|
+
wiki_articles: [true, -> (subfields) { subfields.normalized[:wiki_articles] }]
|
241
|
+
)
|
242
|
+
computed def articles
|
243
|
+
(blog_articles + wiki_articles).sort_by { |article| article.created_at }.reverse
|
244
|
+
end
|
245
|
+
end
|
246
|
+
```
|
247
|
+
|
248
|
+
### 依存関係のフォーマット
|
249
|
+
|
250
|
+
`dependency` には0個以上の引数を渡すことができます。
|
251
|
+
これらは内部で配列に積まれていき、直後の `computed def` や `define_loader` によって消費されます。
|
252
|
+
そのため、以下は同じ意味です。
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
dependency :profile
|
256
|
+
dependency :preference
|
257
|
+
computed def display_name; ...; end
|
258
|
+
```
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
dependency :profile, :preference
|
262
|
+
computed def display_name; ...; end
|
263
|
+
```
|
264
|
+
|
265
|
+
渡された配列は `ComputedModel.normalize_dependencies` によってハッシュに正規化されます。これは以下のようなルールになっています。
|
266
|
+
|
267
|
+
- Symbolの場合はそのシンボルをキーとするHashとみなす。 (`:foo` → `{ foo: [true] }`)
|
268
|
+
- Hashの場合は中の値を以下のように変換する。
|
269
|
+
- 空配列の場合は `[true]` に変換する。 (`{ foo: [] }` → `{ foo: [true] }`)
|
270
|
+
- 配列以外の場合はそれを単独で含む配列に変換する。 (`{ foo: :bar }` → `{ foo: [:bar] }`)
|
271
|
+
- 空以外の配列の場合はそのまま。
|
272
|
+
- 配列の場合は個々の要素を上記のルールに従って変換したあと、ハッシュのキーをマージする。ハッシュの値は配列なのでそのまま結合される。
|
273
|
+
- `[:foo, :bar]` → `{ foo: [true], bar: [true] }`
|
274
|
+
- `[{ foo: :foo }, { foo: :bar }]` → `{ foo: [:foo, :bar] }`
|
275
|
+
|
276
|
+
このようにして得られたハッシュのキーは依存するフィールド名、値は下位フィールドセレクタとして解釈されます。
|
277
|
+
|
278
|
+
下位フィールドセレクタは以下のように解釈します。
|
279
|
+
|
280
|
+
- Procなど `#call` を持つオブジェクトがある場合、引数に `subfields` (下位フィールドセレクタの配列) を渡して実行する。
|
281
|
+
- 配列が返ってきた場合はフラットに展開する。 (`{ foo: [-> { [:bar, :baz] }] }` → `{ foo: [:bar, :baz] }`)
|
282
|
+
- それ以外の値が返ってきた場合はその要素で置き換える。 (`{ foo: [-> { :bar }] }` → `{ foo: [:bar] }`)
|
283
|
+
- Procの置き換え後、真値 (nilとfalse以外の値) が1つ以上含まれているかを判定する。
|
284
|
+
- 真値がひとつもない場合は、条件つき依存の判定が偽になったとみなし、その依存関係は使わない。
|
285
|
+
- それ以外の場合は依存関係を使う。Procの置き換え後に得られた下位フィールドセレクタはそのまま依存先フィールドに送られる。
|
286
|
+
|
287
|
+
そのため下位フィールドセレクタには通常 `true` が含まれています。特別な条件として以下の場合は取り除かれます。
|
288
|
+
|
289
|
+
- `define_loader` や `define_primary_loader` のブロックに渡されるときは、下位フィールドセレクタに含まれる `nil`, `false`, `true` は
|
290
|
+
取り除かれます。
|
291
|
+
- いくつかの場面では `subfields.normalize` という特別なメソッドが使えることがあります。これは下位フィールドセレクタに含まれる
|
292
|
+
`nil`, `false`, `true` を取り除いたあと、 `ComputedModel.normalize_dependencies` の正規化にかけたハッシュを返します。
|
293
|
+
|
294
|
+
## 継承
|
295
|
+
|
296
|
+
ComputedModelで部分的にフィールドを定義したクラス (モジュール) を作り、それを継承 (インクルード) したクラスで定義を完成させることができます。
|
297
|
+
|
298
|
+
```ruby
|
299
|
+
module UserLikeConcern
|
300
|
+
extends ActiveSupport::Concern
|
301
|
+
include ComputedModel::Model
|
302
|
+
|
303
|
+
dependency :preference, :profile
|
304
|
+
computed def display_name
|
305
|
+
"#{preference.title} #{profile.name}"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
class User
|
310
|
+
include UserLikeConcern
|
311
|
+
|
312
|
+
define_loader :preference, key: -> { id } do ... end
|
313
|
+
define_loader :profile, key: -> { id } do ... end
|
314
|
+
end
|
315
|
+
|
316
|
+
class Admin
|
317
|
+
include UserLikeConcern
|
318
|
+
|
319
|
+
define_loader :preference, key: -> { id } do ... end
|
320
|
+
define_loader :profile, key: -> { id } do ... end
|
321
|
+
end
|
322
|
+
```
|
323
|
+
|
324
|
+
オーバーライドは正しく動かない可能性があります。 (computed def が内部的にメソッドのリネームを行っているため)
|