jsonapi-resources 0.0.12 → 0.0.13

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
  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