json_api_client 1.1.1 → 1.2.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: 97343e5f4ab100e19f26bc047874835f90d8e76a
4
- data.tar.gz: 6009f0ddb1f43af631e3b10a6e0bd9bd5cf7c9c7
3
+ metadata.gz: 48365ff770cd7617bd8d4ca4bdc47815b1f16096
4
+ data.tar.gz: 1691aa8373f863f00f9cf8020c810424aebd0fb2
5
5
  SHA512:
6
- metadata.gz: f35608521648cf8337507f466831598a4bb77e5254f60c18a0bbeeac854c8832eeb5b7d8faf63b356e953d1bb4f11958339c98e9d453e69dab21d50c3b0e211d
7
- data.tar.gz: e3a07f87d572b36eab928742f87688d38dc02bbb2f65e62ee8e30f71fc125a6c1c5715f32e17e62a6d0a5bc63ab7f4386d76ccbecdfb9ed6719ae5b670accc72
6
+ metadata.gz: 72606bea939fb0c0770fd85120dcd7fa09762b44abcb8fcb53f95c2f0e60420b7c3acebfc55e3fec6e586585f7dad97df62a5bc6fab50961205f35085a7461c0
7
+ data.tar.gz: f08837628c8717af1b6040f296d6609843b708d274da8d2197464445278eca13066f28d30a5e68da880b69080444f7b3f4be59b98dbf1f13ffb8a6f9081a9014
data/README.md CHANGED
@@ -27,7 +27,7 @@ module MyApi
27
27
  end
28
28
  ```
29
29
 
30
- By convention, we figure guess the resource route from the class name. In the above example, `Article`'s path is "http://example.com/articles" and `Person`'s path would be "http://example.com/people".
30
+ By convention, we guess the resource route from the class name. In the above example, `Article`'s path is "http://example.com/articles" and `Person`'s path would be "http://example.com/people".
31
31
 
32
32
  Some basic example usage:
33
33
 
@@ -204,7 +204,7 @@ authors = results.map(&:author)
204
204
 
205
205
  ```ruby
206
206
  # makes request to /articles?fields[articles]=title,body
207
- article = Article.select("title,body").first
207
+ article = Article.select("title", "body").first
208
208
 
209
209
  # should have fetched the requested fields
210
210
  article.title
@@ -25,6 +25,12 @@ module JsonApiClient
25
25
  end
26
26
  end
27
27
 
28
+ class Conflict < ServerError
29
+ def message
30
+ "Resource already exists"
31
+ end
32
+ end
33
+
28
34
  class NotFound < ServerError
29
35
  attr_reader :uri
30
36
  def initialize(uri)
@@ -47,4 +53,4 @@ module JsonApiClient
47
53
  end
48
54
 
49
55
  end
50
- end
56
+ end
@@ -0,0 +1,145 @@
1
+ # Taken form jsonapi_resources formatter
2
+
3
+ require 'active_support/inflector'
4
+
5
+ module JsonApiClient
6
+ class Formatter
7
+ class << self
8
+ def format(arg)
9
+ arg.to_s
10
+ end
11
+
12
+ def unformat(arg)
13
+ # We call to_s() here so that unformat consistently returns a string
14
+ # (instead of a symbol) regardless which Formatter subclass it is called on
15
+ arg.to_s
16
+ end
17
+
18
+ def formatter_for(format)
19
+ formatter_class_name = "JsonApiClient::#{format.to_s.camelize}Formatter"
20
+ formatter_class_name.safe_constantize
21
+ end
22
+ end
23
+ end
24
+
25
+ class KeyFormatter < Formatter
26
+ class << self
27
+ def format(key)
28
+ super
29
+ end
30
+
31
+ def format_keys(hash)
32
+ Hash[
33
+ hash.map do |key, value|
34
+ [format(key).to_sym, value]
35
+ end
36
+ ]
37
+ end
38
+
39
+ def unformat(formatted_key)
40
+ super
41
+ end
42
+ end
43
+ end
44
+
45
+ class RouteFormatter < Formatter
46
+ class << self
47
+ def format(route)
48
+ super
49
+ end
50
+
51
+ def unformat(formatted_route)
52
+ super
53
+ end
54
+ end
55
+ end
56
+
57
+ class ValueFormatter < Formatter
58
+ class << self
59
+ def format(raw_value)
60
+ super(raw_value)
61
+ end
62
+
63
+ def unformat(value)
64
+ super(value)
65
+ end
66
+
67
+ def value_formatter_for(type)
68
+ formatter_name = "#{type.to_s.camelize}Value"
69
+ formatter_for(formatter_name)
70
+ end
71
+ end
72
+ end
73
+
74
+ class UnderscoredKeyFormatter < KeyFormatter
75
+ end
76
+
77
+ class CamelizedKeyFormatter < KeyFormatter
78
+ class << self
79
+ def format(key)
80
+ super.camelize(:lower)
81
+ end
82
+
83
+ def unformat(formatted_key)
84
+ formatted_key.to_s.underscore
85
+ end
86
+ end
87
+ end
88
+
89
+ class DasherizedKeyFormatter < KeyFormatter
90
+ class << self
91
+ def format(key)
92
+ super.dasherize
93
+ end
94
+
95
+ def unformat(formatted_key)
96
+ formatted_key.to_s.underscore
97
+ end
98
+ end
99
+ end
100
+
101
+ class DefaultValueFormatter < ValueFormatter
102
+ class << self
103
+ def format(raw_value)
104
+ raw_value
105
+ end
106
+ end
107
+ end
108
+
109
+ class IdValueFormatter < ValueFormatter
110
+ class << self
111
+ def format(raw_value)
112
+ return if raw_value.nil?
113
+ raw_value.to_s
114
+ end
115
+ end
116
+ end
117
+
118
+ class UnderscoredRouteFormatter < RouteFormatter
119
+ end
120
+
121
+ class CamelizedRouteFormatter < RouteFormatter
122
+ class << self
123
+ def format(route)
124
+ super.camelize(:lower)
125
+ end
126
+
127
+ def unformat(formatted_route)
128
+ formatted_route.to_s.underscore
129
+ end
130
+ end
131
+ end
132
+
133
+ class DasherizedRouteFormatter < RouteFormatter
134
+ class << self
135
+ def format(route)
136
+ super.dasherize
137
+ end
138
+
139
+ def unformat(formatted_route)
140
+ formatted_route.to_s.underscore
141
+ end
142
+ end
143
+ end
144
+
145
+ end
@@ -38,7 +38,13 @@ module JsonApiClient
38
38
  protected
39
39
 
40
40
  def method_missing(method, *args, &block)
41
- if method.to_s =~ /^(.*)=$/
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 =~ /^(.*)=$/
42
48
  set_attribute($1, args.first)
43
49
  elsif has_attribute?(method)
44
50
  attributes[method]
@@ -55,6 +61,10 @@ module JsonApiClient
55
61
  attributes[name] = value
56
62
  end
57
63
 
64
+ def key_formatter
65
+ self.class.respond_to?(:key_formatter) && self.class.key_formatter
66
+ end
67
+
58
68
  end
59
69
  end
60
70
  end
@@ -6,7 +6,7 @@ module JsonApiClient
6
6
  record_class = result_set.record_class
7
7
  grouped_data = data.group_by{|datum| datum["type"]}
8
8
  @data = grouped_data.inject({}) do |h, (type, records)|
9
- klass = Utils.compute_type(record_class, type.singularize.classify)
9
+ klass = Utils.compute_type(record_class, record_class.key_formatter.unformat(type).singularize.classify)
10
10
  h[type] = records.map do |datum|
11
11
  params = klass.parser.parameters_from_resource(datum)
12
12
  resource = klass.load(params)
@@ -2,9 +2,18 @@ module JsonApiClient
2
2
  class MetaData
3
3
  include Helpers::DynamicAttributes
4
4
 
5
- def initialize(data)
5
+ attr_accessor :record_class
6
+
7
+ def initialize(data, record_class = nil)
8
+ self.record_class = record_class
6
9
  self.attributes = data
7
10
  end
8
11
 
12
+ protected
13
+
14
+ def key_formatter
15
+ record_class && record_class.key_formatter
16
+ end
17
+
9
18
  end
10
- end
19
+ end
@@ -26,6 +26,8 @@ module JsonApiClient
26
26
  raise Errors::AccessDenied, env
27
27
  when 404
28
28
  raise Errors::NotFound, env[:url]
29
+ when 409
30
+ raise Errors::Conflict, env
29
31
  when 400..499
30
32
  # some other error
31
33
  when 500..599
@@ -36,4 +38,4 @@ module JsonApiClient
36
38
  end
37
39
  end
38
40
  end
39
- end
41
+ end
@@ -40,7 +40,7 @@ module JsonApiClient
40
40
  def total_entries
41
41
  per_page * total_pages
42
42
  end
43
- alias_method :total_count, :total_entries
43
+ def total_count; total_entries; end
44
44
 
45
45
  def offset
46
46
  per_page * (current_page - 1)
@@ -65,7 +65,7 @@ module JsonApiClient
65
65
 
66
66
  # we will treat everything as an Array
67
67
  results = [results] unless results.is_a?(Array)
68
- resources = results.map do |res|
68
+ resources = results.compact.map do |res|
69
69
  resource = result_set.record_class.load(parameters_from_resource(res))
70
70
  resource.last_result_set = result_set
71
71
  resource
@@ -78,7 +78,7 @@ module JsonApiClient
78
78
  end
79
79
 
80
80
  def handle_meta(result_set, data)
81
- result_set.meta = MetaData.new(data.fetch("meta", {}))
81
+ result_set.meta = MetaData.new(data.fetch("meta", {}), result_set.record_class)
82
82
  end
83
83
 
84
84
  def handle_links(result_set, data)
@@ -3,6 +3,7 @@ module JsonApiClient
3
3
  class Builder
4
4
 
5
5
  attr_reader :klass, :path_params
6
+ delegate :key_formatter, to: :klass
6
7
 
7
8
  def initialize(klass)
8
9
  @klass = klass
@@ -33,8 +34,12 @@ module JsonApiClient
33
34
  self
34
35
  end
35
36
 
36
- def select(fields)
37
- @fields += fields.split(",").map(&:strip)
37
+ def select(*fields)
38
+ fields = Array(fields).flatten
39
+ fields = fields.map { |i| i.to_s.split(",") }.flatten
40
+
41
+ @fields += fields.map(&:strip)
42
+
38
43
  self
39
44
  end
40
45
 
@@ -151,7 +156,7 @@ module JsonApiClient
151
156
  parse_related_links(*v)
152
157
  end
153
158
  else
154
- table
159
+ key_formatter.format(table)
155
160
  end
156
161
  end.flatten
157
162
  end
@@ -29,6 +29,8 @@ module JsonApiClient
29
29
  :read_only_attributes,
30
30
  :requestor_class,
31
31
  :associations,
32
+ :json_key_format,
33
+ :route_format,
32
34
  instance_accessor: false
33
35
  self.primary_key = :id
34
36
  self.parser = Parsers::Parser
@@ -42,6 +44,12 @@ module JsonApiClient
42
44
  self.requestor_class = Query::Requestor
43
45
  self.associations = []
44
46
 
47
+ #:underscored_key, :camelized_key, :dasherized_key, or custom
48
+ self.json_key_format = :underscored_key
49
+
50
+ #:underscored_route, :camelized_route, :dasherized_route, or custom
51
+ self.route_format = :underscored_route
52
+
45
53
  include Associations::BelongsTo
46
54
  include Associations::HasMany
47
55
  include Associations::HasOne
@@ -54,7 +62,7 @@ module JsonApiClient
54
62
  #
55
63
  # @return [String] The table name for this resource
56
64
  def table_name
57
- resource_name.pluralize
65
+ route_formatter.format(resource_name.pluralize)
58
66
  end
59
67
 
60
68
  # The name of a single resource. i.e. Article -> article, Person -> person
@@ -64,6 +72,22 @@ module JsonApiClient
64
72
  name.demodulize.underscore
65
73
  end
66
74
 
75
+ # Specifies the JSON API resource type. By default this is inferred
76
+ # from the resource class name.
77
+ #
78
+ # @return [String] Resource path
79
+ def type
80
+ table_name
81
+ end
82
+
83
+ # Specifies the relative path that should be used for this resource;
84
+ # by default, this is inferred from the resource class name.
85
+ #
86
+ # @return [String] Resource path
87
+ def resource_path
88
+ table_name
89
+ end
90
+
67
91
  # Load a resource object from attributes and consider it persisted
68
92
  #
69
93
  # @return [Resource] Persisted resource object
@@ -92,14 +116,14 @@ module JsonApiClient
92
116
 
93
117
  # Return the path or path pattern for this resource
94
118
  def path(params = nil)
95
- parts = [table_name]
96
- if params
119
+ parts = [resource_path]
120
+ if params && _prefix_path.present?
97
121
  path_params = params.delete(:path) || params
98
122
  parts.unshift(_prefix_path % path_params.symbolize_keys)
99
123
  else
100
124
  parts.unshift(_prefix_path)
101
125
  end
102
- parts.reject!{|part| part == "" }
126
+ parts.reject!(&:blank?)
103
127
  File.join(*parts)
104
128
  rescue KeyError
105
129
  raise ArgumentError, "Not all prefix parameters specified"
@@ -147,7 +171,7 @@ module JsonApiClient
147
171
  #
148
172
  # @return [Hash] Default attributes
149
173
  def default_attributes
150
- {type: table_name}
174
+ {type: type}
151
175
  end
152
176
 
153
177
  # Returns the schema for this resource class
@@ -157,6 +181,14 @@ module JsonApiClient
157
181
  @schema ||= Schema.new
158
182
  end
159
183
 
184
+ def key_formatter
185
+ JsonApiClient::Formatter.formatter_for(json_key_format)
186
+ end
187
+
188
+ def route_formatter
189
+ JsonApiClient::Formatter.formatter_for(route_format)
190
+ end
191
+
160
192
  protected
161
193
 
162
194
  # Declares a new class/instance method that acts on the collection/member
@@ -258,6 +290,7 @@ module JsonApiClient
258
290
  #
259
291
  # @param params [Hash] Attributes, links, and relationships
260
292
  def initialize(params = {})
293
+ @persisted = nil
261
294
  self.links = self.class.linker.new(params.delete("links") || {})
262
295
  self.relationships = self.class.relationship_linker.new(params.delete("relationships") || {})
263
296
  self.class.associations.each do |association|
@@ -321,9 +354,9 @@ module JsonApiClient
321
354
  def as_json_api(*)
322
355
  attributes.slice(:id, :type).tap do |h|
323
356
  relationships_for_serialization.tap do |r|
324
- h[:relationships] = r unless r.empty?
357
+ h[:relationships] = self.class.key_formatter.format_keys(r) unless r.empty?
325
358
  end
326
- h[:attributes] = attributes_for_serialization
359
+ h[:attributes] = self.class.key_formatter.format_keys(attributes_for_serialization)
327
360
  end
328
361
  end
329
362
 
@@ -444,7 +477,9 @@ module JsonApiClient
444
477
  end
445
478
 
446
479
  def association_for(name)
447
- self.class.associations.detect{|association| association.attr_name == name}
480
+ self.class.associations.detect do |association|
481
+ association.attr_name.to_s == self.class.key_formatter.unformat(name)
482
+ end
448
483
  end
449
484
 
450
485
  def attributes_for_serialization
@@ -1,3 +1,3 @@
1
1
  module JsonApiClient
2
- VERSION = "1.1.1"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -1,7 +1,7 @@
1
1
  require 'faraday'
2
2
  require 'faraday_middleware'
3
3
  require 'json'
4
- require "addressable/uri"
4
+ require 'addressable/uri'
5
5
 
6
6
  module JsonApiClient
7
7
  autoload :Associations, 'json_api_client/associations'
@@ -9,6 +9,7 @@ module JsonApiClient
9
9
  autoload :Connection, 'json_api_client/connection'
10
10
  autoload :Errors, 'json_api_client/errors'
11
11
  autoload :ErrorCollector, 'json_api_client/error_collector'
12
+ autoload :Formatter, 'json_api_client/formatter'
12
13
  autoload :Helpers, 'json_api_client/helpers'
13
14
  autoload :Implementation, 'json_api_client/implementation'
14
15
  autoload :IncludedData, 'json_api_client/included_data'
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.1.1
4
+ version: 1.2.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: 2016-02-25 00:00:00.000000000 Z
11
+ date: 2016-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -126,6 +126,7 @@ files:
126
126
  - lib/json_api_client/connection.rb
127
127
  - lib/json_api_client/error_collector.rb
128
128
  - lib/json_api_client/errors.rb
129
+ - lib/json_api_client/formatter.rb
129
130
  - lib/json_api_client/helpers.rb
130
131
  - lib/json_api_client/helpers/callbacks.rb
131
132
  - lib/json_api_client/helpers/dirty.rb
@@ -175,9 +176,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
175
176
  version: '0'
176
177
  requirements: []
177
178
  rubyforge_project:
178
- rubygems_version: 2.4.8
179
+ rubygems_version: 2.4.5
179
180
  signing_key:
180
181
  specification_version: 4
181
182
  summary: Build client libraries compliant with specification defined by jsonapi.org
182
183
  test_files: []
183
- has_rdoc: