jat 0.0.3 → 0.0.5

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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jat/attribute.rb +36 -4
  3. data/lib/jat/plugins/_activerecord_preloads/_activerecord_preloads.rb +7 -3
  4. data/lib/jat/plugins/_activerecord_preloads/lib/preloader.rb +7 -13
  5. data/lib/jat/plugins/_lower_camel_case/_lower_camel_case.rb +32 -0
  6. data/lib/jat/plugins/_preloads/_preloads.rb +8 -2
  7. data/lib/jat/plugins/cache/cache.rb +9 -5
  8. data/lib/jat/plugins/json_api/json_api.rb +145 -105
  9. data/lib/jat/plugins/json_api/lib/fields_param_parser.rb +40 -0
  10. data/lib/jat/plugins/json_api/lib/include_param_parser.rb +84 -0
  11. data/lib/jat/plugins/json_api/lib/map.rb +92 -27
  12. data/lib/jat/plugins/json_api/lib/params/fields/validate.rb +8 -5
  13. data/lib/jat/plugins/json_api/lib/response.rb +84 -196
  14. data/lib/jat/plugins/json_api/lib/response_piece.rb +166 -0
  15. data/lib/jat/plugins/json_api_activerecord/json_api_activerecord.rb +31 -0
  16. data/lib/jat/plugins/{_json_api_activerecord → json_api_activerecord}/lib/preloads.rb +16 -24
  17. data/lib/jat/plugins/json_api_lower_camel_case/json_api_lower_camel_case.rb +30 -0
  18. data/lib/jat/plugins/json_api_maps_cache/json_api_maps_cache.rb +54 -0
  19. data/lib/jat/plugins/json_api_validate_params/json_api_validate_params.rb +57 -0
  20. data/lib/jat/plugins/json_api_validate_params/lib/params_error.rb +6 -0
  21. data/lib/jat/plugins/json_api_validate_params/lib/validate_fields_param.rb +59 -0
  22. data/lib/jat/plugins/json_api_validate_params/lib/validate_include_param.rb +33 -0
  23. data/lib/jat/plugins/simple_api/lib/fields_param_parser.rb +97 -0
  24. data/lib/jat/plugins/simple_api/lib/map.rb +80 -10
  25. data/lib/jat/plugins/simple_api/lib/response.rb +78 -89
  26. data/lib/jat/plugins/simple_api/lib/response_piece.rb +84 -0
  27. data/lib/jat/plugins/simple_api/simple_api.rb +83 -24
  28. data/lib/jat/plugins/simple_api_activerecord/lib/preloads.rb +55 -0
  29. data/lib/jat/plugins/simple_api_activerecord/simple_api_activerecord.rb +31 -0
  30. data/lib/jat/plugins/simple_api_lower_camel_case/simple_api_lower_camel_case.rb +30 -0
  31. data/lib/jat/plugins/simple_api_maps_cache/simple_api_maps_cache.rb +48 -0
  32. data/lib/jat/plugins/simple_api_validate_params/lib/fields_error.rb +6 -0
  33. data/lib/jat/plugins/simple_api_validate_params/lib/validate_fields_param.rb +45 -0
  34. data/lib/jat/plugins/simple_api_validate_params/simple_api_validate_params.rb +45 -0
  35. data/lib/jat/plugins/to_str/to_str.rb +10 -4
  36. data/lib/jat/plugins.rb +3 -16
  37. data/lib/jat.rb +28 -30
  38. data/test/lib/jat/attribute_test.rb +15 -5
  39. data/test/lib/jat/plugins/_activerecord_preloads/_activerecord_preloads_test.rb +34 -15
  40. data/test/lib/jat/plugins/_activerecord_preloads/lib/preloader_test.rb +10 -24
  41. data/test/lib/jat/plugins/_camel_lower/_camel_lower_test.rb +26 -0
  42. data/test/lib/jat/plugins/_preloads/lib/format_user_preloads_test.rb +1 -1
  43. data/test/lib/jat/plugins/_preloads/lib/preloads_with_path_test.rb +1 -1
  44. data/test/lib/jat/plugins/cache/cache_test.rb +11 -11
  45. data/test/lib/jat/plugins/json_api/json_api_test.rb +63 -47
  46. data/test/lib/jat/plugins/json_api/lib/{params/fields_test.rb → fields_param_parser_test.rb} +7 -6
  47. data/test/lib/jat/plugins/json_api/lib/{params/include_test.rb → include_param_parser_test.rb} +4 -4
  48. data/test/lib/jat/plugins/json_api/lib/map_test.rb +150 -79
  49. data/test/lib/jat/plugins/json_api/lib/response_test.rb +32 -32
  50. data/test/lib/jat/plugins/{_json_api_activerecord/_json_api_activerecord_test.rb → json_api_activerecord/json_api_activerecord_test.rb} +14 -5
  51. data/test/lib/jat/plugins/{_json_api_activerecord → json_api_activerecord}/lib/preloads_test.rb +11 -10
  52. data/test/lib/jat/plugins/json_api_camel_lower/json_api_camel_lower_test.rb +79 -0
  53. data/test/lib/jat/plugins/json_api_maps_cache/json_api_maps_cache_test.rb +107 -0
  54. data/test/lib/jat/plugins/json_api_validate_params/json_api_validate_params_test.rb +84 -0
  55. data/test/lib/jat/plugins/simple_api/lib/{params/parse_test.rb → fields_param_parser_test.rb} +10 -4
  56. data/test/lib/jat/plugins/simple_api/lib/map_test.rb +111 -34
  57. data/test/lib/jat/plugins/simple_api/lib/response_test.rb +80 -74
  58. data/test/lib/jat/plugins/simple_api/simple_api_test.rb +91 -25
  59. data/test/lib/jat/plugins/simple_api_activerecord/lib/preloads_test.rb +135 -0
  60. data/test/lib/jat/plugins/simple_api_activerecord/simple_api_activerecord_test.rb +38 -0
  61. data/test/lib/jat/plugins/simple_api_camel_lower/simple_api_camel_lower_test.rb +48 -0
  62. data/test/lib/jat/plugins/simple_api_maps_cache/simple_api_maps_cache_test.rb +95 -0
  63. data/test/lib/jat/plugins/simple_api_validate_params/simple_api_validate_params_test.rb +89 -0
  64. data/test/lib/jat/plugins/to_str/to_str_test.rb +3 -3
  65. data/test/lib/jat_test.rb +47 -24
  66. data/test/lib/plugin_test.rb +3 -3
  67. data/test/test_helper.rb +0 -3
  68. data/test/test_plugin.rb +9 -12
  69. metadata +60 -71
  70. data/CHANGELOG.md +0 -7
  71. data/README.md +0 -21
  72. data/jat.gemspec +0 -37
  73. data/lib/jat/plugins/_json_api_activerecord/_json_api_activerecord.rb +0 -22
  74. data/lib/jat/plugins/camel_lower/camel_lower.rb +0 -18
  75. data/lib/jat/plugins/json_api/lib/construct_traversal_map.rb +0 -91
  76. data/lib/jat/plugins/json_api/lib/presenters/document_links_presenter.rb +0 -48
  77. data/lib/jat/plugins/json_api/lib/presenters/document_meta_presenter.rb +0 -48
  78. data/lib/jat/plugins/json_api/lib/presenters/jsonapi_presenter.rb +0 -48
  79. data/lib/jat/plugins/json_api/lib/presenters/links_presenter.rb +0 -48
  80. data/lib/jat/plugins/json_api/lib/presenters/meta_presenter.rb +0 -48
  81. data/lib/jat/plugins/json_api/lib/presenters/relationship_links_presenter.rb +0 -53
  82. data/lib/jat/plugins/json_api/lib/presenters/relationship_meta_presenter.rb +0 -53
  83. data/lib/jat/plugins/json_api/lib/traversal_map.rb +0 -34
  84. data/lib/jat/plugins/simple_api/lib/construct_traversal_map.rb +0 -45
  85. data/lib/jat/plugins/simple_api/lib/params/parse.rb +0 -68
  86. data/lib/jat/presenter.rb +0 -51
  87. data/test/lib/jat/plugins/camel_lower/camel_lower_test.rb +0 -78
  88. data/test/lib/jat/plugins/json_api/lib/construct_traversal_map_test.rb +0 -119
  89. data/test/lib/jat/plugins/json_api/lib/params/fields/parse_test.rb +0 -24
  90. data/test/lib/jat/plugins/json_api/lib/params/fields/validate_test.rb +0 -47
  91. data/test/lib/jat/plugins/json_api/lib/params/include/parse_test.rb +0 -46
  92. data/test/lib/jat/plugins/json_api/lib/params/include/validate_test.rb +0 -51
  93. data/test/lib/jat/plugins/json_api/lib/presenters/document_links_presenter_test.rb +0 -69
  94. data/test/lib/jat/plugins/json_api/lib/presenters/document_meta_presenter_test.rb +0 -69
  95. data/test/lib/jat/plugins/json_api/lib/presenters/jsonapi_presenter_test.rb +0 -69
  96. data/test/lib/jat/plugins/json_api/lib/presenters/links_presenter_test.rb +0 -69
  97. data/test/lib/jat/plugins/json_api/lib/presenters/meta_presenter_test.rb +0 -69
  98. data/test/lib/jat/plugins/json_api/lib/presenters/relationship_links_presenter_test.rb +0 -75
  99. data/test/lib/jat/plugins/json_api/lib/presenters/relationship_meta_presenter_test.rb +0 -75
  100. data/test/lib/jat/plugins/json_api/lib/traversal_map_test.rb +0 -58
  101. data/test/lib/jat/plugins/simple_api/lib/construct_traversal_map_test.rb +0 -100
  102. data/test/lib/jat/presenter_test.rb +0 -61
