json_api_client 1.0.0.beta5 → 1.0.0.beta6

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: cf70c1b87181f83e74d93e6711ff52d3ba70cf49
4
- data.tar.gz: 065d5cf0d8559fbe051f312643763f0b508dd351
3
+ metadata.gz: c39c70dd7ca6c162a658718790120555ac010083
4
+ data.tar.gz: 83eaa82464904a5761933f4b2f41576f280a281a
5
5
  SHA512:
6
- metadata.gz: 8bf495991e43b1e034a4e23c915af9b18130c9a58fe1882927f5ffa9aac3b33ecd1b66c658c3f8ece03164579adcc46234356487a3e1e2aa15871ff280892577
7
- data.tar.gz: e588c5bec7188484fa1f215660187cc69acd23dc5c8431d34bf58fc4d54a0aafd75748318f8c1bb06291bdddad73a97388317c24c0a9cca75911c4d3f980fb4c
6
+ metadata.gz: 36612db285d7249f01b5cfbff6594297ed3c838faee90b5d646ad9c51767b6cbd1bc4511063416691f19db92e06fecf54d60aa47c1d0fa23701ca43eaced2b92
7
+ data.tar.gz: ad590905aa3c8921ccd199525ad367465acfd048e3516da9722f364c39bdcbd0dbf01d5f4c84a12b9e43c3e9e68cebee16eb73c67436c69ac7ea014d7d270e42
@@ -0,0 +1,27 @@
1
+ module JsonApiClient
2
+ module Helpers
3
+ module Callbacks
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ extend ActiveModel::Callbacks
8
+ define_model_callbacks :save, :destroy, :create, :update
9
+ end
10
+
11
+ def save
12
+ run_callbacks :save do
13
+ run_callbacks (persisted? ? :update : :create) do
14
+ super
15
+ end
16
+ end
17
+ end
18
+
19
+ def destroy
20
+ run_callbacks :destroy do
21
+ super
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,71 @@
1
+ module JsonApiClient
2
+ module Helpers
3
+ module Dirty
4
+
5
+ def changed?
6
+ changed_attributes.present?
7
+ end
8
+
9
+ def changed
10
+ changed_attributes.keys
11
+ end
12
+
13
+ def changed_attributes
14
+ @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
15
+ end
16
+
17
+ def clear_changes_information
18
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
19
+ end
20
+
21
+ def set_all_attributes_dirty
22
+ attributes.each do |k, v|
23
+ set_attribute_was(k, v)
24
+ end
25
+ end
26
+
27
+ def attribute_will_change!(attr)
28
+ return if attribute_changed?(attr)
29
+ set_attribute_was(attr, attributes[attr])
30
+ end
31
+
32
+ def set_attribute_was(attr, value)
33
+ begin
34
+ value = value.duplicable? ? value.clone : value
35
+ changed_attributes[attr] = value
36
+ rescue TypeError, NoMethodError
37
+ end
38
+ end
39
+
40
+ def attribute_was(attr) # :nodoc:
41
+ attribute_changed?(attr) ? changed_attributes[attr] : attributes[attr]
42
+ end
43
+
44
+ def attribute_changed?(attr)
45
+ changed.include?(attr.to_s)
46
+ end
47
+
48
+ def attribute_change(attr)
49
+ [changed_attributes[attr], attributes[attr]] if attribute_changed?(attr)
50
+ end
51
+
52
+ protected
53
+
54
+ def method_missing(method, *args, &block)
55
+ if method.to_s =~ /^(.*)_changed\?$/
56
+ has_attribute?($1) ? attribute_changed?($1) : nil
57
+ elsif method.to_s =~ /^(.*)_was$/
58
+ has_attribute?($1) ? attribute_was($1) : nil
59
+ else
60
+ super
61
+ end
62
+ end
63
+
64
+ def set_attribute(name, value)
65
+ attribute_will_change!(name) if value != attributes[name]
66
+ super
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -7,7 +7,7 @@ module JsonApiClient
7
7
  end
8
8
 
9
9
  def attributes=(attrs = {})
10
- @attributes ||= {}.with_indifferent_access
10
+ @attributes ||= ActiveSupport::HashWithIndifferentAccess.new
11
11
 
12
12
  return @attributes unless attrs.present?
13
13
  attrs.each do |key, value|
@@ -1,18 +1,7 @@
1
1
  module JsonApiClient
2
2
  module Helpers
3
- autoload :Associable, 'json_api_client/helpers/associable'
4
- autoload :Attributable, 'json_api_client/helpers/attributable'
5
- autoload :CustomEndpoints, 'json_api_client/helpers/custom_endpoints'
6
- autoload :CustomHeaders, 'json_api_client/helpers/custom_headers'
3
+ autoload :Callbacks, 'json_api_client/helpers/callbacks'
4
+ autoload :Dirty, 'json_api_client/helpers/dirty'
7
5
  autoload :DynamicAttributes, 'json_api_client/helpers/dynamic_attributes'
