jsonapi-consumer 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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