jsonapi-consumer 0.1.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +27 -0
  3. data/.gitignore +1 -0
  4. data/Gemfile +6 -4
  5. data/README.md +9 -38
  6. data/Rakefile +17 -6
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/jsonapi-consumer.gemspec +10 -11
  10. data/lib/jsonapi/consumer/associations/base_association.rb +26 -0
  11. data/lib/jsonapi/consumer/associations/belongs_to.rb +30 -0
  12. data/lib/jsonapi/consumer/associations/has_many.rb +26 -0
  13. data/lib/jsonapi/consumer/associations/has_one.rb +19 -0
  14. data/lib/jsonapi/consumer/connection.rb +36 -0
  15. data/lib/jsonapi/consumer/error_collector.rb +91 -0
  16. data/lib/jsonapi/consumer/errors.rb +34 -76
  17. data/lib/jsonapi/consumer/formatter.rb +145 -0
  18. data/lib/jsonapi/consumer/helpers/callbacks.rb +27 -0
  19. data/lib/jsonapi/consumer/helpers/dirty.rb +71 -0
  20. data/lib/jsonapi/consumer/helpers/dynamic_attributes.rb +83 -0
  21. data/lib/jsonapi/consumer/helpers/uri.rb +9 -0
  22. data/lib/jsonapi/consumer/implementation.rb +12 -0
  23. data/lib/jsonapi/consumer/included_data.rb +49 -0
  24. data/lib/jsonapi/consumer/linking/links.rb +22 -0
  25. data/lib/jsonapi/consumer/linking/top_level_links.rb +39 -0
  26. data/lib/jsonapi/consumer/meta_data.rb +19 -0
  27. data/lib/jsonapi/consumer/middleware/json_request.rb +26 -0
  28. data/lib/jsonapi/consumer/middleware/parse_json.rb +22 -23
  29. data/lib/jsonapi/consumer/middleware/status.rb +41 -0
  30. data/lib/jsonapi/consumer/paginating/paginator.rb +89 -0
  31. data/lib/jsonapi/consumer/parsers/parser.rb +113 -0
  32. data/lib/jsonapi/consumer/query/builder.rb +212 -0
  33. data/lib/jsonapi/consumer/query/requestor.rb +67 -0
  34. data/lib/jsonapi/consumer/relationships/relations.rb +56 -0
  35. data/lib/jsonapi/consumer/relationships/top_level_relations.rb +30 -0
  36. data/lib/jsonapi/consumer/resource.rb +514 -54
  37. data/lib/jsonapi/consumer/result_set.rb +25 -0
  38. data/lib/jsonapi/consumer/schema.rb +153 -0
  39. data/lib/jsonapi/consumer/utils.rb +28 -0
  40. data/lib/jsonapi/consumer/version.rb +1 -1
  41. data/lib/jsonapi/consumer.rb +59 -34
  42. metadata +51 -111
  43. data/.rspec +0 -2
  44. data/CHANGELOG.md +0 -36
  45. data/lib/jsonapi/consumer/middleware/raise_error.rb +0 -21
  46. data/lib/jsonapi/consumer/middleware/request_headers.rb +0 -20
  47. data/lib/jsonapi/consumer/middleware/request_timeout.rb +0 -9
  48. data/lib/jsonapi/consumer/middleware.rb +0 -5
  49. data/lib/jsonapi/consumer/parser.rb +0 -75
  50. data/lib/jsonapi/consumer/query/base.rb +0 -34
  51. data/lib/jsonapi/consumer/query/create.rb +0 -9
  52. data/lib/jsonapi/consumer/query/delete.rb +0 -10
  53. data/lib/jsonapi/consumer/query/find.rb +0 -16
  54. data/lib/jsonapi/consumer/query/new.rb +0 -15
  55. data/lib/jsonapi/consumer/query/update.rb +0 -11
  56. data/lib/jsonapi/consumer/query.rb +0 -5
  57. data/lib/jsonapi/consumer/resource/association_concern.rb +0 -203
  58. data/lib/jsonapi/consumer/resource/attributes_concern.rb +0 -70
  59. data/lib/jsonapi/consumer/resource/connection_concern.rb +0 -99
  60. data/lib/jsonapi/consumer/resource/finders_concern.rb +0 -28
  61. data/lib/jsonapi/consumer/resource/object_build_concern.rb +0 -28
  62. data/lib/jsonapi/consumer/resource/serializer_concern.rb +0 -63
  63. data/spec/fixtures/.gitkeep +0 -0
  64. data/spec/fixtures/resources.rb +0 -45
  65. data/spec/fixtures/responses.rb +0 -64
  66. data/spec/jsonapi/consumer/associations_spec.rb +0 -166
  67. data/spec/jsonapi/consumer/attributes_spec.rb +0 -27
  68. data/spec/jsonapi/consumer/connection_spec.rb +0 -147
  69. data/spec/jsonapi/consumer/error_handling_spec.rb +0 -37
  70. data/spec/jsonapi/consumer/object_build_spec.rb +0 -20
  71. data/spec/jsonapi/consumer/parser_spec.rb +0 -39
  72. data/spec/jsonapi/consumer/resource_spec.rb +0 -62
  73. data/spec/jsonapi/consumer/serializer_spec.rb +0 -41
  74. data/spec/spec_helper.rb +0 -97
  75. data/spec/support/.gitkeep +0 -0
  76. data/spec/support/load_fixtures.rb +0 -4
@@ -1,75 +0,0 @@
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.pluralize, []).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
@@ -1,34 +0,0 @@
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
@@ -1,9 +0,0 @@
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
@@ -1,10 +0,0 @@
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
-
@@ -1,16 +0,0 @@
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
@@ -1,15 +0,0 @@
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
-
@@ -1,11 +0,0 @@
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
-
@@ -1,5 +0,0 @@
1
- module JSONAPI::Consumer
2
- module Query
3
-
4
- end
5
- end
@@ -1,203 +0,0 @@
1
- module JSONAPI::Consumer::Resource
2
- class MisconfiguredAssociation < StandardError; end
3
-
4
- module AssociationConcern
5
- extend ActiveSupport::Concern
6
-
7
- module ClassMethods
8
- attr_writer :_associations
9
-
10
- # Defines a has many relationship.
11
- #
12
- # @example
13
- # class User
14
- # include JSONAPI::Consumer::Resource
15
- # has_many :articles, class_name: 'Article'
16
- # end
17
- def has_many(*attrs)
18
- associate(:has_many, attrs)
19
- end
20
-
21
- # @todo belongs to is not supported yet.
22
- #
23
- def belongs_to(*attrs)
24
- associate(:belongs_to, attrs)
25
- end
26
-
27
- # Defines a single relationship.
28
- #
29
- # @example
30
- # class Article
31
- # include JSONAPI::Consumer::Resource
32
- # has_one :user, class_name: 'User'
33
- # end
34
- def has_one(*attrs)
35
- associate(:has_one, attrs)
36
- end
37
-
38
- # :nodoc:
39
- def _associations
40
- @_associations ||= {}
41
- end
42
-
43
- # :nodoc:
44
- def _association_for(name)
45
- _associations[name.to_sym]
46
- end
47
-
48
- # :nodoc:
49
- def _association_type(name)
50
- _association_for(name).fetch(:type)
51
- end
52
-
53
- # :nodoc:
54
- def _association_class_name(name)
55
- if class_name = _association_for(name).fetch(:class_name)
56
- begin
57
- class_name.constantize
58
- rescue NameError
59
- raise MisconfiguredAssociation,
60
- "#{self}##{_association_type(name)} #{name} has a class_name specified that does not exist."
61
- end
62
- else
63
- raise MisconfiguredAssociation,
64
- "#{self}##{_association_type(name)} #{name} is missing an explicit `:class_name` value."
65
- end
66
- end
67
-
68
- # :nodoc:
69
- def associate(type, attrs)
70
- options = attrs.extract_options!
71
-
72
- self._associations = _associations.dup
73
-
74
- attrs.each do |attr|
75
- unless method_defined?(attr)
76
- define_method attr do
77
- read_association(attr)
78
- end
79
- end
80
-
81
- if type == :has_many
82
- unless method_defined?(:"#{attr.to_s.singularize}_ids")
83
- define_method :"#{attr.to_s.singularize}_ids" do
84
- if objs = read_association(attr)
85
- objs.collect {|o| o.send(o.primary_key)}
86
- end
87
- end
88
- end
89
- else
90
- unless method_defined?(:"#{attr.to_s.singularize}_id")
91
- define_method :"#{attr.to_s.singularize}_id" do
92
- if obj = read_association(attr)
93
- obj.send(obj.primary_key)
94
- end
95
- end
96
- end
97
- end
98
- unless method_defined?(:"#{attr}=")
99
- define_method :"#{attr}=" do |val|
100
- val = [val].flatten if type == :has_many && !val.nil?
101
- set_association(attr, val)
102
- end
103
- end
104
-
105
- self._associations[attr] = {type: type, class_name: options.delete(:class_name), options: options}
106
- end
107
- end
108
- end
109
-
110
- # :nodoc:
111
- def each_association(&block)
112
- self.class._associations.dup.each do |name, options|
113
- association = self.send(name)
114
-
115
- if block_given?
116
- block.call(name, association, options[:options])
117
- end
118
- end
119
- end
120
-
121
- # Helper method that returns the names of defined associations.
122
- #
123
- # @return [Array<Symbol>] a list of association names
124
- def association_names
125
- self.class._associations.keys
126
- end
127
-
128
- protected
129
-
130
-
131
- # Read the specified association.
132
- #
133
- # @param name [Symbol, String] the association name, `:users` or `:author`
134
- #
135
- # @return [Array, Object, nil] the value(s) of that association.
136
- def read_association(name)
137
- type = _association_type(name)
138
- _associations.fetch(name, nil)
139
- end
140
-
141
- # Set values for the key'd association.
142
- #
143
- # @param key [Symbol] the association name, `:users` or `:author`
144
- # @param value the value to set on the specified association
145
- def set_association(key, value)
146
- _associations[key.to_sym] = _cast_association(key, value)
147
- end
148
-
149
-
150
- # Helper method that verifies a given association exists.
151
- #
152
- # @param attr_name [String, Symbol] the association name
153
- #
154
- # @return [true, false]
155
- def has_association?(attr_name)
156
- _associations.has_key?(attr_name.to_sym)
157
- end
158
-
159
- private
160
-
161
- # :nodoc:
162
- def _cast_association(name, value)
163
- return if value.is_a?(Array) && _association_type(name) != :has_many
164
- return value if value.nil?
165
-
166
- association_class = _association_class_name(name)
167
-
168
- case value
169
- when association_class
170
- value
171
- when Array
172
- value.collect {|i| _cast_association(name, i) }
173
- when Hash
174
- association_class.new(value)
175
- when NilClass
176
- nil
177
- else
178
- association_class.new({association_class.primary_key => value})
179
- end
180
- end
181
-
182
- # :nodoc:
183
- def _association_for(name)
184
- self.class._association_for(name)
185
- end
186
-
187
- # :nodoc:
188
- def _association_type(name)
189
- self.class._association_type(name)
190
- end
191
-
192
- # :nodoc:
193
- def _association_class_name(name)
194
- self.class._association_class_name(name)
195
- end
196
-
197
- # :nodoc:
198
- def _associations
199
- @associations ||= {}
200
- end
201
-
202
- end
203
- end
@@ -1,70 +0,0 @@
1
- module JSONAPI::Consumer::Resource
2
- module AttributesConcern
3
- extend ActiveSupport::Concern
4
-
5
- include ActiveModel::AttributeMethods
6
- include ActiveModel::Dirty
7
-
8
- included do
9
- attr_reader :attributes
10
- end
11
-
12
- def attributes=(attrs={})
13
- @attributes ||= {}
14
-
15
- return @attributes unless attrs.present?
16
- attrs.each do |key, value|
17
- set_attribute(key, value)
18
- end
19
- end
20
-
21
- def update_attributes(attrs={})
22
- self.attributes = attrs
23
- # FIXME save
24
- end
25
-
26
- def persisted?
27
- !self.to_param.blank?
28
- end
29
-
30
- def to_param
31
- attributes.fetch(primary_key, '').to_s
32
- end
33
-
34
- # def [](key)
35
- # read_attribute(key)
36
- # end
37
-
38
- # def []=(key, value)
39
- # set_attribute(key, value)
40
- # end
41
-
42
- alias :respond_to_without_attributes? :respond_to?
43
- def respond_to?(method, include_private_methods=false)
44
- if super
45
- true
46
- elsif !include_private_methods && super(method, true)
47
- # If we're here then we haven't found among non-private methods
48
- # but found among all methods. Which means that the given method is private.
49
- false
50
- else
51
- has_attribute?(method)
52
- end
53
- end
54
-
55
- private
56
-
57
- def read_attribute(name)
58
- attributes.fetch(name, nil)
59
- end
60
-
61
- def set_attribute(key, value)
62
- attributes[key.to_sym] = value
63
- end
64
-
65
- def has_attribute?(attr_name)
66
- attributes.has_key?(attr_name.to_sym)
67
- end
68
-
69
- end
70
- end
@@ -1,99 +0,0 @@
1
- module JSONAPI::Consumer
2
- module ConnectionConcern
3
- extend ActiveSupport::Concern
4
-
5
- module ClassMethods
6
- def parser_class
7
- @parser ||= Parser
8
- end
9
-
10
- def parse(response)
11
- parser = parser_class.new(self, response)
12
-
13
- if response.status && response.status == 204
14
- true
15
- else
16
- parser.build
17
- end
18
- end
19
-
20
- # :nodoc:
21
- def _run_request(query_object)
22
- parse(_connection.send(query_object.request_method, query_object.path, query_object.params, query_object.headers))
23
- end
24
-
25
- # :nodoc:
26
- def _connection(reload=false)
27
- self.connection = nil if !!reload
28
- self.connection ||= begin
29
- Faraday.new(url: self.host, ssl: self.ssl) do |conn|
30
- conn.request :json
31
- conn.request :request_headers, accept: "application/json"
32
-
33
- yield(conn) if block_given?
34
-
35
- conn.use Middleware::RequestTimeout
36
- conn.use Middleware::ParseJson
37
-
38
- conn.use Middleware::RaiseError
39
- conn.adapter Faraday.default_adapter
40
- end
41
-
42
- end
43
- end
44
- end
45
-
46
- def is_valid?
47
- errors.empty?
48
- end
49
-
50
- def save
51
- query = persisted? ?
52
- Query::Update.new(self.class, self.serializable_hash) :
53
- Query::Create.new(self.class, self.serializable_hash)
54
-
55
- results = run_request(query)
56
-
57
- if self.errors.empty?
58
- self.attributes = results.first.attributes
59
- true
60
- else
61
- false
62
- end
63
- end
64
-
65
- def destroy
66
- if run_request(Query::Delete.new(self.class, self.serializable_hash))
67
- self.attributes.clear
68
- true
69
- else
70
- false
71
- end
72
- end
73
-
74
- private
75
-
76
- # :nodoc:
77
- def run_request(*args)
78
- begin
79
- self.errors.clear
80
- request = self.class._run_request(*args)
81
- rescue JSONAPI::Consumer::Errors::BadRequest => e
82
- e.errors.map do |error|
83
- process_error(error.dup)
84
- end
85
- end
86
- end
87
-
88
- # :nodoc:
89
- def process_error(err)
90
- field = err.fetch('path', '')
91
- attr = field.match(/\A\/(\w+)\z/)
92
- if attr[1] && has_attribute?(attr[1])
93
- self.errors.add(attr[1].to_sym, err.fetch('detail', ''))
94
- else
95
- self.errors.add(:base, err.fetch('detail', ''))
96
- end
97
- end
98
- end
99
- end
@@ -1,28 +0,0 @@
1
- module JSONAPI::Consumer::Resource
2
- module FindersConcern
3
- extend ActiveSupport::Concern
4
-
5
- module ClassMethods
6
- def all(options={})
7
- _run_request(JSONAPI::Consumer::Query::Find.new(self, options))
8
- end
9
-
10
- def find(options)
11
- options = {self.primary_key => options} unless options.is_a?(Hash)
12
- _run_request(JSONAPI::Consumer::Query::Find.new(self, options))
13
- end
14
-
15
- def primary_key
16
- @primary_key ||= :id
17
- end
18
-
19
- def primary_key=(val)
20
- @primary_key = val.to_sym
21
- end
22
- end
23
-
24
- def primary_key
25
- self.class.primary_key
26
- end
27
- end
28
- end
@@ -1,28 +0,0 @@
1
- module JSONAPI::Consumer::Resource
2
- module ObjectBuildConcern
3
- extend ActiveSupport::Concern
4
-
5
- included do
6
- class_attribute :request_new_object_on_build
7
- end
8
-
9
- module ClassMethods
10
-
11
- # If class attribute `request_new_object_on_build`:
12
- #
13
- # True:
14
- # will send a request to `{path}/new` to get an attributes list
15
- #
16
- # False:
17
- # acts as an alias for `new`
18
- #
19
- def build
20
- if !!self.request_new_object_on_build
21
- _run_request(JSONAPI::Consumer::Query::New.new(self, {})).first
22
- else
23
- new
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,63 +0,0 @@
1
- module JSONAPI::Consumer::Resource
2
- module SerializerConcern
3
- extend ActiveSupport::Concern
4
-
5
- def serializable_hash(options={})
6
- @hash = persisted? ? attributes : attributes.except(self.class.primary_key)
7
-
8
- self.each_association do |name, association, options|
9
- @hash[:links] ||= {}
10
-
11
- if association.respond_to?(:each) or _association_type(name) == :has_many
12
- add_links(name, association, options)
13
- else
14
- add_link(name, association, options)
15
- end
16
- @hash.delete(:links) if remove_links?
17
- end
18
-
19
- @hash
20
- end
21
-
22
- def add_links(name, association, options)
23
- @hash[:links][name] ||= []
24
- @hash[:links][name] += (association || []).map do |obj|
25
- case obj.class
26
- when String, Integer
27
- obj
28
- else
29
- obj.to_param
30
- end
31
- end
32
- end
33
-
34
- def add_link(name, association, options)
35
- return if association.nil?
36
-
37
- @hash[:links][name] = case association.class
38
- when String, Integer
39
- association
40
- else
41
- association.to_param
42
- end
43
- end
44
-
45
- def to_json(options={})
46
- serializable_hash(options).to_json
47
- end
48
-
49
- private
50
-
51
- def remove_links?
52
- if persisted?
53
- false
54
- else # not persisted, new object
55
- if @hash[:links].length == 0 or @hash[:links].values.flatten.empty?
56
- true
57
- else
58
- false
59
- end
60
- end
61
- end
62
- end
63
- end
File without changes