8
- autoload :Initializable, 'json_api_client/helpers/initializable'
9
- autoload :Linkable, 'json_api_client/helpers/linkable'
10
- autoload :Relatable, 'json_api_client/helpers/relatable'
11
- autoload :Paginatable, 'json_api_client/helpers/paginatable'
12
- autoload :Parsable, 'json_api_client/helpers/parsable'
13
- autoload :Queryable, 'json_api_client/helpers/queryable'
14
- autoload :Requestable, 'json_api_client/helpers/requestable'
15
- autoload :Schemable, 'json_api_client/helpers/schemable'
16
- autoload :Serializable, 'json_api_client/helpers/serializable'
17
6
  end
18
7
  end
@@ -10,7 +10,7 @@ module JsonApiClient
10
10
  h[type] = records.map do |datum|
11
11
  params = klass.parser.parameters_from_resource(datum)
12
12
  resource = klass.load(params)
13
- resource.result_set = result_set
13
+ resource.last_result_set = result_set
14
14
  resource
15
15
  end.index_by(&:id)
16
16
  h
@@ -3,7 +3,8 @@ module JsonApiClient
3
3
  class Parser
4
4
  class << self
5
5
  def parse(klass, response)
6
- data = response.body
6
+ data = response.body.present? ? response.body : {}
7
+
7
8
  ResultSet.new.tap do |result_set|
8
9
  result_set.record_class = klass
9
10
  result_set.uri = response.env[:url]
@@ -66,7 +67,7 @@ module JsonApiClient
66
67
  results = [results] unless results.is_a?(Array)
67
68
  resources = results.map do |res|
68
69
  resource = result_set.record_class.load(parameters_from_resource(res))
69
- resource.result_set = result_set
70
+ resource.last_result_set = result_set
70
71
  resource
71
72
  end
72
73
  result_set.concat(resources)
@@ -10,13 +10,13 @@ module JsonApiClient
10
10
  # expects a record
11
11
  def create(record)
12
12
  request(:post, klass.path(record.attributes), {
13
- data: record.serializable_hash
13
+ data: record.as_json_api
14
14
  })
15
15
  end
16
16
 
17
17
  def update(record)
18
18
  request(:patch, resource_path(record.attributes), {
19
- data: record.serializable_hash
19
+ data: record.as_json_api
20
20
  })
21
21
  end
22
22
 
@@ -60,7 +60,7 @@ module JsonApiClient
60
60
  end
61
61
 
62
62
  def request(type, path, params)
63
- klass.parse(connection.run(type, path, params, klass.custom_headers))
63
+ klass.parser.parse(klass, connection.run(type, path, params, klass.custom_headers))
64
64
  end
65
65
 
66
66
  end
@@ -2,32 +2,43 @@ module JsonApiClient
2
2
  module Relationships
3
3
  class Relations
4
4
  include Helpers::DynamicAttributes
5
+ include Helpers::Dirty
6
+ include ActiveModel::Serialization
5
7
 
6
8
  def initialize(relations)
7
9
  self.attributes = relations
10
+ clear_changes_information
8
11
  end
9
12
 
10
13
  def present?
11
14
  attributes.present?
12
15
  end
13
16
 
14
- def serializable_hash
15
- Hash[attributes.map do |k, v|
16
- [k, v.slice("data")]
17
- end]
17
+ def as_json_api
18
+ Hash[attributes_for_serialization.map do |k, v|
19
+ [k, v.slice("data")] if v.has_key?("data")
20
+ end.compact]
21
+ end
22
+
23
+ def attributes_for_serialization
24
+ attributes.slice(*changed)
18
25
  end
19
26
 
20
27
  protected
21
28
 
22
29
  def set_attribute(name, value)
23
- attributes[name] = case value
30
+ value = case value
24
31
  when JsonApiClient::Resource
25
32
  {data: value.as_relation}
26
33
  when Array
27
34
  {data: value.map(&:as_relation)}
35
+ when NilClass
36
+ {data: nil}
28
37
  else
29
38
  value
30
39
  end
40
+ attribute_will_change!(name) if value != attributes[name]
41
+ attributes[name] = value
31
42
  end
32
43
 
33
44
  end
@@ -1,40 +1,413 @@
1
1
  require 'forwardable'
2
2
  require 'active_support/all'
3
+ require 'active_model'
3
4
 
4
5
  module JsonApiClient
5
6
  class Resource
