jsonapi-resources 0.0.12 → 0.0.13

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
  SHA1:
3
- metadata.gz: 8dde1ba06781fcada973e949ba64e7b7821c8b23
4
- data.tar.gz: cf3aefe531485a4ed3efeba48cf9b7deecf78a74
3
+ metadata.gz: 2d31c91126186e543683030f0b2c2a0fb513d64e
4
+ data.tar.gz: 8bd319a9b582d306093f9cfaf82733f5edeb7aa1
5
5
  SHA512:
6
- metadata.gz: 89cb1ccd8c7b620cced0007df3735ddec354e972e2a7f1a88907277089a5e509a23c7e90bb877bf676fd5edd6e71a49028b698fb888656a37854361e40a25c83
7
- data.tar.gz: 2a7211219f7ae1849220122d49fc785b41639a48b79d3666fa8da553957ed6f09031e61c2aad6311117b558f8fc73232f9094ebd8c2bc914ff6c73e1779a0202
6
+ metadata.gz: d4b2634d12eba8207a651bdb60a42e82bb85136d7098feaaf70821ea58c8200636b8b4e480a423b8ab8e1af1f2f8837cb33fe307adf55843c6da770e4c02e7f9
7
+ data.tar.gz: ebd5e529dd9662930ee305309cba83324325e090abdda8fcb7cf6a8cc204b437fde534d6de58d5815c5c1278efa9c527b4ea6ce3113940fcc651b00c5015bd37
data/README.md CHANGED
@@ -260,7 +260,7 @@ end
260
260
 
261
261
  ##### Finders
262
262
 
263
- Basic finding by filters is supported by resources. However if you have more complex requirements for finding you can override the `find` and `find_by_key` methods on the resource.
263
+ Basic finding by filters is supported by resources. However if you have more complex requirements for finding you can override the `find`, `find_by_key` and `find_by_keys` methods on the resource class.
264
264
 
265
265
  Here's an example that defers the `find` operation to a `current_user` set on the `context` option:
266
266
 
@@ -283,6 +283,23 @@ class AuthorResource < JSONAPI::Resource
283
283
  end
284
284
  ```
285
285
 
286
+ ##### Customizing base records for finder methods
287
+
288
+ If you need to change the base records on which `find`, `find_by_key` and `find_by_keys` operate, you can override the `records` method on the resource class.
289
+
290
+ For example to allow a user to only retrieve his own posts you can do the following:
291
+
292
+ ```ruby
293
+ class PostResource < JSONAPI::Resource
294
+ attribute :id, :title, :body
295
+
296
+ def self.records(options = {})
297
+ context = options[:context]
298
+ context.current_user.posts
299
+ end
300
+ end
301
+ ```
302
+
286
303
  ### Controllers
287
304
 
288
305
  JSONAPI::Resources provides a class, `ResourceController`, that can be used as the base class for your controllers. `ResourceController` supports `index`, `show`, `create`, `update`, and `destroy` methods. Just deriving your controller from `ResourceController` will give you a fully functional controller.
@@ -345,12 +362,14 @@ These codes can be customized in your app by creating an initializer to override
345
362
 
346
363
  ### Serializer
347
364
 
348
- The `ResourceSerializer` can be used to serialize a resource into JSON API compliant JSON. `ResourceSerializer` has a `serialize_to_hash` method that takes a resource instance to serialize. For example:
365
+ The `ResourceSerializer` can be used to serialize a resource into JSON API compliant JSON. `ResourceSerializer` must be
366
+ initialized with the primary resource type it will be serializing. `ResourceSerializer` has a `serialize_to_hash`
367
+ method that takes a resource instance or array of resource instances to serialize. For example:
349
368
 
350
369
  ```ruby
351
370
  require 'jsonapi/resource_serializer'
352
371
  post = Post.find(1)
353
- JSONAPI::ResourceSerializer.new.serialize_to_hash(PostResource.new(post))
372
+ JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(PostResource.new(post))
354
373
  ```
355
374
 
356
375
  This returns results like this:
@@ -393,7 +412,7 @@ A hash of resource types and arrays of fields for each resource type.
393
412
 
394
413
  ```ruby
395
414
  post = Post.find(1)
