computed_model 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 が内部的にメソッドのリネームを行っているため)
|