jsonapi-resources 0.9.6 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aca973f2c9c1164fc79771cc71b363be15d07159cbe8d2b273d63886fee30b05
4
- data.tar.gz: cdd800dde1c3f08d6b7c1d6f904f7519f0168c7585a4a9e6b8d4ecf89e834e1c
3
+ metadata.gz: 3fc3aa84a3eee153f95a97c3d982e6fef51146998c0860ed4986f3c3911eb5c4
4
+ data.tar.gz: c75e87dad876b378b4aef634c7103b6399b171b8392831b1a0365146c82c02f9
5
5
  SHA512:
6
- metadata.gz: 509137860c1e0a0a842a15ebeb580e229cc0636623ebc3d70aef2d752979bd066dc6ba2268bbdad483acdc1a5c25701c62eb9876b796effa80dd2e841fd4ef4d
7
- data.tar.gz: b9dfc40e043337a749502f70f790e9280bf08e8a267bf0181e48522b27f58b8d1205cce28bc7e866adc6c566399cc313f3442a4aba86b1a6ef256e36b0f50873
6
+ metadata.gz: a35bf550f5cdd014236b4ec680526bc1041a024644ba83a036e1d57c2c7e837456f713ad7f3f4bca95bacf7cc50bc7588fcfdf3550c96eb3802bf086a48dac43
7
+ data.tar.gz: b322f9b3d22e6a3667f4b938559c3cf73af93fc54ad18311b443acafce36594cac7e2c8166c3b204e9ef50b07cb39f17b39edcbfa342d7a41434b359e7c9bd35
@@ -234,7 +234,7 @@ module JSONAPI
234
234
  end
235
235
 
236
236
  render_options[:location] = content[:data]["links"][:self] if (
237
- response_doc.status == :created && content[:data].class != Array
237
+ response_doc.status == :created && content[:data].class != Array && content[:data]["links"]
238
238
  )
239
239
 
240
240
  # For whatever reason, `render` ignores :status and :content_type when :body is set.
@@ -8,6 +8,7 @@ module JSONAPI
8
8
  :resource_key_type,
9
9
  :route_format,
10
10
  :raise_if_parameters_not_allowed,
11
+ :warn_on_missing_routes,
11
12
  :allow_include,
12
13
  :allow_sort,
13
14
  :allow_filter,
@@ -51,6 +52,8 @@ module JSONAPI
51
52
 
52
53
  self.raise_if_parameters_not_allowed = true
53
54
 
55
+ self.warn_on_missing_routes = true
56
+
54
57
  # :none, :offset, :paged, or a custom paginator name
55
58
  self.default_paginator = :none
56
59
 
@@ -235,6 +238,8 @@ module JSONAPI
235
238
 
236
239
  attr_writer :raise_if_parameters_not_allowed
237
240
 
241
+ attr_writer :warn_on_missing_routes
242
+
238
243
  attr_writer :use_relationship_reflection
239
244
 
240
245
  attr_writer :resource_cache
@@ -2,32 +2,33 @@ module JSONAPI
2
2
  class LinkBuilder
3
3
  attr_reader :base_url,
4
4
  :primary_resource_klass,
5
- :route_formatter,
6
- :engine_name
5
+ :engine,
6
+ :routes
7
7
 
8
8
  def initialize(config = {})
9
9
  @base_url = config[:base_url]
10
10
  @primary_resource_klass = config[:primary_resource_klass]
11
- @route_formatter = config[:route_formatter]
12
- @engine_name = build_engine_name
11
+ @engine = build_engine
13
12
 
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)
13
+ if engine?
14
+ @routes = @engine.routes
15
+ else
16
+ @routes = Rails.application.routes
18
17
  end
18
+
19
+ # ToDo: Use NaiveCache for values. For this we need to not return nils and create composite keys which work
20
+ # as efficient cache lookups. This could be an array of the [source.identifier, relationship] since the
21
+ # ResourceIdentity will compare equality correctly
19
22
  end
20
23
 
21
24
  def engine?
22
- !!@engine_name
25
+ !!@engine
23
26
  end
24
27
 
25
28
  def primary_resources_url
26
- if engine?
27
- engine_primary_resources_url
28
- else
29
- regular_primary_resources_url
30
- end
29
+ @primary_resources_url_cached ||= "#{ base_url }#{ primary_resources_path }"
30
+ rescue NoMethodError
31
+ warn "primary_resources_url for #{@primary_resource_klass} could not be generated" if JSONAPI.configuration.warn_on_missing_routes
31
32
  end
32
33
 
33
34
  def query_link(query_params)
@@ -35,118 +36,148 @@ module JSONAPI
35
36
  end
36
37
 