396
- JSONAPI::ResourceSerializer.new.serialize_to_hash(PostResource.new(post),
415
+ JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(PostResource.new(post),
397
416
  include: ['comments','author','comments.tags','author.posts'],
398
417
  fields: {
399
418
  people: [:id, :email, :comments],
@@ -2,17 +2,31 @@ require 'jsonapi/formatter'
2
2
 
3
3
  module JSONAPI
4
4
  class Configuration
5
- attr_reader :json_key_format, :key_formatter
5
+ attr_reader :json_key_format, :key_formatter, :allowed_request_params, :route_format, :route_formatter
6
6
 
7
7
  def initialize
8
8
  #:underscored_key, :camelized_key, :dasherized_key, or custom
9
9
  self.json_key_format = :underscored_key
10
+
11
+ #:underscored_route, :camelized_route, :dasherized_route, or custom
12
+ self.route_format = :underscored_route
13
+
14
+ self.allowed_request_params = [:include, :fields, :format, :controller, :action, :sort]
10
15
  end
11
16
 
12
17
  def json_key_format=(format)
13
18
  @json_key_format = format
14
19
  @key_formatter = JSONAPI::Formatter.formatter_for(format)
15
20
  end
21
+
22
+ def route_format=(format)
23
+ @route_format = format
24
+ @route_formatter = JSONAPI::Formatter.formatter_for(format)
25
+ end
26
+
27
+ def allowed_request_params=(allowed_request_params)
28
+ @allowed_request_params = allowed_request_params
29
+ end
16
30
  end
17
31
 
18
32
  class << self
@@ -37,6 +37,18 @@ module JSONAPI
37
37
  end
38
38
  end
39
39
 
40
+ class RouteFormatter < Formatter
41
+ class << self
42
+ def format(route)
43
+ super
44
+ end
45
+
46
+ def unformat(formatted_route)
47
+ super.to_sym
48
+ end
49
+ end
50
+ end
51
+
40
52
  class ValueFormatter < Formatter
41
53
  class << self
42
54
  def format(raw_value, context)
@@ -93,4 +105,31 @@ class DefaultValueFormatter < JSONAPI::ValueFormatter
93
105
  end
94
106
  end
95
107
  end
108
+ end
109
+
110
+ class UnderscoredRouteFormatter < JSONAPI::RouteFormatter
111
+ end
112
+
113
+ class CamelizedRouteFormatter < JSONAPI::RouteFormatter
114
+ class << self
115
+ def format(route)
116
+ super.camelize(:lower)
117
+ end
118
+
119
+ def unformat(formatted_route)
120
+ formatted_route.to_s.underscore.to_sym
121
+ end
122
+ end
123
+ end
124
+
125
+ class DasherizedRouteFormatter < JSONAPI::RouteFormatter
126
+ class << self
127
+ def format(route)
128
+ super.dasherize
129
+ end
130
+
131
+ def unformat(formatted_route)
132
+ formatted_route.to_s.underscore.to_sym
133
+ end
134
+ end
96
135
  end
@@ -134,9 +134,11 @@ module JSONAPI
134
134
  params.each do |key, value|
135
135
  filter = key.to_sym
136
136
 
137
- if [:include, :fields, :format, :controller, :action, :sort].include?(filter)
138
- # Ignore non-filter parameters
139
- elsif @resource_klass._allowed_filter?(filter)
137
+ # Ignore non-filter parameters
138
+ next if JSONAPI.configuration.allowed_request_params.include?(filter)
139
+
140
+ filter = unformat_key(key).to_sym
141
+ if @resource_klass._allowed_filter?(filter)
140
142
  filters[filter] = value
141
143
  else
142
144
  @errors.concat(JSONAPI::Exceptions::FilterNotAllowed.new(filter).errors)
@@ -147,8 +149,18 @@ module JSONAPI
147
149
 
148
150
  def parse_sort_params(params)
149
151
  @sort_params = if params[:sort].present?
150
- CSV.parse_line(params[:sort]).each do |sort_param|
151
- check_sort_param(@resource_klass, sort_param)
152
+ CSV.parse_line(params[:sort]).collect do |sort_param|
153
+ # A parameter name may not start with a dash
154
+ # We need to preserve the dash as the sort direction before we unformat the string
155
+ if sort_param.start_with?('-')
156
+ unformatted_sort_param = unformat_key(sort_param[1..-1]).to_s
157
+ unformatted_sort_param.prepend('-')
158
+ else
159
+ unformatted_sort_param = unformat_key(sort_param).to_s
160
+ end
161
+
162
+ check_sort_param(@resource_klass, unformatted_sort_param)
163
+ unformatted_sort_param
152
164
  end
153
165
  else
154
166
  []
@@ -239,7 +239,7 @@ module JSONAPI
239
239
 
240
240
  resources = []
241
241
  order_options = construct_order_options(sort_params)
242
- _model_class.where(where_filters).order(order_options).includes(includes).each do |model|
242
+ records(options).where(where_filters).order(order_options).includes(includes).each do |model|
243
243
  resources.push self.new(model, context)
244
244
  end
245
245
 
@@ -248,7 +248,7 @@ module JSONAPI
248
248
 
249
249
  def find_by_key(key, options = {})
250
250
  context = options[:context]
251
- model = _model_class.where({_primary_key => key}).first
251
+ model = records(options).where({_primary_key => key}).first
252
252
  if model.nil?
253
253
  raise JSONAPI::Exceptions::RecordNotFound.new(key)
254
254
  end
@@ -257,7 +257,7 @@ module JSONAPI
257
257
 
258
258
  def find_by_keys(keys, options = {})
259
259
  context = options[:context]
260
- _models = _model_class.where({_primary_key => keys})
260
+ _models = records(options).where({_primary_key => keys})
261
261
 
262
262
  unless _models.length == keys.length
263
263
  key = (keys - _models.pluck(:id).map(&:to_s)).first
@@ -267,6 +267,12 @@ module JSONAPI
267
267
  _models.map { |model| self.new(model, context) }
268
268
  end
269
269
 
270
+ # Override this method if you want to customize the relation for
271
+ # finder methods (find, find_by_key, find_by_keys)
272
+ def records(options = {})
273
+ _model_class
274
+ end
275
+
270
276
  def verify_filters(filters, context = nil)
271
277
  verified_filters = {}
272
278
  filters.each do |filter, raw_value|
@@ -16,7 +16,7 @@ module JSONAPI
16
16
  before_filter :setup_request
17
17
 
18
18
  def index
19
- render json: JSONAPI::ResourceSerializer.new.serialize_to_hash(
19
+ render json: JSONAPI::ResourceSerializer.new(resource_klass).serialize_to_hash(
20
20
  resource_klass.find(resource_klass.verify_filters(@request.filters, context),
21
21
  context: context, sort_params: @request.sort_params),
22
22
  include: @request.include,
@@ -36,7 +36,7 @@ module JSONAPI
36
36
  resource_klass.find_by_key(keys[0], context: context)
37
37
  end
38
38
 
39
- render json: JSONAPI::ResourceSerializer.new.serialize_to_hash(
39
+ render json: JSONAPI::ResourceSerializer.new(resource_klass).serialize_to_hash(
40
40
  resources,
41
41
  include: @request.include,
42
42
  fields: @request.fields,
@@ -54,7 +54,7 @@ module JSONAPI
54
54
  parent_resource = resource_klass.find_by_key(parent_key, context: context)
55
55
 
56
56
  association = resource_klass._association(association_type)
57
- render json: { association_type => parent_resource.send(association.foreign_key)}
57
+ render json: { key_formatter.format(association_type) => parent_resource.send(association.foreign_key)}
58
58
  rescue => e
59
59
  # :nocov:
60
60
  handle_exceptions(e)
@@ -168,11 +168,12 @@ module JSONAPI
168
168
  else
169
169
  if results.length > 0 && resources.length > 0
170
170
  render status: results[0].code,
171
- json: JSONAPI::ResourceSerializer.new.serialize_to_hash(resources.length > 1 ? resources : resources[0],
172
- include: @request.include,
173
- fields: @request.fields,
174
- attribute_formatters: attribute_formatters,
175
- key_formatter: key_formatter)
171
+ json: JSONAPI::ResourceSerializer.new(resource_klass).serialize_to_hash(
172
+ resources.length > 1 ? resources : resources[0],
173
+ include: @request.include,
174
+ fields: @request.fields,
175
+ attribute_formatters: attribute_formatters,
176
+ key_formatter: key_formatter)
176
177
  else
177
178
  render status: results[0].code, json: nil
178
179
  end
@@ -1,6 +1,11 @@
1
1
  module JSONAPI
2
2
  class ResourceSerializer
3
3
 
4
+ def initialize(primary_resource_klass)
5
+ @primary_resource_klass = primary_resource_klass
6
+ @primary_class_name = @primary_resource_klass._type
7
+ end
8
+
4
9
  # Converts a single resource, or an array of resources to a hash, conforming to the JSONAPI structure
5
10
  # include:
6
11
  # Purpose: determines which objects will be side loaded with the source objects in a linked section
@@ -11,7 +16,6 @@ module JSONAPI
11
16
  # Example: { people: [:id, :email, :comments], posts: [:id, :title, :author], comments: [:id, :body, :post]}
12
17
  def serialize_to_hash(source, options = {})
13
18
  is_resource_collection = source.respond_to?(:to_ary)
14
- return {} if source.nil? || (is_resource_collection && source.size == 0)
15
19
 
16
20
  @fields = options.fetch(:fields, {})
17
21
  include = options.fetch(:include, [])
@@ -22,16 +26,8 @@ module JSONAPI
22
26
 
23
27
  requested_associations = parse_includes(include)
24
28
 
25
- if is_resource_collection
26
- @primary_class_name = source[0].class._type
27
- else
28
- @primary_class_name = source.class._type
29
- end
30
-
31
29
  process_primary(source, requested_associations)
32
30
 
33
- primary_class_name = @primary_class_name.to_sym
34
-
35
31
  linked_hash = {}
36
32
  primary_objects = []
37
33
  @linked_objects.each do |class_name, objects|
@@ -49,9 +45,9 @@ module JSONAPI
49
45
  end
50
46
 
51
47
  if is_resource_collection
52
- primary_hash = {format_key(primary_class_name) => primary_objects}
48
+ primary_hash = {format_key(@primary_class_name) => primary_objects}
53
49
  else
54
- primary_hash = {format_key(primary_class_name) => primary_objects[0]}
50
+ primary_hash = {format_key(@primary_class_name) => primary_objects[0]}
55
51
  end
56
52
 
57
53
  if linked_hash.size > 0
@@ -1,5 +1,5 @@
1
1
  module JSONAPI
2
2
  module Resources
3
- VERSION = "0.0.12"
3
+ VERSION = "0.0.13"
4
4
  end
5
5
  end
@@ -1,14 +1,32 @@
1
1
  module ActionDispatch
2
2
  module Routing
3
3
  class Mapper
4
+
5
+ Resource.class_eval do
6
+ def unformat_route(route)
7
+ JSONAPI.configuration.route_formatter.unformat(route.to_s)
8
+ end
9
+
10
+ def nested_param
11
+ :"#{unformat_route(singular)}_#{param}"
12
+ end
13
+ end
14
+
4
15
  Resources.class_eval do
16
+ def format_route(route)
17
+ JSONAPI.configuration.route_formatter.format(route.to_s)
18
+ end
19
+
5
20
  def jsonapi_resource(*resources, &block)
6
21
  resource_type = resources.first
7
- options = resources.extract_options!.dup
8
-
9
22
  res = JSONAPI::Resource.resource_for(resource_type)
10
23
 
11
- resource resource_type, options.merge(res.routing_resource_options) do
24
+ options = resources.extract_options!.dup
25
+ options[:controller] ||= resource_type
26
+ options.merge!(res.routing_resource_options)
27
+ options[:path] = format_route(resource_type)
28
+
29
+ resource resource_type, options do
12
30
  @scope[:jsonapi_resource] = resource_type
13
31
 
14
32
  if block_given?
@@ -27,14 +45,18 @@ module ActionDispatch
27
45
 
28
46
  def jsonapi_resources(*resources, &block)
29
47
  resource_type = resources.first
30
- options = resources.extract_options!.dup
31
-
32
48
  res = JSONAPI::Resource.resource_for(resource_type)
33
49
 
50
+ options = resources.extract_options!.dup
51
+ options[:controller] ||= resource_type
52
+ options.merge!(res.routing_resource_options)
53
+
34
54
  # Route using the primary_key. Can be overridden using routing_resource_options
35
- options.merge!(param: res._primary_key)
55
+ options[:param] ||= res._primary_key
56
+
57
+ options[:path] = format_route(resource_type)
36
58
 
37
- resources resource_type, options.merge(res.routing_resource_options) do
59
+ resources resource_type, options do
38
60
  @scope[:jsonapi_resource] = resource_type
39
61
 
40
62
  if block_given?
@@ -64,6 +86,7 @@ module ActionDispatch
64
86
 
65
87
  def jsonapi_link(*links)
66
88
  link_type = links.first
89
+ formatted_association_name = format_route(link_type)
67
90
  options = links.extract_options!.dup
68
91
 
69
92
  res = JSONAPI::Resource.resource_for(@scope[:jsonapi_resource])
@@ -71,24 +94,29 @@ module ActionDispatch
71
94
  methods = links_methods(options)
72
95
 
73
96
  if methods.include?(:show)
74
- match "links/#{link_type}", controller: res._type.to_s, action: 'show_association', association: link_type.to_s, via: [:get]
97
+ match "links/#{formatted_association_name}", controller: res._type.to_s,
98
+ action: 'show_association', association: link_type.to_s, via: [:get]
75
99
  end
76
100
 
77
101
  if methods.include?(:create)
78
- match "links/#{link_type}", controller: res._type.to_s, action: 'create_association', association: link_type.to_s, via: [:post]
102
+ match "links/#{formatted_association_name}", controller: res._type.to_s,
103
+ action: 'create_association', association: link_type.to_s, via: [:post]
79
104
  end
80
105
 
81
106
  if methods.include?(:update)
82
- match "links/#{link_type}", controller: res._type.to_s, action: 'update_association', association: link_type.to_s, via: [:put]
107
+ match "links/#{formatted_association_name}", controller: res._type.to_s,
108
+ action: 'update_association', association: link_type.to_s, via: [:put]
83
109
  end
84
110
 
85
111
  if methods.include?(:destroy)
86
- match "links/#{link_type}", controller: res._type.to_s, action: 'destroy_association', association: link_type.to_s, via: [:delete]
112
+ match "links/#{formatted_association_name}", controller: res._type.to_s,
113
+ action: 'destroy_association', association: link_type.to_s, via: [:delete]
87
114
  end
88
115
  end
89
116
 
90
117
  def jsonapi_links(*links)
91
118
  link_type = links.first
119
+ formatted_association_name = format_route(link_type)
92
120
  options = links.extract_options!.dup
93
121
 
94
122
  res = JSONAPI::Resource.resource_for(@scope[:jsonapi_resource])
@@ -96,19 +124,23 @@ module ActionDispatch
96
124
  methods = links_methods(options)
97
125
 
98
126
  if methods.include?(:show)
99
- match "links/#{link_type}", controller: res._type.to_s, action: 'show_association', association: link_type.to_s, via: [:get]
127
+ match "links/#{formatted_association_name}", controller: res._type.to_s,
128
+ action: 'show_association', association: link_type.to_s, via: [:get]
100
129
  end
101
130
 
102
131
  if methods.include?(:create)
103
- match "links/#{link_type}", controller: res._type.to_s, action: 'create_association', association: link_type.to_s, via: [:post]
132
+ match "links/#{formatted_association_name}", controller: res._type.to_s,
133
+ action: 'create_association', association: link_type.to_s, via: [:post]
104
134
  end
105
135
 
106
136
  if methods.include?(:update) && res._association(link_type).acts_as_set
107
- match "links/#{link_type}", controller: res._type.to_s, action: 'update_association', association: link_type.to_s, via: [:put]
137
+ match "links/#{formatted_association_name}", controller: res._type.to_s,
138
+ action: 'update_association', association: link_type.to_s, via: [:put]
108
139
  end
109
140
 
110
141
  if methods.include?(:destroy)
111
- match "links/#{link_type}/:keys", controller: res._type.to_s, action: 'destroy_association', association: link_type.to_s, via: [:delete]
142
+ match "links/#{formatted_association_name}/:keys", controller: res._type.to_s,
143
+ action: 'destroy_association', association: link_type.to_s, via: [:delete]
112
144
  end
113
145
  end
114
146
  end
@@ -8,6 +8,13 @@ class PostsControllerTest < ActionController::TestCase
8
8
  assert json_response['posts'].is_a?(Array)
9
9
  end
10
10
 
11
+ def test_index_filter_with_empty_result
12
+ get :index, {title: 'post that does not exist'}
13
+ assert_response :success
14
+ assert json_response['posts'].is_a?(Array)
15
+ assert_equal 0, json_response['posts'].size
16
+ end
17
+
11
18
  def test_index_filter_by_id
12
19
  get :index, {id: '1'}
13
20
  assert_response :success
@@ -100,7 +107,7 @@ class PostsControllerTest < ActionController::TestCase
100
107
  def test_filter_associations_multiple_not_found
101
108
  get :index, {tags: '1', comments: '3'}
102
109
  assert_response :success
103
- assert_equal 0, json_response.size
110
+ assert_equal 0, json_response['posts'].size
104
111
  end
105
112
 
106
113
  def test_bad_filter
@@ -1166,30 +1173,31 @@ class IsoCurrenciesControllerTest < ActionController::TestCase
1166
1173
  end
1167
1174
 
1168
1175
  def test_currencies_index
1176
+ JSONAPI.configuration.json_key_format = :camelized_key
1169
1177
  get :index
1170
1178
  assert_response :success
1171
- assert_equal 2, json_response['isoCurrencies'].size
1179
+ assert_equal 3, json_response['isoCurrencies'].size
1172
1180
  end
1173
1181
 
1174
1182
  def test_currencies_json_key_underscored
1175
1183
  JSONAPI.configuration.json_key_format = :underscored_key
1176
1184
  get :index
1177
1185
  assert_response :success
1178
- assert_equal 2, json_response['iso_currencies'].size
1186
+ assert_equal 3, json_response['iso_currencies'].size
1179
1187
  end
1180
1188
 
1181
1189
  def test_currencies_json_key_dasherized
1182
1190
  JSONAPI.configuration.json_key_format = :dasherized_key
1183
1191
  get :index
1184
1192
  assert_response :success
1185
- assert_equal 2, json_response['iso-currencies'].size
1193
+ assert_equal 3, json_response['iso-currencies'].size
1186
1194
  end
1187
1195
 
1188
1196
  def test_currencies_custom_json_key
1189
1197
  JSONAPI.configuration.json_key_format = :upper_camelized_key
1190
1198
  get :index
1191
1199
  assert_response :success
1192
- assert_equal 2, json_response['IsoCurrencies'].size
1200
+ assert_equal 3, json_response['IsoCurrencies'].size
1193
1201
  end
1194
1202
 
1195
1203
  def test_currencies_show
@@ -1197,6 +1205,84 @@ class IsoCurrenciesControllerTest < ActionController::TestCase
1197
1205
  assert_response :success
1198
1206
  assert json_response['isoCurrencies'].is_a?(Hash)
1199
1207
  end
1208
+
1209
+ def test_currencies_json_key_underscored_sort
1210
+ JSONAPI.configuration.json_key_format = :underscored_key
1211
+ get :index, {sort: 'country_name'}
1212
+ assert_response :success
1213
+ assert_equal 3, json_response['iso_currencies'].size
1214
+ assert_equal 'Canada', json_response['iso_currencies'][0]['country_name']
1215
+ assert_equal 'Euro Member Countries', json_response['iso_currencies'][1]['country_name']
1216
+ assert_equal 'United States', json_response['iso_currencies'][2]['country_name']
1217
+
1218
+ # reverse sort
1219
+ get :index, {sort: '-country_name'}
1220
+ assert_response :success
1221
+ assert_equal 3, json_response['iso_currencies'].size
1222
+ assert_equal 'United States', json_response['iso_currencies'][0]['country_name']
1223
+ assert_equal 'Euro Member Countries', json_response['iso_currencies'][1]['country_name']
1224
+ assert_equal 'Canada', json_response['iso_currencies'][2]['country_name']
1225
+ end
1226
+
1227
+ def test_currencies_json_key_dasherized_sort
1228
+ JSONAPI.configuration.json_key_format = :dasherized_key
1229
+ get :index, {sort: 'country-name'}
1230
+ assert_response :success
1231
+ assert_equal 3, json_response['iso-currencies'].size
1232
+ assert_equal 'Canada', json_response['iso-currencies'][0]['country-name']
1233
+ assert_equal 'Euro Member Countries', json_response['iso-currencies'][1]['country-name']
1234
+ assert_equal 'United States', json_response['iso-currencies'][2]['country-name']
1235
+
1236
+ # reverse sort
1237
+ get :index, {sort: '-country-name'}
1238
+ assert_response :success
1239
+ assert_equal 3, json_response['iso-currencies'].size
1240
+ assert_equal 'United States', json_response['iso-currencies'][0]['country-name']
1241
+ assert_equal 'Euro Member Countries', json_response['iso-currencies'][1]['country-name']
1242
+ assert_equal 'Canada', json_response['iso-currencies'][2]['country-name']
1243
+ end
1244
+
1245
+ def test_currencies_json_key_custom_json_key_sort
1246
+ JSONAPI.configuration.json_key_format = :upper_camelized_key
1247
+ get :index, {sort: 'CountryName'}
1248
+ assert_response :success
1249
+ assert_equal 3, json_response['IsoCurrencies'].size
1250
+ assert_equal 'Canada', json_response['IsoCurrencies'][0]['CountryName']
1251
+ assert_equal 'Euro Member Countries', json_response['IsoCurrencies'][1]['CountryName']
1252
+ assert_equal 'United States', json_response['IsoCurrencies'][2]['CountryName']
1253
+
1254
+ # reverse sort
1255
+ get :index, {sort: '-CountryName'}
1256
+ assert_response :success
1257
+ assert_equal 3, json_response['IsoCurrencies'].size
1258
+ assert_equal 'United States', json_response['IsoCurrencies'][0]['CountryName']
1259
+ assert_equal 'Euro Member Countries', json_response['IsoCurrencies'][1]['CountryName']
1260
+ assert_equal 'Canada', json_response['IsoCurrencies'][2]['CountryName']
1261
+ end
1262
+
1263
+ def test_currencies_json_key_underscored_filter
1264
+ JSONAPI.configuration.json_key_format = :underscored_key
1265
+ get :index, {country_name: 'Canada'}
1266
+ assert_response :success
1267
+ assert_equal 1, json_response['iso_currencies'].size
1268
+ assert_equal 'Canada', json_response['iso_currencies'][0]['country_name']
1269
+ end
1270
+
1271
+ def test_currencies_json_key_camelized_key_filter
1272
+ JSONAPI.configuration.json_key_format = :camelized_key
1273
+ get :index, {'countryName' => 'Canada'}
1274
+ assert_response :success
1275
+ assert_equal 1, json_response['isoCurrencies'].size
1276
+ assert_equal 'Canada', json_response['isoCurrencies'][0]['countryName']
1277
+ end
1278
+
1279
+ def test_currencies_json_key_custom_json_key_filter
1280
+ JSONAPI.configuration.json_key_format = :upper_camelized_key
1281
+ get :index, {'CountryName' => 'Canada'}
1282
+ assert_response :success
1283
+ assert_equal 1, json_response['IsoCurrencies'].size
1284
+ assert_equal 'Canada', json_response['IsoCurrencies'][0]['CountryName']
1285
+ end
1200
1286
  end
1201
1287
 
1202
1288
  class PeopleControllerTest < ActionController::TestCase
@@ -288,6 +288,28 @@ module Api
288
288
  class PostsController < JSONAPI::ResourceController
289
289
  end
290
290
  end
291
+
292
+ module V4
293
+ class PostsController < JSONAPI::ResourceController
294
+ end
295
+
296
+ class ExpenseEntriesController < JSONAPI::ResourceController
297
+ end
298
+
299
+ class IsoCurrenciesController < JSONAPI::ResourceController
300
+ end
301
+ end
302
+
303
+ module V5
304
+ class PostsController < JSONAPI::ResourceController
305
+ end
306
+
307
+ class ExpenseEntriesController < JSONAPI::ResourceController
308
+ end
309
+
310
+ class IsoCurrenciesController < JSONAPI::ResourceController
311
+ end
312
+ end
291
313
  end
292
314
 
293
315
  ### RESOURCES
@@ -414,6 +436,8 @@ end
414
436
  class IsoCurrencyResource < JSONAPI::Resource
415
437
  primary_key :code
416
438
  attributes :id, :name, :country_name, :minor_unit
439
+
440
+ filter :country_name
417
441
  end
418
442
 
419
443
  class ExpenseEntryResource < JSONAPI::Resource
@@ -616,6 +640,7 @@ end
616
640
 
617
641
  IsoCurrency.create(code: 'USD', name: 'United States Dollar', country_name: 'United States', minor_unit: 'cent')
618
642
  IsoCurrency.create(code: 'EUR', name: 'Euro Member Countries', country_name: 'Euro Member Countries', minor_unit: 'cent')
643
+ IsoCurrency.create(code: 'CAD', name: 'Canadian dollar', country_name: 'Canada', minor_unit: 'cent')
619
644
 
620
645
  ExpenseEntry.create(currency_code: 'USD',
621
646
  employee_id: c.id,
@@ -8,6 +8,43 @@ class RequestTest < ActionDispatch::IntegrationTest
8
8
  assert_equal 200, status
9
9
  end
10
10
 
11
+ def test_get_underscored_key
12
+ JSONAPI.configuration.json_key_format = :underscored_key
13
+ get '/iso_currencies'
14
+ assert_equal 200, status
15
+ assert_equal 3, json_response['iso_currencies'].size
16
+ end
17
+
18
+ def test_get_underscored_key_filtered
19
+ JSONAPI.configuration.json_key_format = :underscored_key
20
+ get '/iso_currencies?country_name=Canada'
21
+ assert_equal 200, status
22
+ assert_equal 1, json_response['iso_currencies'].size
23
+ assert_equal 'Canada', json_response['iso_currencies'][0]['country_name']
24
+ end
25
+
26
+ def test_get_camelized_key_filtered
27
+ JSONAPI.configuration.json_key_format = :camelized_key
28
+ get '/iso_currencies?countryName=Canada'
29
+ assert_equal 200, status
30
+ assert_equal 1, json_response['isoCurrencies'].size
31
+ assert_equal 'Canada', json_response['isoCurrencies'][0]['countryName']
32
+ end
33
+
34
+ def test_get_camelized_route_and_key_filtered
35
+ get '/api/v4/isoCurrencies?countryName=Canada'
36
+ assert_equal 200, status
37
+ assert_equal 1, json_response['isoCurrencies'].size
38
+ assert_equal 'Canada', json_response['isoCurrencies'][0]['countryName']
39
+ end
40
+
41
+ def test_get_camelized_route_and_links
42
+ JSONAPI.configuration.json_key_format = :camelized_key
43
+ get '/api/v4/expenseEntries/1/links/isoCurrency'
44
+ assert_equal 200, status
45
+ assert_equal 'USD', json_response['isoCurrency']
46
+ end
47
+
11
48
  def test_put_single
12
49
  put '/posts/3',
13
50
  {
@@ -111,6 +111,44 @@ class RoutesTest < ActionDispatch::IntegrationTest
111
111
  {action: 'show', controller: 'api/v3/posts', id: '1'})
112
112
  end
113
113
 
114
+ # V4 camelCase
115
+ def test_routing_v4_posts_show
116
+ assert_routing({path: '/api/v4/posts/1', method: :get},
117
+ {action: 'show', controller: 'api/v4/posts', id: '1'})
118
+ end
119
+
120
+ def test_routing_v4_isoCurrencies_resources
121
+ assert_routing({path: '/api/v4/isoCurrencies/USD', method: :get},
122
+ {action: 'show', controller: 'api/v4/iso_currencies', code: 'USD'})
123
+ end
124
+
125
+ def test_routing_v4_expenseEntries_resources
126
+ assert_routing({path: '/api/v4/expenseEntries/1', method: :get},
127
+ {action: 'show', controller: 'api/v4/expense_entries', id: '1'})
128
+
129
+ assert_routing({path: '/api/v4/expenseEntries/1/links/isoCurrency', method: :get},
130
+ {controller: 'api/v4/expense_entries', action: 'show_association', expense_entry_id: '1', association: 'iso_currency'})
131
+ end
132
+
133
+ # V5 dasherized
134
+ def test_routing_v5_posts_show
135
+ assert_routing({path: '/api/v5/posts/1', method: :get},
136
+ {action: 'show', controller: 'api/v5/posts', id: '1'})
137
+ end
138
+
139
+ def test_routing_v5_isoCurrencies_resources
140
+ assert_routing({path: '/api/v5/iso-currencies/USD', method: :get},
141
+ {action: 'show', controller: 'api/v5/iso_currencies', code: 'USD'})
142
+ end
143
+
144
+ def test_routing_v5_expenseEntries_resources
145
+ assert_routing({path: '/api/v5/expense-entries/1', method: :get},
146
+ {action: 'show', controller: 'api/v5/expense_entries', id: '1'})
147
+
148
+ assert_routing({path: '/api/v5/expense-entries/1/links/iso-currency', method: :get},
149
+ {controller: 'api/v5/expense_entries', action: 'show_association', expense_entry_id: '1', association: 'iso_currency'})
150
+ end
151
+
114
152
  #primary_key
115
153
  def test_routing_primary_key_jsonapi_resources
116
154
  assert_routing({path: '/iso_currencies/USD', method: :get},
@@ -135,5 +173,4 @@ class RoutesTest < ActionDispatch::IntegrationTest
135
173
 
136
174
  # Test that non acts as set has_many association update route is not created
137
175
 
138
- end
139
-
176
+ end
data/test/test_helper.rb CHANGED
@@ -54,7 +54,6 @@ TestApp.routes.draw do
54
54
  jsonapi_resources :moons
55
55
  jsonapi_resources :preferences
56
56
 
57
-
58
57
  namespace :api do
59
58
  namespace :v1 do
60
59
  jsonapi_resources :authors
@@ -88,6 +87,20 @@ TestApp.routes.draw do
88
87
  jsonapi_links :tags, only: [:show, :create]
89
88
  end
90
89
  end
90
+
91
+ JSONAPI.configuration.route_format = :camelized_route
92
+ namespace :v4 do
93
+ jsonapi_resources :posts
94
+ jsonapi_resources :expense_entries
95
+ jsonapi_resources :iso_currencies
96
+ end
97
+ JSONAPI.configuration.route_format = :dasherized_route
98
+ namespace :v5 do
99
+ jsonapi_resources :posts
100
+ jsonapi_resources :expense_entries
101
+ jsonapi_resources :iso_currencies
102
+ end
103
+ JSONAPI.configuration.route_format = :underscored_route
91
104
  end
92
105
  end
93
106
 
@@ -108,6 +121,10 @@ class UpperCamelizedKeyFormatter < JSONAPI::KeyFormatter
108
121
  def format(key)
109
122
  super.camelize(:upper)
110
123
  end
124
+
125
+ def unformat(formatted_key)
126
+ formatted_key.to_s.underscore.to_sym
127
+ end
111
128
  end
112
129
  end
113
130
 
@@ -3,6 +3,10 @@ require File.expand_path('../../../fixtures/active_record', __FILE__)
3
3
 
4
4
  class ArticleResource < JSONAPI::Resource
5
5
  model_name 'Post'
6
+
7
+ def self.records(options)
8
+ options[:context].posts
9
+ end
6
10
  end
7
11
 
8
12
  class CatResource < JSONAPI::Resource
@@ -42,4 +46,34 @@ class ResourceTest < MiniTest::Unit::TestCase
42
46
  assert_kind_of(Hash, associations)
43
47
  assert_equal(associations.size, 2)
44
48
  end
49
+
50
+ def test_find_with_customized_base_records
51
+ author = Person.find(1)
52
+ posts = ArticleResource.find([], context: author).map(&:model)
53
+
54
+ assert(posts.include?(Post.find(1)))
55
+ refute(posts.include?(Post.find(3)))
56
+ end
57
+
58
+ def test_find_by_key_with_customized_base_records
59
+ author = Person.find(1)
60
+
61
+ post = ArticleResource.find_by_key(1, context: author).model
62
+ assert_equal(post, Post.find(1))
63
+
64
+ assert_raises JSONAPI::Exceptions::RecordNotFound do
65
+ ArticleResource.find_by_key(3, context: author).model
66
+ end
67
+ end
68
+
69
+ def test_find_by_keys_with_customized_base_records
70
+ author = Person.find(1)
71
+
72
+ posts = ArticleResource.find_by_keys([1, 2], context: author)
73
+ assert_equal(posts.length, 2)
74
+
75
+ assert_raises JSONAPI::Exceptions::RecordNotFound do
76
+ ArticleResource.find_by_keys([1, 3], context: author).model
77
+ end
78
+ end
45
79
  end
@@ -9,6 +9,12 @@ class SerializerTest < MiniTest::Unit::TestCase
9
9
  @fred = Person.find_by(name: 'Fred Reader')
10
10
 
11
11
  @expense_entry = ExpenseEntry.find(1)
12
+
13
+ JSONAPI.configuration.json_key_format = :camelized_key
14
+ end
15
+
16
+ def after_teardown
17
+ JSONAPI.configuration.json_key_format = :underscored_key
12
18
  end
13
19
 
14
20
  def test_serializer
@@ -28,7 +34,7 @@ class SerializerTest < MiniTest::Unit::TestCase
28
34
  }
29
35
  }
30
36
  },
31
- JSONAPI::ResourceSerializer.new.serialize_to_hash(
37
+ JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(
32
38
  PostResource.new(@post)))
33
39
  end
34
40
 
@@ -44,7 +50,7 @@ class SerializerTest < MiniTest::Unit::TestCase
44
50
  }
45
51
  }
46
52
  },
47
- JSONAPI::ResourceSerializer.new.serialize_to_hash(
53
+ JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(
48
54
  PostResource.new(@post),
49
55
  fields: {posts: [:id, :title, :author]}))
50
56
  end
@@ -78,7 +84,7 @@ class SerializerTest < MiniTest::Unit::TestCase
78
84
  }]
79
85
  }