@@ -1,28 +1,98 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "./params/parse"
4
- require_relative "./construct_traversal_map"
5
-
6
3
  class Jat
7
4
  module Plugins
8
5
  module SimpleApi
9
6
  class Map
10
- class << self
7
+ module ClassMethods
8
+ # Returns the Jat class that this Map class is namespaced under.
9
+ attr_accessor :jat_class
10
+
11
+ # Since Map is anonymously subclassed when Jat is subclassed,
12
+ # and then assigned to a constant of the Jat subclass, make inspect
13
+ # reflect the likely name for the class.
14
+ def inspect
15
+ "#{jat_class.inspect}::Map"
16
+ end
17
+
11
18
  # Returns structure like
12
19
  # {
13
20
  # key1 => { key11 => {}, key12 => { ... } },
14
21
  # key2 => { key21 => {}, key22 => { ... } },
15
22
  # }
16
- def call(jat)
17
- params = jat.context[:params]
18
- fields = params && (params[:fields] || params["fields"])
19
- exposed = jat.context[:exposed] || :default
23
+ def call(context)
24
+ exposed = context[:exposed]&.to_sym || :default
25
+ fields = context[:fields]
26
+
27
+ construct_map(exposed, fields)
28
+ end
29
+
30
+ private
31
+
32
+ def construct_map(exposed, fields)
33
+ fields = jat_class::FieldsParamParser.parse(fields) if fields
34
+ new(exposed, fields).to_h
35
+ end
36
+ end
37
+
38
+ module InstanceMethods
39
+ attr_reader :exposed, :fields
40
+
41
+ EXPOSED_TYPES = {all: :all, default: :default, none: :none}.freeze
42
+
43
+ def initialize(exposed, fields)
44
+ @exposed = EXPOSED_TYPES.fetch(exposed)
45
+ @fields = fields
46
+ end
47
+
48
+ def to_h
49
+ map_for(self.class.jat_class, fields)
50
+ end
51
+
52
+ def map_for(serializer, fields, stack = [])
53
+ serializer.attributes.each_with_object({}) do |name_attr, result|
54
+ name = name_attr[0]
55
+ attribute = name_attr[1]
56
+ next unless expose?(attribute, fields)
20
57
 
