graphql-filters 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +1635 -0
  4. data/CHANGELOG.md +13 -0
  5. data/Gemfile +10 -0
  6. data/Gemfile.lock +77 -0
  7. data/LICENSE +21 -0
  8. data/README.md +265 -0
  9. data/Rakefile +10 -0
  10. data/lib/graphql/filters/activerecord_patch/arel/nodes/contained.rb +9 -0
  11. data/lib/graphql/filters/activerecord_patch/arel/predications.rb +9 -0
  12. data/lib/graphql/filters/activerecord_patch.rb +3 -0
  13. data/lib/graphql/filters/dsl/graphql/schema/enum.rb +16 -0
  14. data/lib/graphql/filters/dsl/graphql/schema/field.rb +64 -0
  15. data/lib/graphql/filters/dsl/graphql/schema/list.rb +28 -0
  16. data/lib/graphql/filters/dsl/graphql/schema/member.rb +25 -0
  17. data/lib/graphql/filters/dsl/graphql/schema/non_null.rb +15 -0
  18. data/lib/graphql/filters/dsl/graphql/schema/object.rb +16 -0
  19. data/lib/graphql/filters/dsl/graphql/schema/scalar.rb +16 -0
  20. data/lib/graphql/filters/dsl/graphql/types/numeric.rb +20 -0
  21. data/lib/graphql/filters/dsl/graphql/types/string.rb +16 -0
  22. data/lib/graphql/filters/dsl.rb +3 -0
  23. data/lib/graphql/filters/filterable.rb +42 -0
  24. data/lib/graphql/filters/input_types/base_comparison_input_type.rb +12 -0
  25. data/lib/graphql/filters/input_types/base_list_comparison_input_type.rb +23 -0
  26. data/lib/graphql/filters/input_types/base_scalar_comparison_input_type.rb +63 -0
  27. data/lib/graphql/filters/input_types/fields_comparison_input_type.rb +58 -0
  28. data/lib/graphql/filters/input_types/list_object_comparison_input_type.rb +97 -0
  29. data/lib/graphql/filters/input_types/list_scalar_comparison_input_type.rb +134 -0
  30. data/lib/graphql/filters/input_types/numeric_comparison_input_type.rb +45 -0
  31. data/lib/graphql/filters/input_types/object_comparison_input_type.rb +71 -0
  32. data/lib/graphql/filters/input_types/string_comparison_input_type.rb +64 -0
  33. data/lib/graphql/filters/utility/cached_class.rb +74 -0
  34. data/lib/graphql/filters/version.rb +5 -0
  35. data/lib/graphql/filters.rb +26 -0
  36. data/lib/graphql/models_connect/dsl/graphql/schema/object.rb +27 -0
  37. data/lib/graphql/models_connect/dsl.rb +3 -0
  38. data/lib/graphql/models_connect.rb +15 -0
  39. metadata +126 -0
