jsonapi-resources 0.9.12 → 0.10.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|