21
- manually_exposed = Params::Parse.new(fields).parse
58
+ raise Error, recursive_error_message(stack, name) if stack.any?(name_attr)
59
+ stack << name_attr
22
60
 
23
- ConstructTraversalMap.new(jat.class, exposed, manually_exposed: manually_exposed).to_h
61
+ result[name] =
62
+ if attribute.relation?
63
+ nested_serializer = attribute.serializer.call
64
+ nested_fields = fields&.[](name)
65
+ map_for(nested_serializer, nested_fields, stack)
66
+ else
67
+ FROZEN_EMPTY_HASH
68
+ end
69
+
70
+ stack.pop
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def expose?(attribute, fields)
77
+ case exposed
78
+ when :all then true
79
+ when :none then manually_exposed?(attribute, fields)
80
+ else attribute.exposed? || manually_exposed?(attribute, fields)
81
+ end
82
+ end
83
+
84
+ def manually_exposed?(attribute, fields)
85
+ fields&.include?(attribute.name)
86
+ end
87
+
88
+ def recursive_error_message(stack, name)
89
+ recursion = (stack.map!(&:first) << name).join(" -> ")
90
+ "Recursive serialization: #{recursion}"
24
91
  end
25
92
  end
93
+
94
+ extend ClassMethods
95
+ include InstanceMethods
26
96
  end