37
38
  def relationships_related_link(source, relationship, query_params = {})
38
- url = "#{ self_link(source) }/#{ route_for_relationship(relationship) }"
39
+ if relationship.parent_resource.singleton?
40
+ url_helper_name = singleton_related_url_helper_name(relationship)
41
+ url = call_url_helper(url_helper_name)
42
+ else
43
+ url_helper_name = related_url_helper_name(relationship)
44
+ url = call_url_helper(url_helper_name, source.id)
45
+ end
46
+
47
+ url = "#{ base_url }#{ url }"
39
48
  url = "#{ url }?#{ query_params.to_query }" if query_params.present?
40
49
  url
50
+ rescue NoMethodError
51
+ warn "related_link for #{relationship} could not be generated" if JSONAPI.configuration.warn_on_missing_routes
41
52
  end
42
53
 
43
54
  def relationships_self_link(source, relationship)
44
- "#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }"
55
+ if relationship.parent_resource.singleton?
56
+ url_helper_name = singleton_relationship_self_url_helper_name(relationship)
57
+ url = call_url_helper(url_helper_name)
58
+ else
59
+ url_helper_name = relationship_self_url_helper_name(relationship)
60
+ url = call_url_helper(url_helper_name, source.id)
61
+ end
62
+
63
+ url = "#{ base_url }#{ url }"
64
+ url
65
+ rescue NoMethodError
66
+ warn "self_link for #{relationship} could not be generated" if JSONAPI.configuration.warn_on_missing_routes
45
67
  end
46
68
 
47
69
  def self_link(source)
48
- if engine?
49
- engine_resource_url(source)
50
- else
51
- regular_resource_url(source)
52
- end
70
+ "#{ base_url }#{ resource_path(source) }"
71
+ rescue NoMethodError
72
+ warn "self_link for #{source.class} could not be generated" if JSONAPI.configuration.warn_on_missing_routes
53
73
  end
54
74
 
55
75
  private
56
76
 
57
- def build_engine_name
77
+ def build_engine
58
78
  scopes = module_scopes_from_class(primary_resource_klass)
59
79
 
60
80
  begin
61
81
  unless scopes.empty?
62
82
  "#{ scopes.first.to_s.camelize }::Engine".safe_constantize
63
83
  end
64
- rescue LoadError => e
84
+ # :nocov:
85
+ rescue LoadError => _e
65
86
  nil
87
+ # :nocov:
66
88
  end
67
89
  end
68
90
 
69
- def engine_path_from_resource_class(klass)
70
- path_name = engine_resources_path_name_from_class(klass)
71
- engine_name.routes.url_helpers.public_send(path_name)
91
+ def call_url_helper(method, *args)
92
+ routes.url_helpers.public_send(method, args)
93
+ rescue NoMethodError => e
94
+ raise e
72
95
  end
73
96
 
74
- def engine_primary_resources_path
75
- engine_path_from_resource_class(primary_resource_klass)
97
+ def path_from_resource_class(klass)
98
+ url_helper_name = resources_url_helper_name_from_class(klass)
99
+ call_url_helper(url_helper_name)
76
100
  end
77
101
 
78
- def engine_primary_resources_url
79
- "#{ base_url }#{ engine_primary_resources_path }"
102
+ def resource_path(source)
103
+ url_helper_name = resource_url_helper_name_from_source(source)
104
+ if source.class.singleton?
105
+ call_url_helper(url_helper_name)
106
+ else
107
+ call_url_helper(url_helper_name, source.id)
108
+ end
80
109
  end
81
110
 
82
- def engine_resource_path(source)
83
- resource_path_name = engine_resource_path_name_from_source(source)
84
- engine_name.routes.url_helpers.public_send(resource_path_name, source.id)
111
+ def primary_resources_path
112
+ path_from_resource_class(primary_resource_klass)
85
113
  end
86
114
 
87
- def engine_resource_path_name_from_source(source)
88
- scopes = module_scopes_from_class(source.class)[1..-1]
89
- base_path_name = scopes.map { |scope| scope.underscore }.join("_")
90
- end_path_name = source.class._type.to_s.singularize
91
- [base_path_name, end_path_name, "path"].reject(&:blank?).join("_")
115
+ def url_helper_name_from_parts(parts)
116
+ (parts << "path").reject(&:blank?).join("_")
92
117
  end
93
118
 
94
- def engine_resource_url(source)
95
- "#{ base_url }#{ engine_resource_path(source) }"
96
- end
119
+ def resources_path_parts_from_class(klass)
120
+ if engine?
121
+ scopes = module_scopes_from_class(klass)[1..-1]
122
+ else
123
+ scopes = module_scopes_from_class(klass)
124
+ end
97
125
 