6
- attr_accessor :result_set
7
- class_attribute :site, :primary_key
8
- self.primary_key = :id
7
+ extend ActiveModel::Naming
8
+ extend ActiveModel::Translation
9
+ include ActiveModel::Validations
10
+ include ActiveModel::Conversion
11
+ include ActiveModel::Serialization
12
+
13
+ include Helpers::DynamicAttributes
14
+ include Helpers::Dirty
15
+
16
+ attr_accessor :last_result_set,
17
+ :links,
18
+ :relationships
19
+ class_attribute :site,
20
+ :primary_key,
21
+ :parser,
22
+ :paginator,
23
+ :connection_class,
24
+ :connection_object,
25
+ :connection_options,
26
+ :query_builder,
27
+ :linker,
28
+ :relationship_linker,
29
+ :read_only_attributes,
30
+ :requestor_class,
31
+ :associations,
32
+ instance_accessor: false
33
+ self.primary_key = :id
34
+ self.parser = Parsers::Parser
35
+ self.paginator = Paginating::Paginator
36
+ self.connection_class = Connection
37
+ self.connection_options = {}
38
+ self.query_builder = Query::Builder
39
+ self.linker = Linking::Links
40
+ self.relationship_linker = Relationships::Relations
41
+ self.read_only_attributes = [:id, :type, :links, :meta, :relationships]
42
+ self.requestor_class = Query::Requestor
43
+ self.associations = []
44
+
45
+ include Associations::BelongsTo
46
+ include Associations::HasMany
47
+ include Associations::HasOne
9
48
 
10
49
  class << self
11
- # base URL for this resource
12
- def resource
13
- File.join(site, path)
14
- end
50
+ extend Forwardable
51
+ def_delegators :_new_scope, :where, :order, :includes, :select, :all, :paginate, :page, :first, :find
15
52
 
53
+ # The table name for this resource. i.e. Article -> articles, Person -> people
54
+ #
55
+ # @return [String] The table name for this resource
16
56
  def table_name
17
57
  resource_name.pluralize
18
58
  end
19
59
 
60
+ # The name of a single resource. i.e. Article -> article, Person -> person
61
+ #
62
+ # @return [String]
20
63
  def resource_name
21
64
  name.demodulize.underscore
22
65
  end