27
97
  end
28
98
  end
@@ -5,129 +5,118 @@ class Jat
5
5
  module Plugins
6
6
  module SimpleApi
7
7
  class Response
8
- attr_reader :jat, :jat_class, :object, :context
8
+ module ClassMethods
9
+ # Returns the Jat class that this Response class is namespaced under.
10
+ attr_accessor :jat_class
11
+
12
+ # Since Response is anonymously subclassed when Jat is subclassed,
13
+ # and then assigned to a constant of the Jat subclass, make inspect
14
+ # reflect the likely name for the class.
15
+ def inspect
16
+ "#{jat_class.inspect}::Response"
17
+ end
9
18
 
10
- def initialize(jat)
11
- @jat = jat
12
- @jat_class = jat.class
13
- @object = jat.object
14
- @context = jat.context
19
+ def call(object, context)
20
+ new(object, context).to_h
21
+ end
15
22
  end
16
23
 
17
- def response
18
- # Add main response
19
- result = many? ? many(object) : one(object)
20
- result = {root_key => result} if root_key
21
- result ||= {}
24
+ module InstanceMethods
25
+ attr_reader :object, :context, :jat_class
22
26
 
23
- # Add metadata to response
24
- # We can add metadata to empty response, or to top-level namespace
25
- # We should not mix metadata with object attributes
26
- metadata.tap do |meta|
27
- next if meta.empty?
28
- raise Error, "Response must have a root key to add metadata" if !result.empty? && !root_key
29
- result[meta_key] = meta
27
+ def initialize(object, context)
28
+ @object = object
29
+ @context = context
30
+ @jat_class = self.class.jat_class
30
31
  end
31
32
 
32
- result
33
- end
33
+ def to_h
34
+ # Add main response
35
+ is_many = many?
36
+ root = root_key(is_many)
34
37
 
35
- private
38
+ response = is_many ? many(object) : one(object)
39
+ response = {root => response} if root
40
+ response ||= {}
36
41
 
37
- def metadata
38
- result = context[:meta] || {}
42
+ add_metadata(response, root)
39
43
 
40
- config_meta = jat_class.config[:meta]
41
- return result unless config_meta
44
+ response
45
+ end
42
46
 
