computed_model 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d4c9e19dfb66454b769cf0cb92e95fb01de3aeace7276953182fcebf82fdcd2
4
- data.tar.gz: 836d822953b14420ee7ed6f4dd3d17fba896b41e2caa638eb4fe9e1d59323df5
3
+ metadata.gz: 5fbb65b996358c1bad2d9f03cde8c2fa0e326341d5ef6964a273370d111bc90f
4
+ data.tar.gz: 26869abf1e1ddf74b3c94e836d192379fdc1159328b9c260c14a8968b95ec9fd
5
5
  SHA512:
6
- metadata.gz: e1d34a7a00c560ea97d96927e643d40e02164f94181601e39bcde334cd86f9c5f618c7d6c62556adb5331f2f3f8e61bc015d295f82ee03237d75d4dc8c1d3240
7
- data.tar.gz: 556daff44be9ae2408c9e5d11590fecd440d802067071b53563ee6646ca01296bafa01944df8e691bc770f9c7b613a66631cf60cffa5472abb9673b1ef82b95c
6
+ metadata.gz: bcc03d655e79ef7941b1fc765f9dc56c698aa1afa832c9dac43b409b7a104332a77d9d87ff288146bcb348597af225cc666320eac51d543805cd8d0c39f92a43
7
+ data.tar.gz: e0677afa15a0c1e0b33ce94e2543b9101a985ef2a9ce852c62e1bf69de54321f3fa825c38332a7e3f22810ac90f0beb8c4900ca8b4d509ed6607872028b5bb98
@@ -0,0 +1 @@
1
+ --markup markdown
@@ -1,5 +1,10 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 0.1.1
4
+
5
+ - Expand docs.
6
+ - Add `ComputedModel#computed_model_error` for load cancellation
7
+
3
8
  ## 0.1.0
4
9
 
5
10
  Initial release.
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/qnighy/computed_model.
129
+ Bug reports and pull requests are welcome on GitHub at https://github.com/wantedly/computed_model.
@@ -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
- # @param deps [Array]
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
- # @param meth_name [Symbol]
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
- # @param methods [Array<Symbol>]
47
- # @param to [Symbol]
48
- # @param allow_nil [nil, Boolean]
49
- # @param prefix [nil, Symbol]
50
- # @param include_subdeps [nil, Boolean]
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
- # @param meth_name [Symbol]
75
- # @yieldparam objects [Array]
76
- # @yieldparam options [Hash]
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
- # @param objs [Array]
93
- # @param deps [Array]
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ComputedModel
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
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.0
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