98
- def engine_resources_path_name_from_class(klass)
99
- scopes = module_scopes_from_class(klass)[1..-1]
100
126
  base_path_name = scopes.map { |scope| scope.underscore }.join("_")
101
127
  end_path_name = klass._type.to_s
102
-
103
- if base_path_name.blank?
104
- "#{ end_path_name }_path"
105
- else
106
- "#{ base_path_name }_#{ end_path_name }_path"
107
- end
128
+ [base_path_name, end_path_name]
108
129
  end
109
130
 
110
- def format_route(route)
111
- route_formatter.format(route)
131
+ def resources_url_helper_name_from_class(klass)
132
+ url_helper_name_from_parts(resources_path_parts_from_class(klass))
112
133
  end
113
134
 
114
- def formatted_module_path_from_class(klass)
115
- scopes = module_scopes_from_class(klass)
116
-
117
- unless scopes.empty?
118
- "/#{ scopes.map{ |scope| format_route(scope.to_s.underscore) }.join('/') }/"
135
+ def resource_path_parts_from_class(klass)
136
+ if engine?
137
+ scopes = module_scopes_from_class(klass)[1..-1]
119
138
  else
120
- "/"
139
+ scopes = module_scopes_from_class(klass)
121
140
  end
122
- end
123
141
 
124
- def module_scopes_from_class(klass)
125
- klass.name.to_s.split("::")[0...-1]
142
+ base_path_name = scopes.map { |scope| scope.underscore }.join("_")
143
+ end_path_name = klass._type.to_s.singularize
144
+ [base_path_name, end_path_name]
126
145
  end
127
146
 
128
- def regular_resources_path(source_klass)
129
- @resources_path_cache.get(source_klass)
147
+ def resource_url_helper_name_from_source(source)
148
+ url_helper_name_from_parts(resource_path_parts_from_class(source.class))
130
149
  end
131
150
 
132
- def regular_primary_resources_path
133
- regular_resources_path(primary_resource_klass)
151
+ def related_url_helper_name(relationship)
152
+ relationship_parts = resource_path_parts_from_class(relationship.parent_resource)
153
+ relationship_parts << relationship.name
154
+ url_helper_name_from_parts(relationship_parts)
134
155
  end
135
156
 
136
- def regular_primary_resources_url
137
- "#{ base_url }#{ regular_primary_resources_path }"
157
+ def singleton_related_url_helper_name(relationship)
158
+ relationship_parts = []
159
+ relationship_parts << relationship.name
160
+ relationship_parts += resource_path_parts_from_class(relationship.parent_resource)
161
+ url_helper_name_from_parts(relationship_parts)
138
162
  end
139
163
 
140
- def regular_resource_path(source)
141
- "#{regular_resources_path(source.class)}/#{source.id}"
164
+ def relationship_self_url_helper_name(relationship)
165
+ relationship_parts = resource_path_parts_from_class(relationship.parent_resource)
166
+ relationship_parts << "relationships"
167
+ relationship_parts << relationship.name
168
+ url_helper_name_from_parts(relationship_parts)
142
169
  end
143
170
 
144
- def regular_resource_url(source)
145
- "#{ base_url }#{ regular_resource_path(source) }"
171
+ def singleton_relationship_self_url_helper_name(relationship)
172
+ relationship_parts = []
173
+ relationship_parts << "relationships"
174
+ relationship_parts << relationship.name
175
+ relationship_parts += resource_path_parts_from_class(relationship.parent_resource)
176
+ url_helper_name_from_parts(relationship_parts)
146
177
  end
147
178
 
148
- def route_for_relationship(relationship)
149
- format_route(relationship.name)
179
+ def module_scopes_from_class(klass)
180
+ klass.name.to_s.split("::")[0...-1]
150
181
  end
151
182
  end
152
183
  end
@@ -53,7 +53,7 @@ module JSONAPI
53
53
  end
54
54
  end
55
55
 
56
- class LinksObjectOperationResult < OperationResult
56
+ class RelationshipOperationResult < OperationResult
57
57
  attr_accessor :parent_resource, :relationship
58
58
 
59
59
  def initialize(code, parent_resource, relationship, options = {})
@@ -136,9 +136,9 @@ module JSONAPI
136
136
 
137
137
  parent_resource = resource_klass.find_by_key(parent_key, context: context)
138
138
 
139
- return JSONAPI::LinksObjectOperationResult.new(:ok,
140
- parent_resource,
141
- resource_klass._relationship(relationship_type))
139
+ return JSONAPI::RelationshipOperationResult.new(:ok,
140
+ parent_resource,
141
+ resource_klass._relationship(relationship_type))
142
142
  end