43
- config_meta.each_with_object(result) do |(key, value), res|
44
- next if res.key?(key) # do not overwrite manually added meta
47
+ private
45
48
 
46
- value = value.call(object, context) if value.respond_to?(:call)
47
- res[key] = value unless value.nil?
49
+ def many(objects)
50
+ objects.map { |obj| one(obj) }
48
51
  end
49
- end
50
-
51
- def many(objects)
52
- objects.map { |obj| one(obj) }
53
- end
54
52
 
55
- def one(obj)
56
- ResponseData.new(jat_class, obj, context, jat.traversal_map).data
57
- end
53
+ def one(obj)
54
+ map = jat_class.map(context)
55
+ jat_class::ResponsePiece.to_h(obj, context, map)
56
+ end
58
57
 
59
- def many?
60
- @is_many ||= begin
58
+ def many?
61
59
  many = context[:many]
62
60
  many.nil? ? object.is_a?(Enumerable) : many
63
61
  end
64
- end
65
62
 
66
- # We can provide nil or false to remove root
67
- def root_key
68
- @root_key ||=
63
+ # We can provide nil or false to remove root
64
+ def root_key(is_many)
69
65
  if context.key?(:root)
70
- context[:root]
66
+ root = context[:root]
67
+ root ? root.to_sym : root
71
68
  else
72
- (many? ? jat_class.root_for_many : jat_class.root_for_one) || jat_class.root
69
+ config = jat_class.config
70
+ is_many ? config[:root_many] : config[:root_one]
73
71
  end
74
- end
72
+ end
75
73
 
76
- def meta_key
77
- context[:meta_key] || jat_class.meta_key
78
- end
79
- end
74
+ # Add metadata to response
75
+ # We can add metadata whether to empty response or to top-level namespace
76
+ # We should not mix metadata with object attributes
77
+ def add_metadata(response, root)
78
+ meta = metadata
79
+ return if meta.empty?
80
80
 
81
- class ResponseData
82
- attr_reader :jat_class, :object, :context, :map, :presenter
81
+ raise Error, "Response must have a root key to add metadata" if !response.empty? && !root
82
+ response[meta_key] = meta
83
+ end
83
84
 
84
- def initialize(jat_class, object, context, map)
85
- @jat_class = jat_class
86
- @object = object
87
- @context = context
88
- @map = map
89
- @presenter = jat_class::Presenter.new(object, context)
90
- end
85
+ def meta_key
86
+ context[:meta_key]&.to_sym || jat_class.config[:meta_key]
87
+ end
88
+
89
+ def metadata
90
+ data = context_metadata
91
91
 
92
- def data
93
- return unless object
92
+ meta = jat_class.added_meta
93
+ return data if meta.empty?
94
94
 
95
- result = {}
95
+ meta.each do |name, attribute|
96
+ next if data.key?(name)
96
97
 
97
- map.each do |key, inner_keys|
98
- attribute = jat_class.attributes.fetch(key)
99
- value = presenter.public_send(attribute.original_name)
98
+ value = attribute_value(attribute)
100
99
 
101
- result[key] =
102
- if attribute.relation?
103
- if many?(attribute, value)
104
- value.map { |obj| response_data(attribute, obj, inner_keys) }
105
- else
106
- response_data(attribute, value, inner_keys)
107
- end
108
- else
109
- value
100
+ unless value.nil?
101
+ data = data.dup if data.equal?(FROZEN_EMPTY_HASH)
102
+ data[name] = value
110
103
  end
111
- end
104
+ end
112
105
 
113
- result
114
- end
106
+ data
107
+ end
115
108
 
116
- private
109
+ def context_metadata
110
+ context[:meta]&.transform_keys(&:to_sym) || FROZEN_EMPTY_HASH
111
+ end
117
112
 
118
- def response_data(attribute, value, map)
119
- ResponseData.new(attribute.serializer.call, value, context, map).data
113
+ def attribute_value(attribute)
114
+ attribute.block.call(object, context)
115
+ end
120
116
  end