80
86
  },
81
- JSONAPI::ResourceSerializer.new.serialize_to_hash(
87
+ JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(
82
88
  PostResource.new(@post), include: [:author]))
83
89
  end
84
90
 
@@ -111,7 +117,7 @@ class SerializerTest < MiniTest::Unit::TestCase
111
117
  }]
112
118
  }
113
119
  },
114
- JSONAPI::ResourceSerializer.new.serialize_to_hash(
120
+ JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(
115
121
  PostResource.new(@post),
116
122
  include: [:author],
117
123
  key_formatter: UnderscoredKeyFormatter))
@@ -179,7 +185,7 @@ class SerializerTest < MiniTest::Unit::TestCase
179
185
  ]
180
186
  }
181
187
  },
182
- JSONAPI::ResourceSerializer.new.serialize_to_hash(
188
+ JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(
183
189
  PostResource.new(@post), include: [:comments, 'comments.tags']))
184
190
  end
185
191
 
@@ -225,7 +231,7 @@ class SerializerTest < MiniTest::Unit::TestCase
225
231
  ]
226
232
  }
227
233
  },
228
- JSONAPI::ResourceSerializer.new.serialize_to_hash(
234
+ JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(
229
235
  PostResource.new(@post), include: ['comments.tags']))
230
236
  end
