computed_model 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/CHANGELOG.md +5 -0
- data/README.md +3 -2
- data/lib/computed_model.rb +110 -12
- data/lib/computed_model/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5fbb65b996358c1bad2d9f03cde8c2fa0e326341d5ef6964a273370d111bc90f
|
4
|
+
data.tar.gz: 26869abf1e1ddf74b3c94e836d192379fdc1159328b9c260c14a8968b95ec9fd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bcc03d655e79ef7941b1fc765f9dc56c698aa1afa832c9dac43b409b7a104332a77d9d87ff288146bcb348597af225cc666320eac51d543805cd8d0c39f92a43
|
7
|
+
data.tar.gz: e0677afa15a0c1e0b33ce94e2543b9101a985ef2a9ce852c62e1bf69de54321f3fa825c38332a7e3f22810ac90f0beb8c4900ca8b4d509ed6607872028b5bb98
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -12,7 +12,7 @@ ActiveRecord and remote server (such as ActiveResource).
|
|
12
12
|
Add this line to your application's Gemfile:
|
13
13
|
|
14
14
|
```ruby
|
15
|
-
gem 'computed_model'
|
15
|
+
gem 'computed_model', '~> 0.1.1'
|
16
16
|
```
|
17
17
|
|
18
18
|
And then execute:
|
@@ -115,6 +115,7 @@ users = User.list([1, 2, 3], with: [:name, :name_with_playing_music, :premium_us
|
|
115
115
|
This library is distributed under MIT license.
|
116
116
|
|
117
117
|
Copyright (c) 2020 Masaki Hara
|
118
|
+
|
118
119
|
Copyright (c) 2020 Wantedly, Inc.
|
119
120
|
|
120
121
|
## Development
|
@@ -125,4 +126,4 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
125
126
|
|
126
127
|
## Contributing
|
127
128
|
|
128
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
129
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/wantedly/computed_model.
|
data/lib/computed_model.rb
CHANGED
@@ -3,19 +3,71 @@
|
|
3
3
|
require "computed_model/version"
|
4
4
|
require 'set'
|
5
5
|
|
6
|
+
# A mixin for batch-loadable compound models.
|
7
|
+
#
|
8
|
+
# @example typical structure of a computed model
|
9
|
+
# class User
|
10
|
+
# include ComputedModel
|
11
|
+
#
|
12
|
+
# attr_reader :id
|
13
|
+
# def initialize(id)
|
14
|
+
# @id = id
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# define_loader do ... end
|
18
|
+
#
|
19
|
+
# dependency :foo, :bar
|
20
|
+
# computed def something ... end
|
21
|
+
# end
|
6
22
|
module ComputedModel
|
23
|
+
# An error raised when you tried to read from a loaded/computed attribute,
|
24
|
+
# but that attribute isn't loaded by the batch loader.
|
7
25
|
class NotLoaded < StandardError; end
|
8
26
|
|
27
|
+
# A return value from {ComputedModel::ClassMethods#computing_plan}.
|
9
28
|
Plan = Struct.new(:load_order, :subdeps_hash)
|
10
29
|
|
30
|
+
# A set of class methods for {ComputedModel}. Automatically included to the
|
31
|
+
# singleton class when you include {ComputedModel}.
|
11
32
|
module ClassMethods
|
12
|
-
#
|
33
|
+
# Declares the dependency of a computed attribute. See {#computed} too.
|
34
|
+
#
|
35
|
+
# @param deps [Array<Symbol, Hash{Symbol=>Array}>]
|
36
|
+
# Dependency description. If a symbol `:foo` is given,
|
37
|
+
# it's interpreted as `{ foo: [] }`.
|
38
|
+
# When the same symbol occurs multiple times, the array is concatenated.
|
39
|
+
# The contents of the array (called "sub-dependency") is treated opaquely
|
40
|
+
# by the `computed_model` gem. It is up to the user to design the format
|
41
|
+
# of sub-dependencies.
|
42
|
+
# @return [void]
|
43
|
+
#
|
44
|
+
# @example declaring dependencies
|
45
|
+
# dependency :user, :user_external_resource
|
46
|
+
# computed def something
|
47
|
+
# # Use user and user_external_resource ...
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# @example declaring dependencies with sub-dependencies
|
51
|
+
# dependency user: [:user_names, :premium], user_external_resource: [:received_stars]
|
52
|
+
# computed def something
|
53
|
+
# # Use user and user_external_resource ...
|
54
|
+
# end
|
13
55
|
def dependency(*deps)
|
14
56
|
@__computed_model_next_dependency ||= []
|
15
57
|
@__computed_model_next_dependency.push(*deps)
|
16
58
|
end
|
17
59
|
|
18
|
-
#
|
60
|
+
# Declares a computed attribute. See {#dependency} too.
|
61
|
+
#
|
62
|
+
# @param meth_name [Symbol] a method name to promote to a computed attribute.
|
63
|
+
# Typically used in the form of `computed def ...`.
|
64
|
+
# @return [Symbol] passes through the argument.
|
65
|
+
#
|
66
|
+
# @example define a field which is calculated from loaded models
|
67
|
+
# dependency :user, :user_external_resource
|
68
|
+
# computed def something
|
69
|
+
# # Use user and user_external_resource ...
|
70
|
+
# end
|
19
71
|
def computed(meth_name)
|
20
72
|
var_name = :"@#{meth_name}"
|
21
73
|
meth_name_orig = :"#{meth_name}_orig"
|
@@ -43,12 +95,25 @@ module ComputedModel
|
|
43
95
|
meth_name
|
44
96
|
end
|
45
97
|
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
# @param
|
98
|
+
# A shorthand for simple computed attributes.
|
99
|
+
#
|
100
|
+
# Use {#computed} for more complex definition.
|
101
|
+
#
|
102
|
+
# @param methods [Array<Symbol>] method names to delegate
|
103
|
+
# @param to [Symbol] which attribute to delegate the methods to.
|
104
|
+
# This parameter is used for the dependency declaration too.
|
105
|
+
# @param allow_nil [nil, Boolean] If `true`,
|
106
|
+
# nil receivers are is ignored, and nil is returned instead.
|
107
|
+
# @param prefix [nil, Symbol] A prefix for the delegating method name.
|
108
|
+
# @param include_subdeps [nil, Boolean] If `true`,
|
109
|
+
# sub-dependencies are also included.
|
51
110
|
# @return [void]
|
111
|
+
#
|
112
|
+
# @example delegate name from raw_user
|
113
|
+
# delegate_dependency :name, to: :raw_user
|
114
|
+
#
|
115
|
+
# @example delegate name from raw_user, but expose as user_name
|
116
|
+
# delegate_dependency :name, to: :raw_user, prefix: :user
|
52
117
|
def delegate_dependency(*methods, to:, allow_nil: nil, prefix: nil, include_subdeps: nil)
|
53
118
|
method_prefix = prefix ? "#{prefix_}" : ""
|
54
119
|
methods.each do |meth_name|
|
@@ -71,10 +136,30 @@ module ComputedModel
|
|
71
136
|
end
|
72
137
|
end
|
73
138
|
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
139
|
+
# Declares a loaded attribute. See {#dependency} too.
|
140
|
+
#
|
141
|
+
# `define_loader :foo do ... end` generates a reader `foo` and a writer `foo=`.
|
142
|
+
# The writer is only meant to be used in the loader.
|
143
|
+
#
|
144
|
+
# The responsibility of loader is to call `foo=` for all the given objects,
|
145
|
+
# or set `computed_model_error` otherwise.
|
146
|
+
#
|
147
|
+
# @param meth_name [Symbol] the name of the loaded attribute.
|
148
|
+
# @return [void]
|
149
|
+
# @yield [objects, **options]
|
150
|
+
# @yieldparam objects [Array] The objects to preload the attribute into.
|
151
|
+
# @yieldparam options [Hash] A verbatim copy of what is passed to {#bulk_load_and_compute}.
|
77
152
|
# @yieldreturn [void]
|
153
|
+
#
|
154
|
+
# @example define a loader for ActiveRecord-based models
|
155
|
+
# define_loader :raw_user do |users, subdeps, **options|
|
156
|
+
# user_ids = users.map(&:id)
|
157
|
+
# raw_users = RawUser.where(id: user_ids).preload(subdeps).index_by(&:id)
|
158
|
+
# users.each do |user|
|
159
|
+
# # Even if it doesn't exist, you must explicitly assign nil to the field.
|
160
|
+
# user.raw_user = raw_users[user.id]
|
161
|
+
# end
|
162
|
+
# end
|
78
163
|
def define_loader(meth_name, &block)
|
79
164
|
raise ArgumentError, "No block given" unless block
|
80
165
|
|
@@ -89,9 +174,15 @@ module ComputedModel
|
|
89
174
|
attr_writer meth_name
|
90
175
|
end
|
91
176
|
|
92
|
-
#
|
93
|
-
#
|
177
|
+
# The core routine for batch-loading.
|
178
|
+
#
|
179
|
+
# @param objs [Array] The objects to preload attributes into.
|
180
|
+
# @param deps [Array<Symbol, Hash{Symbol=>Array}>] A set of dependencies.
|
181
|
+
# @param options [Hash] An arbitrary hash to pass to loaders
|
182
|
+
# defined by {#define_loader}.
|
183
|
+
# @return [void]
|
94
184
|
def bulk_load_and_compute(objs, deps, **options)
|
185
|
+
objs = objs.dup
|
95
186
|
plan = computing_plan(deps)
|
96
187
|
plan.load_order.each do |dep_name|
|
97
188
|
if @__computed_model_dependencies.key?(dep_name)
|
@@ -103,6 +194,7 @@ module ComputedModel
|
|
103
194
|
else
|
104
195
|
raise "No dependency info for #{self}##{dep_name}"
|
105
196
|
end
|
197
|
+
objs.reject! { |obj| !obj.computed_model_error.nil? }
|
106
198
|
end
|
107
199
|
end
|
108
200
|
|
@@ -168,6 +260,12 @@ module ComputedModel
|
|
168
260
|
normalized
|
169
261
|
end
|
170
262
|
|
263
|
+
# An error field to prevent {ComputedModel::ClassMethods#bulk_load_and_compute}
|
264
|
+
# from loading remaining attributes.
|
265
|
+
#
|
266
|
+
# @return [StandardError]
|
267
|
+
attr_accessor :computed_model_error
|
268
|
+
|
171
269
|
def self.included(klass)
|
172
270
|
super
|
173
271
|
klass.extend ClassMethods
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: computed_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Masaki Hara
|
@@ -70,6 +70,7 @@ files:
|
|
70
70
|
- ".gitignore"
|
71
71
|
- ".rspec"
|
72
72
|
- ".travis.yml"
|
73
|
+
- ".yardopts"
|
73
74
|
- CHANGELOG.md
|
74
75
|
- Gemfile
|
75
76
|
- README.md
|