121
117
 
122
- def many?(attribute, object)
123
- is_many = attribute.many?
124
-
125
- # handle boolean
126
- return is_many if (is_many == true) || (is_many == false)
127
-
128
- # handle nil
129
- object.is_a?(Enumerable)
130
- end
118
+ extend ClassMethods
119
+ include InstanceMethods
131
120
  end
132
121
  end
133
122
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Serializes to JSON-API format
4
+ class Jat
5
+ module Plugins
6
+ module SimpleApi
7
+ class ResponsePiece
8
+ module ClassMethods
9
+ # Returns the Jat class that this ResponsePiece class is namespaced under.
10
+ attr_accessor :jat_class
11
+
12
+ # Since ResponsePiece is anonymously subclassed when Jat is subclassed,
13
+ # and then assigned to a constant of the Jat subclass, make inspect
14
+ # reflect the likely name for the class.
15
+ def inspect
16
+ "#{jat_class.inspect}::ResponsePiece"
17
+ end
18
+
19
+ def to_h(object, context, map)
20
+ new(object, context).piece(map)
21
+ end
22
+ end
23
+
24
+ module InstanceMethods
25
+ attr_reader :jat_class, :object, :context
26
+
27
+ def initialize(object, context)
28
+ @object = object
29
+ @context = context
30
+ @jat_class = self.class.jat_class
31
+ end
32
+
33
+ def piece(map)
34
+ return unless object
35
+
36
+ result = {}
37
+
38
+ map.each do |key, inner_map|
39
+ attribute = jat_class.attributes.fetch(key)
40
+ value = attribute_value(attribute)
41
+
42
+ result[key] =
43
+ if attribute.relation?
44
+ if many?(attribute, value)
45
+ value.map { |obj| inner_piece(attribute, obj, inner_map) }
46
+ else
47
+ inner_piece(attribute, value, inner_map)
48
+ end
49
+ else
50
+ value
51
+ end
52
+ end
53
+
54
+ result
55
+ end
56
+
57
+ private
58
+
59
+ def attribute_value(attribute)
60
+ attribute.block.call(object, context)
61
+ end
62
+
63
+ def inner_piece(attribute, value, inner_map)
64
+ serializer = attribute.serializer.call
65
+ serializer::ResponsePiece.to_h(value, context, inner_map)
66
+ end
67
+
68
+ def many?(attribute, nested_object)
69
+ is_many = attribute.many?
70
+
71
+ # handle boolean
72
+ return is_many if (is_many == true) || (is_many == false)
73
+
74
+ # handle nil
75
+ nested_object.is_a?(Enumerable)
76
+ end
77
+ end
78
+
79
+ extend ClassMethods
80
+ include InstanceMethods
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,61 +1,120 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "./lib/fields_param_parser"
3
4
  require_relative "./lib/map"
4
5
  require_relative "./lib/response"
6
+ require_relative "./lib/response_piece"
5
7
 
6
- # Serializes to JSON-API format
7
8
  class Jat
8
9
  module Plugins
9
10
  module SimpleApi
10
11
  DEFAULT_META_KEY = :meta
11
12
 
13
+ def self.before_load(jat_class, **_opts)
14
+ response_plugin = jat_class.config[:response_plugin_loaded]
15
+ return unless response_plugin
16
+
17
+ raise Error, "Response plugin `#{response_plugin}` was already loaded before"
18
+ end
19
+
20
+ def self.load(jat_class, **_opts)
21
+ jat_class.include(InstanceMethods)
22
+ jat_class.extend(ClassMethods)
23
+ end
24
+
12
25
  def self.after_load(jat_class, **opts)
