jsonapi-resources 0.9.12 → 0.10.0.beta1
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 +5 -5
- data/LICENSE.txt +1 -1
- data/README.md +34 -11
- data/lib/bug_report_templates/rails_5_latest.rb +125 -0
- data/lib/bug_report_templates/rails_5_master.rb +140 -0
- data/lib/jsonapi-resources.rb +8 -3
- data/lib/jsonapi/active_relation_resource_finder.rb +640 -0
- data/lib/jsonapi/active_relation_resource_finder/join_tree.rb +126 -0
- data/lib/jsonapi/acts_as_resource_controller.rb +121 -106
- data/lib/jsonapi/{cached_resource_fragment.rb → cached_response_fragment.rb} +13 -30
- data/lib/jsonapi/compiled_json.rb +11 -1
- data/lib/jsonapi/configuration.rb +44 -18
- data/lib/jsonapi/error.rb +27 -0
- data/lib/jsonapi/exceptions.rb +43 -40
- data/lib/jsonapi/formatter.rb +3 -3
- data/lib/jsonapi/include_directives.rb +2 -45
- data/lib/jsonapi/link_builder.rb +87 -80
- data/lib/jsonapi/operation.rb +16 -5
- data/lib/jsonapi/operation_result.rb +74 -16
- data/lib/jsonapi/processor.rb +233 -112
- data/lib/jsonapi/relationship.rb +77 -53
- data/lib/jsonapi/request_parser.rb +378 -423
- data/lib/jsonapi/resource.rb +224 -524
- data/lib/jsonapi/resource_controller_metal.rb +2 -2
- data/lib/jsonapi/resource_fragment.rb +47 -0
- data/lib/jsonapi/resource_id_tree.rb +112 -0
- data/lib/jsonapi/resource_identity.rb +42 -0
- data/lib/jsonapi/resource_serializer.rb +133 -301
- data/lib/jsonapi/resource_set.rb +108 -0
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/response_document.rb +100 -88
- data/lib/jsonapi/routing_ext.rb +21 -43
- metadata +29 -45
- data/lib/jsonapi/operation_dispatcher.rb +0 -88
- data/lib/jsonapi/operation_results.rb +0 -35
- data/lib/jsonapi/relationship_builder.rb +0 -167
data/lib/jsonapi/formatter.rb
CHANGED
@@ -108,7 +108,7 @@ end
|
|
108
108
|
|
109
109
|
class DasherizedKeyFormatter < JSONAPI::KeyFormatter
|
110
110
|
class << self
|
111
|
-
def format(
|
111
|
+
def format(_key)
|
112
112
|
super.underscore.dasherize
|
113
113
|
end
|
114
114
|
|
@@ -146,7 +146,7 @@ end
|
|
146
146
|
|
147
147
|
class CamelizedRouteFormatter < JSONAPI::RouteFormatter
|
148
148
|
class << self
|
149
|
-
def format(
|
149
|
+
def format(_route)
|
150
150
|
super.camelize(:lower)
|
151
151
|
end
|
152
152
|
|
@@ -158,7 +158,7 @@ end
|
|
158
158
|
|
159
159
|
class DasherizedRouteFormatter < JSONAPI::RouteFormatter
|
160
160
|
class << self
|
161
|
-
def format(
|
161
|
+
def format(_route)
|
162
162
|
super.dasherize
|
163
163
|
end
|
164
164
|
|
@@ -19,9 +19,8 @@ module JSONAPI
|
|
19
19
|
# }
|
20
20
|
# }
|
21
21
|
|
22
|
-
def initialize(resource_klass, includes_array
|
22
|
+
def initialize(resource_klass, includes_array)
|
23
23
|
@resource_klass = resource_klass
|
24
|
-
@force_eager_load = force_eager_load
|
25
24
|
@include_directives_hash = { include_related: {} }
|
26
25
|
includes_array.each do |include|
|
27
26
|
parse_include(include)
|
@@ -32,24 +31,6 @@ module JSONAPI
|
|
32
31
|
@include_directives_hash
|
33
32
|
end
|
34
33
|
|
35
|
-
def model_includes
|
36
|
-
get_includes(@include_directives_hash)
|
37
|
-
end
|
38
|
-
|
39
|
-
def paths
|
40
|
-
delve_paths(get_includes(@include_directives_hash, false))
|
41
|
-
end
|
42
|
-
|
43
|
-
def merge_filter(relation, filter)
|
44
|
-
config = include_config(relation.to_sym)
|
45
|
-
config[:include_filters] ||= {}
|
46
|
-
config[:include_filters].merge!(filter)
|
47
|
-
end
|
48
|
-
|
49
|
-
def include_config(relation)
|
50
|
-
@include_directives_hash[:include_related][relation]
|
51
|
-
end
|
52
|
-
|
53
34
|
private
|
54
35
|
|
55
36
|
def get_related(current_path)
|
@@ -65,24 +46,13 @@ module JSONAPI
|
|
65
46
|
raise JSONAPI::Exceptions::InvalidInclude.new(current_resource_klass, current_path)
|
66
47
|
end
|
67
48
|
|
68
|
-
include_in_join = @force_eager_load || !current_relationship || current_relationship.eager_load_on_include
|
69
49
|
|
70
|
-
current[:include_related][fragment] ||= { include: false, include_related: {}
|
50
|
+
current[:include_related][fragment] ||= { include: false, include_related: {} }
|
71
51
|
current = current[:include_related][fragment]
|
72
52
|
end
|
73
53
|
current
|
74
54
|
end
|
75
55
|
|
76
|
-
def get_includes(directive, only_joined_includes = true)
|
77
|
-
ir = directive[:include_related]
|
78
|
-
ir = ir.select { |k,v| v[:include_in_join] } if only_joined_includes
|
79
|
-
|
80
|
-
ir.map do |name, sub_directive|
|
81
|
-
sub = get_includes(sub_directive, only_joined_includes)
|
82
|
-
sub.any? ? { name => sub } : name
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
56
|
def parse_include(include)
|
87
57
|
parts = include.split('.')
|
88
58
|
local_path = ''
|
@@ -93,18 +63,5 @@ module JSONAPI
|
|
93
63
|
related[:include] = true
|
94
64
|
end
|
95
65
|
end
|
96
|
-
|
97
|
-
def delve_paths(obj)
|
98
|
-
case obj
|
99
|
-
when Array
|
100
|
-
obj.map{|elem| delve_paths(elem)}.flatten(1)
|
101
|
-
when Hash
|
102
|
-
obj.map{|k,v| [[k]] + delve_paths(v).map{|path| [k] + path } }.flatten(1)
|
103
|
-
when Symbol, String
|
104
|
-
[[obj]]
|
105
|
-
else
|
106
|
-
raise "delve_paths cannot descend into #{obj.class.name}"
|
107
|
-
end
|
108
|
-
end
|
109
66
|
end
|
110
67
|
end
|
data/lib/jsonapi/link_builder.rb
CHANGED
@@ -3,100 +3,109 @@ module JSONAPI
|
|
3
3
|
attr_reader :base_url,
|
4
4
|
:primary_resource_klass,
|
5
5
|
:route_formatter,
|
6
|
-
:
|
7
|
-
:engine_mount_point,
|
8
|
-
:url_helpers
|
9
|
-
|
10
|
-
@@url_helper_methods = {}
|
6
|
+
:engine_name
|
11
7
|
|
12
8
|
def initialize(config = {})
|
13
|
-
@base_url
|
9
|
+
@base_url = config[:base_url]
|
14
10
|
@primary_resource_klass = config[:primary_resource_klass]
|
15
|
-
@route_formatter
|
16
|
-
@
|
17
|
-
@engine_mount_point = @engine ? @engine.routes.find_script_name({}) : ""
|
11
|
+
@route_formatter = config[:route_formatter]
|
12
|
+
@engine_name = build_engine_name
|
18
13
|
|
19
|
-
#
|
20
|
-
#
|
21
|
-
|
22
|
-
|
14
|
+
# Warning: These make LinkBuilder non-thread-safe. That's not a problem with the
|
15
|
+
# request-specific way it's currently used, though.
|
16
|
+
@resources_path_cache = JSONAPI::NaiveCache.new do |source_klass|
|
17
|
+
formatted_module_path_from_class(source_klass) + format_route(source_klass._type.to_s)
|
18
|
+
end
|
23
19
|
end
|
24
20
|
|
25
21
|
def engine?
|
26
|
-
!!@
|
22
|
+
!!@engine_name
|
27
23
|
end
|
28
24
|
|
29
25
|
def primary_resources_url
|
30
|
-
if
|
31
|
-
|
32
|
-
@primary_resources_url_cached ||= "#{ base_url }#{ serialized_engine_mount_point }#{ primary_resources_path }"
|
26
|
+
if engine?
|
27
|
+
engine_primary_resources_url
|
33
28
|
else
|
34
|
-
|
35
|
-
warn "primary_resources_url for #{@primary_resource_klass} could not be generated"
|
36
|
-
@primary_resource_klass._warned_missing_route = true
|
37
|
-
end
|
38
|
-
nil
|
29
|
+
regular_primary_resources_url
|
39
30
|
end
|
40
31
|
end
|
41
32
|
|
42
33
|
def query_link(query_params)
|
43
|
-
|
44
|
-
return url if url.nil?
|
45
|
-
"#{ url }?#{ query_params.to_query }"
|
34
|
+
"#{ primary_resources_url }?#{ query_params.to_query }"
|
46
35
|
end
|
47
36
|
|
48
37
|
def relationships_related_link(source, relationship, query_params = {})
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
url
|
53
|
-
else
|
54
|
-
if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route
|
55
|
-
warn "related_link for #{relationship} could not be generated"
|
56
|
-
relationship._warned_missing_route = true
|
57
|
-
end
|
58
|
-
nil
|
59
|
-
end
|
38
|
+
url = "#{ self_link(source) }/#{ route_for_relationship(relationship) }"
|
39
|
+
url = "#{ url }?#{ query_params.to_query }" if query_params.present?
|
40
|
+
url
|
60
41
|
end
|
61
42
|
|
62
43
|
def relationships_self_link(source, relationship)
|
63
|
-
|
64
|
-
"#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }"
|
65
|
-
else
|
66
|
-
if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route
|
67
|
-
warn "self_link for #{relationship} could not be generated"
|
68
|
-
relationship._warned_missing_route = true
|
69
|
-
end
|
70
|
-
nil
|
71
|
-
end
|
44
|
+
"#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }"
|
72
45
|
end
|
73
46
|
|
74
47
|
def self_link(source)
|
75
|
-
if
|
76
|
-
|
48
|
+
if engine?
|
49
|
+
engine_resource_url(source)
|
77
50
|
else
|
78
|
-
|
79
|
-
warn "self_link for #{source.class} could not be generated"
|
80
|
-
source.class._warned_missing_route = true
|
81
|
-
end
|
82
|
-
nil
|
51
|
+
regular_resource_url(source)
|
83
52
|
end
|
84
53
|
end
|
85
54
|
|
86
55
|
private
|
87
56
|
|
88
|
-
def
|
57
|
+
def build_engine_name
|
89
58
|
scopes = module_scopes_from_class(primary_resource_klass)
|
90
59
|
|
91
60
|
begin
|
92
61
|
unless scopes.empty?
|
93
62
|
"#{ scopes.first.to_s.camelize }::Engine".safe_constantize
|
94
63
|
end
|
95
|
-
|
96
|
-
# :nocov:
|
64
|
+
# :nocov:
|
97
65
|
rescue LoadError => _e
|
98
66
|
nil
|
99
|
-
|
67
|
+
# :nocov:
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def engine_path_from_resource_class(klass)
|
72
|
+
path_name = engine_resources_path_name_from_class(klass)
|
73
|
+
engine_name.routes.url_helpers.public_send(path_name)
|
74
|
+
end
|
75
|
+
|
76
|
+
def engine_primary_resources_path
|
77
|
+
engine_path_from_resource_class(primary_resource_klass)
|
78
|
+
end
|
79
|
+
|
80
|
+
def engine_primary_resources_url
|
81
|
+
"#{ base_url }#{ engine_primary_resources_path }"
|
82
|
+
end
|
83
|
+
|
84
|
+
def engine_resource_path(source)
|
85
|
+
resource_path_name = engine_resource_path_name_from_source(source)
|
86
|
+
engine_name.routes.url_helpers.public_send(resource_path_name, source.id)
|
87
|
+
end
|
88
|
+
|
89
|
+
def engine_resource_path_name_from_source(source)
|
90
|
+
scopes = module_scopes_from_class(source.class)[1..-1]
|
91
|
+
base_path_name = scopes.map { |scope| scope.underscore }.join("_")
|
92
|
+
end_path_name = source.class._type.to_s.singularize
|
93
|
+
[base_path_name, end_path_name, "path"].reject(&:blank?).join("_")
|
94
|
+
end
|
95
|
+
|
96
|
+
def engine_resource_url(source)
|
97
|
+
"#{ base_url }#{ engine_resource_path(source) }"
|
98
|
+
end
|
99
|
+
|
100
|
+
def engine_resources_path_name_from_class(klass)
|
101
|
+
scopes = module_scopes_from_class(klass)[1..-1]
|
102
|
+
base_path_name = scopes.map { |scope| scope.underscore }.join("_")
|
103
|
+
end_path_name = klass._type.to_s
|
104
|
+
|
105
|
+
if base_path_name.blank?
|
106
|
+
"#{ end_path_name }_path"
|
107
|
+
else
|
108
|
+
"#{ base_path_name }_#{ end_path_name }_path"
|
100
109
|
end
|
101
110
|
end
|
102
111
|
|
@@ -105,19 +114,12 @@ module JSONAPI
|
|
105
114
|
end
|
106
115
|
|
107
116
|
def formatted_module_path_from_class(klass)
|
108
|
-
|
109
|
-
@_module_path_cache[klass] ||= begin
|
110
|
-
scopes = if @engine
|
111
|
-
module_scopes_from_class(klass)[1..-1]
|
112
|
-
else
|
113
|
-
module_scopes_from_class(klass)
|
114
|
-
end
|
117
|
+
scopes = module_scopes_from_class(klass)
|
115
118
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
end
|
119
|
+
unless scopes.empty?
|
120
|
+
"/#{ scopes.map{ |scope| format_route(scope.to_s.underscore) }.compact.join('/') }/"
|
121
|
+
else
|
122
|
+
"/"
|
121
123
|
end
|
122
124
|
end
|
123
125
|
|
@@ -125,25 +127,30 @@ module JSONAPI
|
|
125
127
|
klass.name.to_s.split("::")[0...-1]
|
126
128
|
end
|
127
129
|
|
128
|
-
def
|
129
|
-
|
130
|
+
def regular_resources_path(source_klass)
|
131
|
+
@resources_path_cache.get(source_klass)
|
130
132
|
end
|
131
133
|
|
132
|
-
def
|
133
|
-
|
134
|
+
def regular_primary_resources_path
|
135
|
+
regular_resources_path(primary_resource_klass)
|
136
|
+
end
|
134
137
|
|
135
|
-
|
136
|
-
|
137
|
-
end
|
138
|
-
url
|
138
|
+
def regular_primary_resources_url
|
139
|
+
"#{ base_url }#{ regular_primary_resources_path }"
|
139
140
|
end
|
140
141
|
|
141
|
-
def
|
142
|
-
|
142
|
+
def regular_resource_path(source)
|
143
|
+
if source.is_a?(JSONAPI::CachedResponseFragment)
|
144
|
+
# :nocov:
|
145
|
+
"#{regular_resources_path(source.resource_klass)}/#{source.id}"
|
146
|
+
# :nocov:
|
147
|
+
else
|
148
|
+
"#{regular_resources_path(source.class)}/#{source.id}"
|
149
|
+
end
|
143
150
|
end
|
144
151
|
|
145
|
-
def
|
146
|
-
|
152
|
+
def regular_resource_url(source)
|
153
|
+
"#{ base_url }#{ regular_resource_path(source) }"
|
147
154
|
end
|
148
155
|
|
149
156
|
def route_for_relationship(relationship)
|
data/lib/jsonapi/operation.rb
CHANGED
@@ -8,17 +8,28 @@ module JSONAPI
|
|
8
8
|
@options = options
|
9
9
|
end
|
10
10
|
|
11
|
-
def transactional?
|
12
|
-
JSONAPI::Processor._processor_from_resource_type(resource_klass).transactional_operation_type?(operation_type)
|
13
|
-
end
|
14
|
-
|
15
11
|
def process
|
16
12
|
processor.process
|
17
13
|
end
|
18
14
|
|
19
15
|
private
|
20
16
|
def processor
|
21
|
-
|
17
|
+
self.class.processor_instance_for(resource_klass, operation_type, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def processor_instance_for(resource_klass, operation_type, params)
|
22
|
+
_processor_from_resource_type(resource_klass).new(resource_klass, operation_type, params)
|
23
|
+
end
|
24
|
+
|
25
|
+
def _processor_from_resource_type(resource_klass)
|
26
|
+
processor = resource_klass.name.gsub(/Resource$/,'Processor').safe_constantize
|
27
|
+
if processor.nil?
|
28
|
+
processor = JSONAPI.configuration.default_processor_klass
|
29
|
+
end
|
30
|
+
|
31
|
+
return processor
|
32
|
+
end
|
22
33
|
end
|
23
34
|
end
|
24
35
|
end
|
@@ -4,12 +4,18 @@ module JSONAPI
|
|
4
4
|
attr_accessor :meta
|
5
5
|
attr_accessor :links
|
6
6
|
attr_accessor :options
|
7
|
+
attr_accessor :warnings
|
7
8
|
|
8
9
|
def initialize(code, options = {})
|
9
|
-
@code = code
|
10
|
+
@code = Rack::Utils.status_code(code)
|
10
11
|
@options = options
|
11
12
|
@meta = options.fetch(:meta, {})
|
12
13
|
@links = options.fetch(:links, {})
|
14
|
+
@warnings = options.fetch(:warnings, {})
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_hash(serializer = nil)
|
18
|
+
{}
|
13
19
|
end
|
14
20
|
end
|
15
21
|
|
@@ -20,46 +26,98 @@ module JSONAPI
|
|
20
26
|
@errors = errors
|
21
27
|
super(code, options)
|
22
28
|
end
|
29
|
+
|
30
|
+
def to_hash(serializer = nil)
|
31
|
+
{
|
32
|
+
errors: errors.collect do |error|
|
33
|
+
# :nocov:
|
34
|
+
error.to_hash
|
35
|
+
# :nocov:
|
36
|
+
end
|
37
|
+
}
|
38
|
+
end
|
23
39
|
end
|
24
40
|
|
25
|
-
class
|
26
|
-
attr_accessor :
|
41
|
+
class ResourceSetOperationResult < OperationResult
|
42
|
+
attr_accessor :resource_set, :pagination_params
|
27
43
|
|
28
|
-
def initialize(code,
|
29
|
-
@
|
44
|
+
def initialize(code, resource_set, options = {})
|
45
|
+
@resource_set = resource_set
|
46
|
+
@pagination_params = options.fetch(:pagination_params, {})
|
30
47
|
super(code, options)
|
31
48
|
end
|
49
|
+
|
50
|
+
def to_hash(serializer)
|
51
|
+
if serializer
|
52
|
+
serializer.serialize_resource_set_to_hash_single(resource_set)
|
53
|
+
else
|
54
|
+
# :nocov:
|
55
|
+
{}
|
56
|
+
# :nocov:
|
57
|
+
end
|
58
|
+
end
|
32
59
|
end
|
33
60
|
|
34
|
-
class
|
35
|
-
attr_accessor :
|
61
|
+
class ResourcesSetOperationResult < OperationResult
|
62
|
+
attr_accessor :resource_set, :pagination_params, :record_count, :page_count
|
36
63
|
|
37
|
-
def initialize(code,
|
38
|
-
@
|
64
|
+
def initialize(code, resource_set, options = {})
|
65
|
+
@resource_set = resource_set
|
39
66
|
@pagination_params = options.fetch(:pagination_params, {})
|
40
67
|
@record_count = options[:record_count]
|
41
68
|
@page_count = options[:page_count]
|
42
69
|
super(code, options)
|
43
70
|
end
|
71
|
+
|
72
|
+
def to_hash(serializer)
|
73
|
+
if serializer
|
74
|
+
serializer.serialize_resource_set_to_hash_plural(resource_set)
|
75
|
+
else
|
76
|
+
# :nocov:
|
77
|
+
{}
|
78
|
+
# :nocov:
|
79
|
+
end
|
80
|
+
end
|
44
81
|
end
|
45
82
|
|
46
|
-
class
|
47
|
-
attr_accessor :source_resource, :_type
|
83
|
+
class RelatedResourcesSetOperationResult < ResourcesSetOperationResult
|
84
|
+
attr_accessor :resource_set, :source_resource, :_type
|
48
85
|
|
49
|
-
def initialize(code, source_resource, type,
|
86
|
+
def initialize(code, source_resource, type, resource_set, options = {})
|
50
87
|
@source_resource = source_resource
|
51
88
|
@_type = type
|
52
|
-
super(code,
|
89
|
+
super(code, resource_set, options)
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_hash(serializer = nil)
|
93
|
+
if serializer
|
94
|
+
serializer.serialize_related_resource_set_to_hash_plural(resource_set, source_resource)
|
95
|
+
else
|
96
|
+
# :nocov:
|
97
|
+
{}
|
98
|
+
# :nocov:
|
99
|
+
end
|
53
100
|
end
|
54
101
|
end
|
55
102
|
|
56
|
-
class
|
57
|
-
attr_accessor :parent_resource, :relationship
|
103
|
+
class LinksObjectOperationResult < OperationResult
|
104
|
+
attr_accessor :parent_resource, :relationship, :resource_ids
|
58
105
|
|
59
|
-
def initialize(code, parent_resource, relationship, options = {})
|
106
|
+
def initialize(code, parent_resource, relationship, resource_ids, options = {})
|
60
107
|
@parent_resource = parent_resource
|
61
108
|
@relationship = relationship
|
109
|
+
@resource_ids = resource_ids
|
62
110
|
super(code, options)
|
63
111
|
end
|
112
|
+
|
113
|
+
def to_hash(serializer = nil)
|
114
|
+
if serializer
|
115
|
+
serializer.serialize_to_links_hash(parent_resource, relationship, resource_ids)
|
116
|
+
else
|
117
|
+
# :nocov:
|
118
|
+
{}
|
119
|
+
# :nocov:
|
120
|
+
end
|
121
|
+
end
|
64
122
|
end
|
65
123
|
end
|