http-api-tools 0.1.0

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.
Files changed (53) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +485 -0
  7. data/Rakefile +4 -0
  8. data/http-api-tools.gemspec +29 -0
  9. data/lib/hat/base_json_serializer.rb +107 -0
  10. data/lib/hat/expanded_relation_includes.rb +77 -0
  11. data/lib/hat/identity_map.rb +42 -0
  12. data/lib/hat/json_serializer_dsl.rb +62 -0
  13. data/lib/hat/model/acts_like_active_model.rb +16 -0
  14. data/lib/hat/model/attributes.rb +159 -0
  15. data/lib/hat/model/has_many_array.rb +47 -0
  16. data/lib/hat/model/transformers/date_time_transformer.rb +31 -0
  17. data/lib/hat/model/transformers/registry.rb +55 -0
  18. data/lib/hat/model.rb +2 -0
  19. data/lib/hat/nesting/json_serializer.rb +45 -0
  20. data/lib/hat/nesting/relation_loader.rb +89 -0
  21. data/lib/hat/relation_includes.rb +140 -0
  22. data/lib/hat/serializer_registry.rb +27 -0
  23. data/lib/hat/sideloading/json_deserializer.rb +121 -0
  24. data/lib/hat/sideloading/json_deserializer_mapping.rb +27 -0
  25. data/lib/hat/sideloading/json_serializer.rb +125 -0
  26. data/lib/hat/sideloading/relation_sideloader.rb +79 -0
  27. data/lib/hat/sideloading/sideload_map.rb +54 -0
  28. data/lib/hat/type_key_resolver.rb +27 -0
  29. data/lib/hat/version.rb +3 -0
  30. data/lib/hat.rb +9 -0
  31. data/reports/empty.png +0 -0
  32. data/reports/minus.png +0 -0
  33. data/reports/plus.png +0 -0
  34. data/spec/hat/expanded_relation_includes_spec.rb +32 -0
  35. data/spec/hat/identity_map_spec.rb +31 -0
  36. data/spec/hat/model/attributes_spec.rb +170 -0
  37. data/spec/hat/model/has_many_array_spec.rb +48 -0
  38. data/spec/hat/model/transformers/date_time_transformer_spec.rb +36 -0
  39. data/spec/hat/model/transformers/registry_spec.rb +53 -0
  40. data/spec/hat/nesting/json_serializer_spec.rb +173 -0
  41. data/spec/hat/relation_includes_spec.rb +185 -0
  42. data/spec/hat/sideloading/json_deserializer_spec.rb +93 -0
  43. data/spec/hat/sideloading/json_serializer_performance_spec.rb +51 -0
  44. data/spec/hat/sideloading/json_serializer_spec.rb +185 -0
  45. data/spec/hat/sideloading/sideload_map_spec.rb +59 -0
  46. data/spec/hat/support/company_deserializer_mapping.rb +11 -0
  47. data/spec/hat/support/person_deserializer_mapping.rb +9 -0
  48. data/spec/hat/support/spec_models.rb +89 -0
  49. data/spec/hat/support/spec_nesting_serializers.rb +41 -0
  50. data/spec/hat/support/spec_sideloading_serializers.rb +41 -0
  51. data/spec/hat/type_key_resolver_spec.rb +19 -0
  52. data/spec/spec_helper.rb +8 -0
  53. metadata +214 -0
@@ -0,0 +1,140 @@
1
+ require 'active_support/core_ext'
2
+ require 'hat/expanded_relation_includes'
3
+
4
+ # Hopefully the spec is robust enough that we can
5
+ # break this down and refactor as we go. I'm not
6
+ # happy with the complexity of it, but it's a
7
+ # reasonably complex problem
8
+ # ~ Stu
9
+
10
+ module Hat
11
+ class RelationIncludes < SimpleDelegator
12
+ include Comparable
13
+
14
+ def initialize(*includes)
15
+ @includes = includes.compact
16
+ super(@includes)
17
+ end
18
+
19
+ def self.from_params(params)
20
+ from_string(params[:include])
21
+ end
22
+
23
+ def self.from_string(string)
24
+ return new if string.blank?
25
+
26
+ includes_hash = build_hash_from_string(string)
27
+ new(*flatten(includes_hash))
28
+ end
29
+
30
+ def to_s
31
+ @to_s ||= begin
32
+ paths = []
33
+ includes.each do |item|
34
+ if item.is_a? Hash
35
+ stringify_keys(paths, item)
36
+ else
37
+ paths << [item]
38
+ end
39
+ end
40
+ paths = paths.map { |p| p.join('.') }
41
+ paths.sort.join(',')
42
+ end
43
+ end
44
+
45
+ def &(other_includes)
46
+ hash = self.class.build_hash_from_string(to_s)
47
+ other_hash = self.class.build_hash_from_string(other_includes.to_s)
48
+
49
+ intersected_paths = (to_s.split(',') & other_includes.to_s.split(','))
50
+ self.class.from_string(intersected_paths.join(','))
51
+ end
52
+
53
+ def <=>(other_includes)
54
+ to_s <=> other_includes.to_s
55
+ end
56
+
57
+ def include(additional_includes)
58
+ includes.concat(additional_includes)
59
+ self
60
+ end
61
+
62
+ def includes_relation?(attr_name)
63
+ find(attr_name).present?
64
+ end
65
+
66
+ def find(attr_name)
67
+ includes.find do |relation|
68
+ (relation.kind_of?(Symbol) && attr_name == relation) ||
69
+ (relation.kind_of?(Hash) && relation.keys.include?(attr_name))
70
+ end
71
+ end
72
+
73
+ def nested_includes_for(attr_name)
74
+ nested = find(attr_name)
75
+ if nested.kind_of?(Hash)
76
+ nested[attr_name]
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ attr_accessor :includes
83
+
84
+ def self.build_hash_from_string(string)
85
+ includes_hash = {}
86
+ include_paths = string.split(',').map { |path| path.split('.') }
87
+
88
+ include_paths.each do |path|
89
+ current_hash = includes_hash
90
+ path.each do |token|
91
+ current_hash[token] ||= {}
92
+ current_hash = current_hash[token]
93
+ end
94
+ end
95
+ includes_hash
96
+ end
97
+
98
+ def stringify_keys(top_level_paths, hash, path_attrs = [])
99
+ current_key = hash.keys.first
100
+ path_attrs << current_key
101
+ top_level_paths << path_attrs.dup
102
+
103
+ hash[current_key].each do |path_value|
104
+ if path_value.is_a? Hash
105
+ path_attrs << stringify_keys(top_level_paths, path_value, path_attrs)
106
+ else
107
+ top_level_paths << (path_attrs.dup << path_value)
108
+ end
109
+ end
110
+ end
111
+
112
+ # Turns this:
113
+ #
114
+ # [ :tags, {images: [:comments]}, {reviews: [:author]} ]
115
+ #
116
+ # Into this:
117
+ #
118
+ # {
119
+ # tags: {},
120
+ # images: {
121
+ # comments: {}
122
+ # }
123
+ # reviews: {}
124
+ # }
125
+ # }
126
+ #
127
+ def self.flatten(hash)
128
+ result = []
129
+ hash.each do |k, v|
130
+ if v.keys.size == 0
131
+ result << k.to_sym
132
+ else
133
+ result << { "#{k}".to_sym => flatten(v) }
134
+ end
135
+ end
136
+ result
137
+ end
138
+
139
+ end
140
+ end
@@ -0,0 +1,27 @@
1
+ require 'singleton'
2
+
3
+ module Hat
4
+ class SerializerRegistry
5
+
6
+ include Singleton
7
+
8
+ def get(type, class_name)
9
+ registry.fetch(type.to_sym, {})[class_name]
10
+ end
11
+
12
+ def register(type, class_name, serializer)
13
+ if existing_serializer = get(type, class_name)
14
+ raise "A '#{type}' serializer for '#{class_name}' instances has already been registered as #{existing_serializer.name}"
15
+ else
16
+ registry[type.to_sym][class_name] = serializer
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def registry
23
+ @registry ||= { sideloading: {}, nesting: {} }
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,121 @@
1
+ # encoding: utf-8
2
+
3
+ #Takes a json response based on the active-model-serializer relationship sideloading pattern
4
+ #and given a root object key, builds an object graph with cyclic relationships.
5
+ #See the id based pattern here - https://github.com/rails-api/active_model_serializers
6
+ require 'active_support/core_ext/hash/indifferent_access'
7
+ require_relative 'sideload_map'
8
+ require_relative '../identity_map'
9
+
10
+ module Hat
11
+ module Sideloading
12
+ class JsonDeserializer
13
+
14
+ def initialize(json)
15
+ @json = json
16
+ @root_key = json['meta']['root_key'].to_s
17
+ @identity_map = IdentityMap.new
18
+ @sideload_map = SideloadMap.new(json, root_key)
19
+ @key_to_class_mappings = {}
20
+ end
21
+
22
+ def deserialize
23
+ json[root_key].map do |json_item|
24
+ create_from_json_item(target_class_for_key(root_key), json_item)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ attr_accessor :json, :root_key, :sideload_map, :identity_map, :key_to_class_mappings
31
+
32
+ def create_from_json_item(target_class, json_item)
33
+
34
+ return nil unless target_class
35
+
36
+ existing_deserialized = identity_map.get(target_class.name, json_item['id'])
37
+ return existing_deserialized if existing_deserialized
38
+
39
+ relations = {}
40
+ target_class_name = target_class.name
41
+
42
+ #we have to add this before we process subtree or we'll get circular issues
43
+ target = target_class.new(json_item.with_indifferent_access)
44
+ identity_map.put(target_class_name, json_item['id'], target)
45
+
46
+ links = json_item['links'] || {}
47
+ apply_linked_relations_to_target(target, links)
48
+
49
+ target
50
+
51
+ end
52
+
53
+ def apply_linked_relations_to_target(target, links)
54
+
55
+ target_class_name = target.class.name
56
+
57
+ links.each do |relation_name, value|
58
+ if value.kind_of?(Array)
59
+ target.send("#{relation_name}=", create_has_manys(target_class_name, relation_name, value))
60
+ target.send("#{relation_name.singularize}_ids=", value)
61
+ else
62
+ target.send("#{relation_name}=", create_belongs_to(target_class_name, relation_name, value))
63
+ target.send("#{relation_name}_id=", value)
64
+ end
65
+ end
66
+ end
67
+
68
+ def create_belongs_to(parent_class_name, sideload_key, id)
69
+
70
+ sideload_key = mapped_sideload_key_for(parent_class_name, sideload_key)
71
+
72
+ if sideloaded_json = sideload_map.get(sideload_key, id)
73
+ sideloaded_object = create_from_json_item(target_class_for_key(sideload_key), sideloaded_json)
74
+ else
75
+ nil
76
+ end
77
+ end
78
+
79
+ def create_has_manys(parent_class_name, sideload_key, ids)
80
+ sideload_key = mapped_sideload_key_for(parent_class_name, sideload_key)
81
+ target_class = target_class_for_key(sideload_key)
82
+ sideloaded_json_items = sideload_map.get_all(sideload_key, ids)
83
+
84
+ sideloaded_json_items.map do |json_item|
85
+ create_from_json_item(target_class, json_item)
86
+ end
87
+ end
88
+
89
+ def mapped_sideload_key_for(parent_class_name, sideload_key)
90
+
91
+ resolve_class_mappings_for(parent_class_name)
92
+ class_mapping = key_to_class_mappings[parent_class_name]
93
+
94
+ if attribute_mapping = class_mapping[sideload_key.to_sym]
95
+ return attribute_mapping.name.underscore
96
+ end
97
+
98
+ sideload_key
99
+ end
100
+
101
+ def resolve_class_mappings_for(parent_class_name)
102
+ unless key_to_class_mappings[parent_class_name]
103
+ mapping_class_name = "#{parent_class_name}DeserializerMapping"
104
+ if Object.const_defined?(mapping_class_name)
105
+ key_to_class_mappings[parent_class_name] = mapping_class_name.constantize.mappings
106
+ else
107
+ key_to_class_mappings[parent_class_name] = {}
108
+ end
109
+ end
110
+ end
111
+
112
+ def target_class_for_key(key)
113
+ key.to_s.singularize.camelize.constantize
114
+ rescue NameError
115
+ nil
116
+ end
117
+
118
+
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,27 @@
1
+ require "active_support/core_ext/class/attribute"
2
+
3
+ module Hat
4
+ module Sideloading
5
+ module JsonDeserializerMapping
6
+
7
+ #----Module Inclusion
8
+
9
+ def self.included(base)
10
+ base.class_attribute :_mappings
11
+ base._mappings = {}
12
+ base.extend(Dsl)
13
+ end
14
+
15
+ module Dsl
16
+ def map(attr_name, target_class)
17
+ self._mappings[attr_name] = target_class
18
+ end
19
+
20
+ def mappings
21
+ self._mappings
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,125 @@
1
+ require_relative '../base_json_serializer'
2
+ require_relative '../json_serializer_dsl'
3
+ require_relative 'relation_sideloader'
4
+
5
+ module Hat
6
+ module Sideloading
7
+ module JsonSerializer
8
+
9
+ include Hat::BaseJsonSerializer
10
+
11
+ attr_reader :relation_includes, :result, :attribute_mappings, :has_one_mappings, :has_many_mappings, :cached
12
+
13
+ def initialize(serializable, attrs = {})
14
+ super
15
+ @identity_map = attrs[:identity_map] || IdentityMap.new
16
+ @type_key_resolver = attrs[:type_key_resolver] || TypeKeyResolver.new
17
+ end
18
+
19
+ def as_json(*args)
20
+
21
+ result[root_key] = []
22
+
23
+ Array(serializable).each do |serializable_item|
24
+ serialize_item_and_cache_relationships(serializable_item)
25
+ end
26
+
27
+ result.merge({ meta: meta_data.merge(includes_meta_data), linked: relation_sideloader.as_json })
28
+ end
29
+
30
+ def as_sideloaded_hash
31
+ hash = attribute_hash.merge(links: has_one_hash.merge(has_many_hash))
32
+ relation_sideloader.sideload_relations
33
+ hash
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :relation_sideloader
39
+
40
+ def serialize_item_and_cache_relationships(serializable_item)
41
+
42
+ assert_id_present(serializable_item)
43
+
44
+ serializer = serializer_for(serializable_item)
45
+ hashed = { id: serializable_item.id }
46
+
47
+ result[root_key] << hashed
48
+
49
+ hashed.merge!(serializer.includes(*relation_includes.to_a).as_sideloaded_hash)
50
+
51
+ end
52
+
53
+ def has_one_hash
54
+
55
+ has_one_hash = {}
56
+
57
+ has_ones.each do |attr_name|
58
+
59
+ id_attr = "#{attr_name}_id"
60
+
61
+ #Use id attr if possible as it's cheaper than referencing the object
62
+ if serializable.respond_to?(id_attr)
63
+ related_id = serializable.send(id_attr)
64
+ else
65
+ related_id = serializable.send(attr_name).try(:id)
66
+ end
67
+
68
+ has_one_hash[attr_name] = related_id
69
+
70
+ end
71
+
72
+ has_one_hash
73
+
74
+ end
75
+
76
+
77
+ def has_many_hash
78
+
79
+ has_many_hash = {}
80
+
81
+ has_manys.each do |attr_name|
82
+ has_many_relation = serializable.send(attr_name) || []
83
+ has_many_hash[attr_name] = has_many_relation.map(&:id)
84
+ end
85
+
86
+ has_many_hash
87
+
88
+ end
89
+
90
+ def relation_sideloader
91
+ @relation_sideloader ||= RelationSideloader.new(
92
+ serializable: serializable,
93
+ has_ones: has_ones,
94
+ has_manys: has_manys,
95
+ relation_includes: relation_includes,
96
+ identity_map: identity_map,
97
+ type_key_resolver: type_key_resolver,
98
+ result: result
99
+ )
100
+ end
101
+
102
+ def serializer_for(serializable_item)
103
+
104
+ serializer_class = serializer_class_for(serializable_item)
105
+
106
+ serializer_class.new(serializable_item, {
107
+ result: result,
108
+ identity_map: identity_map,
109
+ type_key_resolver: type_key_resolver
110
+ })
111
+
112
+ end
113
+
114
+ def serializer_class_for(serializable)
115
+ Hat::SerializerRegistry.instance.get(:sideloading, serializable.class)
116
+ end
117
+
118
+ def self.included(serializer_class)
119
+ JsonSerializerDsl.apply_to(serializer_class)
120
+ end
121
+
122
+ end
123
+ end
124
+ end
125
+
@@ -0,0 +1,79 @@
1
+ module Hat
2
+ module Sideloading
3
+ class RelationSideloader
4
+
5
+ def initialize(opts = {})
6
+ @serializable = opts[:serializable]
7
+ @has_ones = opts[:has_ones]
8
+ @has_manys = opts[:has_manys]
9
+ @relation_includes = opts[:relation_includes]
10
+ @result = opts[:result]
11
+ @identity_map = opts[:identity_map]
12
+ @type_key_resolver = opts[:type_key_resolver]
13
+ end
14
+
15
+ def sideload_relations
16
+ sideload_has_ones
17
+ sideload_has_manys
18
+ end
19
+
20
+ def as_json
21
+ identity_map.to_hash.inject({}) do |sideload_data, (key, type_map)|
22
+ sideload_data[key] = type_map.values
23
+ sideload_data
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :serializable, :has_ones, :has_manys, :relation_includes, :identity_map, :type_key_resolver, :result
30
+
31
+ def sideload_has_ones
32
+
33
+ has_ones.each do |attr_name|
34
+ if related_item = relation_for(attr_name)
35
+ type_key = type_key_for(related_item)
36
+ sideload_item(related_item, attr_name, type_key) unless identity_map.get(type_key, related_item.id)
37
+ end
38
+ end
39
+ end
40
+
41
+ def sideload_has_manys
42
+
43
+ has_manys.each do |attr_name|
44
+
45
+ if related_items = relation_for(attr_name)
46
+ type_key = nil
47
+ related_items.each do |related_item|
48
+ type_key ||= type_key_for(related_item)
49
+ sideload_item(related_item, attr_name, type_key) unless identity_map.get(type_key, related_item.id)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+
56
+ def relation_for(attr_name)
57
+ serializable.send(attr_name) if relation_includes.includes_relation?(attr_name)
58
+ end
59
+
60
+ def sideload_item(related, attr_name, type_key)
61
+ serializer_class = serializer_class_for(related)
62
+ includes = relation_includes.nested_includes_for(attr_name) || []
63
+ sideloaded_hash = serializer_class.new(related, { result: result, identity_map: identity_map, type_key_resolver: type_key_resolver }).includes(*includes).as_sideloaded_hash
64
+
65
+ identity_map.put(type_key, related.id, sideloaded_hash)
66
+ end
67
+
68
+ def type_key_for(related)
69
+ type_key_resolver.for_class(related.class)
70
+ end
71
+
72
+ def serializer_class_for(serializable)
73
+ Hat::SerializerRegistry.instance.get(:sideloading, serializable.class)
74
+ end
75
+
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,54 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+ require 'hat/identity_map'
3
+ #Holds a fast access map of all sideloaded json for deserialization
4
+
5
+ module Hat
6
+ module Sideloading
7
+ class SideloadMap
8
+
9
+ def initialize(json, root_key)
10
+
11
+ @root_key = root_key
12
+ @identity_map = IdentityMap.new
13
+
14
+ build_from_json(json)
15
+ end
16
+
17
+ def get(type, id)
18
+ identity_map.get(type.singularize, id)
19
+ end
20
+
21
+ def get_all(type, ids)
22
+ ids.map { |id| get(type, id)}.compact
23
+ end
24
+
25
+ def inspect
26
+ identity_map.inspect
27
+ end
28
+
29
+ private
30
+
31
+ attr_accessor :root_key, :identity_map
32
+
33
+
34
+ def put(type, id, object)
35
+ identity_map.put(type.singularize, id, object)
36
+ end
37
+
38
+ def build_from_json(json)
39
+
40
+ json[root_key].each do |json_item|
41
+ put(root_key, json_item['id'], json_item)
42
+ end
43
+
44
+ if json['linked']
45
+ json['linked'].each do |type_key, sideloaded_json_item|
46
+ sideloaded_json_item.each { |json_item| put(type_key, json_item['id'], json_item) }
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,27 @@
1
+ #Provides a cache for resolved type keys against classes. When serializing a lot of
2
+ #relationships this can have a significant improvement on performance.
3
+
4
+ module Hat
5
+ class TypeKeyResolver
6
+
7
+ def initialize
8
+ @cache = {}
9
+ end
10
+
11
+ def for_class(klass)
12
+ class_name = klass.name
13
+ cache[class_name] || resolve_and_store_type_key_for(class_name)
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :cache
19
+
20
+ def resolve_and_store_type_key_for(class_name)
21
+ type_key = class_name.underscore.pluralize
22
+ cache[class_name] = type_key
23
+ type_key
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module Hat
2
+ VERSION = "0.1.0"
3
+ end
data/lib/hat.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "hat/version"
2
+ require 'hat/sideloading/json_serializer'
3
+ require 'hat/sideloading/json_deserializer'
4
+ require 'hat/model'
5
+ require 'hat/relation_includes'
6
+
7
+ module Hat
8
+ # Your code goes here...
9
+ end
data/reports/empty.png ADDED
Binary file
data/reports/minus.png ADDED
Binary file
data/reports/plus.png ADDED
Binary file
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ require 'hat/expanded_relation_includes'
3
+
4
+ module Hat
5
+ describe ExpandedRelationIncludes do
6
+
7
+ describe "#to_a" do
8
+
9
+ let(:serializer) { Hat::Sideloading::PersonSerializer.new(Person.new) }
10
+ let(:expanded_includes) { ExpandedRelationIncludes.new(includes, serializer) }
11
+
12
+ context 'with single-level includes' do
13
+
14
+ let(:includes) { [:employer, :skills] }
15
+
16
+ it "expands includes to include has_many relationships defined by serializers but not in original includes" do
17
+ expect(expanded_includes.to_a).to eq([{ employer: [:employees] }, :skills])
18
+ end
19
+ end
20
+
21
+ context 'with multi-level includes' do
22
+
23
+ let(:includes) { [:employer, { skills: [:person] }] }
24
+
25
+ it "expands includes to include has_many relationships defined by serializers but not in original includes" do
26
+ expect(expanded_includes.to_a).to eq([{ employer: [:employees] }, { skills: [{ person: [:skills] }] }])
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+ require 'hat/identity_map'
3
+
4
+ module Hat
5
+ describe IdentityMap do
6
+
7
+ let(:identity_map) { IdentityMap.new }
8
+ let(:thing) { 'a thing' }
9
+
10
+ describe "putting/getting items" do
11
+ it "puts and revieves same object with string type key" do
12
+
13
+ identity_map.put('thing', 1, thing)
14
+ expect(identity_map.get('thing', 1)).to eql thing
15
+ end
16
+
17
+ it "puts and revieves same object with symbol type key" do
18
+ identity_map.put(:thing, 1, thing)
19
+ expect(identity_map.get(:thing, 1)).to eql thing
20
+ end
21
+
22
+ it "puts and revieves same object with mixed type key" do
23
+ identity_map.put(:thing, 1, thing)
24
+ expect(identity_map.get('thing', 1)).to eql thing
25
+ end
26
+
27
+ end
28
+ end
29
+ class IdentityMapThing ; end
30
+ end
31
+