13
- jat_class.plugin(:_json_api_activerecord, **opts) if opts[:activerecord]
26
+ jat_class.config[:response_plugin_loaded] = :simple_api
27
+ jat_class.plugin(:simple_api_activerecord, **opts) if opts[:activerecord]
28
+
29
+ jat_class.meta_key(DEFAULT_META_KEY)
30
+
31
+ fields_parser_class = Class.new(FieldsParamParser)
32
+ fields_parser_class.jat_class = jat_class
33
+ jat_class.const_set(:FieldsParamParser, fields_parser_class)
34
+
35
+ map_class = Class.new(Map)
36
+ map_class.jat_class = jat_class
37
+ jat_class.const_set(:Map, map_class)
38
+
39
+ response_class = Class.new(Response)
40
+ response_class.jat_class = jat_class
41
+ jat_class.const_set(:Response, response_class)
42
+
43
+ response_piece_class = Class.new(ResponsePiece)
44
+ response_piece_class.jat_class = jat_class
45
+ jat_class.const_set(:ResponsePiece, response_piece_class)
14
46
  end
15
47
 
16
48
  module InstanceMethods
17
- def to_h
18
- Response.new(self).response
49
+ def to_h(object)
50
+ self.class::Response.call(object, context)
19
51
  end
20
52
 
21
- def traversal_map
22
- @traversal_map ||= Map.call(self)
53
+ def map
54
+ @map ||= self.class.map(context)
23
55
  end
24
56
  end
25
57
 
26
58
  module ClassMethods
27
59
  def inherited(subclass)
28
- subclass.root(@root) if defined?(@root)
29
-
30
60
  super
61
+
62
+ fields_parser_class = Class.new(self::FieldsParamParser)
63
+ fields_parser_class.jat_class = subclass
64
+ subclass.const_set(:FieldsParamParser, fields_parser_class)
65
+
66
+ map_class = Class.new(self::Map)
67
+ map_class.jat_class = subclass
68
+ subclass.const_set(:Map, map_class)
69
+
70
+ response_class = Class.new(self::Response)
71
+ response_class.jat_class = subclass
72
+ subclass.const_set(:Response, response_class)
73
+
74
+ response_piece_class = Class.new(self::ResponsePiece)
75
+ response_piece_class.jat_class = subclass
76
+ subclass.const_set(:ResponsePiece, response_piece_class)
77
+
78
+ # Assign same meta
79
+ added_meta.each_value do |attribute|
80
+ params = attribute.params
81
+ subclass.attribute(params[:name], **params[:opts], &params[:block])
82
+ end
31
83
  end
32
84
 
33
- def root(new_root = nil)
34
- return (defined?(@root) && @root) unless new_root
85
+ def root(default = nil, one: nil, many: nil)
86
+ root_one = one || default
87
+ root_many = many || default
88
+
89
+ config[:root_one] = root_one ? root_one.to_sym : nil
90
+ config[:root_many] = root_many ? root_many.to_sym : nil
35
91
 
36
- new_root = new_root.to_sym
37
- @root = new_root
92
+ {root_one: root_one, root_many: root_many}
38
93
  end
39
94
 
40
- def root_for_one(new_root_for_one = nil)
41
- return (defined?(@root_for_one) && @root_for_one) unless new_root_for_one
95
+ def meta_key(new_meta_key)
96
+ config[:meta_key] = new_meta_key.to_sym
97
+ end
42
98
 
43
- new_root_for_one = new_root_for_one.to_sym
44
- @root_for_one = new_root_for_one
99
+ def added_meta
100
+ @added_meta ||= {}
45
101
  end
46
102
 
47
- def root_for_many(new_root_for_many = nil)
48
- return (defined?(@root_for_many) && @root_for_many) unless new_root_for_many
103
+ def meta(name, **opts, &block)
104
+ new_attr = self::Attribute.new(name: name, opts: opts, block: block)
105
+ added_meta[new_attr.name] = new_attr
106
+ end
49
107
 
50
- new_root_for_many = new_root_for_many.to_sym
51
- @root_for_many = new_root_for_many
108
+ def map(context)
109
+ self::Map.call(context)
52
110
  end
