json_api_client 1.5.3 → 1.6.0

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