jsonapi-resources 0.9.6 → 0.9.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 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