66
+
67
+ # Load a resource object from attributes and consider it persisted
68
+ #
69
+ # @return [Resource] Persisted resource object
70
+ def load(params)
71
+ new(params).tap do |resource|
72
+ resource.mark_as_persisted!
73
+ resource.clear_changes_information
74
+ end
75
+ end
76
+
77
+ # Return/build a connection object
78
+ #
79
+ # @return [Connection] The connection to the json api server
80
+ def connection(rebuild = false, &block)
81
+ _build_connection(&block)
82
+ connection_object
83
+ end
84
+
85
+ # Param names that will be considered path params. They will be used
86
+ # to build the resource path rather than treated as attributes
87
+ #
88
+ # @return [Array] Param name symbols of parameters that will be treated as path parameters
89
+ def prefix_params
90
+ _belongs_to_associations.map(&:param)
91
+ end
92
+
93
+ # Return the path or path pattern for this resource
94
+ def path(params = nil)
95
+ parts = [table_name]
96
+ if params
97
+ path_params = params.delete(:path) || params
98
+ parts.unshift(_prefix_path % path_params.symbolize_keys)
99
+ else
100
+ parts.unshift(_prefix_path)
101
+ end
102
+ parts.reject!{|part| part == "" }
103
+ File.join(*parts)
104
+ rescue KeyError
105
+ raise ArgumentError, "Not all prefix parameters specified"
106
+ end
107
+
108
+ # Create a new instance of this resource class
109
+ #
110
+ # @param attributes [Hash] The attributes to create this resource with
111
+ # @return [Resource] The instance you tried to create. You will have to check the persisted state or errors on this object to see success/failure.
112
+ def create(attributes = {})
113
+ new(attributes).tap do |resource|
114
+ resource.save
115
+ end
116
+ end
117
+
118
+ # Within the given block, add these headers to all requests made by
119
+ # the resource class
120
+ #
121
+ # @param headers [Hash] The headers to send along
122
+ # @param block [Block] The block where headers will be set for
123
+ def with_headers(headers)
124
+ self._custom_headers = headers
125
+ yield
126
+ ensure
127
+ self._custom_headers = {}
128
+ end
129
+
130
+ # The current custom headers to send with any request made by this
131
+ # resource class
132
+ #
133
+ # @return [Hash] Headers
134
+ def custom_headers
135
+ _header_store.to_h
136
+ end
137
+
138
+ # Returns the requestor for this resource class
139
+ #
140
+ # @return [Requestor] The requestor for this resource class
141
+ def requestor
142
+ @requestor ||= requestor_class.new(self)
143
+ end
144
+
145
+ # Default attributes that every instance of this resource should be
146
+ # intialized with. Optionally, override this method in a subclass.
147
+ #
148
+ # @return [Hash] Default attributes
149
+ def default_attributes
150
+ {type: table_name}
151
+ end
152
+
153
+ # Returns the schema for this resource class
154
+ #
155
+ # @return [Schema] The schema for this resource class
156
+ def schema
157
+ @schema ||= Schema.new
158
+ end
159
+
160
+ protected
161
+
162
+ # Declares a new class/instance method that acts on the collection/member
163
+ #
164
+ # @param name [Symbol] the name of the endpoint
165
+ # @param options [Hash] endpoint options
166
+ # @option [Symbol] :on One of [:collection or :member] to decide whether it's a collect or member method
167
+ # @option [Symbol] :request_method The request method (:get, :post, etc)
168
+ def custom_endpoint(name, options = {})
169
+ if :collection == options.delete(:on)
170
+ collection_endpoint(name, options)
171
+ else
172
+ member_endpoint(name, options)
173
+ end
174
+ end
175
+
176
+ # Declares a new class method that acts on the collection
177
+ #
178
+ # @param name [Symbol] the name of the endpoint and the method name
179
+ # @param options [Hash] endpoint options
180
+ # @option options [Symbol] :request_method The request method (:get, :post, etc)
181
+ def collection_endpoint(name, options = {})
182
+ metaclass = class << self
183
+ self
184
+ end
185
+ metaclass.instance_eval do
186
+ define_method(name) do |*params|
187
+ request_params = params.first || {}
188
+ requestor.custom(name, options, request_params)
189
+ end
190
+ end
191
+ end
192
+
193
+ # Declares a new instance method that acts on the member object
194
+ #
195
+ # @param name [Symbol] the name of the endpoint and the method name
196
+ # @param options [Hash] endpoint options
197
+ # @option options [Symbol] :request_method The request method (:get, :post, etc)
198
+ def member_endpoint(name, options = {})
199
+ define_method name do |*params|
200
+ request_params = params.first || {}
201
+ request_params[self.class.primary_key] = attributes.fetch(self.class.primary_key)
202
+ self.class.requestor.custom(name, options, request_params)
203
+ end
204
+ end
205
+
206
+ # Declares a new property by name
207
+ #
208
+ # @param name [Symbol] the name of the property
209
+ # @param options [Hash] property options
210
+ # @option options [Symbol] :type The property type
211
+ # @option options [Symbol] :default The default value for the property
212
+ def property(name, options = {})
213
+ schema.add(name, options)
214
+ end
215
+
216
+ # Declare multiple properties with the same optional options
217
+ #
218
+ # @param [Array<Symbol>] names
219
+ # @param options [Hash] property options
220
+ # @option options [Symbol] :type The property type
221
+ # @option options [Symbol] :default The default value for the property
222
+ def properties(*names)
223
+ options = names.last.is_a?(Hash) ? names.pop : {}
224
+ names.each do |name|
225
+ property name, options
226
+ end
227
+ end
228
+
229
+ def _belongs_to_associations
230
+ associations.select{|association| association.is_a?(Associations::BelongsTo::Association) }
231
+ end
232
+
233
+ def _prefix_path
234
+ _belongs_to_associations.map(&:to_prefix_path).join("/")
235
+ end
236
+
237
+ def _new_scope
238
+ query_builder.new(self)
239
+ end
240
+
241
+ def _custom_headers=(headers)
242
+ _header_store.replace(headers)
243
+ end
244
+
245
+ def _header_store
246
+ Thread.current["json_api_client-#{resource_name}"] ||= {}
247
+ end
248
+
249
+ def _build_connection(rebuild = false)
250
+ return connection_object unless connection_object.nil? || rebuild
251
+ self.connection_object = connection_class.new(connection_options.merge(site: site)).tap do |conn|
252
+ yield(conn) if block_given?
253
+ end
254
+ end
255
+ end
256
+
257
+ # Instantiate a new resource object
258
+ #
259
+ # @param params [Hash] Attributes, links, and relationships
260
+ def initialize(params = {})
261
+ self.links = self.class.linker.new(params.delete("links") || {})
262
+ self.relationships = self.class.relationship_linker.new(params.delete("relationships") || {})
263
+ self.class.associations.each do |association|
264
+ if params.has_key?(association.attr_name.to_s)
265
+ set_attribute(association.attr_name, association.parse(params[association.attr_name.to_s]))
266
+ end
267
+ end
268
+ self.attributes = params.merge(self.class.default_attributes)
269
+ self.class.schema.each_property do |property|
270
+ attributes[property.name] = property.default unless attributes.has_key?(property.name)
271
+ end
272
+ end
273
+
274
+ # Set the current attributes and try to save them
275
+ #
276
+ # @param attrs [Hash] Attributes to update
277
+ # @return [Boolean] Whether the update succeeded or not
278
+ def update_attributes(attrs = {})
279
+ self.attributes = attrs
280
+ save
281
+ end
282
+
283
+ # Mark the record as persisted
284
+ def mark_as_persisted!
285
+ @persisted = true
286
+ end
287
+
288
+ # Whether or not this record has been persisted to the database previously
289
+ #
290
+ # @return [Boolean]
291
+ def persisted?
292
+ !!@persisted && has_attribute?(self.class.primary_key)
23
293
  end
24
294
 
25
- include Helpers::Initializable
26
- include Helpers::Attributable
27
- include Helpers::Associable
28
- include Helpers::Parsable
29
- include Helpers::Queryable
30
- include Helpers::Serializable
31
- include Helpers::Linkable
32
- include Helpers::Relatable
33
- include Helpers::CustomEndpoints
34
- include Helpers::Schemable
35
- include Helpers::Paginatable
36
- include Helpers::Requestable
37
- include Helpers::CustomHeaders
295
+ # Returns true if this is a new record (never persisted to the database)
296
+ #
297
+ # @return [Boolean]
298
+ def new_record?
299
+ !persisted?
300
+ end
301
+
302
+ # When we represent this resource as a relationship, we do so with id & type
303
+ #
304
+ # @return [Hash] Representation of this object as a relation
305
+ def as_relation
306
+ attributes.slice(:type, self.class.primary_key)
307
+ end
308
+
309
+ # When we represent this resource for serialization (create/update), we do so
310
+ # with this implementation
311
+ #
312
+ # @return [Hash] Representation of this object as JSONAPI object
313
+ def as_json_api(*)
314
+ attributes.slice(:id, :type).tap do |h|
315
+ relationships_for_serialization.tap do |r|
316
+ h[:relationships] = r unless r.empty?
317
+ end
318
+ h[:attributes] = attributes_for_serialization
319
+ end
320
+ end
321
+
322
+ # Mark all attributes for this record as dirty
323
+ def set_all_dirty!
324
+ set_all_attributes_dirty
325
+ relationships.set_all_attributes_dirty if relationships
326
+ end
327
+
328
+ # Commit the current changes to the resource to the remote server.
329
+ # If the resource was previously loaded from the server, we will
330
+ # try to update the record. Otherwise if it's a new record, then
331
+ # we will try to create it
332
+ #
333
+ # @return [Boolean] Whether or not the save succeeded
334
+ def save
335
+ return false unless valid?
336
+
337
+ self.last_result_set = if persisted?
338
+ self.class.requestor.update(self)
339
+ else
340
+ self.class.requestor.create(self)
341
+ end
38
342
 
343
+ if last_result_set.has_errors?
344
+ last_result_set.errors.each do |error|
345
+ if error.source_parameter
346
+ errors.add(error.source_parameter, error.title)
347
+ else
348
+ errors.add(:base, error.title)
349
+ end
350
+ end
351
+ false
352
+ else
353
+ self.errors.clear if self.errors
354
+ mark_as_persisted!
355
+ if updated = last_result_set.first
356
+ self.attributes = updated.attributes
357
+ end
358
+ true
359
+ end
360
+ end
361
+
362
+ # Try to destroy this resource
363
+ #
364
+ # @return [Boolean] Whether or not the destroy succeeded
365
+ def destroy
366
+ self.last_result_set = self.class.requestor.destroy(self)
367
+ if !last_result_set.has_errors?
368
+ self.attributes.clear
369
+ true
370
+ else
371
+ false
372
+ end
373
+ end
374
+
375
+ def inspect
376
+ "#<#{self.class.name}:@attributes=#{attributes.inspect}>"
377
+ end
378
+
379
+ protected
380
+
381
+ def method_missing(method, *args)
382
+ return super unless relationships && relationships.has_attribute?(method) && last_result_set.included
383
+ last_result_set.included.data_for(method, relationships[method])
384
+ end
385
+
386
+ def respond_to_missing?(symbol, include_all = false)
387
+ return true if relationships && relationships.has_attribute?(symbol)
388
+ super
389
+ end
390
+
391
+ def set_attribute(name, value)
392
+ property = property_for(name)
393
+ value = property.cast(value) if property
394
+ super(name, value)
395
+ end
396
+
397
+ def has_attribute?(attr_name)
398
+ !!property_for(attr_name) || super
399
+ end
400
+
401
+ def property_for(name)
402
+ self.class.schema.find(name)
403
+ end
404
+
405
+ def attributes_for_serialization
406
+ attributes.except(*self.class.read_only_attributes).slice(*changed)
407
+ end
408
+
409
+ def relationships_for_serialization
410
+ relationships.as_json_api
411
+ end
39
412
  end
40
413
  end
@@ -1,3 +1,3 @@
1
1
  module JsonApiClient
2
- VERSION = "1.0.0.beta5"
2
+ VERSION = "1.0.0.beta6"
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.0.0.beta5
4
+ version: 1.0.0.beta6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Ching
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-13 00:00:00.000000000 Z
11
+ date: 2015-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '2.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activemodel
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 3.2.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 3.2.0
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: webmock
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -113,20 +127,9 @@ files:
113
127
  - lib/json_api_client/error_collector.rb
114
128
  - lib/json_api_client/errors.rb
115
129
  - lib/json_api_client/helpers.rb
116
- - lib/json_api_client/helpers/associable.rb
117
- - lib/json_api_client/helpers/attributable.rb
118
- - lib/json_api_client/helpers/custom_endpoints.rb
119
- - lib/json_api_client/helpers/custom_headers.rb
130
+ - lib/json_api_client/helpers/callbacks.rb
131
+ - lib/json_api_client/helpers/dirty.rb
120
132
  - lib/json_api_client/helpers/dynamic_attributes.rb
121
- - lib/json_api_client/helpers/initializable.rb
122
- - lib/json_api_client/helpers/linkable.rb
123
- - lib/json_api_client/helpers/paginatable.rb
124
- - lib/json_api_client/helpers/parsable.rb
125
- - lib/json_api_client/helpers/queryable.rb
126
- - lib/json_api_client/helpers/relatable.rb
127
- - lib/json_api_client/helpers/requestable.rb
128
- - lib/json_api_client/helpers/schemable.rb
129
- - lib/json_api_client/helpers/serializable.rb
130
133
  - lib/json_api_client/implementation.rb
131
134
  - lib/json_api_client/included_data.rb
132
135
  - lib/json_api_client/linking.rb
@@ -172,9 +175,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
172
175
  version: 1.3.1
173
176
  requirements: []
174
177
  rubyforge_project:
175
- rubygems_version: 2.2.2
178
+ rubygems_version: 2.4.5
176
179
  signing_key:
177
180
  specification_version: 4
178
181
  summary: Build client libraries compliant with specification defined by jsonapi.org
179
182
  test_files: []
180
- has_rdoc:
@@ -1,57 +0,0 @@
1
- module JsonApiClient
2
- module Helpers
3
- module Associable
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- class_attribute :associations, instance_accessor: false
8
- self.associations = []
9
-
10
- include Associations::BelongsTo
11
- include Associations::HasMany
12
- include Associations::HasOne
13
-
14
- initializer :load_associations
15
- end
16
-
17
- module ClassMethods
18
- def belongs_to_associations
19
- associations.select{|association| association.is_a?(Associations::BelongsTo::Association) }
20
- end
21
-
22
- def prefix_params
23
- belongs_to_associations.map(&:param)
24
- end
25
-
26
- def prefix_path
27
- belongs_to_associations.map(&:to_prefix_path).join("/")
28
- end
29
-
30
- def path(params = nil)
31
- parts = [table_name]
32
- if params
33
- path_params = params.delete(:path) || params
34
- parts.unshift(prefix_path % path_params.symbolize_keys)
35
- else
36
- parts.unshift(prefix_path)
37
- end
38
- parts.reject!{|part| part == "" }
39
- File.join(*parts)
40
- rescue KeyError
41
- raise ArgumentError, "Not all prefix parameters specified"
42
- end
43
- end
44
-
45
- protected
46
-
47
- def load_associations(params)
48
- self.class.associations.each do |association|
49
- if params.has_key?(association.attr_name.to_s)
50
- set_attribute(association.attr_name, association.parse(params[association.attr_name.to_s]))
51
- end
52
- end
53
- end
54
-
55
- end
56
- end
57
- end
@@ -1,55 +0,0 @@
1
- module JsonApiClient
2
- module Helpers
3
- module Attributable
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- include DynamicAttributes
8
- attr_accessor :errors
9
- initializer do |obj, params|
10
- obj.attributes = params.merge(obj.class.default_attributes)
11
- end
12
- end
13
-
14
- module ClassMethods
15
- def load(params)
16
- new(params).tap do |resource|
17
- resource.mark_as_persisted!
18
- end
19
- end
20
-
21
- def default_attributes
22
- {type: table_name}
23
- end
24
- end
25
-
26
- def update_attributes(attrs = {})
27
- self.attributes = attrs
28
- save
29
- end
30
-
31
- def mark_as_persisted!
32
- @persisted = true
33
- end
34
-
35
- def persisted?
36
- !!@persisted && has_attribute?(primary_key)
37
- end
38
-
39
- def query_params
40
- attributes.except(primary_key)
41
- end
42
-
43
- def to_param
44
- attributes.fetch(primary_key, "").to_s
45
- end
46
-
47
- protected
48
-
49
- def ==(other)
50
- self.class == other.class && attributes == other.attributes
51
- end
52
-
53
- end
54
- end
55
- end
@@ -1,37 +0,0 @@
1
- module JsonApiClient
2
- module Helpers
3
- module CustomEndpoints
4
- extend ActiveSupport::Concern
5
-
6
- module ClassMethods
7
- def custom_endpoint(name, options = {})
8
- if :collection == options.delete(:on)
9
- collection_endpoint(name, options)
10
- else
11
- member_endpoint(name, options)
12
- end
13
- end
14
-
15
- def collection_endpoint(name, options = {})
16
- metaclass = class << self
17
- self
18
- end
19
- metaclass.instance_eval do
20
- define_method(name) do |*params|
21
- request_params = params.first || {}
22
- requestor.custom(name, options, request_params)
23
- end
24
- end
25
- end
26
-
27
- def member_endpoint(name, options = {})
28
- define_method name do |*params|
29
- request_params = params.first || {}
30
- request_params[self.class.primary_key] = attributes.fetch(primary_key)
31
- self.class.requestor.custom(name, options, request_params)
32
- end
33
- end
34
- end
35
- end
36
- end
37
- end
@@ -1,31 +0,0 @@
1
- module JsonApiClient
2
- module Helpers
3
- module CustomHeaders
4
- extend ActiveSupport::Concern
5
-
6
- module ClassMethods
7
- def with_headers(headers)
8
- self.custom_headers = headers
9
- yield
10
- ensure
11
- self.custom_headers = {}
12
- end
13
-
14
- def custom_headers
15
- header_store
16
- end
17
-
18
- def custom_headers=(headers)
19
- header_store.replace(headers)
20
- end
21
-
22
- protected
23
-
24
- def header_store
25
- Thread.current["json_api_client-#{resource_name}"] ||= {}
26
- end
27
- end
28
-
29
- end
30
- end
31
- end
@@ -1,28 +0,0 @@
1
- module JsonApiClient
2
- module Helpers
3
- module Initializable
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- class_attribute :initializers, instance_accessor: false
8
- self.initializers = []
9
- end
10
-
11
- module ClassMethods
12
- def initializer(method = nil, &block)
13
- self.initializers.push(method || block)
14
- end
15
- end
16
-
17
- def initialize(params = {})
18
- self.class.initializers.each do |initializer|
19
- if initializer.respond_to?(:call)
20
- initializer.call(self, params)
21
- else
22
- self.send(initializer, params)
23
- end
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,22 +0,0 @@
1
- module JsonApiClient
2
- module Helpers
3
- module Linkable
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- class_attribute :linker, instance_accessor: false
8
- self.linker = Linking::Links
9
-
10
- # the links for this resource
11
- attr_accessor :links
12
-
13
- initializer do |obj, params|
14
- links = params && params.delete("links")
15
- links ||= {}
16
- obj.links = obj.class.linker.new(links)
17
- end
18
- end
19
-
20
- end
21
- end
22
- end
@@ -1,14 +0,0 @@
1
- module JsonApiClient
2
- module Helpers
3
- module Paginatable
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- class_attribute :paginator, instance_accessor: false
8
- self.paginator = Paginating::Paginator
9
- end
10
-
11
- end
12
- end
13
- end
14
-
@@ -1,19 +0,0 @@
1
- module JsonApiClient
2
- module Helpers
3
- module Parsable
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- class_attribute :parser, instance_accessor: false
8
- self.parser = Parsers::Parser
9
- end
10
-
11
- module ClassMethods
12
- def parse(data)
13
- parser.parse(self, data)
14
- end
15
- end
16
-
17
- end
18
- end
19
- end
@@ -1,37 +0,0 @@
1
- module JsonApiClient
2
- module Helpers
3
- module Queryable
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- class << self
8
- extend Forwardable
9
- def_delegators :new_scope, :where, :order, :includes, :select, :all, :paginate, :page, :first, :find
10
- end
11
- class_attribute :connection_class, :connection_object, :connection_options, :query_builder, instance_accessor: false
12
- self.connection_class = Connection
13
- self.connection_options = {}
14
- self.query_builder = Query::Builder
15
- end
16
-
17
- module ClassMethods
18
- def new_scope
19
- query_builder.new(self)
20
- end
21
-
22
- def connection(&block)
23
- build_connection(&block)
24
- connection_object
25
- end
26
-
27
- def build_connection
28
- return connection_object unless connection_object.nil?
29
- self.connection_object = connection_class.new(connection_options.merge(site: site)).tap do |conn|
30
- yield(conn) if block_given?
31
- end
32
- end
33
- end
34
-
35
- end
36
- end
37
- end
@@ -1,39 +0,0 @@
1
- module JsonApiClient
2
- module Helpers
3
- module Relatable
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- class_attribute :relationship_linker, instance_accessor: false
8
- self.relationship_linker = Relationships::Relations
9
-
10
- # the relationships for this resource
11
- attr_accessor :relationships
12
-
13
- initializer do |obj, params|
14
- relationships = params && params.delete("relationships")
15
- relationships ||= {}
16
- obj.relationships = obj.class.relationship_linker.new(relationships)
17
- end
18
- end
19
-
20
- def as_relation
21
- {
22
- :type => self.class.table_name,
23
- primary_key => self[primary_key]
24
- }
25
- end
26
-
27
- def method_missing(method, *args)
28
- return super unless relationships and relationships.has_attribute?(method) and result_set.included
29
- result_set.included.data_for(method, relationships[method])
30
- end
31
-
32
- def respond_to_missing?(symbol, include_all = false)
33
- return true if relationships && relationships.has_attribute?(symbol)
34
- super
35
- end
36
-
37
- end
38
- end
39
- end
@@ -1,56 +0,0 @@
1
- module JsonApiClient
2
- module Helpers
3
- module Requestable
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- attr_accessor :last_result_set, :errors
8
- class_attribute :requestor_class, instance_accessor: false
9
- self.requestor_class = Query::Requestor
10
- end
11
-
12
- module ClassMethods
13
- def create(conditions = {})
14
- new(conditions).tap do |resource|
15
- resource.save
16
- end
17
- end
18
-
19
- def requestor
20
- @requestor ||= requestor_class.new(self)
21
- end
22
- end
23
-
24
- def save
25
- self.last_result_set = if persisted?
26
- self.class.requestor.update(self)
27
- else
28
- self.class.requestor.create(self)
29
- end
30
-
31
- self.errors = last_result_set.errors
32
- if last_result_set.has_errors?
33
- false
34
- else
35
- self.errors.clear if self.errors
36
- mark_as_persisted!
37
- if updated = last_result_set.first
38
- self.attributes = updated.attributes
39
- end
40
- true
41
- end
42
- end
43
-
44
- def destroy
45
- self.last_result_set = self.class.requestor.destroy(self)
46
- if !last_result_set.has_errors?
47
- self.attributes.clear
48
- true
49
- else
50
- false
51
- end
52
- end
53
-
54
- end
55
- end
56
- end
@@ -1,68 +0,0 @@
1
- module JsonApiClient
2
- module Helpers
3
- module Schemable
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- initializer do |obj, params|
8
- obj.send(:set_default_values)
9
- end
10
- end
11
-
12
- module ClassMethods
13
- # Returns the schema for this resource class
14
- #
15
- # @return [Schema] the schema for this resource class
16
- def schema
17
- @schema ||= Schema.new
18
- end
19
-
20
- # Declares a new property by name
21
- #
22
- # @param name [Symbol] the name of the property
23
- # @param options [Hash] property options
24
- # @option options [Symbol] :type The property type
25
- # @option options [Symbol] :default The default value for the property
26
- def property(name, options = {})
27
- schema.add(name, options)
28
- end
29
-
30
- # Declare multiple properties with the same optional options
31
- #
32
- # @param [Array<Symbol>] names
33
- # @param options [Hash] property options
34
- # @option options [Symbol] :type The property type
35
- # @option options [Symbol] :default The default value for the property
36
- def properties(*names)
37
- options = names.last.is_a?(Hash) ? names.pop : {}
38
- names.each do |name|
39
- property name, options
40
- end
41
- end
42
- end
43
-
44
- protected
45
-
46
- def set_attribute(name, value)
47
- property = property_for(name)
48
- value = property.cast(value) if property
49
- super(name, value)
50
- end
51
-
52
- def has_attribute?(attr_name)
53
- !!property_for(attr_name) || super
54
- end
55
-
56
- def set_default_values
57
- self.class.schema.each_property do |property|
58
- attributes[property.name] = property.default unless attributes.has_key?(property.name)
59
- end
60
- end
61
-
62
- def property_for(name)
63
- self.class.schema.find(name)
64
- end
65
-
66
- end
67
- end
68
- end
@@ -1,28 +0,0 @@
1
- module JsonApiClient
2
- module Helpers
3
- module Serializable
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- class_attribute :read_only_attributes, instance_accessor: false
8
- self.read_only_attributes = ['id', 'type', 'links', 'meta', 'relationships']
9
- end
10
-
11
- def serializable_hash
12
- attributes.slice('id', 'type').tap do |h|
13
- relationships.serializable_hash.tap do |r|
14
- h['relationships'] = r unless r.empty?
15
- end
16
- h['attributes'] = attributes_for_serialization
17
- end
18
- end
19
-
20
- protected
21
-
22
- def attributes_for_serialization
23
- attributes.except(*self.class.read_only_attributes)
24
- end
25
-
26
- end
27
- end
28
- end