Module: ComputedModel::Model::ClassMethods
- Defined in:
- lib/computed_model/model.rb
Overview
A set of class methods for ComputedModel::Model. Automatically included to the singleton class when you include ComputedModel::Model.
See ComputedModel::Model for examples.
Instance Method Summary collapse
-
#bulk_load_and_compute(deps, **options) ⇒ Array<Object>
The core routine for batch-loading.
-
#computed(meth_name) ⇒ Symbol
Declares a computed field.
-
#define_loader(meth_name, key:) {|keys, subfields, **options| ... } ⇒ void
Declares a loaded field.
-
#define_primary_loader(meth_name) {|subfields, **options| ... } ⇒ Array
Declares a primary field.
-
#delegate_dependency(*methods, to:, allow_nil: nil, prefix: nil, include_subfields: nil) ⇒ void
A shorthand for simple computed field.
-
#dependency(*deps) ⇒ void
Declares the dependency of a computed field.
-
#verify_dependencies ⇒ void
Verifies the dependency graph for errors.
Instance Method Details
#bulk_load_and_compute(deps, **options) ⇒ Array<Object>
The core routine for batch-loading.
Each model class is expected to provide its own wrapper of this method. See CONCEPTS.md for examples.
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 |
# File 'lib/computed_model/model.rb', line 349 def bulk_load_and_compute(deps, **) objs = nil sorted = __computed_model_sorted_graph plan = sorted.plan(deps) plan.load_order.each do |node| case sorted.original[node.name].type when :primary loader_name = :"__computed_model_enumerate_#{node.name}" objs = send(loader_name, ComputedModel.filter_subfields(node.subfields), **) dummy_toplevel_node = ComputedModel::Plan::Node.new(nil, plan.toplevel, nil) objs.each do |obj| obj.instance_variable_set(:@__computed_model_plan, plan) obj.instance_variable_set(:@__computed_model_stack, [dummy_toplevel_node]) end when :loaded loader_name = :"__computed_model_load_#{node.name}" objs.each do |obj| obj.instance_variable_get(:@__computed_model_stack) << node end begin send(loader_name, objs, ComputedModel.filter_subfields(node.subfields), **) ensure objs.each do |obj| obj.instance_variable_get(:@__computed_model_stack).pop end end else # when :computed objs.each do |obj| obj.send(:"compute_#{node.name}") end end end objs end |
#computed(meth_name) ⇒ Symbol
Declares a computed field. Normally it follows a call to #dependency.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/computed_model/model.rb', line 138 def computed(meth_name) var_name = :"@#{meth_name}" meth_name_orig = :"#{meth_name}_orig" compute_meth_name = :"compute_#{meth_name}" __computed_model_graph << ComputedModel::DepGraph::Node.new(:computed, meth_name, @__computed_model_next_dependency) remove_instance_variable(:@__computed_model_next_dependency) if defined?(@__computed_model_next_dependency) alias_method meth_name_orig, meth_name define_method(meth_name) do raise ComputedModel::NotLoaded, "the field #{meth_name} is not loaded" unless instance_variable_defined?(var_name) __computed_model_check_availability(meth_name) instance_variable_get(var_name) end define_method(compute_meth_name) do @__computed_model_stack << @__computed_model_plan[meth_name] begin instance_variable_set(var_name, send(meth_name_orig)) ensure @__computed_model_stack.pop end end if public_method_defined?(meth_name_orig) public meth_name elsif protected_method_defined?(meth_name_orig) protected meth_name else # elsif private_method_defined?(meth_name_orig) private meth_name end meth_name end |
#define_loader(meth_name, key:) {|keys, subfields, **options| ... } ⇒ void
This method returns an undefined value.
Declares a loaded field. See #dependency and #define_primary_loader too.
define_loader :foo do ... end
generates a reader foo
and a writer foo=
.
The writer only exists for historical reasons.
The block passed to define_loader
is called a loader.
Loader should return a hash containing field values.
- The keys of the hash must match
record.instance_exec(&key)
. - The values of the hash represents the field values.
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/computed_model/model.rb', line 240 def define_loader(meth_name, key:, &block) raise ArgumentError, "No block given" unless block var_name = :"@#{meth_name}" loader_name = :"__computed_model_load_#{meth_name}" writer_name = :"#{meth_name}=" __computed_model_graph << ComputedModel::DepGraph::Node.new(:loaded, meth_name, @__computed_model_next_dependency) remove_instance_variable(:@__computed_model_next_dependency) if defined?(@__computed_model_next_dependency) define_singleton_method(loader_name) do |objs, subfields, **| keys = objs.map { |o| o.instance_exec(&key) } field_values = block.call(keys, subfields, **) objs.zip(keys) do |obj, key| obj.send(writer_name, field_values[key]) end end define_method(meth_name) do raise ComputedModel::NotLoaded, "the field #{meth_name} is not loaded" unless instance_variable_defined?(var_name) __computed_model_check_availability(meth_name) instance_variable_get(var_name) end # TODO: remove writer? attr_writer meth_name end |
#define_primary_loader(meth_name) {|subfields, **options| ... } ⇒ Array
Declares a primary field. See #define_loader and #dependency too. ComputedModel should have exactly one primary field.
define_primary_loader :foo do ... end
generates a reader foo
and
a writer foo=
.
The writer only exists for historical reasons.
The block passed to define_loader
is called a primary loader.
The primary loader's responsibility is batch loading + enumeration (search).
In contrast to #define_loader, where a hash of field values are returned,
the primary loader should return an array of record objects.
For example, if your class is User
, the primary loader must return Array<User>
.
Additionally, the primary loader must initialize all the record objects
so that the same instance variable @#{meth_name}
is set.
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
# File 'lib/computed_model/model.rb', line 309 def define_primary_loader(meth_name, &block) # TODO: The current API requires the user to initialize a specific instance variable. # TODO: this design is a bit ugly. if defined?(@__computed_model_next_dependency) remove_instance_variable(:@__computed_model_next_dependency) raise ArgumentError, 'primary field cannot have a dependency' end raise ArgumentError, "No block given" unless block var_name = :"@#{meth_name}" loader_name = :"__computed_model_enumerate_#{meth_name}" __computed_model_graph << ComputedModel::DepGraph::Node.new(:primary, meth_name, {}) define_singleton_method(loader_name) do |subfields, **| block.call(subfields, **) end define_method(meth_name) do raise ComputedModel::NotLoaded, "the field #{meth_name} is not loaded" unless instance_variable_defined?(var_name) __computed_model_check_availability(meth_name) instance_variable_get(var_name) end # TODO: remove writer? attr_writer meth_name end |
#delegate_dependency(*methods, to:, allow_nil: nil, prefix: nil, include_subfields: nil) ⇒ void
This method returns an undefined value.
A shorthand for simple computed field.
Use #computed for more complex definition.
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/computed_model/model.rb', line 191 def delegate_dependency(*methods, to:, allow_nil: nil, prefix: nil, include_subfields: nil) method_prefix = prefix ? "#{prefix}_" : "" methods.each do |meth_name| pmeth_name = :"#{method_prefix}#{meth_name}" if include_subfields dependency to=>meth_name else dependency to end if allow_nil define_method(pmeth_name) do send(to)&.public_send(meth_name) end else define_method(pmeth_name) do send(to).public_send(meth_name) end end computed pmeth_name end end |
#dependency(*deps) ⇒ void
This method returns an undefined value.
Declares the dependency of a computed field. Normally a call to this method will be followed by a call to #computed (or #define_loader).
122 123 124 125 |
# File 'lib/computed_model/model.rb', line 122 def dependency(*deps) @__computed_model_next_dependency ||= [] @__computed_model_next_dependency.push(*deps) end |
#verify_dependencies ⇒ void
This method returns an undefined value.
Verifies the dependency graph for errors. Useful for early error detection. It also prevents concurrency issues.
Place it after all the relevant declarations. Otherwise a mysterious bug may occur.
405 406 407 408 |
# File 'lib/computed_model/model.rb', line 405 def verify_dependencies __computed_model_sorted_graph nil end |