jat 0.0.1 → 0.0.7
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 +4 -4
- data/lib/jat/attribute.rb +107 -0
- data/lib/jat/config.rb +35 -0
- data/lib/jat/plugins/activerecord/activerecord.rb +23 -0
- data/lib/jat/plugins/cache/cache.rb +47 -0
- data/lib/jat/plugins/common/_activerecord_preloads/_activerecord_preloads.rb +38 -0
- data/lib/jat/plugins/common/_activerecord_preloads/lib/preloader.rb +93 -0
- data/lib/jat/plugins/common/_lower_camel_case/_lower_camel_case.rb +36 -0
- data/lib/jat/plugins/common/_preloads/_preloads.rb +63 -0
- data/lib/jat/plugins/common/_preloads/lib/format_user_preloads.rb +52 -0
- data/lib/jat/plugins/common/_preloads/lib/preloads_with_path.rb +78 -0
- data/lib/jat/plugins/json_api/json_api.rb +251 -0
- data/lib/jat/plugins/json_api/lib/fields_param_parser.rb +40 -0
- data/lib/jat/plugins/json_api/lib/include_param_parser.rb +84 -0
- data/lib/jat/plugins/json_api/lib/map.rb +119 -0
- data/lib/jat/plugins/json_api/lib/params/fields/parse.rb +27 -0
- data/lib/jat/plugins/json_api/lib/params/fields/validate.rb +58 -0
- data/lib/jat/plugins/json_api/lib/params/fields.rb +23 -0
- data/lib/jat/plugins/json_api/lib/params/include/parse.rb +55 -0
- data/lib/jat/plugins/json_api/lib/params/include/validate.rb +29 -0
- data/lib/jat/plugins/json_api/lib/params/include.rb +49 -0
- data/lib/jat/plugins/json_api/lib/response.rb +123 -0
- data/lib/jat/plugins/json_api/lib/response_piece.rb +175 -0
- data/lib/jat/plugins/json_api/plugins/json_api_activerecord/json_api_activerecord.rb +23 -0
- data/lib/jat/plugins/json_api/plugins/json_api_lower_camel_case/json_api_lower_camel_case.rb +34 -0
- data/lib/jat/plugins/json_api/plugins/json_api_maps_cache/json_api_maps_cache.rb +58 -0
- data/lib/jat/plugins/json_api/plugins/json_api_preloads/json_api_preloads.rb +38 -0
- data/lib/jat/plugins/json_api/plugins/json_api_preloads/lib/preloads.rb +76 -0
- data/lib/jat/plugins/json_api/plugins/json_api_validate_params/json_api_validate_params.rb +61 -0
- data/lib/jat/plugins/json_api/plugins/json_api_validate_params/lib/params_error.rb +6 -0
- data/lib/jat/plugins/json_api/plugins/json_api_validate_params/lib/validate_fields_param.rb +59 -0
- data/lib/jat/plugins/json_api/plugins/json_api_validate_params/lib/validate_include_param.rb +33 -0
- data/lib/jat/plugins/lower_camel_case/lower_camel_case.rb +23 -0
- data/lib/jat/plugins/maps_cache/maps_cache.rb +23 -0
- data/lib/jat/plugins/preloads/preloads.rb +23 -0
- data/lib/jat/plugins/simple_api/lib/fields_param_parser.rb +97 -0
- data/lib/jat/plugins/simple_api/lib/map.rb +99 -0
- data/lib/jat/plugins/simple_api/lib/response.rb +119 -0
- data/lib/jat/plugins/simple_api/lib/response_piece.rb +80 -0
- data/lib/jat/plugins/simple_api/plugins/simple_api_activerecord/simple_api_activerecord.rb +23 -0
- data/lib/jat/plugins/simple_api/plugins/simple_api_lower_camel_case/simple_api_lower_camel_case.rb +34 -0
- data/lib/jat/plugins/simple_api/plugins/simple_api_maps_cache/simple_api_maps_cache.rb +50 -0
- data/lib/jat/plugins/simple_api/plugins/simple_api_preloads/lib/preloads.rb +55 -0
- data/lib/jat/plugins/simple_api/plugins/simple_api_preloads/simple_api_preloads.rb +38 -0
- data/lib/jat/plugins/simple_api/plugins/simple_api_validate_params/lib/fields_error.rb +6 -0
- data/lib/jat/plugins/simple_api/plugins/simple_api_validate_params/lib/validate_fields_param.rb +45 -0
- data/lib/jat/plugins/simple_api/plugins/simple_api_validate_params/simple_api_validate_params.rb +49 -0
- data/lib/jat/plugins/simple_api/simple_api.rb +125 -0
- data/lib/jat/plugins/to_str/to_str.rb +54 -0
- data/lib/jat/plugins/types/types.rb +54 -0
- data/lib/jat/plugins/validate_params/validate_params.rb +23 -0
- data/lib/jat/plugins.rb +39 -0
- data/lib/jat/utils/enum_deep_dup.rb +29 -0
- data/lib/jat/utils/enum_deep_freeze.rb +19 -0
- data/lib/jat.rb +66 -141
- data/test/lib/jat/attribute_test.rb +152 -0
- data/test/lib/jat/config_test.rb +57 -0
- data/test/lib/jat/plugins/_activerecord_preloads/_activerecord_preloads_test.rb +59 -0
- data/test/lib/jat/plugins/_activerecord_preloads/lib/preloader_test.rb +84 -0
- data/test/lib/jat/plugins/_camel_lower/_camel_lower_test.rb +26 -0
- data/test/lib/jat/plugins/_preloads/_preloads_test.rb +68 -0
- data/test/lib/jat/plugins/_preloads/lib/format_user_preloads_test.rb +47 -0
- data/test/lib/jat/plugins/_preloads/lib/preloads_with_path_test.rb +33 -0
- data/test/lib/jat/plugins/cache/cache_test.rb +82 -0
- data/test/lib/jat/plugins/json_api/json_api_test.rb +162 -0
- data/test/lib/jat/plugins/json_api/lib/fields_param_parser_test.rb +38 -0
- data/test/lib/jat/plugins/json_api/lib/include_param_parser_test.rb +41 -0
- data/test/lib/jat/plugins/json_api/lib/map_test.rb +188 -0
- data/test/lib/jat/plugins/json_api/lib/response_test.rb +489 -0
- data/test/lib/jat/plugins/json_api_activerecord/json_api_activerecord_test.rb +24 -0
- data/test/lib/jat/plugins/json_api_camel_lower/json_api_camel_lower_test.rb +79 -0
- data/test/lib/jat/plugins/json_api_maps_cache/json_api_maps_cache_test.rb +107 -0
- data/test/lib/jat/plugins/json_api_preloads/json_api_preloads_test.rb +37 -0
- data/test/lib/jat/plugins/json_api_preloads/lib/preloads_test.rb +197 -0
- data/test/lib/jat/plugins/json_api_validate_params/json_api_validate_params_test.rb +84 -0
- data/test/lib/jat/plugins/simple_api/lib/fields_param_parser_test.rb +77 -0
- data/test/lib/jat/plugins/simple_api/lib/map_test.rb +133 -0
- data/test/lib/jat/plugins/simple_api/lib/response_test.rb +348 -0
- data/test/lib/jat/plugins/simple_api/simple_api_test.rb +139 -0
- data/test/lib/jat/plugins/simple_api_activerecord/simple_api_activerecord_test.rb +24 -0
- data/test/lib/jat/plugins/simple_api_camel_lower/simple_api_camel_lower_test.rb +48 -0
- data/test/lib/jat/plugins/simple_api_maps_cache/simple_api_maps_cache_test.rb +95 -0
- data/test/lib/jat/plugins/simple_api_preloads/lib/preloads_test.rb +140 -0
- data/test/lib/jat/plugins/simple_api_preloads/simple_api_preloads_test.rb +37 -0
- data/test/lib/jat/plugins/simple_api_validate_params/simple_api_validate_params_test.rb +89 -0
- data/test/lib/jat/plugins/to_str/to_str_test.rb +52 -0
- data/test/lib/jat/plugins/types/types_test.rb +79 -0
- data/test/lib/jat/utils/enum_deep_dup_test.rb +31 -0
- data/test/lib/jat/utils/enum_deep_freeze_test.rb +28 -0
- data/test/lib/jat_test.rb +143 -0
- data/test/lib/plugin_test.rb +49 -0
- data/test/support/activerecord.rb +24 -0
- data/test/test_helper.rb +13 -0
- data/test/test_plugin.rb +56 -0
- metadata +243 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e4b14981546d524f665e1d268857bd8b2caafec9af07157a1f57fd2ca8f9c83c
|
|
4
|
+
data.tar.gz: 1335c436d0e4ae1f70fb70a9ef5757610e09ce3b5a03b87ba356d31625af0ab3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fcaccbe3cb5e5a27cd5700e3677886fb9966ef8ce2775bf2be6638578489747b8a488acbd3f6f7f55752323c40bc92410af1d079dfeefe2d4b41d4f768fbfbfb
|
|
7
|
+
data.tar.gz: 23a2cbda699f9e8f2a9588dc8cfb4350bbd1a1994603577bd3649a356148ad88d2bd754529bb63905aba59a993cc747c2e5ad762038fb571d5d83b1ecc6d40b4
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "utils/enum_deep_dup"
|
|
4
|
+
require_relative "utils/enum_deep_freeze"
|
|
5
|
+
|
|
6
|
+
class Jat
|
|
7
|
+
class Attribute
|
|
8
|
+
module InstanceMethods
|
|
9
|
+
attr_reader :params, :opts
|
|
10
|
+
|
|
11
|
+
def initialize(name:, opts: {}, block: nil)
|
|
12
|
+
@opts = EnumDeepDup.call(opts)
|
|
13
|
+
@params = EnumDeepFreeze.call(name: name, opts: @opts, block: block)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Attribute name that was provided when initializing attribute
|
|
17
|
+
def original_name
|
|
18
|
+
@original_name ||= params.fetch(:name).to_sym
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Object method name to get attribute value
|
|
22
|
+
def key
|
|
23
|
+
@key ||= opts.key?(:key) ? opts[:key].to_sym : original_name
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Attribute name that will be used in serialized response
|
|
27
|
+
def name
|
|
28
|
+
@name ||= original_name
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Checks if attribute is exposed
|
|
32
|
+
def exposed?
|
|
33
|
+
return @exposed if instance_variable_defined?(:@exposed)
|
|
34
|
+
|
|
35
|
+
@exposed =
|
|
36
|
+
case self.class.jat_class.config[:exposed]
|
|
37
|
+
when :all then opts.fetch(:exposed, true)
|
|
38
|
+
when :none then opts.fetch(:exposed, false)
|
|
39
|
+
else opts.fetch(:exposed, !relation?)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def many?
|
|
44
|
+
return @many if instance_variable_defined?(:@many)
|
|
45
|
+
|
|
46
|
+
@many = opts[:many]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def relation?
|
|
50
|
+
return @relation if instance_variable_defined?(:@relation)
|
|
51
|
+
|
|
52
|
+
@relation = opts.key?(:serializer)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def serializer
|
|
56
|
+
return @serializer if instance_variable_defined?(:@serializer)
|
|
57
|
+
|
|
58
|
+
@serializer = opts[:serializer]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def block
|
|
62
|
+
return @block if instance_variable_defined?(:@block)
|
|
63
|
+
|
|
64
|
+
current_block = params.fetch(:block).tap { |bl| check_block_valid(bl) if bl }
|
|
65
|
+
current_block ||= keyword_block
|
|
66
|
+
|
|
67
|
+
@block = current_block
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def value(object, context)
|
|
71
|
+
block.call(object, context)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def keyword_block
|
|
77
|
+
key_method_name = key
|
|
78
|
+
proc { |object| object.public_send(key_method_name) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def check_block_valid(block)
|
|
82
|
+
raise Error, "Block must be a Proc (not lambda)" if block.lambda?
|
|
83
|
+
|
|
84
|
+
params = block.parameters
|
|
85
|
+
raise Error, "Block can have 0-2 parameters" if params.count > 2
|
|
86
|
+
|
|
87
|
+
valid_params_types = params.all? { |param| param[0] == :opt }
|
|
88
|
+
raise Error, "Block parameters must be optional and no keyword parameters" unless valid_params_types
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
module ClassMethods
|
|
93
|
+
# Returns the Jat class that this Attribute class is namespaced under.
|
|
94
|
+
attr_accessor :jat_class
|
|
95
|
+
|
|
96
|
+
# Since Attribute is anonymously subclassed when Jat is subclassed,
|
|
97
|
+
# and then assigned to a constant of the Jat subclass, make inspect
|
|
98
|
+
# reflect the likely name for the class.
|
|
99
|
+
def inspect
|
|
100
|
+
"#{jat_class.inspect}::Attribute"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
extend ClassMethods
|
|
105
|
+
include InstanceMethods
|
|
106
|
+
end
|
|
107
|
+
end
|
data/lib/jat/config.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
require_relative "utils/enum_deep_dup"
|
|
5
|
+
|
|
6
|
+
class Jat
|
|
7
|
+
class Config
|
|
8
|
+
module InstanceMethods
|
|
9
|
+
extend Forwardable
|
|
10
|
+
|
|
11
|
+
attr_reader :opts
|
|
12
|
+
|
|
13
|
+
def initialize(opts = {})
|
|
14
|
+
@opts = EnumDeepDup.call(opts)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def_delegators :@opts, :[], :[]=, :fetch
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module ClassMethods
|
|
21
|
+
# Returns the Jat class that this config class is namespaced under.
|
|
22
|
+
attr_accessor :jat_class
|
|
23
|
+
|
|
24
|
+
# Since Config is anonymously subclassed when Jat is subclassed,
|
|
25
|
+
# and then assigned to a constant of the Jat subclass, make inspect
|
|
26
|
+
# reflect the likely name for the class.
|
|
27
|
+
def inspect
|
|
28
|
+
"#{jat_class.inspect}::Config"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
include InstanceMethods
|
|
33
|
+
extend ClassMethods
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Jat
|
|
4
|
+
module Plugins
|
|
5
|
+
module Activerecord
|
|
6
|
+
def self.plugin_name
|
|
7
|
+
:activerecord
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.load(jat_class, **opts)
|
|
11
|
+
if jat_class.plugin_used?(:json_api)
|
|
12
|
+
jat_class.plugin :json_api_activerecord, **opts
|
|
13
|
+
elsif jat_class.plugin_used?(:simple_api)
|
|
14
|
+
jat_class.plugin :simple_api_activerecord, **opts
|
|
15
|
+
else
|
|
16
|
+
raise Error, "Please load :json_api or :simple_api plugin first"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
register_plugin(Activerecord.plugin_name, Activerecord)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Jat
|
|
4
|
+
module Plugins
|
|
5
|
+
module Cache
|
|
6
|
+
def self.plugin_name
|
|
7
|
+
:cache
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.before_load(jat_class, **opts)
|
|
11
|
+
jat_class.plugin :to_str, **opts
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.load(jat_class, **_opts)
|
|
15
|
+
jat_class.include(InstanceMethods)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module InstanceMethods
|
|
19
|
+
FORMAT_TO_STR = :to_str
|
|
20
|
+
FORMAT_TO_H = :to_h
|
|
21
|
+
|
|
22
|
+
def to_h(object)
|
|
23
|
+
return super if context[:_format] == FORMAT_TO_STR
|
|
24
|
+
|
|
25
|
+
context[:_format] = FORMAT_TO_H
|
|
26
|
+
cached(object) { super }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_str(object)
|
|
30
|
+
context[:_format] = FORMAT_TO_STR
|
|
31
|
+
cached(object) { super }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def cached(object, &block)
|
|
37
|
+
cache = context[:cache]
|
|
38
|
+
return yield unless cache
|
|
39
|
+
|
|
40
|
+
cache.call(object, context, &block)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
register_plugin(Cache.plugin_name, Cache)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "./lib/preloader"
|
|
4
|
+
|
|
5
|
+
class Jat
|
|
6
|
+
module Plugins
|
|
7
|
+
module ActiverecordPreloads
|
|
8
|
+
def self.plugin_name
|
|
9
|
+
:_activerecord_preloads
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.load(jat_class, **_opts)
|
|
13
|
+
jat_class.include(InstanceMethods)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module InstanceMethods
|
|
17
|
+
def to_h(object)
|
|
18
|
+
object = add_preloads(object)
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def add_preloads(obj)
|
|
25
|
+
return obj if obj.nil? || (obj.is_a?(Array) && obj.empty?)
|
|
26
|
+
|
|
27
|
+
# preloads() method comes from simple_api_activerecord or json_api_activerecord plugin
|
|
28
|
+
preloads = preloads()
|
|
29
|
+
return obj if preloads.empty?
|
|
30
|
+
|
|
31
|
+
Preloader.preload(obj, preloads)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
register_plugin(ActiverecordPreloads.plugin_name, ActiverecordPreloads)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Jat
|
|
4
|
+
module Plugins
|
|
5
|
+
module ActiverecordPreloads
|
|
6
|
+
class Preloader
|
|
7
|
+
module ClassMethods
|
|
8
|
+
def preload(object, preloads)
|
|
9
|
+
preload_handler = handlers.find { |handler| handler.fit?(object) }
|
|
10
|
+
raise Error, "Can't preload #{preloads.inspect} to #{object.inspect}" unless preload_handler
|
|
11
|
+
|
|
12
|
+
preload_handler.preload(object, preloads)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def handlers
|
|
16
|
+
@handlers ||= [ActiverecordRelation, ActiverecordObject, ActiverecordArray].freeze
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
extend ClassMethods
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Loader
|
|
24
|
+
def self.call(records, associations)
|
|
25
|
+
if ActiveRecord::VERSION::MAJOR >= 7
|
|
26
|
+
ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call
|
|
27
|
+
else
|
|
28
|
+
ActiveRecord::Associations::Preloader.new.preload(records, associations)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class ActiverecordObject
|
|
34
|
+
module ClassMethods
|
|
35
|
+
def fit?(object)
|
|
36
|
+
object.is_a?(ActiveRecord::Base)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def preload(object, preloads)
|
|
40
|
+
Loader.call([object], preloads)
|
|
41
|
+
object
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
extend ClassMethods
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class ActiverecordRelation
|
|
49
|
+
module ClassMethods
|
|
50
|
+
def fit?(objects)
|
|
51
|
+
objects.is_a?(ActiveRecord::Relation)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def preload(objects, preloads)
|
|
55
|
+
if objects.loaded?
|
|
56
|
+
array_objects = objects.to_a
|
|
57
|
+
Loader.call(array_objects, preloads)
|
|
58
|
+
objects
|
|
59
|
+
else
|
|
60
|
+
objects.preload(preloads).load
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
extend ClassMethods
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class ActiverecordArray
|
|
69
|
+
module ClassMethods
|
|
70
|
+
def fit?(objects)
|
|
71
|
+
objects.is_a?(Array) &&
|
|
72
|
+
ActiverecordObject.fit?(objects.first) &&
|
|
73
|
+
same_kind?(objects)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def preload(objects, preloads)
|
|
77
|
+
Loader.call(objects, preloads)
|
|
78
|
+
objects
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def same_kind?(objects)
|
|
84
|
+
first_object_class = objects.first.class
|
|
85
|
+
objects.all? { |object| object.instance_of?(first_object_class) }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
extend ClassMethods
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Jat
|
|
4
|
+
module Plugins
|
|
5
|
+
module LowerCamelCase
|
|
6
|
+
def self.plugin_name
|
|
7
|
+
:_lower_camel_case
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.load(jat_class, **_opts)
|
|
11
|
+
jat_class::Attribute.include(AttributeInstanceMethods)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module AttributeInstanceMethods
|
|
15
|
+
def name
|
|
16
|
+
LowerCamelCaseTransformation.call(original_name)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
register_plugin(LowerCamelCase.plugin_name, LowerCamelCase)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class LowerCamelCaseTransformation
|
|
25
|
+
SEPARATOR = "_"
|
|
26
|
+
|
|
27
|
+
def self.call(string)
|
|
28
|
+
first_word, *others = string.to_s.split(SEPARATOR)
|
|
29
|
+
|
|
30
|
+
first_word[0] = first_word[0].downcase
|
|
31
|
+
last_words = others.each(&:capitalize!).join
|
|
32
|
+
|
|
33
|
+
:"#{first_word}#{last_words}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "./lib/format_user_preloads"
|
|
4
|
+
require_relative "./lib/preloads_with_path"
|
|
5
|
+
|
|
6
|
+
# This plugin adds attribute methods #preloads, #preloads_path
|
|
7
|
+
class Jat
|
|
8
|
+
module Plugins
|
|
9
|
+
module Preloads
|
|
10
|
+
def self.plugin_name
|
|
11
|
+
:_preloads
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.load(jat_class, **_opts)
|
|
15
|
+
jat_class::Attribute.include(AttributeMethods)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module AttributeMethods
|
|
19
|
+
NULL_PRELOADS = [nil, [].freeze].freeze
|
|
20
|
+
|
|
21
|
+
def preloads
|
|
22
|
+
return @preloads if defined?(@preloads)
|
|
23
|
+
|
|
24
|
+
@preloads, @preloads_path = get_preloads_with_path
|
|
25
|
+
@preloads
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def preloads_path
|
|
29
|
+
return @preloads_path if defined?(@preloads_path)
|
|
30
|
+
|
|
31
|
+
@preloads, @preloads_path = get_preloads_with_path
|
|
32
|
+
@preloads_path
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# When provided multiple values in preloads, such as { user: [:profile] },
|
|
36
|
+
# we don't know which entity is main (:user or :profile in this example) but
|
|
37
|
+
# we need to know main value to add nested preloads to it.
|
|
38
|
+
# User can specify main preloaded entity by adding "!" suffix
|
|
39
|
+
# ({ user!: [:profile] } for example), otherwise the latest key will be considered main.
|
|
40
|
+
def get_preloads_with_path
|
|
41
|
+
preloads_provided = opts.key?(:preload)
|
|
42
|
+
preloads =
|
|
43
|
+
if preloads_provided
|
|
44
|
+
opts[:preload]
|
|
45
|
+
elsif relation?
|
|
46
|
+
key
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Nulls and empty hash differs as we can preload nested results to
|
|
50
|
+
# empty hash, but we will skip nested preloading if null or false provided
|
|
51
|
+
return NULL_PRELOADS if preloads_provided && !preloads
|
|
52
|
+
|
|
53
|
+
preloads = FormatUserPreloads.to_hash(preloads)
|
|
54
|
+
preloads, path = PreloadsWithPath.call(preloads)
|
|
55
|
+
|
|
56
|
+
[EnumDeepFreeze.call(preloads), path.freeze]
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
register_plugin(Preloads.plugin_name, Preloads)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Jat
|
|
4
|
+
module Plugins
|
|
5
|
+
module Preloads
|
|
6
|
+
class FormatUserPreloads
|
|
7
|
+
METHODS = {
|
|
8
|
+
Array => :array_to_hash,
|
|
9
|
+
FalseClass => :nil_to_hash,
|
|
10
|
+
Hash => :hash_to_hash,
|
|
11
|
+
NilClass => :nil_to_hash,
|
|
12
|
+
String => :string_to_hash,
|
|
13
|
+
Symbol => :symbol_to_hash
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
module ClassMethods
|
|
17
|
+
def to_hash(value)
|
|
18
|
+
send(METHODS.fetch(value.class), value)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def array_to_hash(values)
|
|
24
|
+
values.each_with_object({}) do |value, obj|
|
|
25
|
+
obj.merge!(to_hash(value))
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def hash_to_hash(values)
|
|
30
|
+
values.each_with_object({}) do |(key, value), obj|
|
|
31
|
+
obj[key.to_sym] = to_hash(value)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def nil_to_hash(_value)
|
|
36
|
+
{}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def string_to_hash(value)
|
|
40
|
+
symbol_to_hash(value.to_sym)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def symbol_to_hash(value)
|
|
44
|
+
{value => {}}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
extend ClassMethods
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Jat
|
|
4
|
+
module Plugins
|
|
5
|
+
module Preloads
|
|
6
|
+
class PreloadsWithPath
|
|
7
|
+
module ClassMethods
|
|
8
|
+
BANG = "!"
|
|
9
|
+
NO_PRELOADS = [{}.freeze, [].freeze].freeze
|
|
10
|
+
|
|
11
|
+
# @param preload<Hash> Formatted user provided preloads hash
|
|
12
|
+
def call(preloads)
|
|
13
|
+
return NO_PRELOADS if preloads.empty?
|
|
14
|
+
|
|
15
|
+
path = main_path(preloads)
|
|
16
|
+
return [preloads, path] unless has_bang?(path)
|
|
17
|
+
|
|
18
|
+
# We should remove bangs from last key in path and from associated preloads key.
|
|
19
|
+
# We use mutable methods here.
|
|
20
|
+
remove_bangs(preloads, path)
|
|
21
|
+
[preloads, path]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# Generates path (Array) to main included resource.
|
|
27
|
+
# We need to know main included resource to include nested associations.
|
|
28
|
+
#
|
|
29
|
+
# User should mark main included resource with "!"
|
|
30
|
+
# When nothing marked, last included resource is considered main.
|
|
31
|
+
#
|
|
32
|
+
# main_path(a: { b!: { c: {} }, d: {} }) # => [:a, :b]
|
|
33
|
+
# main_path(a: { b: { c: {} }, d: {} }) # => [:a, :d]
|
|
34
|
+
#
|
|
35
|
+
def main_path(hash, path = [])
|
|
36
|
+
current_level = path.size
|
|
37
|
+
|
|
38
|
+
hash.each do |key, data|
|
|
39
|
+
path.pop(path.size - current_level)
|
|
40
|
+
path << key
|
|
41
|
+
return path if key[-1] == BANG
|
|
42
|
+
|
|
43
|
+
main_path(data, path)
|
|
44
|
+
return path if path.last[-1] == BANG
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
path
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def remove_bangs(preloads, path)
|
|
51
|
+
# Remove last path with bang
|
|
52
|
+
bang_key = path.pop
|
|
53
|
+
|
|
54
|
+
# Delete bang from key
|
|
55
|
+
key = bang_key.to_s.delete_suffix!(BANG).to_sym
|
|
56
|
+
|
|
57
|
+
# Navigate to main resource and replace key with BANG
|
|
58
|
+
nested_preloads = empty_dig(preloads, path)
|
|
59
|
+
nested_preloads[key] = nested_preloads.delete(bang_key)
|
|
60
|
+
|
|
61
|
+
# Add cleared key to path
|
|
62
|
+
path << key
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def empty_dig(hash, path)
|
|
66
|
+
path.empty? ? hash : hash.dig(*path)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def has_bang?(path)
|
|
70
|
+
path.last[-1] == BANG
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
extend ClassMethods
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|