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 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