143
143
 
144
144
  def show_related_resource
@@ -14,6 +14,8 @@ module JSONAPI
14
14
  @polymorphic = options.fetch(:polymorphic, false) == true
15
15
  @always_include_linkage_data = options.fetch(:always_include_linkage_data, false) == true
16
16
  @eager_load_on_include = options.fetch(:eager_load_on_include, true) == true
17
+
18
+ exclude_links(options.fetch(:exclude_links, :none))
17
19
  end
18
20
 
19
21
  alias_method :polymorphic?, :polymorphic
@@ -60,6 +62,27 @@ module JSONAPI
60
62
  false
61
63
  end
62
64
 
65
+ def exclude_links(exclude)
66
+ case exclude
67
+ when :default, "default"
68
+ @_exclude_links = [:self, :related]
69
+ when :none, "none"
70
+ @_exclude_links = []
71
+ when Array
72
+ @_exclude_links = exclude.collect {|link| link.to_sym}
73
+ else
74
+ fail "Invalid exclude_links"
75
+ end
76
+ end
77
+
78
+ def _exclude_links
79
+ @_exclude_links ||= []
80
+ end
81
+
82
+ def exclude_link?(link)
83
+ _exclude_links.include?(link.to_sym)
84
+ end
85
+
63
86
  class ToOne < Relationship
64
87
  attr_reader :foreign_key_on
65
88
 
@@ -70,6 +93,12 @@ module JSONAPI
70
93
  @foreign_key_on = options.fetch(:foreign_key_on, :self)
71
94
  end
72
95
 
96
+ def to_s
97
+ # :nocov:
98
+ "#{parent_resource}.#{name}(#{belongs_to? ? 'BelongsToOne' : 'ToOne'})"
99
+ # :nocov:
100
+ end
101
+
73
102
  def belongs_to?
74
103
  foreign_key_on == :self
75
104
  end
@@ -89,6 +118,12 @@ module JSONAPI
89
118
  @reflect = options.fetch(:reflect, true) == true
90
119
  @inverse_relationship = options.fetch(:inverse_relationship, parent_resource._type.to_s.singularize.to_sym) if parent_resource
91
120
  end
121
+
122
+ def to_s
123
+ # :nocov:
124
+ "#{parent_resource}.#{name}(ToMany)"
125
+ # :nocov:
126
+ end
92
127
  end
93
128
  end
94
129
  end
@@ -52,6 +52,7 @@ module JSONAPI
52
52
  end
53
53
 
54
54
  def setup_get_related_resource_action(params)
55
+ resolve_singleton_id(params)
55
56
  initialize_source(params)
56
57
  parse_fields(params[:fields])
57
58
  parse_include_directives(params[:include])
@@ -63,6 +64,7 @@ module JSONAPI
63
64
  end
64
65
 
65
66
  def setup_get_related_resources_action(params)
67
+ resolve_singleton_id(params)
66
68
  initialize_source(params)
67
69
  parse_fields(params[:fields])
68
70
  parse_include_directives(params[:include])
@@ -74,6 +76,7 @@ module JSONAPI
74
76
  end
75
77
 
76
78
  def setup_show_action(params)
79
+ resolve_singleton_id(params)
77
80
  parse_fields(params[:fields])
78
81
  parse_include_directives(params[:include])
79
82
  parse_filters(params[:filter])
@@ -83,6 +86,7 @@ module JSONAPI
83
86
  end
84
87
 
85
88
  def setup_show_relationship_action(params)
89
+ resolve_singleton_id(params)
86
90
  add_show_relationship_operation(params[:relationship], params.require(@resource_klass._as_parent_key))
87
91
  end
88
92
 
@@ -93,24 +97,29 @@ module JSONAPI
93
97
  end
94
98
 
95
99
  def setup_create_relationship_action(params)
100
+ resolve_singleton_id(params)
96
101
  parse_modify_relationship_action(params, :add)
97
102
  end
98
103
 
99
104
  def setup_update_relationship_action(params)
105
+ resolve_singleton_id(params)
100
106
  parse_modify_relationship_action(params, :update)
101
107
  end
102
108
 
103
109
  def setup_update_action(params)
110
+ resolve_singleton_id(params)
104
111
  parse_fields(params[:fields])
105
112
  parse_include_directives(params[:include])
106
113
  parse_replace_operation(params.require(:data), params[:id])
107
114
  end
108
115
 
109
116
  def setup_destroy_action(params)
117
+ resolve_singleton_id(params)
110
118
  parse_remove_operation(params)
111
119
  end
112
120
 
113
121
  def setup_destroy_relationship_action(params)