@@ -0,0 +1,42 @@
1
+ module GraphQL
2
+ module Filters
3
+ module Filterable
4
+ extend ActiveSupport::Concern
5
+
6
+ # We need the two inner_type chunks of code because type.list? is true if any of the types in the of_type chain
7
+ # is a list, but type.unwrap returns the raw type. I want the filter to be based on the of_type of the list,
8
+ # which could be the unwrapped type, but it could also be a non nullable version of that type. The non
9
+ # nullability is relevant.
10
+
11
+ included do
12
+ raise 'You can only apply a filter to a list field' unless type.list?
13
+
14
+ unless defined?(SearchObject::Base) && include?(SearchObject::Base)
15
+ raise 'If you don\'t use SearchObject, you must *prepend* Filterable, not *include* it.'
16
+ end
17
+
18
+ inner_type = type
19
+ inner_type = type.of_type until inner_type.kind == GraphQL::TypeKinds::LIST
20
+ inner_type = inner_type.of_type
21
+
22
+ option :filter, type: inner_type.comparison_input_type do |scope, filter|
23
+ filter.call scope
24
+ end
25
+ end
26
+
27
+ prepended do
28
+ raise 'You can only apply a filter to a list field' unless type.list?
29
+
30
+ inner_type = type
31
+ inner_type = type.of_type while inner_type.kind == GraphQL::TypeKinds::LIST
32
+ inner_type = inner_type.of_type
33
+
34
+ argument :filter, inner_type.comparison_input_type, required: false
35
+
36
+ def resolve filter: nil, **kwargs
37
+ filter.call super(**kwargs)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,12 @@
1
+ module GraphQL
2
+ module Filters
3
+ module InputTypes
4
+ class BaseComparisonInputType < Filters.base_input_object_class
5
+ def self.argument *args, **kwargs, &block
6
+ kwargs[:required] = false
7
+ super(*args, **kwargs, &block)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ require_relative 'base_comparison_input_type'
2
+
3
+ module GraphQL
4
+ module Filters
5
+ module InputTypes
6
+ class BaseListComparisonInputType
7
+ include CachedClass
8
+
9
+ resolve_cache_miss do |value_type, klass|
10
+ klass.new BaseComparisonInputType do
11
+ graphql_name "#{value_type.unwrap.graphql_name}ListComparisonInput"
12
+
13
+ one_of
14
+
15
+ def prepare
16
+ values.sole
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,63 @@
1
+ require_relative 'base_comparison_input_type'
2
+
3
+ module GraphQL
4
+ module Filters
5
+ module InputTypes
6
+ class BaseScalarComparisonInputType
7
+ include CachedClass
8
+
9
+ resolve_cache_miss do |value_type, klass|
10
+ klass.new BaseComparisonInputType do
11
+ graphql_name "#{value_type.graphql_name}ComparisonInput"
12
+
13
+ one_of
14
+
15
+ argument :constant,
16
+ Types::Boolean,
17
+ prepare: lambda { |value, _context|
18
+ lambda { |scope, _column_name|
19
+ if value
20
+ scope.all
21
+ else
22
+ scope.where false
23
+ end
24
+ }
25
+ }
26
+ argument :equals,
27
+ value_type,
28
+ prepare: lambda { |value, _context|
29
+ lambda { |scope, column_name|
30
+ scope.where(column_name => value)
31
+ }
32
+ }
33
+ argument :not_equals,
34
+ value_type,
35
+ prepare: lambda { |value, _context|
36
+ lambda { |scope, column_name|
37
+ scope.where.not(column_name => value)
38
+ }
39
+ }
40
+ argument :in,
41
+ [value_type],
42
+ prepare: lambda { |value, _context|
43
+ lambda { |scope, column_name|
44
+ scope.where(column_name => value)
45
+ }
46
+ }
47
+ argument :not_in,
48
+ [value_type],
49
+ prepare: lambda { |value, _context|
50
+ lambda { |scope, column_name|
51
+ scope.where.not(column_name => value)
52
+ }
53
+ }
54
+
55
+ def prepare
56
+ values.sole
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,58 @@
1
+ require_relative 'base_comparison_input_type'
2
+
3
+ module GraphQL
4
+ module Filters
5
+ module InputTypes
6
+ class FieldsComparisonInputType
7
+ include CachedClass
8
+
9
+ resolve_cache_miss do |object_type, klass|
10
+ klass.new BaseComparisonInputType do
11
+ graphql_name "#{object_type.graphql_name}ComparisonInput"
12
+
13
+ object_type.fields.each_value do |field_object|
14
+ next unless field_object.filter_options[:enabled]
15
+
16
+ type = field_object.type
17
+ filter_options = field_object.filter_options
18
+
19
+ argument field_object.name,
20
+ type.comparison_input_type,
21
+ required: false,
22
+ prepare: lambda { |field_comparator, _context|
23
+ lambda { |scope|
24
+ if scope.klass.attribute_names.include? filter_options[:attribute_name]
25
+ field_comparator.call scope, filter_options[:attribute_name]
26
+ else
27
+ field_comparator.call scope, filter_options[:association_name]
28
+ end
29
+ }
30
+ }
31
+ end
32
+
33
+ define_method :prepare do
34
+ lambda do |scope, association_name=nil|
35
+ model_class = object_type.model_class
36
+
37
+ nested_query = values.reduce model_class.all do |acc, val|
38
+ val.call acc
39
+ end
40
+
41
+ if association_name.nil?
42
+ unless scope.structurally_compatible? nested_query
43
+ raise "the produced nested query on model #{model_class}"\
44
+ 'is not compatible with the provided scope'
45
+ end
46
+
47
+ scope.and nested_query
48
+ else
49
+ scope.where association_name => nested_query
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,97 @@
1
+ require_relative 'base_list_comparison_input_type'
2
+
3
+ module GraphQL
4
+ module Filters
5
+ module InputTypes
6
+ class ListObjectComparisonInputType
7
+ include CachedClass
8
+
9
+ resolve_cache_miss do |value_type, klass|
10
+ klass.new BaseListComparisonInputType[value_type] do
11
+ raw_type = value_type.unwrap
12
+
13
+ argument :constant,
14
+ Types::Boolean,
15
+ prepare: lambda { |value, _context|
16
+ lambda { |scope, _column_name=nil|
17
+ if value
18
+ scope.all
19
+ else
20
+ scope.where false
21
+ end
22
+ }
23
+ }
24
+ argument :any,
25
+ value_type.of_type.comparison_input_type,
26
+ prepare: lambda { |field_comparator, _context|
27
+ lambda { |scope, association_name|
28
+ subquery = field_comparator.call raw_type.model_class.all
29
+
30
+ reflection = scope.reflect_on_association association_name
31
+
32
+ finished_subquery = apply_reflection_to_subquery reflection, scope, subquery
33
+
34
+ scope.where finished_subquery.arel.exists
35
+ }
36
+ }
37
+ argument :all,
38
+ value_type.of_type.comparison_input_type,
39
+ prepare: lambda { |field_comparator, _context|
40
+ lambda { |scope, association_name|
41
+ subquery = field_comparator.call raw_type.model_class.all
42
+
43
+ reflection = scope.reflect_on_association association_name
44
+
45
+ finished_subquery = apply_reflection_to_subquery reflection, scope, subquery.invert_where
46
+
47
+ scope.where.not finished_subquery.arel.exists
48
+ }
49
+ }
50
+ argument :none,
51
+ value_type.of_type.comparison_input_type,
52
+ prepare: lambda { |field_comparator, _context|
53
+ lambda { |scope, association_name|
54
+ subquery = field_comparator.call raw_type.model_class.all
55
+
56
+ reflection = scope.reflect_on_association association_name
57
+
58
+ finished_subquery = apply_reflection_to_subquery reflection, scope, subquery
59
+
60
+ scope.where.not finished_subquery.arel.exists
61
+ }
62
+ }
63
+
64
+ class << self
65
+ private
66
+
67
+ def apply_reflection_to_subquery reflection, scope, subquery
68
+ unless reflection.is_a? ActiveRecord::Reflection::AbstractReflection
69
+ raise 'Lists of objects are only supported through associations at the moment'
70
+ end
71
+
72
+ scope_klass = scope.klass
73
+ scope_table = scope.table
74
+ scope_aliased_table = scope_table.alias "other_#{scope_table.name}"
75
+
76
+ table_metadata = ActiveRecord::TableMetadata.new scope_klass, scope_aliased_table
77
+ predicate_builder = ActiveRecord::PredicateBuilder.new table_metadata
78
+ aliased_relation = ActiveRecord::Relation.new scope_klass,
79
+ table: scope_aliased_table,
80
+ predicate_builder: predicate_builder
81
+
82
+ aliased_primary_key_node = scope_aliased_table[scope.primary_key]
83
+ scope_primary_key_node = scope_table[scope.primary_key]
84
+
85
+ aliased_relation
86
+ .joins(reflection.name)
87
+ .merge(subquery)
88
+ .where(aliased_primary_key_node.eq(scope_primary_key_node))
89
+ .select(:id) # Improves performance
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,134 @@
1
+ require_relative 'base_list_comparison_input_type'
2
+
3
+ module GraphQL
4
+ module Filters
5
+ module InputTypes
6
+ class ListScalarComparisonInputType
7
+ include CachedClass
8
+
9
+ resolve_cache_miss do |value_type, klass|
10
+ klass.new BaseListComparisonInputType[value_type] do
11
+ argument :equals,
12
+ value_type,
13
+ prepare: lambda { |value, _context|
14
+ lambda { |scope, column_name|
15
+ scope.where(column_name => value)
16
+ }
17
+ }
18
+ argument :not_equals,
19
+ value_type,
20
+ prepare: lambda { |value, _context|
21
+ lambda { |scope, column_name|
22
+ scope.where.not(column_name => value)
23
+ }
24
+ }
25
+ argument :in,
26
+ [value_type],
27
+ prepare: lambda { |value, _context|
28
+ lambda { |scope, column_name|
29
+ scope.where(column_name => value)
30
+ }
31
+ }
32
+ argument :not_in,
33
+ [value_type],
34
+ prepare: lambda { |value, _context|
35
+ lambda { |scope, column_name|
36
+ scope.where.not(column_name => value)
37
+ }
38
+ }
39
+ argument :subset_of,
40
+ value_type,
41
+ prepare: lambda { |value, _context|
42
+ lambda { |scope, column_name|
43
+ table = scope.table[column_name]
44
+ attribute = scope.predicate_builder.build_bind_attribute column_name, value
45
+ scope.where table.contained(attribute)
46
+ }
47
+ }
48
+ argument :not_subset_of,
49
+ value_type,
50
+ prepare: lambda { |value, _context|
51
+ lambda { |scope, column_name|
52
+ table = scope.table[column_name]
53
+ attribute = scope.predicate_builder.build_bind_attribute column_name, value
54
+ scope.where.not table.contained(attribute)
55
+ }
56
+ }
57
+ argument :superset_of,
58
+ value_type,
59
+ prepare: lambda { |value, _context|
60
+ lambda { |scope, column_name|
61
+ table = scope.table[column_name]
62
+ attribute = scope.predicate_builder.build_bind_attribute column_name, value
63
+ scope.where table.contains(attribute)
64
+ }
65
+ }
66
+ argument :not_superset_of,
67
+ value_type,
68
+ prepare: lambda { |value, _context|
69
+ lambda { |scope, column_name|
70
+ table = scope.table[column_name]
71
+ attribute = scope.predicate_builder.build_bind_attribute column_name, value
72
+ scope.where.not table.contains(attribute)
73
+ }
74
+ }
75
+ argument :any,
76
+ value_type.of_type.comparison_input_type,
77
+ prepare: lambda { |field_comparator, _context|
78
+ lambda { |scope, column_name|
79
+ sub_scope = create_sub_scope scope, column_name, field_comparator
80
+ scope.where sub_scope.arel.exists
81
+ }
82
+ }
83
+ argument :all,
84
+ value_type.of_type.comparison_input_type,
85
+ prepare: lambda { |field_comparator, _context|
86
+ lambda { |scope, column_name|
87
+ sub_scope = create_sub_scope scope, column_name, field_comparator
88
+ scope.where.not sub_scope.invert_where.arel.exists
89
+ }
90
+ }
91
+ argument :none,
92
+ value_type.of_type.comparison_input_type,
93
+ prepare: lambda { |field_comparator, _context|
94
+ lambda { |scope, column_name|
95
+ sub_scope = create_sub_scope scope, column_name, field_comparator
96
+ scope.where.not sub_scope.arel.exists
97
+ }
98
+ }
99
+
100
+ class << self
101
+ private
102
+
103
+ def create_sub_scope scope, column_name, field_comparator
104
+ type_caster = Object.new
105
+
106
+ type_caster.define_singleton_method :type_for_attribute do |_name|
107
+ scope.klass.type_for_attribute(column_name).subtype
108
+ end
109
+
110
+ type_caster.define_singleton_method :type_cast_for_database do |attr_name, value|
111
+ type = type_for_attribute attr_name
112
+ type.serialize value
113
+ end
114
+
115
+ table = Arel::Table.new 'temp_table', type_caster: type_caster
116
+ predicate_builder = ActiveRecord::PredicateBuilder.new ActiveRecord::TableMetadata.new(
117
+ ActiveRecord::Base, table
118
+ )
119
+ temp_relation = ActiveRecord::Relation.new(ActiveRecord::Base,
120
+ table: table,
121
+ predicate_builder: predicate_builder)
122
+ .from(Arel::Nodes::NamedFunction.new 'UNNEST',
123
+ [scope.arel_table[column_name]],
124
+ 'temp_table(value)')
125
+
126
+ field_comparator.call temp_relation, 'value'
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,45 @@
1
+ require_relative 'base_scalar_comparison_input_type'
2
+ require 'arel'
3
+
4
+ module GraphQL
5
+ module Filters
6
+ module InputTypes
7
+ class NumericComparisonInputType
8
+ include CachedClass
9
+
10
+ resolve_cache_miss do |value_type, klass|
11
+ klass.new BaseScalarComparisonInputType[value_type] do
12
+ argument :greater_than,
13
+ value_type,
14
+ prepare: lambda { |value, _context|
15
+ lambda { |scope, column_name|
16
+ scope.where.not(column_name => (..value))
17
+ }
18
+ }
19
+ argument :greater_than_or_equals_to,
20
+ value_type,
21
+ prepare: lambda { |value, _context|
22
+ lambda { |scope, column_name|
23
+ scope.where(column_name => (value..))
24
+ }
25
+ }
26
+ argument :less_than,
27
+ value_type,
28
+ prepare: lambda { |value, _context|
29
+ lambda { |scope, column_name|
30
+ scope.where(column_name => (...value))
31
+ }
32
+ }
33
+ argument :less_than_or_equals_to,
34
+ value_type,
35
+ prepare: lambda { |value, _context|
36
+ lambda { |scope, column_name|
37
+ scope.where(column_name => (..value))
38
+ }
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,71 @@
1
+ require_relative 'base_comparison_input_type'
2
+ require_relative 'fields_comparison_input_type'
3
+
4
+ module GraphQL
5
+ module Filters
6
+ module InputTypes
7
+ class ObjectComparisonInputType
8
+ include CachedClass
9
+
10
+ resolve_cache_miss do |value_type, klass|
11
+ klass.new BaseComparisonInputType do
12
+ graphql_name "#{value_type.graphql_name}ComplexFilterInput"
13
+
14
+ fields_comparison_input_type = FieldsComparisonInputType[value_type]
15
+
16
+ define_singleton_method :fields_comparison_input_type do
17
+ fields_comparison_input_type
18
+ end
19
+
20
+ one_of
21
+
22
+ argument :constant,
23
+ Types::Boolean,
24
+ prepare: lambda { |value, _context|
25
+ lambda { |scope, _association_name=nil|
26
+ if value
27
+ scope.all
28
+ else
29
+ scope.where false
30
+ end
31
+ }
32
+ }
33
+ argument :and,
34
+ [self],
35
+ required: false,
36
+ prepare: lambda { |and_arg, _context|
37
+ lambda { |scope, association_name=nil|
38
+ and_arg.reduce scope do |acc, val|
39
+ acc.and val.call(scope, association_name)
40
+ end
41
+ }
42
+ }
43
+ argument :or,
44
+ [self],
45
+ required: false,
46
+ prepare: lambda { |or_arg, _context|
47
+ lambda { |scope, association_name=nil|
48
+ or_arg.reduce scope.none do |acc, val|
49
+ acc.or val.call(scope, association_name)
50
+ end
51
+ }
52
+ }
53
+ argument :not,
54
+ self,
55
+ required: false,
56
+ prepare: lambda { |not_arg, _context|
57
+ lambda { |scope, association_name=nil|
58
+ scope.and(not_arg.call(scope.unscope(:where), association_name).invert_where)
59
+ }
60
+ }
61
+ argument :fields, fields_comparison_input_type, required: false
62
+
63
+ def prepare
64
+ values.sole
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,64 @@
1
+ require_relative 'base_scalar_comparison_input_type'
2
+
3
+ module GraphQL
4
+ module Filters
5
+ module InputTypes
6
+ class StringComparisonInputType < BaseScalarComparisonInputType[GraphQL::Types::String]
7
+ argument :match,
8
+ String,
9
+ prepare: lambda { |value, _context|
10
+ lambda { |scope, column_name|
11
+ column_node = scope.table[column_name]
12
+ scope.where resolve_pattern(column_node, value)
13
+ }
14
+ }
15
+
16
+ class << self
17
+ alias inspect to_s
18
+
19
+ private
20
+
21
+ def resolve_pattern column_node, expression
22
+ expression.match %r{v(?<version>\d)+/(?<full_pattern>.*)} do |match_data|
23
+ raise 'The only supported version of pattern is v1' if match_data[:version].to_i != 1
24
+
25
+ resolve_v1 column_node, match_data[:full_pattern]
26
+ end
27
+ end
28
+
29
+ def resolve_v1 column_node, full_pattern
30
+ full_pattern.match %r{(?<pattern>.*?)/(?<options>.*)} do |match_data|
31
+ options = match_data[:options].chars.map(&:to_sym)
32
+
33
+ if options.present? && options != [:i]
34
+ raise 'The only supported option is \'i\' for case insensitive matching'
35
+ end
36
+
37
+ case_sensitive = !options.include?(:i)
38
+
39
+ characters = match_data[:pattern].chars
40
+ pattern = ''
41
+
42
+ while characters.present?
43
+ char = characters.shift
44
+
45
+ pattern << case char
46
+ when '\\'
47
+ ActiveRecord::Base.sanitize_sql_like characters.shift
48
+ when '*'
49
+ '%'
50
+ when '.'
51
+ '_'
52
+ else
53
+ ActiveRecord::Base.sanitize_sql_like char
54
+ end
55
+ end
56
+
57
+ column_node.matches pattern, nil, case_sensitive
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,74 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/module/delegation'
3
+ require 'active_support/core_ext/class/attribute'
4
+
5
+ module GraphQL
6
+ module Filters
7
+ module Utility
8
+ module CachedClass
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ next unless is_a? Class
13
+
14
+ class_attribute :cache,
15
+ default: Hash.new { |h, k|
16
+ h[k] = Class.new
17
+ },
18
+ instance_accessor: false,
19
+ instance_predicate: false
20
+
21
+ singleton_class.delegate :[], :[]=, to: :cache
22
+ singleton_class.undef_method :new
23
+ end
24
+
25
+ class CacheClassConstructor
26
+ attr_reader :key
27
+ attr_reader :owner
28
+
29
+ delegate_missing_to :owner
30
+
31
+ def initialize key, owner
32
+ @key = key
33
+ @owner = owner
34
+ end
35
+
36
+ def new base_class, &block
37
+ klass = Class.new base_class
38
+ owner[key] = klass
39
+
40
+ outer_self = self
41
+
42
+ klass.define_singleton_method :inspect do
43
+ "#{outer_self.owner}<#{outer_self.key}>"
44
+ end
45
+
46
+ klass.class_exec(&block)
47
+ klass
48
+ end
49
+ end
50
+ private_constant :CacheClassConstructor
51
+
52
+ class_methods do
53
+ # Pass this method the block that will be called on a cache miss. The block will receive the `key` that caused
54
+ # the miss, and a class constructor `klass`. Use `klass.new` instead of `Class.new` to create the class that
55
+ # you want to associate with `key` in the cache. This prevents loops in the case you reference the same key
56
+ # during the definition of the class. Notice that the class is first created with `Class.new`, then it is
57
+ # associated with `key` in the cache, and only then it is properly defined using `class_exec` and the block
58
+ # you passed to `new`, so be careful about what assumptions you make on the classes you pull from the cache
59
+ # during a cache miss resolution.
60
+ def resolve_cache_miss &block
61
+ cache.default_proc = proc do |h, k|
62
+ class_constructor = CacheClassConstructor.new k, self
63
+ result = block.call k, class_constructor
64
+
65
+ h[k] = result unless h.key? k
66
+
67
+ result
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end