53
111
 
54
- def meta_key(new_meta_key = nil)
55
- return ((defined?(@meta_key) && @meta_key) || DEFAULT_META_KEY) unless new_meta_key
112
+ def map_full
113
+ @map_full ||= self::Map.call(exposed: :all)
114
+ end
56
115
 
57
- new_meta_key = new_meta_key.to_sym
58
- @meta_key = new_meta_key
116
+ def map_exposed
117
+ @map_exposed ||= self::Map.call(exposed: :default)
59
118
  end
60
119
  end
61
120
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Jat
4
+ module Plugins
5
+ module SimpleApiActiverecord
6
+ class Preloads
7
+ module ClassMethods
8
+ def call(jat)
9
+ result = {}
10
+ append_many(result, jat.class, jat.map)
11
+ result
12
+ end
13
+
14
+ private
15
+
16
+ def append_many(result, jat_class, keys)
17
+ keys.each do |key, inner_keys|
18
+ attribute = jat_class.attributes.fetch(key)
19
+ preloads = attribute.preloads
20
+ next unless preloads
21
+
22
+ append_one(result, jat_class, preloads)
23
+ next if inner_keys.empty?
24
+
25
+ path = attribute.preloads_path
26
+ nested_result = nested(result, path)
27
+ nested_serializer = attribute.serializer.call
28
+
29
+ append_many(nested_result, nested_serializer, inner_keys)
30
+ end
31
+ end
32
+
33
+ def append_one(result, jat_class, preloads)
34
+ return if preloads.empty?
35
+
36
+ preloads = EnumDeepDup.call(preloads)
37
+ merge(result, preloads)
38
+ end
39
+
40
+ def merge(result, preloads)
41
+ result.merge!(preloads) do |_key, value_one, value_two|
42
+ merge(value_one, value_two)
43
+ end
44
+ end
45
+
46
+ def nested(result, path)
47
+ !path || path.empty? ? result : result.dig(*path)
48
+ end
49
+ end
50
+
51
+ extend ClassMethods
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./lib/preloads"
4
+
5
+ class Jat
6
+ module Plugins
7
+ module SimpleApiActiverecord
8
+ def self.before_load(jat_class, **_opts)
9
+ return if jat_class.plugin_used?(:simple_api)
10
+ raise Error, "Please load :simple_api plugin first"
11
+ end
12
+
13
+ def self.load(jat_class, **_opts)
14
+ jat_class.include(InstanceMethods)
15
+ end
16
+
17
+ def self.after_load(jat_class, **opts)
18
+ jat_class.plugin :_preloads, **opts
19
+ jat_class.plugin :_activerecord_preloads, **opts
20
+ end
21
+
22
+ module InstanceMethods
23
+ def preloads
24
+ @preloads ||= Preloads.call(self)
25
+ end
26
+ end
27
+ end
28
+
29
+ register_plugin(:simple_api_activerecord, SimpleApiActiverecord)
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Jat
4
+ module Plugins
5
+ module SimpleApiLowerCamelCase
6
+ def self.before_load(jat_class, **_opts)
7
+ raise Error, "Please load :simple_api plugin first" unless jat_class.plugin_used?(:simple_api)
8
+
9
+ jat_class.plugin :_lower_camel_case
10
+ end
11
+
12
+ def self.load(jat_class, **_opts)
13
+ jat_class::Response.include(ResponseInstanceMethods)
14
+ end
15
+
16
+ module ResponseInstanceMethods
17
+ private
18
+
19
+ def context_metadata
20
+ metadata = super
21
+ return metadata if metadata.empty?
22
+
23
+ metadata.transform_keys! { |key| Jat::LowerCamelCaseTransformation.call(key) }
24
+ end
25
+ end
26
+ end
27
+
28
+ register_plugin(:simple_api_lower_camel_case, SimpleApiLowerCamelCase)
29
+ end
30
+ end