122
+ resolve_singleton_id(params)
114
123
  parse_modify_relationship_action(params, :remove)
115
124
  end
116
125
 
@@ -694,7 +703,8 @@ module JSONAPI
694
703
  end
695
704
 
696
705
  def parse_replace_operation(data, keys)
697
- parse_single_replace_operation(data, [keys], id_key_presence_check_required: keys.present?)
706
+ parse_single_replace_operation(data, [keys],
707
+ id_key_presence_check_required: keys.present? && !@resource_klass.singleton?)
698
708
  rescue JSONAPI::Exceptions::Error => e
699
709
  @errors.concat(e.errors)
700
710
  end
@@ -725,6 +735,13 @@ module JSONAPI
725
735
  end
726
736
  end
727
737
 
738
+ def resolve_singleton_id(params)
739
+ if @resource_klass.singleton? && params[:id].nil?
740
+ key = @resource_klass.singleton_key(context)
741
+ params[:id] = key
742
+ end
743
+ end
744
+
728
745
  def format_key(key)
729
746
  @key_formatter.format(key)
730
747
  end
@@ -435,6 +435,8 @@ module JSONAPI
435
435
  subclass.abstract(false)
436
436
  subclass.immutable(false)
437
437
  subclass.caching(false)
438
+ subclass.singleton(singleton?, (_singleton_options.dup || {}))
439
+ subclass.exclude_links(_exclude_links)
438
440
  subclass._attributes = (_attributes || {}).dup
439
441
 
440
442
  subclass._model_hints = (_model_hints || {}).dup
@@ -606,6 +608,19 @@ module JSONAPI
606
608
  _model_hints[model.to_s.gsub('::', '/').underscore] = resource_type.to_s
607
609
  end
608
610
 
611
+ def singleton(*attrs)
612
+ @_singleton = (!!attrs[0] == attrs[0]) ? attrs[0] : true
613
+ @_singleton_options = attrs.extract_options!
614
+ end
615
+
616
+ def _singleton_options
617
+ @_singleton_options ||= {}
618
+ end
619
+
620
+ def singleton?
621
+ @_singleton ||= false
622
+ end
623
+
609
624
  def filters(*attrs)
610
625
  @_allowed_filters.merge!(attrs.inject({}) { |h, attr| h[attr] = {}; h })
611
626
  end
@@ -908,6 +923,24 @@ module JSONAPI
908
923
  @_resource_key_type ||= JSONAPI.configuration.resource_key_type
909
924
  end
910
925
 
926
+ # override to all resolution of masked ids to actual ids. Because singleton routes do not specify the id this
927
+ # will be needed to allow lookup of singleton resources. Alternately singleton resources can override
928
+ # `verify_key`
929
+ def singleton_key(context)
930
+ if @_singleton_options && @_singleton_options[:singleton_key]
931
+ strategy = @_singleton_options[:singleton_key]
932
+ case strategy
933
+ when Proc
934
+ key = strategy.call(context)
935
+ when Symbol, String
936
+ key = send(strategy, context)
937
+ else
938
+ raise "singleton_key must be a proc or function name"
939
+ end
940
+ end
941
+ key
942
+ end
943
+
911
944
  def verify_key(key, context = nil)
912
945
  key_type = resource_key_type
913
946
 
@@ -1032,6 +1065,27 @@ module JSONAPI
1032
1065
  !@immutable
1033
1066
  end
1034
1067
 
1068
+ def exclude_links(exclude)
1069
+ case exclude
1070
+ when :default, "default"
1071
+ @_exclude_links = [:self]
1072
+ when :none, "none"
1073
+ @_exclude_links = []
1074
+ when Array
1075
+ @_exclude_links = exclude.collect {|link| link.to_sym}
1076
+ else
1077
+ fail "Invalid exclude_links"
1078
+ end
1079
+ end
1080
+
1081
+ def _exclude_links
1082
+ @_exclude_links ||= []
1083
+ end
1084
+
1085
+ def exclude_link?(link)
1086
+ _exclude_links.include?(link.to_sym)
1087
+ end
1088
+
1035
1089
  def caching(val = true)
1036
1090
  @caching = val
1037
1091
  end
@@ -90,20 +90,19 @@ module JSONAPI
90
90
  primary_hash
91
91
  end
92
92
 
93
- def serialize_to_links_hash(source, requested_relationship)
93
+ def serialize_to_relationship_hash(source, requested_relationship)
94
94
  if requested_relationship.is_a?(JSONAPI::Relationship::ToOne)
95
95
  data = to_one_linkage(source, requested_relationship)
96
96
  else
97
97
  data = to_many_linkage(source, requested_relationship)
98
98
  end
99
99
 
100
- {
101
- links: {
102
- self: self_link(source, requested_relationship),
103
- related: related_link(source, requested_relationship)
104
- },
105
- data: data
106
- }
100
+ rel_hash = { 'data': data }
101
+
102
+ links = default_relationship_links(source, requested_relationship)
103
+ rel_hash['links'] = links unless links.blank?
104
+
105
+ rel_hash
107
106
  end
108
107
 
109
108
  def query_link(query_params)
@@ -133,7 +132,6 @@ module JSONAPI
133
132
  supplying_attribute_fields: supplying_attribute_fields(resource_klass).sort,
134
133
  supplying_relationship_fields: supplying_relationship_fields(resource_klass).sort,
135
134
  link_builder_base_url: link_builder.base_url,
136
- route_formatter_class: link_builder.route_formatter.uncached.class.name,
137
135
  key_formatter_class: key_formatter.uncached.class.name,
138
136
  always_include_to_one_linkage_data: always_include_to_one_linkage_data,
139
137
  always_include_to_many_linkage_data: always_include_to_many_linkage_data
@@ -152,7 +150,7 @@ module JSONAPI
152
150
  obj_hash['attributes'] = source.attributes_json if source.attributes_json
153
151
 
154
152
  relationships = cached_relationships_hash(source, include_directives)
155
- obj_hash['relationships'] = relationships unless relationships.empty?
153
+ obj_hash['relationships'] = relationships unless relationships.blank?
156
154
 
157
155
  obj_hash['meta'] = source.meta_json if source.meta_json
158
156
  else
@@ -173,7 +171,7 @@ module JSONAPI
173
171
  obj_hash['attributes'] = attributes unless attributes.empty?
174
172
 
175
173
  relationships = relationships_hash(source, fetchable_fields, include_directives)
176
- obj_hash['relationships'] = relationships unless relationships.nil? || relationships.empty?
174
+ obj_hash['relationships'] = relationships unless relationships.blank?
177
175
 
178
176
  meta = meta_hash(source)
179
177
  obj_hash['meta'] = meta unless meta.empty?
@@ -251,7 +249,9 @@ module JSONAPI
251
249
 
252
250
  def links_hash(source)
253
251
  links = custom_links_hash(source)
254
- links[:self] = link_builder.self_link(source) unless links.key?(:self)
252
+ if !links.key?('self') && !source.class.exclude_link?(:self)
253
+ links['self'] = link_builder.self_link(source)
254
+ end
255
255
  links.compact
256
256
  end
257
257
 
@@ -289,7 +289,8 @@ module JSONAPI
289
289
 
290
290
  options = { filters: ia && ia[:include_filters] || {} }
291
291
  if field_set.include?(name)
292
- hash[format_key(name)] = link_object(source, relationship, include_linkage)
292
+ ro = relationship_object(source, relationship, include_linkage)
293
+ hash[format_key(name)] = ro unless ro.blank?
293
294
  end
294
295
 
295
296
  # If the object has been serialized once it will be in the related objects list,
@@ -381,6 +382,13 @@ module JSONAPI
381
382
  link_builder.relationships_related_link(source, relationship)
382
383
  end
383
384
 
385
+ def default_relationship_links(source, relationship)
386
+ links = {}
387
+ links['self'] = self_link(source, relationship) unless relationship.exclude_link?(:self)
388
+ links['related'] = related_link(source, relationship) unless relationship.exclude_link?(:related)
389
+ links.compact
390
+ end
391
+
384
392
  def to_one_linkage(source, relationship)
385
393
  linkage_id = foreign_key_value(source, relationship)
386
394
  linkage_type = format_key(relationship.type_for_source(source))
@@ -428,31 +436,32 @@ module JSONAPI
428
436
  linkage
429
437
  end
430
438
 
431
- def link_object_to_one(source, relationship, include_linkage)
439
+ def relationship_object_to_one(source, relationship, include_linkage)
432
440
  include_linkage = include_linkage | @always_include_to_one_linkage_data | relationship.always_include_linkage_data
433
- link_object_hash = {}
434
- link_object_hash[:links] = {}
435
- link_object_hash[:links][:self] = self_link(source, relationship)
436
- link_object_hash[:links][:related] = related_link(source, relationship)
437
- link_object_hash[:data] = to_one_linkage(source, relationship) if include_linkage
438
- link_object_hash
441
+ relationship_object_hash = {}
442
+
443
+ links = default_relationship_links(source, relationship)
444
+
445
+ relationship_object_hash['links'] = links unless links.blank?
446
+ relationship_object_hash[:data] = to_one_linkage(source, relationship) if include_linkage
447
+ relationship_object_hash
439
448
  end
440
449
 
441
- def link_object_to_many(source, relationship, include_linkage)
450
+ def relationship_object_to_many(source, relationship, include_linkage)
442
451
  include_linkage = include_linkage | relationship.always_include_linkage_data
443
- link_object_hash = {}
444
- link_object_hash[:links] = {}
445
- link_object_hash[:links][:self] = self_link(source, relationship)
446
- link_object_hash[:links][:related] = related_link(source, relationship)
447
- link_object_hash[:data] = to_many_linkage(source, relationship) if include_linkage
448
- link_object_hash
452
+ relationship_object_hash = {}
453
+
454
+ links = default_relationship_links(source, relationship)
455
+ relationship_object_hash['links'] = links unless links.blank?
456
+ relationship_object_hash[:data] = to_many_linkage(source, relationship) if include_linkage
457
+ relationship_object_hash
449
458
  end
450
459
 
451
- def link_object(source, relationship, include_linkage = false)
460
+ def relationship_object(source, relationship, include_linkage = false)
452
461
  if relationship.is_a?(JSONAPI::Relationship::ToOne)
453
- link_object_to_one(source, relationship, include_linkage)
462
+ relationship_object_to_one(source, relationship, include_linkage)
454
463
  elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
455
- link_object_to_many(source, relationship, include_linkage)
464
+ relationship_object_to_many(source, relationship, include_linkage)
456
465
  end
457
466
  end
458
467
 
@@ -528,7 +537,6 @@ module JSONAPI
528
537
  def generate_link_builder(primary_resource_klass, options)
529
538
  LinkBuilder.new(
530
539
  base_url: options.fetch(:base_url, ''),
531
- route_formatter: options.fetch(:route_formatter, JSONAPI.configuration.route_formatter),
532
540
  primary_resource_klass: primary_resource_klass,
533
541
  )
534
542
  end
@@ -1,5 +1,5 @@
1
1
  module JSONAPI
2
2
  module Resources
3
- VERSION = '0.9.6'
3
+ VERSION = '0.9.7'
4
4
  end
5
5
  end
@@ -64,14 +64,19 @@ module JSONAPI
64
64
 
65
65
  # Build pagination links
66
66
  if result.is_a?(JSONAPI::ResourcesOperationResult) || result.is_a?(JSONAPI::RelatedResourcesOperationResult)
67
- result.pagination_params.each_pair do |link_name, params|
68
- if result.is_a?(JSONAPI::RelatedResourcesOperationResult)
69
- relationship = result.source_resource.class._relationships[result._type.to_sym]
70
- links[link_name] = @serializer.link_builder.relationships_related_link(result.source_resource, relationship, query_params(params))
71
- else
72
- links[link_name] = @serializer.query_link(query_params(params))
67
+ result.pagination_params.each_pair do |link_name, params|
68
+ if result.is_a?(JSONAPI::RelatedResourcesOperationResult)
69
+ relationship = result.source_resource.class._relationships[result._type.to_sym]
70
+ unless relationship.exclude_link?(link_name)
71
+ link = @serializer.link_builder.relationships_related_link(result.source_resource, relationship, query_params(params))
72
+ end
73
+ else
74
+ unless @serializer.link_builder.primary_resource_klass.exclude_link?(link_name)
75
+ link = @serializer.link_builder.query_link(query_params(params))
73
76
  end
74
77
  end
78
+ links[link_name] = link unless link.blank?
79
+ end
75
80
  end
76
81
  end
77
82
 
@@ -109,9 +114,9 @@ module JSONAPI
109
114
  @serializer.serialize_to_hash(result.resource)
110
115
  when JSONAPI::ResourcesOperationResult
111
116
  @serializer.serialize_to_hash(result.resources)
112
- when JSONAPI::LinksObjectOperationResult
113
- @serializer.serialize_to_links_hash(result.parent_resource,
114
- result.relationship)
117
+ when JSONAPI::RelationshipOperationResult
118
+ @serializer.serialize_to_relationship_hash(result.parent_resource,
119
+ result.relationship)
115
120
  when JSONAPI::OperationResult
116
121
  {}
117
122
  end
@@ -20,6 +20,10 @@ module ActionDispatch
20
20
  @resource_type = resources.first
21
21
  res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(@resource_type))
22
22
 
23
+ unless res.singleton?
24
+ warn "Singleton routes created for non singleton resource #{res}. Links may not be generated correctly."
25
+ end
26
+
23
27
  options = resources.extract_options!.dup
24
28
  options[:controller] ||= @resource_type
25
29
  options.merge!(res.routing_resource_options)
@@ -80,6 +84,10 @@ module ActionDispatch
80
84
  @resource_type = resources.first
81
85
  res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(@resource_type))
82
86
 
87
+ if res.singleton?
88
+ warn "Singleton resource #{res} should use `jsonapi_resource` instead."
89
+ end
90
+
83
91
  options = resources.extract_options!.dup
84
92
  options[:controller] ||= @resource_type
85
93
  options.merge!(res.routing_resource_options)
@@ -153,19 +161,23 @@ module ActionDispatch
153
161
  methods = links_methods(options)
154
162
 
155
163
  if methods.include?(:show)
156
- match "relationships/#{formatted_relationship_name}", controller: options[:controller],
157
- action: 'show_relationship', relationship: link_type.to_s, via: [:get]
164
+ match "relationships/#{formatted_relationship_name}",
165
+ controller: options[:controller],
166
+ action: 'show_relationship', relationship: link_type.to_s, via: [:get],
167
+ as: "relationships/#{link_type}"
158
168
  end
159
169
 
160
170
  if res.mutable?
161
171
  if methods.include?(:update)
162
- match "relationships/#{formatted_relationship_name}", controller: options[:controller],
163
- action: 'update_relationship', relationship: link_type.to_s, via: [:put, :patch]
172
+ match "relationships/#{formatted_relationship_name}",
173
+ controller: options[:controller],
174
+ action: 'update_relationship', relationship: link_type.to_s, via: [:put, :patch]
164
175
  end
165
176
 
166
177
  if methods.include?(:destroy)
167
- match "relationships/#{formatted_relationship_name}", controller: options[:controller],
168
- action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
178
+ match "relationships/#{formatted_relationship_name}",
179
+ controller: options[:controller],
180
+ action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
169
181
  end
170
182
  end
171
183
  end
@@ -182,23 +194,24 @@ module ActionDispatch
182
194
 
183
195
  if methods.include?(:show)
184
196
  match "relationships/#{formatted_relationship_name}", controller: options[:controller],
185
- action: 'show_relationship', relationship: link_type.to_s, via: [:get]
197
+ action: 'show_relationship', relationship: link_type.to_s, via: [:get],
198
+ as: "relationships/#{link_type}"
186
199
  end
187
200
 
188
201
  if res.mutable?
189
202
  if methods.include?(:create)
190
203
  match "relationships/#{formatted_relationship_name}", controller: options[:controller],
191
- action: 'create_relationship', relationship: link_type.to_s, via: [:post]
204
+ action: 'create_relationship', relationship: link_type.to_s, via: [:post]
192
205
  end
193
206
 
194
207
  if methods.include?(:update)
195
208
  match "relationships/#{formatted_relationship_name}", controller: options[:controller],
196
- action: 'update_relationship', relationship: link_type.to_s, via: [:put, :patch]
209
+ action: 'update_relationship', relationship: link_type.to_s, via: [:put, :patch]
197
210
  end
198
211
 
199
212
  if methods.include?(:destroy)
200
213
  match "relationships/#{formatted_relationship_name}", controller: options[:controller],
201
- action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
214
+ action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
202
215
  end
203
216
  end
204
217
  end
@@ -219,9 +232,10 @@ module ActionDispatch
219
232
  options[:controller] ||= related_resource._type.to_s
220
233
  end
221
234
 
222
- match "#{formatted_relationship_name}", controller: options[:controller],
223
- relationship: relationship.name, source: resource_type_with_module_prefix(source._type),
224
- action: 'get_related_resource', via: [:get]
235
+ match formatted_relationship_name, controller: options[:controller],
236
+ relationship: relationship.name, source: resource_type_with_module_prefix(source._type),
237
+ action: 'get_related_resource', via: [:get],
238
+ as: relationship_name
225
239
  end
226
240
 
227
241
  def jsonapi_related_resources(*relationship)
@@ -235,9 +249,11 @@ module ActionDispatch
235
249
  related_resource = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(relationship.class_name.underscore))
236
250
  options[:controller] ||= related_resource._type.to_s
237
251
 
238
- match "#{formatted_relationship_name}", controller: options[:controller],
239
- relationship: relationship.name, source: resource_type_with_module_prefix(source._type),
240
- action: 'get_related_resources', via: [:get]
252
+ match formatted_relationship_name,
253
+ controller: options[:controller],
254
+ relationship: relationship.name, source: resource_type_with_module_prefix(source._type),
255
+ action: 'get_related_resources', via: [:get],
256
+ as: relationship_name
241
257
  end
242
258
 
243
259
  protected
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi-resources
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.6
4
+ version: 0.9.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Gebhardt
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-03-25 00:00:00.000000000 Z
12
+ date: 2019-05-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler