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.
- checksums.yaml +15 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +485 -0
- data/Rakefile +4 -0
- data/http-api-tools.gemspec +29 -0
- data/lib/hat/base_json_serializer.rb +107 -0
- data/lib/hat/expanded_relation_includes.rb +77 -0
- data/lib/hat/identity_map.rb +42 -0
- data/lib/hat/json_serializer_dsl.rb +62 -0
- data/lib/hat/model/acts_like_active_model.rb +16 -0
- data/lib/hat/model/attributes.rb +159 -0
- data/lib/hat/model/has_many_array.rb +47 -0
- data/lib/hat/model/transformers/date_time_transformer.rb +31 -0
- data/lib/hat/model/transformers/registry.rb +55 -0
- data/lib/hat/model.rb +2 -0
- data/lib/hat/nesting/json_serializer.rb +45 -0
- data/lib/hat/nesting/relation_loader.rb +89 -0
- data/lib/hat/relation_includes.rb +140 -0
- data/lib/hat/serializer_registry.rb +27 -0
- data/lib/hat/sideloading/json_deserializer.rb +121 -0
- data/lib/hat/sideloading/json_deserializer_mapping.rb +27 -0
- data/lib/hat/sideloading/json_serializer.rb +125 -0
- data/lib/hat/sideloading/relation_sideloader.rb +79 -0
- data/lib/hat/sideloading/sideload_map.rb +54 -0
- data/lib/hat/type_key_resolver.rb +27 -0
- data/lib/hat/version.rb +3 -0
- data/lib/hat.rb +9 -0
- data/reports/empty.png +0 -0
- data/reports/minus.png +0 -0
- data/reports/plus.png +0 -0
- data/spec/hat/expanded_relation_includes_spec.rb +32 -0
- data/spec/hat/identity_map_spec.rb +31 -0
- data/spec/hat/model/attributes_spec.rb +170 -0
- data/spec/hat/model/has_many_array_spec.rb +48 -0
- data/spec/hat/model/transformers/date_time_transformer_spec.rb +36 -0
- data/spec/hat/model/transformers/registry_spec.rb +53 -0
- data/spec/hat/nesting/json_serializer_spec.rb +173 -0
- data/spec/hat/relation_includes_spec.rb +185 -0
- data/spec/hat/sideloading/json_deserializer_spec.rb +93 -0
- data/spec/hat/sideloading/json_serializer_performance_spec.rb +51 -0
- data/spec/hat/sideloading/json_serializer_spec.rb +185 -0
- data/spec/hat/sideloading/sideload_map_spec.rb +59 -0
- data/spec/hat/support/company_deserializer_mapping.rb +11 -0
- data/spec/hat/support/person_deserializer_mapping.rb +9 -0
- data/spec/hat/support/spec_models.rb +89 -0
- data/spec/hat/support/spec_nesting_serializers.rb +41 -0
- data/spec/hat/support/spec_sideloading_serializers.rb +41 -0
- data/spec/hat/type_key_resolver_spec.rb +19 -0
- data/spec/spec_helper.rb +8 -0
- metadata +214 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
# Provides an expanded version of includes for use in queries where ids for has_many relationships are
|
2
|
+
# going to be referenced such as with the json serializer. Eradicates n+1 issues in these instances.
|
3
|
+
module Hat
|
4
|
+
class ExpandedRelationIncludes
|
5
|
+
|
6
|
+
def initialize(relation_includes, serializer)
|
7
|
+
@relation_includes = relation_includes
|
8
|
+
@serializer = serializer
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_a
|
12
|
+
@expanded_includes ||= begin
|
13
|
+
expanded_includes = []
|
14
|
+
expand_includes(serializer.class, relation_includes, expanded_includes)
|
15
|
+
expanded_includes
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :serializer, :relation_includes
|
22
|
+
|
23
|
+
def expand_includes(target_serializer_class, base_includes, expanded_includes)
|
24
|
+
|
25
|
+
append_has_many_includes(target_serializer_class, base_includes, expanded_includes)
|
26
|
+
|
27
|
+
base_includes.each do |include_item|
|
28
|
+
if include_item.kind_of?(Symbol)
|
29
|
+
expand_includes_for_symbol(include_item, target_serializer_class, expanded_includes)
|
30
|
+
elsif include_item.kind_of?(Hash)
|
31
|
+
expand_includes_for_hash(include_item, target_serializer_class, expanded_includes)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def expand_includes_for_symbol(include_item, target_serializer_class, expanded_includes)
|
38
|
+
|
39
|
+
related_type = target_serializer_class.serializable_type.reflections[include_item].class_name.constantize
|
40
|
+
related_serializer = SerializerRegistry.instance.get(target_serializer_class.serializer_type, related_type)
|
41
|
+
new_nested_includes = []
|
42
|
+
|
43
|
+
append_has_many_includes(related_serializer, [], new_nested_includes)
|
44
|
+
|
45
|
+
if new_nested_includes.empty?
|
46
|
+
expanded_includes << include_item
|
47
|
+
else
|
48
|
+
expanded_includes << { include_item => new_nested_includes }
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
def expand_includes_for_hash(include_item, target_serializer_class, expanded_includes)
|
54
|
+
|
55
|
+
nested_include_key = include_item.keys.first
|
56
|
+
nested_includes = include_item[nested_include_key]
|
57
|
+
|
58
|
+
related_type = target_serializer_class.serializable_type.reflections[nested_include_key].class_name.constantize
|
59
|
+
related_serializer = SerializerRegistry.instance.get(target_serializer_class.serializer_type, related_type)
|
60
|
+
new_nested_includes = []
|
61
|
+
|
62
|
+
expanded_includes << { nested_include_key => new_nested_includes }
|
63
|
+
expand_includes(related_serializer, nested_includes, new_nested_includes)
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
def append_has_many_includes(related_serializer, base_includes, expanded_includes)
|
68
|
+
|
69
|
+
related_serializer.has_manys.each do |has_many_attr|
|
70
|
+
expanded_includes << has_many_attr unless RelationIncludes.new(*base_includes).find(has_many_attr)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#type/id map for mapping string or symbol keys and ids to objects.
|
2
|
+
module Hat
|
3
|
+
class IdentityMap
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
#Optimised for speed...as tempting as it might be, don't rewrite this to use hash with indifferent access as it is slower.
|
7
|
+
@identity_map = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def get(type, id)
|
11
|
+
if id_map = identity_map[type.to_sym]
|
12
|
+
id_map[id]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def put(type, id, object)
|
17
|
+
|
18
|
+
type_symbol = type.to_sym
|
19
|
+
|
20
|
+
unless identity_map[type_symbol]
|
21
|
+
identity_map[type_symbol] = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
identity_map[type_symbol][id] = object
|
25
|
+
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_hash
|
30
|
+
@identity_map
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
identity_map.inspect
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_accessor :identity_map
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require_relative 'serializer_registry'
|
2
|
+
|
3
|
+
module Hat
|
4
|
+
module JsonSerializerDsl
|
5
|
+
|
6
|
+
def self.apply_to(serializer_class)
|
7
|
+
serializer_class.class_attribute :_attributes
|
8
|
+
serializer_class.class_attribute :_relationships
|
9
|
+
serializer_class.class_attribute :_includable
|
10
|
+
serializer_class.class_attribute :_serializes
|
11
|
+
|
12
|
+
serializer_class._attributes = []
|
13
|
+
serializer_class._relationships = { has_ones: [], has_manys: [] }
|
14
|
+
|
15
|
+
serializer_class.extend(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def serializes(klass)
|
19
|
+
self._serializes = klass
|
20
|
+
Hat::SerializerRegistry.instance.register(serializer_type, klass, self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def serializable_type
|
24
|
+
self._serializes
|
25
|
+
end
|
26
|
+
|
27
|
+
def has_ones
|
28
|
+
self._relationships[:has_ones]
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_manys
|
32
|
+
self._relationships[:has_manys]
|
33
|
+
end
|
34
|
+
|
35
|
+
def attributes(*args)
|
36
|
+
self._attributes = args
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_one(has_one)
|
40
|
+
self.has_ones << has_one
|
41
|
+
end
|
42
|
+
|
43
|
+
def has_many(has_many)
|
44
|
+
self.has_manys << has_many
|
45
|
+
end
|
46
|
+
|
47
|
+
def includable(*includes)
|
48
|
+
self._includable = RelationIncludes.new(*includes)
|
49
|
+
end
|
50
|
+
|
51
|
+
def serializer_type
|
52
|
+
if self.ancestors.any? { |klass| klass == Hat::Sideloading::JsonSerializer }
|
53
|
+
:sideloading
|
54
|
+
elsif self.ancestors.any? { |klass| klass == Hat::Nesting::JsonSerializer }
|
55
|
+
:nesting
|
56
|
+
else
|
57
|
+
raise "Unsupported serializer_type. Must be one of either 'sideloading' or 'nesting' serializer."
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#Adds methods that help a client to behave as if it's dealing with an ActiveModel object.
|
2
|
+
#Principle of least surprise here - if someone is working in Rails and using a model it should
|
3
|
+
#feel normal and they should be able to do all the things the'd do with an active model object except
|
4
|
+
#interact with the database.
|
5
|
+
module Hat
|
6
|
+
module Model
|
7
|
+
module ActsLikeActiveModel
|
8
|
+
|
9
|
+
def to_param
|
10
|
+
if self.respond_to?(:id)
|
11
|
+
self.id
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "active_support/core_ext/class/attribute"
|
4
|
+
require 'active_support/core_ext/string/inflections'
|
5
|
+
require_relative "transformers/registry"
|
6
|
+
require_relative "has_many_array"
|
7
|
+
|
8
|
+
#Mix in to PORO to get basic attribute definition with type coercion and defaults.
|
9
|
+
#class MyClass
|
10
|
+
# attribute :name
|
11
|
+
# attribute :date, type: :date_time
|
12
|
+
# attribute :tags, default: []
|
13
|
+
#
|
14
|
+
#NOTE: Be careful of adding default values other than primitives, arrays or basic hashes. Anything more
|
15
|
+
#Complex will need to be copied into each object rather than by direct reference - see the way
|
16
|
+
#arrays and hashes are handled in the 'default_for' method below.
|
17
|
+
module Hat
|
18
|
+
module Model
|
19
|
+
module Attributes
|
20
|
+
|
21
|
+
def initialize(attrs = {})
|
22
|
+
|
23
|
+
attrs = attrs.with_indifferent_access
|
24
|
+
|
25
|
+
attributes.each do |attr_name, attr_options|
|
26
|
+
raw_value = attrs[attr_name] == nil ? default_for(attr_options) : attrs[attr_name]
|
27
|
+
set_raw_value(attr_name, raw_value, true) unless raw_value == nil
|
28
|
+
end
|
29
|
+
|
30
|
+
self.errors = attrs[:errors] || {}
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def attributes
|
35
|
+
self.class._attributes
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_many_changed(has_many_name)
|
39
|
+
send("#{has_many_name.to_s.singularize}_ids=", send(has_many_name).map(&:id).compact)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def set_raw_value(attr_name, raw_value, apply_if_read_only = false)
|
45
|
+
|
46
|
+
attr_def = attributes[attr_name]
|
47
|
+
value = transformed_value(attr_def[:type], raw_value)
|
48
|
+
|
49
|
+
if attr_def[:read_only] && apply_if_read_only
|
50
|
+
instance_variable_set("@#{attr_name}", value)
|
51
|
+
elsif
|
52
|
+
self.send("#{attr_name}=", value)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def transformed_value(type, raw_value)
|
57
|
+
if type
|
58
|
+
transformer_registry.from_raw(type, raw_value)
|
59
|
+
else
|
60
|
+
raw_value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def transformer_registry
|
65
|
+
Transformers::Registry.instance
|
66
|
+
end
|
67
|
+
|
68
|
+
#make sure we don't pass references to the same default object to each instance. Copy/dup where appropriate
|
69
|
+
def default_for(options)
|
70
|
+
assert_default_type_valid(options)
|
71
|
+
default = options[:default]
|
72
|
+
if default.kind_of? Array
|
73
|
+
[].concat(default)
|
74
|
+
elsif default.kind_of? Hash
|
75
|
+
default.dup
|
76
|
+
else
|
77
|
+
default
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def assert_default_type_valid(options)
|
82
|
+
if options[:default]
|
83
|
+
default_class = options[:default].class
|
84
|
+
unless [Array, Hash, Integer, Float, String].include? default_class
|
85
|
+
raise "Default values of type #{default_class.name} are not supported."
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def set_belongs_to_value(attr_name, value)
|
91
|
+
instance_variable_set("@#{attr_name}", value)
|
92
|
+
send("#{attr_name}_id=", value.try(:id))
|
93
|
+
end
|
94
|
+
|
95
|
+
def set_has_many_value(attr_name, value)
|
96
|
+
instance_variable_set("@#{attr_name}", HasManyArray.new(value, self, attr_name))
|
97
|
+
has_many_changed(attr_name)
|
98
|
+
end
|
99
|
+
|
100
|
+
#----Module Inclusion
|
101
|
+
|
102
|
+
def self.included(base)
|
103
|
+
base.class_attribute :_attributes
|
104
|
+
base._attributes = {}
|
105
|
+
base.extend(ClassMethods)
|
106
|
+
base.send(:attr_accessor, :errors)
|
107
|
+
end
|
108
|
+
|
109
|
+
module ClassMethods
|
110
|
+
|
111
|
+
def attribute(name, options = {})
|
112
|
+
self._attributes[name] = options
|
113
|
+
if options[:read_only]
|
114
|
+
self.send(:attr_reader, name.to_sym)
|
115
|
+
else
|
116
|
+
self.send(:attr_accessor, name.to_sym)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def belongs_to(attr_name, options = {})
|
121
|
+
|
122
|
+
id_attr_name = "#{attr_name}_id"
|
123
|
+
id_setter_method_name = "#{id_attr_name}="
|
124
|
+
|
125
|
+
send(:attr_reader, attr_name)
|
126
|
+
send(:attr_reader, id_attr_name)
|
127
|
+
|
128
|
+
define_method("#{attr_name}=") do |value|
|
129
|
+
set_belongs_to_value(attr_name, value)
|
130
|
+
end
|
131
|
+
|
132
|
+
define_method(id_setter_method_name) do |value|
|
133
|
+
instance_variable_set("@#{id_attr_name}", value)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def has_many(attr_name, options = {})
|
139
|
+
|
140
|
+
ids_attr_name = "#{attr_name.to_s.singularize}_ids"
|
141
|
+
id_setter_method_name = "#{ids_attr_name}="
|
142
|
+
|
143
|
+
send(:attr_reader, attr_name)
|
144
|
+
send(:attr_reader, ids_attr_name)
|
145
|
+
|
146
|
+
define_method("#{attr_name}=") do |value|
|
147
|
+
set_has_many_value(attr_name, value)
|
148
|
+
end
|
149
|
+
|
150
|
+
define_method(id_setter_method_name) do |value|
|
151
|
+
instance_variable_set("@#{ids_attr_name}", value)
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Hat
|
2
|
+
module Model
|
3
|
+
class HasManyArray < SimpleDelegator
|
4
|
+
|
5
|
+
def initialize(array, owner, key)
|
6
|
+
|
7
|
+
if array.kind_of?(HasManyArray)
|
8
|
+
array = array.target_array
|
9
|
+
end
|
10
|
+
|
11
|
+
super(array)
|
12
|
+
|
13
|
+
@owner = owner
|
14
|
+
@key = key
|
15
|
+
end
|
16
|
+
|
17
|
+
[:<<, :push, :delete, :delete_at].each do |method_name|
|
18
|
+
define_method(method_name) do |arg|
|
19
|
+
target_array.send(method_name, arg)
|
20
|
+
notify_owner
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
[:clear, :shift].each do |method_name|
|
25
|
+
define_method(method_name) do
|
26
|
+
target_array.send(method_name)
|
27
|
+
notify_owner
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def target_array
|
34
|
+
__getobj__
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :owner, :key
|
40
|
+
|
41
|
+
def notify_owner
|
42
|
+
owner.has_many_changed(self, key)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Hat
|
4
|
+
module Model
|
5
|
+
module Transformers
|
6
|
+
module DateTimeTransformer
|
7
|
+
|
8
|
+
def self.from_raw(value)
|
9
|
+
if value && value.kind_of?(String)
|
10
|
+
::DateTime.parse(value)
|
11
|
+
elsif value.kind_of?(DateTime) || value.kind_of?(Date) || value.kind_of?(Time)
|
12
|
+
value
|
13
|
+
elsif value == nil
|
14
|
+
nil
|
15
|
+
else
|
16
|
+
raise TransformError, "Cannot transform #{value.class.name} to DateTime"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.to_raw(date_time)
|
21
|
+
if date_time
|
22
|
+
date_time.iso8601
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative 'date_time_transformer'
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
module Hat
|
5
|
+
module Model
|
6
|
+
module Transformers
|
7
|
+
class Registry
|
8
|
+
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
def from_raw(type, value)
|
12
|
+
if transformer = get(type)
|
13
|
+
transformer.from_raw(value)
|
14
|
+
else
|
15
|
+
value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_raw(type, value)
|
20
|
+
if transformer = get(type)
|
21
|
+
transformer.to_raw(value)
|
22
|
+
else
|
23
|
+
value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def get(type)
|
28
|
+
registry[type.to_sym]
|
29
|
+
end
|
30
|
+
|
31
|
+
def register(type, transformer)
|
32
|
+
if existing_transformer = get(type)
|
33
|
+
raise "'#{type}' has already been registered as #{existing_transformer.name}"
|
34
|
+
else
|
35
|
+
registry[type.to_sym] = transformer
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def registry
|
42
|
+
@registry ||= {}
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
class TransformError < StandardError ; end
|
48
|
+
|
49
|
+
#Register Common Transformers
|
50
|
+
Registry.instance.register(:date_time, Transformers::DateTimeTransformer)
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
data/lib/hat/model.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require "active_support/core_ext/class/attribute"
|
2
|
+
require "active_support/json"
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
require_relative '../base_json_serializer'
|
5
|
+
require_relative 'relation_loader'
|
6
|
+
|
7
|
+
module Hat
|
8
|
+
module Nesting
|
9
|
+
module JsonSerializer
|
10
|
+
|
11
|
+
include Hat::BaseJsonSerializer
|
12
|
+
|
13
|
+
def as_json(*args)
|
14
|
+
|
15
|
+
result[root_key] = Array(serializable).map do |serializable_item|
|
16
|
+
serializer = self.class.new(serializable_item, { result: {} })
|
17
|
+
serializer.includes(*relation_includes).serialize
|
18
|
+
end
|
19
|
+
|
20
|
+
result.merge({ meta: meta_data.merge(includes_meta_data) })
|
21
|
+
end
|
22
|
+
|
23
|
+
def serialize
|
24
|
+
assert_id_present(serializable)
|
25
|
+
attribute_hash.merge(relation_loader.relation_hash)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def relation_loader
|
31
|
+
@relation_loader ||= Relationloader.new({
|
32
|
+
serializable: serializable,
|
33
|
+
has_manys: has_manys,
|
34
|
+
has_ones: has_ones,
|
35
|
+
relation_includes: relation_includes
|
36
|
+
})
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.included(serializer_class)
|
40
|
+
JsonSerializerDsl.apply_to(serializer_class)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Hat
|
2
|
+
module Nesting
|
3
|
+
class Relationloader
|
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
|
+
end
|
11
|
+
|
12
|
+
def relation_hash
|
13
|
+
has_one_hash.merge(has_many_hash)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :serializable, :has_ones, :has_manys, :relation_includes
|
19
|
+
|
20
|
+
def has_one_hash
|
21
|
+
has_ones.inject({}) { |has_one_hash, attr_name| serialize_has_one_relation(has_one_hash, attr_name) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def serialize_has_one_relation(has_one_hash, attr_name)
|
25
|
+
|
26
|
+
id_attr = "#{attr_name}_id"
|
27
|
+
|
28
|
+
if related_item = relation_for(attr_name)
|
29
|
+
has_one_hash[attr_name] = serialize_nested_item_with_includes(related_item, includes_for_attr(attr_name))
|
30
|
+
elsif serializable.respond_to?(id_attr)
|
31
|
+
has_one_hash[id_attr] = serializable.send(id_attr)
|
32
|
+
else
|
33
|
+
has_one_hash[id_attr] = serializable.send(attr_name).try(:id)
|
34
|
+
end
|
35
|
+
|
36
|
+
has_one_hash
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def has_many_hash
|
42
|
+
has_manys.inject({}) { |has_many_hash, attr_name| serialize_has_many_relations(has_many_hash, attr_name) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def serialize_has_many_relations(has_many_hash, attr_name)
|
46
|
+
if related_items = relation_for(attr_name)
|
47
|
+
has_many_hash[attr_name] = related_items.map do |related_item|
|
48
|
+
serialize_nested_item_with_includes(related_item, includes_for_attr(attr_name))
|
49
|
+
end
|
50
|
+
else
|
51
|
+
has_many_relation = serializable.send(attr_name) || []
|
52
|
+
has_many_hash["#{attr_name.to_s.singularize}_ids"] = has_many_relation.map(&:id)
|
53
|
+
end
|
54
|
+
|
55
|
+
has_many_hash
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
def relation_for(attr_name)
|
60
|
+
serializable.send(attr_name) if relation_includes.includes_relation?(attr_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
def serialize_nested_item_with_includes(serializable_item, includes)
|
64
|
+
|
65
|
+
serializer = serializer_for(serializable_item)
|
66
|
+
|
67
|
+
serializer.includes(*includes).serialize
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
def includes_for_attr(attr_name)
|
72
|
+
relation_includes.nested_includes_for(attr_name) || []
|
73
|
+
end
|
74
|
+
|
75
|
+
def serializer_for(serializable_item)
|
76
|
+
|
77
|
+
serializer_class_for(serializable_item).new(serializable_item, {
|
78
|
+
result: {}
|
79
|
+
})
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
def serializer_class_for(serializable)
|
84
|
+
Hat::SerializerRegistry.instance.get(:nesting, serializable.class)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|