json_api_client 1.0.0.beta5 → 1.0.0.beta6

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