json_api_client 1.5.3 → 1.6.0

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: ce135d371a55b1a0114e61714e16f1ba25b27e73
4
- data.tar.gz: aa8ef8446aba3641569ceced0810cba716cdffbb
3
+ metadata.gz: 26e65758ae09b482b1225164727d13e14976a9f5
4
+ data.tar.gz: 7307ffd05a5b5336b5cdd1da281913d40c3c0839
5
5
  SHA512:
6
- metadata.gz: 3db5715ed8267149d9c7fcffd41fc3cd6c8a830585ae6cf80d5208972f2370bce819e4e9f00dbd606d535a7e879255ca423787dd6d1c3500d86d1b91c8a35554
7
- data.tar.gz: 53a8c4c8481e60322a961e1fd7bc8ee202062b196c7695f501b1dfdacdb4e7c09de45b04efa9bf8d1ce7971f4cd558003270a2e7c4f913f279d88047e764e621
6
+ metadata.gz: b39d8afd03a7d669cef3225828661f1cc68d2acd02d54e6f3a00fc5105f8b7fa6191f81ba8ebcbb53c1ca6c00bc819efcdcbc085e1885292a44aef373a4cb28a
7
+ data.tar.gz: d815f9852679b6a008c66c806ccf729653f11fdf525e054780cdaecd6b6be567cf41804e6ef571f85df5f38da7383c442ceb7f716700231a0a6331c75572e048
data/README.md CHANGED
@@ -196,6 +196,23 @@ results = Article.includes(:author, :comments => :author).find(1)
196
196
 
197
197
  # should not have to make additional requests to the server
198
198
  authors = results.map(&:author)
199
+
200
+ # makes POST request to /articles?include=author,comments.author
201
+ article = Article.new(title: 'New one').request_includes(:author, :comments => :author)
202
+ article.save
203
+
204
+ # makes PATCH request to /articles/1?include=author,comments.author
205
+ article = Article.find(1)
206
+ article.title = 'Changed'
207
+ article.request_includes(:author, :comments => :author)
208
+ article.save
209
+
210
+ # request includes will be cleared if response is successful
211
+ # to avoid this `keep_request_params` class attribute can be used
212
+ Article.keep_request_params = true
213
+
214
+ # to clear request_includes use
215
+ article.reset_request_includes!
199
216
  ```
200
217
 
201
218
  ## Sparse Fieldsets
@@ -217,6 +234,24 @@ article.created_at
217
234
  # or you can use fieldsets from multiple resources
218
235
  # makes request to /articles?fields[articles]=title,body&fields[comments]=tag
219
236
  article = Article.select("title", "body",{comments: 'tag'}).first
237
+
238
+ # makes POST request to /articles?fields[articles]=title,body&fields[comments]=tag
239
+ article = Article.new(title: 'New one').request_select(:title, :body, comments: 'tag')
240
+ article.save
241
+
242
+ # makes PATCH request to /articles/1?fields[articles]=title,body&fields[comments]=tag
243
+ article = Article.find(1)
244
+ article.title = 'Changed'
245
+ article.request_select(:title, :body, comments: 'tag')
246
+ article.save
247
+
248
+ # request fields will be cleared if response is successful
249
+ # to avoid this `keep_request_params` class attribute can be used
250
+ Article.keep_request_params = true
251
+
252
+ # to clear request fields use
253
+ article.reset_request_select!(:comments) # to clear for comments
254
+ article.reset_request_select! # to clear for all fields
220
255
  ```
221
256
 
222
257
  ## Sorting
@@ -246,6 +281,10 @@ articles = Article.page(2).per(30).to_a
246
281
 
247
282
  # also makes request to /articles?page=2&per_page=30
248
283
  articles = Article.paginate(page: 2, per_page: 30).to_a
284
+
285
+ # keep in mind that page number can be nil - in that case default number will be applied
286
+ # also makes request to /articles?page=1&per_page=30
287
+ articles = Article.paginate(page: nil, per_page: 30).to_a
249
288
  ```
250
289
 
251
290
  *Note: The mapping of pagination parameters is done by the `query_builder` which is [customizable](#custom-paginator).*
@@ -21,6 +21,7 @@ module JsonApiClient
21
21
  autoload :Paginating, 'json_api_client/paginating'
22
22
  autoload :Parsers, 'json_api_client/parsers'
23
23
  autoload :Query, 'json_api_client/query'
24
+ autoload :RequestParams, 'json_api_client/request_params'
24
25
  autoload :Resource, 'json_api_client/resource'
25
26
  autoload :ResultSet, 'json_api_client/result_set'
26
27
  autoload :Schema, 'json_api_client/schema'
@@ -28,8 +28,10 @@ module JsonApiClient
28
28
  faraday.builder.delete(middleware)
29
29
  end
30
30
 
31
- def run(request_method, path, params = {}, headers = {})
32
- faraday.send(request_method, path, params, headers)
31
+ def run(request_method, path, params: nil, headers: {}, body: nil)
32
+ faraday.run_request(request_method, path, body, headers) do |request|
33
+ request.params.update(params) if params
34
+ end
33
35
  end
34
36
 
35
37
  end
@@ -24,7 +24,7 @@ module JsonApiClient
24
24
  end
25
25
 
26
26
  def respond_to_missing?(method, include_private = false)
27
- if (method.to_s =~ /^(.*)=$/) || has_attribute?(method)
27
+ if has_attribute?(method) || method.to_s.end_with?('=')
28
28
  true
29
29
  else
30
30
  super
@@ -38,16 +38,19 @@ module JsonApiClient
38
38
  protected
39
39
 
40
40
  def method_missing(method, *args, &block)
41
- normalized_method = if key_formatter
42
- key_formatter.unformat(method.to_s)
43
- else
44
- method.to_s
45
- end
46
-
47
- if normalized_method =~ /^(.*)=$/
48
- set_attribute($1, args.first)
49
- elsif has_attribute?(method)
50
- attributes[method]
41
+ if has_attribute?(method)
42
+ self.class.class_eval do
43
+ define_method(method) do
44
+ attributes[method]
45
+ end
46
+ end
47
+ return send(method)
48
+ end
49
+
50
+ normalized_method = safe_key_formatter.unformat(method.to_s)
51
+
52
+ if normalized_method.end_with?('=')
53
+ set_attribute(normalized_method[0..-2], args.first)
51
54
  else
52
55
  super
53
56
  end
@@ -61,10 +64,20 @@ module JsonApiClient
61
64
  attributes[name] = value
62
65
  end
63
66
 
67
+ def safe_key_formatter
68
+ @safe_key_formatter ||= (key_formatter || DefaultKeyFormatter.new)
69
+ end
70
+
64
71
  def key_formatter
65
72
  self.class.respond_to?(:key_formatter) && self.class.key_formatter
66
73
  end
67
74
 
75
+ class DefaultKeyFormatter
76
+ def unformat(method)
77
+ method.to_s
78
+ end
79
+ end
80
+
68
81
  end
69
82
  end
70
83
  end
@@ -82,7 +82,7 @@ module JsonApiClient
82
82
  def params_for_uri(uri)
83
83
  return {} unless uri
84
84
  uri = Addressable::URI.parse(uri)
85
- uri.query_values || {}
85
+ ( uri.query_values || {} ).with_indifferent_access
86
86
  end
87
87
  end
88
88
  end
@@ -5,60 +5,55 @@ module JsonApiClient
5
5
  attr_reader :klass
6
6
  delegate :key_formatter, to: :klass
7
7
 
8
- def initialize(klass)
9
- @klass = klass
10
- @primary_key = nil
11
- @pagination_params = {}
12
- @path_params = {}
13
- @additional_params = {}
14
- @filters = {}
15
- @includes = []
16
- @orders = []
17
- @fields = []
8
+ def initialize(klass, opts = {})
9
+ @klass = klass
10
+ @primary_key = opts.fetch( :primary_key, nil )
11
+ @pagination_params = opts.fetch( :pagination_params, {} )
12
+ @path_params = opts.fetch( :path_params, {} )
13
+ @additional_params = opts.fetch( :additional_params, {} )
14
+ @filters = opts.fetch( :filters, {} )
15
+ @includes = opts.fetch( :includes, [] )
16
+ @orders = opts.fetch( :orders, [] )
17
+ @fields = opts.fetch( :fields, [] )
18
18
  end
19
19
 
20
20
  def where(conditions = {})
21
21
  # pull out any path params here
22
- @path_params.merge!(conditions.slice(*klass.prefix_params))
23
- @filters.merge!(conditions.except(*klass.prefix_params))
24
- self
22
+ path_conditions = conditions.slice(*klass.prefix_params)
23
+ unpathed_conditions = conditions.except(*klass.prefix_params)
24
+
25
+ _new_scope( path_params: path_conditions, filters: unpathed_conditions )
25
26
  end
26
27
 
27
28
  def order(*args)
28
- @orders += parse_orders(*args)
29
- self
29
+ _new_scope( orders: parse_orders(*args) )
30
30
  end
31
31
 
32
32
  def includes(*tables)
33
- @includes += parse_related_links(*tables)
34
- self
33
+ _new_scope( includes: parse_related_links(*tables) )
35
34
  end
36
35
 
37
36
  def select(*fields)
38
- @fields += parse_fields(*fields)
39
- self
37
+ _new_scope( fields: parse_fields(*fields) )
40
38
  end
41
39
 
42
40
  def paginate(conditions = {})
43
- scope = self
41
+ scope = _new_scope
44
42
  scope = scope.page(conditions[:page]) if conditions[:page]
45
43
  scope = scope.per(conditions[:per_page]) if conditions[:per_page]
46
44
  scope
47
45
  end
48
46
 
49
47
  def page(number)
50
- @pagination_params[ klass.paginator.page_param ] = number
51
- self
48
+ _new_scope( pagination_params: { klass.paginator.page_param => number || 1 } )
52
49
  end
53
50
 
54
51
  def per(size)
55
- @pagination_params[ klass.paginator.per_page_param ] = size
56
- self
52
+ _new_scope( pagination_params: { klass.paginator.per_page_param => size } )
57
53
  end
58
54
 
59
55
  def with_params(more_params)
60
- @additional_params.merge!(more_params)
61
- self
56
+ _new_scope( additional_params: more_params )
62
57
  end
63
58
 
64
59
  def first
@@ -92,12 +87,12 @@ module JsonApiClient
92
87
  def find(args = {})
93
88
  case args
94
89
  when Hash
95
- where(args)
90
+ scope = where(args)
96
91
  else
97
- @primary_key = args
92
+ scope = _new_scope( primary_key: args )
98
93
  end
99
94
 
100
- klass.requestor.get(params)
95
+ klass.requestor.get(scope.params)
101
96
  end
102
97
 
103
98
  def method_missing(method_name, *args, &block)
@@ -106,6 +101,18 @@ module JsonApiClient
106
101
 
107
102
  private
108
103
 
104
+ def _new_scope( opts = {} )
105
+ self.class.new( @klass,
106
+ primary_key: opts.fetch( :primary_key, @primary_key ),
107
+ pagination_params: @pagination_params.merge( opts.fetch( :pagination_params, {} ) ),
108
+ path_params: @path_params.merge( opts.fetch( :path_params, {} ) ),
109
+ additional_params: @additional_params.merge( opts.fetch( :additional_params, {} ) ),
110
+ filters: @filters.merge( opts.fetch( :filters, {} ) ),
111
+ includes: @includes + opts.fetch( :includes, [] ),
112
+ orders: @orders + opts.fetch( :orders, [] ),
113
+ fields: @fields + opts.fetch( :fields, [] ) )
114
+ end
115
+
109
116
  def path_params
110
117
  @path_params.empty? ? {} : {path: @path_params}
111
118
  end
@@ -159,22 +166,7 @@ module JsonApiClient
159
166
  end
160
167
 
161
168
  def parse_related_links(*tables)
162
- tables.map do |table|
163
- case table
164
- when Hash
165
- table.map do |k, v|
166
- parse_related_links(*v).map do |sub|
167
- "#{k}.#{sub}"
168
- end
169
- end
170
- when Array
171
- table.map do |v|
172
- parse_related_links(*v)
173
- end
174
- else
175
- key_formatter.format(table)
176
- end
177
- end.flatten
169
+ Utils.parse_includes(klass, *tables)
178
170
  end
179
171
 
180
172
  def parse_orders(*args)
@@ -11,36 +11,39 @@ module JsonApiClient
11
11
  # expects a record
12
12
  def create(record)
13
13
  request(:post, klass.path(record.attributes), {
14
- data: record.as_json_api
14
+ body: { data: record.as_json_api },
15
+ params: record.request_params.to_params
15
16
  })
16
17
  end
17
18
 
18
19
  def update(record)
19
20
  request(:patch, resource_path(record.attributes), {
20
- data: record.as_json_api
21
+ body: { data: record.as_json_api },
22
+ params: record.request_params.to_params
21
23
  })
22
24
  end
23
25
 
24
26
  def get(params = {})
25
27
  path = resource_path(params)
26
28
  params.delete(klass.primary_key)
27
- request(:get, path, params)
29
+ request(:get, path, params: params)
28
30
  end
29
31
 
30
32
  def destroy(record)
31
- request(:delete, resource_path(record.attributes), {})
33
+ request(:delete, resource_path(record.attributes))
32
34
  end
33
35
 
34
36
  def linked(path)
35
- request(:get, path, {})
37
+ request(:get, path)
36
38
  end
37
39
 
38
40
  def custom(method_name, options, params)
39
41
  path = resource_path(params)
40
42
  params.delete(klass.primary_key)
41
43
  path = File.join(path, method_name.to_s)
42
-
43
- request(options.fetch(:request_method, :get), path, params)
44
+ request_method = options.fetch(:request_method, :get).to_sym
45
+ query_params, body_params = [:get, :delete].include?(request_method) ? [params, nil] : [nil, params]
46
+ request(request_method, path, params: query_params, body: body_params)
44
47
  end
45
48
 
46
49
  protected
@@ -56,8 +59,9 @@ module JsonApiClient
56
59
  end
57
60
  end
58
61
 
59
- def request(type, path, params)
60
- klass.parser.parse(klass, connection.run(type, path, params, klass.custom_headers))
62
+ def request(type, path, params: nil, body: nil)
63
+ response = connection.run(type, path, params: params, body: body, headers: klass.custom_headers)
64
+ klass.parser.parse(klass, response)
61
65
  end
62
66
 
63
67
  end
@@ -0,0 +1,57 @@
1
+ module JsonApiClient
2
+ class RequestParams
3
+ attr_reader :klass, :includes, :fields
4
+
5
+ def initialize(klass, includes: [], fields: {})
6
+ @klass = klass
7
+ @includes = includes
8
+ @fields = fields
9
+ end
10
+
11
+ def add_includes(includes)
12
+ Utils.parse_includes(klass, *includes).each do |name|
13
+ name = name.to_sym
14
+ self.includes.push(name) unless self.includes.include?(name)
15
+ end
16
+ end
17
+
18
+ def reset_includes!
19
+ @includes = []
20
+ end
21
+
22
+ def set_fields(type, field_names)
23
+ self.fields[type.to_sym] = field_names.map(&:to_sym)
24
+ end
25
+
26
+ def remove_fields(type)
27
+ self.fields.delete(type.to_sym)
28
+ end
29
+
30
+ def field_types
31
+ self.fields.keys
32
+ end
33
+
34
+ def clear
35
+ reset_includes!
36
+ @fields = {}
37
+ end
38
+
39
+ def to_params
40
+ return nil if field_types.empty? && includes.empty?
41
+ parsed_fields.merge(parsed_includes)
42
+ end
43
+
44
+ private
45
+
46
+ def parsed_includes
47
+ return {} if includes.empty?
48
+ {include: includes.join(",")}
49
+ end
50
+
51
+ def parsed_fields
52
+ return {} if field_types.empty?
53
+ {fields: fields.map { |type, names| [type, names.join(",")] }.to_h}
54
+ end
55
+
56
+ end
57
+ end
@@ -15,7 +15,8 @@ module JsonApiClient
15
15
 
16
16
  attr_accessor :last_result_set,
17
17
  :links,
18
- :relationships
18
+ :relationships,
19
+ :request_params
19
20
  class_attribute :site,
20
21
  :primary_key,
21
22
  :parser,
@@ -31,6 +32,8 @@ module JsonApiClient
31
32
  :associations,
32
33
  :json_key_format,
33
34
  :route_format,
35
+ :request_params_class,
36
+ :keep_request_params,
34
37
  instance_accessor: false
35
38
  self.primary_key = :id
36
39
  self.parser = Parsers::Parser
@@ -43,6 +46,8 @@ module JsonApiClient
43
46
  self.read_only_attributes = [:id, :type, :links, :meta, :relationships]
44
47
  self.requestor_class = Query::Requestor
45
48
  self.associations = []
49
+ self.request_params_class = RequestParams
50
+ self.keep_request_params = false
46
51
 
47
52
  #:underscored_key, :camelized_key, :dasherized_key, or custom
48
53
  self.json_key_format = :underscored_key
@@ -156,7 +161,9 @@ module JsonApiClient
156
161
  #
157
162
  # @return [Hash] Headers
158
163
  def custom_headers
159
- _header_store.to_h
164
+ return _header_store.to_h if superclass == Object
165
+
166
+ superclass.custom_headers.merge(_header_store.to_h)
160
167
  end
161
168
 
162
169
  # Returns the requestor for this resource class
@@ -316,6 +323,7 @@ module JsonApiClient
316
323
  set_attribute(association.attr_name, params[association.attr_name.to_s])
317
324
  end
318
325
  end
326
+ self.request_params = self.class.request_params_class.new(self.class)
319
327
  end
320
328
 
321
329
  # Set the current attributes and try to save them
@@ -414,12 +422,14 @@ module JsonApiClient
414
422
  false
415
423
  else
416
424
  self.errors.clear if self.errors
425
+ self.request_params.clear unless self.class.keep_request_params
417
426
  mark_as_persisted!
418
427
  if updated = last_result_set.first
419
428
  self.attributes = updated.attributes
420
429
  self.links.attributes = updated.links.attributes
421
430
  self.relationships.attributes = updated.relationships.attributes
422
431
  clear_changes_information
432
+ self.relationships.clear_changes_information
423
433
  end
424
434
  true
425
435
  end
@@ -443,6 +453,31 @@ module JsonApiClient
443
453
  "#<#{self.class.name}:@attributes=#{attributes.inspect}>"
444
454
  end
445
455
 
456
+ def request_includes(*includes)
457
+ self.request_params.add_includes(includes)
458
+ self
459
+ end
460
+
461
+ def reset_request_includes!
462
+ self.request_params.reset_includes!
463
+ self
464
+ end
465
+
466
+ def request_select(*fields)
467
+ fields_by_type = fields.extract_options!
468
+ fields_by_type[type.to_sym] = fields if fields.any?
469
+ fields_by_type.each do |field_type, field_names|
470
+ self.request_params.set_fields(field_type, field_names)
471
+ end
472
+ self
473
+ end
474
+
475
+ def reset_request_select!(*resource_types)
476
+ resource_types = self.request_params.field_types if resource_types.empty?
477
+ resource_types.each { |resource_type| self.request_params.remove_fields(resource_type) }
478
+ self
479
+ end
480
+
446
481
  protected
447
482
 
448
483
  def method_missing(method, *args)
@@ -492,8 +527,15 @@ module JsonApiClient
492
527
  end
493
528
  end
494
529
 
530
+ def non_serializing_attributes
531
+ [
532
+ self.class.read_only_attributes,
533
+ self.class.prefix_params.map(&:to_s)
534
+ ].flatten
535
+ end
536
+
495
537
  def attributes_for_serialization
496
- attributes.except(*self.class.read_only_attributes).slice(*changed)
538
+ attributes.except(*non_serializing_attributes).slice(*changed)
497
539
  end
498
540
 
499
541
  def relationships_for_serialization
@@ -67,11 +67,11 @@ module JsonApiClient
67
67
  # end
68
68
  # end
69
69
  #
70
- # JsonApiClient::Schema::Types.register money: MyMoneyCaster
70
+ # JsonApiClient::Schema::TypeFactory.register money: MyMoneyCaster
71
71
  #
72
72
  # You can setup several at once:
73
73
  #
74
- # JsonApiClient::Schema::Types.register money: MyMoneyCaster,
74
+ # JsonApiClient::Schema::TypeFactory.register money: MyMoneyCaster,
75
75
  # date: MyJsonDateTypeCaster
76
76
  #
77
77
  #
@@ -24,5 +24,24 @@ module JsonApiClient
24
24
  raise NameError, "uninitialized constant #{candidates.first}"
25
25
  end
26
26
 
27
+ def self.parse_includes(klass, *tables)
28
+ tables.map do |table|
29
+ case table
30
+ when Hash
31
+ table.map do |k, v|
32
+ parse_includes(klass, *v).map do |sub|
33
+ "#{k}.#{sub}"
34
+ end
35
+ end
36
+ when Array
37
+ table.map do |v|
38
+ parse_includes(klass, *v)
39
+ end
40
+ else
41
+ klass.key_formatter.format(table)
42
+ end
43
+ end.flatten
44
+ end
45
+
27
46
  end
28
- end
47
+ end
@@ -1,3 +1,3 @@
1
1
  module JsonApiClient
2
- VERSION = "1.5.3"
2
+ VERSION = "1.6.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_api_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.3
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Ching
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-22 00:00:00.000000000 Z
11
+ date: 2018-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -30,14 +30,20 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.9'
33
+ version: '0.15'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 0.15.2
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
41
  - - "~>"
39
42
  - !ruby/object:Gem::Version
40
- version: '0.9'
43
+ version: '0.15'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 0.15.2
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: faraday_middleware
43
49
  requirement: !ruby/object:Gem::Requirement
@@ -152,6 +158,7 @@ files:
152
158
  - lib/json_api_client/relationships.rb
153
159
  - lib/json_api_client/relationships/relations.rb
154
160
  - lib/json_api_client/relationships/top_level_relations.rb
161
+ - lib/json_api_client/request_params.rb
155
162
  - lib/json_api_client/resource.rb
156
163
  - lib/json_api_client/result_set.rb
157
164
  - lib/json_api_client/schema.rb
@@ -177,7 +184,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
184
  version: '0'
178
185
  requirements: []
179
186
  rubyforge_project:
180
- rubygems_version: 2.6.8
187
+ rubygems_version: 2.6.14
181
188
  signing_key:
182
189
  specification_version: 4
183
190
  summary: Build client libraries compliant with specification defined by jsonapi.org