jsonapi-consumer 0.1.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +12 -0
  5. data/Gemfile +7 -0
  6. data/LICENSE.txt +176 -0
  7. data/README.md +37 -0
  8. data/Rakefile +12 -0
  9. data/jsonapi-consumer.gemspec +32 -0
  10. data/lib/jsonapi/consumer/errors.rb +98 -0
  11. data/lib/jsonapi/consumer/middleware/parse_json.rb +32 -0
  12. data/lib/jsonapi/consumer/middleware/raise_error.rb +21 -0
  13. data/lib/jsonapi/consumer/middleware/request_timeout.rb +9 -0
  14. data/lib/jsonapi/consumer/middleware.rb +5 -0
  15. data/lib/jsonapi/consumer/parser.rb +75 -0
  16. data/lib/jsonapi/consumer/query/base.rb +34 -0
  17. data/lib/jsonapi/consumer/query/create.rb +9 -0
  18. data/lib/jsonapi/consumer/query/delete.rb +10 -0
  19. data/lib/jsonapi/consumer/query/find.rb +16 -0
  20. data/lib/jsonapi/consumer/query/new.rb +15 -0
  21. data/lib/jsonapi/consumer/query/update.rb +11 -0
  22. data/lib/jsonapi/consumer/query.rb +5 -0
  23. data/lib/jsonapi/consumer/resource/association_concern.rb +203 -0
  24. data/lib/jsonapi/consumer/resource/attributes_concern.rb +70 -0
  25. data/lib/jsonapi/consumer/resource/connection_concern.rb +94 -0
  26. data/lib/jsonapi/consumer/resource/finders_concern.rb +28 -0
  27. data/lib/jsonapi/consumer/resource/object_build_concern.rb +28 -0
  28. data/lib/jsonapi/consumer/resource/serializer_concern.rb +64 -0
  29. data/lib/jsonapi/consumer/resource.rb +88 -0
  30. data/lib/jsonapi/consumer/version.rb +5 -0
  31. data/lib/jsonapi/consumer.rb +40 -0
  32. data/spec/fixtures/.gitkeep +0 -0
  33. data/spec/fixtures/resources.rb +33 -0
  34. data/spec/fixtures/responses.rb +51 -0
  35. data/spec/jsonapi/consumer/associations_spec.rb +141 -0
  36. data/spec/jsonapi/consumer/attributes_spec.rb +27 -0
  37. data/spec/jsonapi/consumer/connection_spec.rb +101 -0
  38. data/spec/jsonapi/consumer/error_handling_spec.rb +37 -0
  39. data/spec/jsonapi/consumer/object_build_spec.rb +20 -0
  40. data/spec/jsonapi/consumer/parser_spec.rb +41 -0
  41. data/spec/jsonapi/consumer/resource_spec.rb +62 -0
  42. data/spec/jsonapi/consumer/serializer_spec.rb +41 -0
  43. data/spec/spec_helper.rb +97 -0
  44. data/spec/support/.gitkeep +0 -0
  45. data/spec/support/load_fixtures.rb +4 -0
  46. metadata +242 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: caaedec2074ee4aafca24c5afea80f6ae9422c49
4
+ data.tar.gz: f38a0c0d33691186de1b2ecf24603a0132a52925
5
+ SHA512:
6
+ metadata.gz: 4326500565a0f142502c2db9ac40084875f3345942e97ea7fe5ab3b505d9193e8289f49fd481aaffb836e929bb0aa2a9f50aec27f5a162df50e2c7b9e73a2a90
7
+ data.tar.gz: 06ab89c5bddfe9bfad758b1ddf4a21d6ff3c74ff6dbeb01684f03b8a714a740dc1f408c3322e339b03baef315df42593cc7c3f690af08d7780bc590999d1b0b1
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+
2
+ 0.1.0.pre (unreleased) / 2014-10-19
3
+ ==================
4
+
5
+ * Add yarddoc to associations_concern
6
+ * Add error handling and parsing of Bad Requests
7
+ * Parse `has_one` and `has_many` links attribute from JSONAPI
8
+ * CRUD is supported
9
+ * Support create, find, all finders
10
+ * Create query support
11
+ * Basic serialization and association building.
12
+
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jsonapi-consumer.gemspec
4
+ gemspec
5
+
6
+ gem 'fsevent'
7
+ gem 'guard-rspec', require: false
data/LICENSE.txt ADDED
@@ -0,0 +1,176 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # JSONAPI::Consumer
2
+
3
+ An ActiveModel-compliant consumer framework for communicating with JSONAPI-based APIs.
4
+
5
+ [![Build Status](https://travis-ci.org/jsmestad/jsonapi-consumer.svg?branch=master)](https://travis-ci.org/jsmestad/jsonapi-consumer)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'jsonapi-consumer'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install jsonapi-consumer
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Contributing
28
+
29
+ 1. Fork it ( https://github.com/jsmestad/jsonapi-consumer/fork )
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create a new Pull Request
34
+
35
+ ## Copyright & License
36
+
37
+ JSONAPI::Consumer is distributed under the Apache 2.0 License. See LICENSE.txt file for more information.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
9
+ rescue LoadError
10
+ # no rspec available
11
+ end
12
+
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'jsonapi/consumer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "jsonapi-consumer"
8
+ spec.version = Jsonapi::Consumer::VERSION
9
+ spec.authors = ["Justin Smestad"]
10
+ spec.email = ["justin.smestad@gmail.com"]
11
+ spec.summary = %q{JSONAPI client framework for API consumption.}
12
+ spec.description = %q{Create ActiveModel-compliant objects for your JSONAPI-based API}
13
+ spec.homepage = "https://github.com/jsmestad/jsonapi-consumer"
14
+ spec.license = "Apache-2.0"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0") - ['Guardfile', '.travis.yml']
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "activemodel"
22
+ spec.add_runtime_dependency "activesupport"
23
+ spec.add_runtime_dependency "faraday", "~> 0.9.0"
24
+ spec.add_runtime_dependency "faraday_middleware"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.6"
27
+ spec.add_development_dependency "factory_girl"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "rspec"
30
+ spec.add_development_dependency "rspec-its"
31
+ spec.add_development_dependency "webmock"
32
+ end
@@ -0,0 +1,98 @@
1
+ # JSONAPI::Consumer::Errors is responsible for wrapping API errors into a consistent
2
+ # form. Handling or Rescuing from these errors is left up to the client, but
3
+ # a possible way to do this is detailed below.
4
+ #
5
+ # # lib/errors/rescue_error.rb
6
+ # module Errors
7
+ # module RescueError
8
+ #
9
+ # def self.included(base)
10
+ # base.rescue_from Errors::ResponseError do |e|
11
+ # render "public/#{e.code}", :status => e.code
12
+ # end
13
+ # end
14
+ #
15
+ # end
16
+ # end
17
+ #
18
+ module JSONAPI::Consumer
19
+ module Errors
20
+ class ConnectionNotConfigured < StandardError; end
21
+ class RecordNotSaved < StandardError; end
22
+
23
+ class ServerNotResponding < StandardError
24
+ def message
25
+ 'Server did not respond in a timely fashion. Is it running?'
26
+ end
27
+ end
28
+
29
+ # Error constants
30
+ BAD_REQUEST = 400
31
+ NOT_AUTHORIZED = 401
32
+ FORBIDDEN = 403
33
+ NOT_FOUND = 404
34
+ METHOD_NOT_ALLOWED = 405
35
+ BAD_FORMAT = 406
36
+ REQUESTED_RANGE_NOT_SATISFIABLE = 416
37
+ UNPROCESSABLE_ENTITY = 422
38
+ INTERNAL_SERVER_ERROR = 500
39
+
40
+ # Base subclass for all response errors
41
+ class ResponseError < StandardError
42
+ attr_accessor :response
43
+
44
+ def initialize(message=nil, response=nil)
45
+ super(message)
46
+ self.response = response
47
+ end
48
+
49
+ def errors
50
+ response[:body].fetch('errors', [])
51
+ end
52
+
53
+ def response_body
54
+ response[:body]
55
+ end
56
+ end
57
+
58
+ class << self
59
+
60
+ # Returns a hash of error names to response codes.
61
+ def error_constants
62
+ constants.each_with_object({}) do |name, hash|
63
+ # Ignore any class constants
64
+ next if (code = Errors.const_get(name)).is_a?(Class)
65
+ hash[name] = code
66
+ end
67
+ end
68
+
69
+ # Returns a class name from a constant name.
70
+ def class_name_for_error_name(name)
71
+ name.to_s.titleize.gsub(' ', '')
72
+ end
73
+
74
+ # Returns the error class for a given constant name.
75
+ # Default to InternalServerError.
76
+ def class_for_error_name(name)
77
+ class_name = class_name_for_error_name(name)
78
+ const_defined?(class_name) ? Errors.const_get(class_name) : Errors::InternalServerError
79
+ end
80
+
81
+ # Returns the error class for a given error code.
82
+ # Default to InternalServerError.
83
+ def class_for_error_code(code)
84
+ name = error_constants.select { |k, v| v == code }.keys.first
85
+ name.present? ? class_for_error_name(name) : Errors::InternalServerError
86
+ end
87
+ end
88
+ end
89
+
90
+ # Dynamically creates a subclass of ResponseError for each error constant.
91
+ # Adds a code method to return the correct response code.
92
+ # Adds the new class to the constants in the Errors module.
93
+ Errors.error_constants.each do |name, code|
94
+ klass = Class.new(Errors::ResponseError)
95
+ klass.send(:define_method, :code) { code }
96
+ Errors.const_set(Errors.class_name_for_error_name(name), klass)
97
+ end
98
+ end
@@ -0,0 +1,32 @@
1
+ module JSONAPI::Consumer::Middleware
2
+ # Credit to chingor13/json_api_client for middleware.
3
+ #
4
+ class ParseJson < Faraday::Response::Middleware
5
+
6
+ def call(environment)
7
+ @app.call(environment).on_complete do |env|
8
+ if process_response_type?(response_type(env))
9
+ env[:raw_body] = env[:body]
10
+ env[:body] = parse(env[:body])
11
+ end
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def parse(body)
18
+ ::JSON.parse(body) unless body.strip.empty?
19
+ end
20
+
21
+ def response_type(env)
22
+ type = env[:response_headers]['Content-Type'].to_s
23
+ type = type.split(';', 2).first if type.index(';')
24
+ type
25
+ end
26
+
27
+ def process_response_type?(type)
28
+ !!type.match(/\bjson$/)
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,21 @@
1
+ module JSONAPI::Consumer::Middleware
2
+ class RaiseError < Faraday::Response::Middleware
3
+ def on_complete(env)
4
+ return if (status = env[:status]) < 400
5
+ message = "#{env[:status]} #{env[:method].upcase} #{env[:url]} #{env[:body]}"
6
+ raise ::JSONAPI::Consumer::Errors.class_for_error_code(status).new(message, response_values(env))
7
+ end
8
+
9
+ def response_values(env)
10
+ {status: env[:status], headers: env[:response_headers], body: parse_body(env[:body])}
11
+ end
12
+
13
+ def parse_body(body)
14
+ if body.nil?
15
+ nil
16
+ else
17
+ JSON.parse(body) rescue nil
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ module JSONAPI::Consumer::Middleware
2
+ class RequestTimeout < Faraday::Middleware
3
+ def call(env)
4
+ @app.call(env)
5
+ rescue Faraday::TimeoutError
6
+ raise JSONAPI::Consumer::Errors::ServerNotResponding
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ module JSONAPI::Consumer
2
+ module Middleware
3
+
4
+ end
5
+ end
@@ -0,0 +1,75 @@
1
+ module JSONAPI::Consumer
2
+ class Parser
3
+ attr_reader :response, :klass
4
+
5
+ def initialize(klass, response)
6
+ @klass = klass
7
+ @response = response
8
+ end
9
+
10
+ def attributes(item)
11
+ item.except(:links)
12
+ end
13
+
14
+ def associations(item)
15
+ associations = {}
16
+ item.fetch(:links, {}).each do |assoc_name, ids|
17
+ associations[assoc_name] = if ids.is_a?(Array)
18
+ ids.collect {|id| find_linked(assoc_name, id) }
19
+ else
20
+ find_linked(assoc_name, ids)
21
+ end
22
+ end
23
+ associations
24
+ end
25
+
26
+ def fetch_resource
27
+ link_body = _body.fetch(:links, {})
28
+
29
+ links = {}
30
+
31
+ link_body.each do |key, url|
32
+ links[key.split('.').last] = url
33
+ end
34
+
35
+ links
36
+ end
37
+
38
+ def fetch_linked(assoc_name, id)
39
+ klass._association_class_name(assoc_name).find(id)
40
+ end
41
+
42
+ def find_linked(assoc_name, id)
43
+ if found = linked.fetch(assoc_name, []).detect {|h| h.fetch(:id) == id }
44
+ klass._association_class_name(assoc_name).new(found)
45
+ else
46
+ fetch_linked(assoc_name, id)
47
+ end
48
+ end
49
+
50
+ def linked
51
+ linked_body = _body.fetch(:linked, {})
52
+
53
+ linked = {}
54
+ linked_body.each do |key, obj_attrs|
55
+ linked[key] = obj_attrs
56
+ end
57
+ linked
58
+ end
59
+
60
+ def build
61
+ _body.fetch(klass.json_key, []).collect do |attrs|
62
+ attrs_hash = attributes(attrs).merge(associations(attrs))
63
+ klass.new(attrs_hash)
64
+ end
65
+ end
66
+
67
+ def _body
68
+ response.body.with_indifferent_access
69
+ end
70
+
71
+ def _status
72
+ reponse.status
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,34 @@
1
+ module JSONAPI::Consumer::Query
2
+ class Base
3
+ class << self
4
+ attr_accessor :request_method
5
+ end
6
+ attr_reader :klass, :headers, :path, :params
7
+
8
+ def initialize(klass, payload)
9
+ @klass = klass
10
+ build_params(payload) if payload.is_a?(Hash) && payload.keys != [klass.primary_key]
11
+
12
+ @path = begin
13
+ if payload.is_a?(Hash) && payload.has_key?(klass.primary_key)
14
+ [klass.path, payload.delete(klass.primary_key)].join('/')
15
+ else
16
+ klass.path
17
+ end
18
+ end
19
+ end
20
+
21
+ def build_params(args)
22
+ @params = args.dup
23
+ end
24
+
25
+ def request_method
26
+ self.class.request_method
27
+ end
28
+
29
+ def inspect
30
+ "#{self.class.name}: method: #{request_method}; path: #{path}; params: #{params}, headers: #{headers}"
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ module JSONAPI::Consumer::Query
2
+ class Create < Base
3
+ self.request_method = :post
4
+
5
+ def build_params(args)
6
+ @params = {klass.json_key => args}
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module JSONAPI::Consumer::Query
2
+ class Delete < Base
3
+ self.request_method = :delete
4
+
5
+ def params
6
+ nil
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,16 @@
1
+ module JSONAPI::Consumer::Query
2
+ class Find < Base
3
+ self.request_method = :get
4
+
5
+ def build_params(args)
6
+ @params = case args
7
+ when Hash
8
+ args
9
+ when Array
10
+ {klass.primary_key.to_s.pluralize.to_sym => args.join(",")}
11
+ else
12
+ {klass.primary_key => args}
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ module JSONAPI::Consumer::Query
2
+ class New < Base
3
+ self.request_method = :get
4
+
5
+ def initialize(klass, payload)
6
+ super
7
+ @path = [klass.path, 'new'].join('/')
8
+ end
9
+
10
+ # def build_params(args)
11
+ # @params = {klass.json_key => args}
12
+ # end
13
+ end
14
+ end
15
+
@@ -0,0 +1,11 @@
1
+ module JSONAPI::Consumer::Query
2
+ class Update < Base
3
+ self.request_method = :put
4
+
5
+ def build_params(args)
6
+ args = args.dup
7
+ @params = {klass.json_key => args.except(klass.primary_key)}
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,5 @@
1
+ module JSONAPI::Consumer
2
+ module Query
3
+
4
+ end
5
+ end