231
237
 
@@ -259,7 +265,7 @@ class SerializerTest < MiniTest::Unit::TestCase
259
265
  ]
260
266
  }
261
267
  },
262
- JSONAPI::ResourceSerializer.new.serialize_to_hash(
268
+ JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(
263
269
  PostResource.new(@post), include: ['author.comments']))
264
270
  end
265
271
 
@@ -299,7 +305,7 @@ class SerializerTest < MiniTest::Unit::TestCase
299
305
  ]
300
306
  }
301
307
  },
302
- JSONAPI::ResourceSerializer.new.serialize_to_hash(
308
+ JSONAPI::ResourceSerializer.new(PersonResource).serialize_to_hash(
303
309
  PersonResource.new(@fred), include: ['comments']))
304
310
  end
305
311
 
@@ -398,7 +404,7 @@ class SerializerTest < MiniTest::Unit::TestCase
398
404
  ]
399
405
  }
400
406
  },
401
- JSONAPI::ResourceSerializer.new.serialize_to_hash(
407
+ JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(
402
408
  posts, include: ['comments', 'comments.tags']))
403
409
  end
404
410
 
@@ -482,7 +488,7 @@ class SerializerTest < MiniTest::Unit::TestCase
482
488
  }]
483
489
  }
484
490
  },
485
- JSONAPI::ResourceSerializer.new.serialize_to_hash(
491
+ JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(
486
492
  posts,
487
493
  include: ['comments', 'author', 'comments.tags', 'author.posts'],
488
494
  fields: {
@@ -520,7 +526,7 @@ class SerializerTest < MiniTest::Unit::TestCase
520
526
  }]
521
527
  }
522
528
  },
523
- JSONAPI::ResourceSerializer.new.serialize_to_hash(
529
+ JSONAPI::ResourceSerializer.new(ExpenseEntryResource).serialize_to_hash(
524
530
  ExpenseEntryResource.new(@expense_entry),
525
531
  include: ['iso_currency', 'employee'],
526
532
  fields: {people: [:id, :name, :email, :date_joined]}
@@ -529,7 +535,8 @@ class SerializerTest < MiniTest::Unit::TestCase
529
535
  end
530
536
 
531
537
  def test_serializer_empty_links_null_and_array
532
- planet_hash = JSONAPI::ResourceSerializer.new.serialize_to_hash(PlanetResource.new(Planet.find(8)))
538
+ planet_hash = JSONAPI::ResourceSerializer.new(PlanetResource).serialize_to_hash(
539
+ PlanetResource.new(Planet.find(8)))
533
540
 
534
541
  assert_hash_equals(
535
542
  {
@@ -556,7 +563,7 @@ class SerializerTest < MiniTest::Unit::TestCase
556
563
  planets.push PlanetResource.new(planet)
557
564
  end
558
565
 
559
- planet_hash = JSONAPI::ResourceSerializer.new.serialize_to_hash(
566
+ planet_hash = JSONAPI::ResourceSerializer.new(PlanetResource).serialize_to_hash(
560
567
  planets,
561
568
  include: ['planet_type'],
562
569
  fields: { planet_types: [:id, :name] }
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.0.12
4
+ version: 0.0.13
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: 2014-11-29 00:00:00.000000000 Z
12
+ date: 2014-12-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler