carwow-json_api_client 1.19.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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +706 -0
  4. data/Rakefile +32 -0
  5. data/lib/json_api_client.rb +30 -0
  6. data/lib/json_api_client/associations.rb +8 -0
  7. data/lib/json_api_client/associations/base_association.rb +33 -0
  8. data/lib/json_api_client/associations/belongs_to.rb +31 -0
  9. data/lib/json_api_client/associations/has_many.rb +8 -0
  10. data/lib/json_api_client/associations/has_one.rb +16 -0
  11. data/lib/json_api_client/connection.rb +41 -0
  12. data/lib/json_api_client/error_collector.rb +91 -0
  13. data/lib/json_api_client/errors.rb +107 -0
  14. data/lib/json_api_client/formatter.rb +145 -0
  15. data/lib/json_api_client/helpers.rb +9 -0
  16. data/lib/json_api_client/helpers/associatable.rb +88 -0
  17. data/lib/json_api_client/helpers/callbacks.rb +27 -0
  18. data/lib/json_api_client/helpers/dirty.rb +75 -0
  19. data/lib/json_api_client/helpers/dynamic_attributes.rb +78 -0
  20. data/lib/json_api_client/helpers/uri.rb +9 -0
  21. data/lib/json_api_client/implementation.rb +12 -0
  22. data/lib/json_api_client/included_data.rb +58 -0
  23. data/lib/json_api_client/linking.rb +6 -0
  24. data/lib/json_api_client/linking/links.rb +22 -0
  25. data/lib/json_api_client/linking/top_level_links.rb +39 -0
  26. data/lib/json_api_client/meta_data.rb +19 -0
  27. data/lib/json_api_client/middleware.rb +7 -0
  28. data/lib/json_api_client/middleware/json_request.rb +26 -0
  29. data/lib/json_api_client/middleware/parse_json.rb +31 -0
  30. data/lib/json_api_client/middleware/status.rb +67 -0
  31. data/lib/json_api_client/paginating.rb +6 -0
  32. data/lib/json_api_client/paginating/nested_param_paginator.rb +140 -0
  33. data/lib/json_api_client/paginating/paginator.rb +89 -0
  34. data/lib/json_api_client/parsers.rb +5 -0
  35. data/lib/json_api_client/parsers/parser.rb +102 -0
  36. data/lib/json_api_client/query.rb +6 -0
  37. data/lib/json_api_client/query/builder.rb +239 -0
  38. data/lib/json_api_client/query/requestor.rb +73 -0
  39. data/lib/json_api_client/relationships.rb +6 -0
  40. data/lib/json_api_client/relationships/relations.rb +55 -0
  41. data/lib/json_api_client/relationships/top_level_relations.rb +30 -0
  42. data/lib/json_api_client/request_params.rb +57 -0
  43. data/lib/json_api_client/resource.rb +643 -0
  44. data/lib/json_api_client/result_set.rb +25 -0
  45. data/lib/json_api_client/schema.rb +154 -0
  46. data/lib/json_api_client/utils.rb +48 -0
  47. data/lib/json_api_client/version.rb +3 -0
  48. metadata +213 -0
@@ -0,0 +1,6 @@
1
+ module JsonApiClient
2
+ module Query
3
+ autoload :Builder, 'json_api_client/query/builder'
4
+ autoload :Requestor, 'json_api_client/query/requestor'
5
+ end
6
+ end
@@ -0,0 +1,239 @@
1
+ require 'active_support/all'
2
+
3
+ module JsonApiClient
4
+ module Query
5
+ class Builder
6
+
7
+ attr_reader :klass
8
+ delegate :key_formatter, to: :klass
9
+
10
+ def initialize(klass, opts = {})
11
+ @klass = klass
12
+ @primary_key = opts.fetch( :primary_key, nil )
13
+ @pagination_params = opts.fetch( :pagination_params, {} )
14
+ @path_params = opts.fetch( :path_params, {} )
15
+ @additional_params = opts.fetch( :additional_params, {} )
16
+ @filters = opts.fetch( :filters, {} )
17
+ @includes = opts.fetch( :includes, [] )
18
+ @orders = opts.fetch( :orders, [] )
19
+ @fields = opts.fetch( :fields, [] )
20
+ end
21
+
22
+ def where(conditions = {})
23
+ # pull out any path params here
24
+ path_conditions = conditions.slice(*klass.prefix_params)
25
+ unpathed_conditions = conditions.except(*klass.prefix_params)
26
+
27
+ _new_scope( path_params: path_conditions, filters: unpathed_conditions )
28
+ end
29
+
30
+ def order(*args)
31
+ _new_scope( orders: parse_orders(*args) )
32
+ end
33
+
34
+ def includes(*tables)
35
+ _new_scope( includes: parse_related_links(*tables) )
36
+ end
37
+
38
+ def select(*fields)
39
+ _new_scope( fields: parse_fields(*fields) )
40
+ end
41
+
42
+ def paginate(conditions = {})
43
+ scope = _new_scope
44
+ scope = scope.page(conditions[:page]) if conditions[:page]
45
+ scope = scope.per(conditions[:per_page]) if conditions[:per_page]
46
+ scope
47
+ end
48
+
49
+ def page(number)
50
+ _new_scope( pagination_params: { klass.paginator.page_param => number || 1 } )
51
+ end
52
+
53
+ def per(size)
54
+ _new_scope( pagination_params: { klass.paginator.per_page_param => size } )
55
+ end
56
+
57
+ def with_params(more_params)
58
+ _new_scope( additional_params: more_params )
59
+ end
60
+
61
+ def first
62
+ paginate(page: 1, per_page: 1).to_a.first
63
+ end
64
+
65
+ def last
66
+ paginate(page: 1, per_page: 1).pages.last.to_a.last
67
+ end
68
+
69
+ def build(attrs = {})
70
+ klass.new @path_params.merge(attrs.symbolize_keys)
71
+ end
72
+
73
+ def create(attrs = {})
74
+ klass.create @path_params.merge(attrs.symbolize_keys)
75
+ end
76
+
77
+ def params
78
+ filter_params
79
+ .merge(pagination_params)
80
+ .merge(includes_params)
81
+ .merge(order_params)
82
+ .merge(select_params)
83
+ .merge(primary_key_params)
84
+ .merge(path_params)
85
+ .merge(additional_params)
86
+ end
87
+
88
+ def to_a
89
+ @to_a ||= _fetch
90
+ end
91
+ alias all to_a
92
+
93
+ def find(args = {})
94
+ if klass.raise_on_blank_find_param && args.blank?
95
+ raise Errors::NotFound, 'blank .find param'
96
+ end
97
+
98
+ case args
99
+ when Hash
100
+ scope = where(args)
101
+ else
102
+ scope = _new_scope( primary_key: args )
103
+ end
104
+
105
+ scope._fetch
106
+ end
107
+
108
+ def method_missing(method_name, *args, &block)
109
+ to_a.send(method_name, *args, &block)
110
+ end
111
+
112
+ def hash
113
+ [
114
+ klass,
115
+ params
116
+ ].hash
117
+ end
118
+
119
+ def ==(other)
120
+ return false unless other.is_a?(self.class)
121
+
122
+ hash == other.hash
123
+ end
124
+ alias_method :eql?, :==
125
+
126
+ protected
127
+
128
+ def _fetch
129
+ klass.requestor.get(params)
130
+ end
131
+
132
+ private
133
+
134
+ def _new_scope( opts = {} )
135
+ self.class.new( @klass,
136
+ primary_key: opts.fetch( :primary_key, @primary_key ),
137
+ pagination_params: @pagination_params.merge( opts.fetch( :pagination_params, {} ) ),
138
+ path_params: @path_params.merge( opts.fetch( :path_params, {} ) ),
139
+ additional_params: @additional_params.merge( opts.fetch( :additional_params, {} ) ),
140
+ filters: @filters.merge( opts.fetch( :filters, {} ) ),
141
+ includes: @includes + opts.fetch( :includes, [] ),
142
+ orders: @orders + opts.fetch( :orders, [] ),
143
+ fields: @fields + opts.fetch( :fields, [] ) )
144
+ end
145
+
146
+ def path_params
147
+ @path_params.empty? ? {} : {path: @path_params}
148
+ end
149
+
150
+ def additional_params
151
+ @additional_params
152
+ end
153
+
154
+ def primary_key_params
155
+ return {} unless @primary_key
156
+
157
+ @primary_key.is_a?(Array) ?
158
+ {klass.primary_key.to_s.pluralize.to_sym => @primary_key.join(",")} :
159
+ {klass.primary_key => @primary_key}
160
+ end
161
+
162
+ def pagination_params
163
+ if klass.paginator.ancestors.include?(Paginating::Paginator)
164
+ # Original Paginator inconsistently wraps pagination params here. Keeping
165
+ # default behavior for now so as not to break backward compatibility.
166
+ @pagination_params.empty? ? {} : {page: @pagination_params}
167
+ else
168
+ @pagination_params
169
+ end
170
+ end
171
+
172
+ def includes_params
173
+ @includes.empty? ? {} : {include: @includes.join(",")}
174
+ end
175
+
176
+ def filter_params
177
+ @filters.empty? ? {} : {filter: @filters}
178
+ end
179
+
180
+ def order_params
181
+ @orders.empty? ? {} : {sort: @orders.join(",")}
182
+ end
183
+
184
+ def select_params
185
+ if @fields.empty?
186
+ {}
187
+ else
188
+ field_result = Hash.new { |h,k| h[k] = [] }
189
+ @fields.each do |field|
190
+ if field.is_a? Hash
191
+ field.each do |k,v|
192
+ field_result[k.to_s] << v
193
+ field_result[k.to_s] = field_result[k.to_s].flatten
194
+ end
195
+ else
196
+ field_result[klass.table_name] << field
197
+ end
198
+ end
199
+ field_result.each { |k,v| field_result[k] = v.join(',') }
200
+ {fields: field_result}
201
+ end
202
+ end
203
+
204
+ def parse_related_links(*tables)
205
+ Utils.parse_includes(klass, *tables)
206
+ end
207
+
208
+ def parse_orders(*args)
209
+ args.map do |arg|
210
+ case arg
211
+ when Hash
212
+ arg.map do |k, v|
213
+ operator = (v == :desc ? "-" : "")
214
+ "#{operator}#{k}"
215
+ end
216
+ else
217
+ "#{arg}"
218
+ end
219
+ end.flatten
220
+ end
221
+
222
+ def parse_fields(*fields)
223
+ fields = fields.split(',') if fields.is_a? String
224
+ fields.map do |field|
225
+ case field
226
+ when Hash
227
+ field.each do |k,v|
228
+ field[k] = parse_fields(v)
229
+ end
230
+ field
231
+ else
232
+ Array(field).flatten.map { |i| i.to_s.split(",") }.flatten.map(&:strip)
233
+ end
234
+ end.flatten
235
+ end
236
+
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,73 @@
1
+ module JsonApiClient
2
+ module Query
3
+ class Requestor
4
+ extend Forwardable
5
+ include Helpers::URI
6
+
7
+ def initialize(klass)
8
+ @klass = klass
9
+ end
10
+
11
+ # expects a record
12
+ def create(record)
13
+ request(
14
+ :post,
15
+ klass.path(record.path_attributes),
16
+ body: { data: record.as_json_api },
17
+ params: record.request_params.to_params
18
+ )
19
+ end
20
+
21
+ def update(record)
22
+ request(
23
+ :patch,
24
+ resource_path(record.path_attributes),
25
+ body: { data: record.as_json_api },
26
+ params: record.request_params.to_params
27
+ )
28
+ end
29
+
30
+ def get(params = {})
31
+ path = resource_path(params)
32
+ params.delete(klass.primary_key)
33
+ request(:get, path, params: params)
34
+ end
35
+
36
+ def destroy(record)
37
+ request(:delete, resource_path(record.path_attributes))
38
+ end
39
+
40
+ def linked(path)
41
+ request(:get, path)
42
+ end
43
+
44
+ def custom(method_name, options, params)
45
+ path = resource_path(params)
46
+ params.delete(klass.primary_key)
47
+ path = File.join(path, method_name.to_s)
48
+ request_method = options.fetch(:request_method, :get).to_sym
49
+ query_params, body_params = [:get, :delete].include?(request_method) ? [params, nil] : [nil, params]
50
+ request(request_method, path, params: query_params, body: body_params)
51
+ end
52
+
53
+ protected
54
+
55
+ attr_reader :klass
56
+ def_delegators :klass, :connection
57
+
58
+ def resource_path(parameters)
59
+ if resource_id = parameters[klass.primary_key]
60
+ File.join(klass.path(parameters), encode_part(resource_id))
61
+ else
62
+ klass.path(parameters)
63
+ end
64
+ end
65
+
66
+ def request(type, path, params: nil, body: nil)
67
+ response = connection.run(type, path, params: params, body: body, headers: klass.custom_headers)
68
+ klass.parser.parse(klass, response)
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,6 @@
1
+ module JsonApiClient
2
+ module Relationships
3
+ autoload :Relations, "json_api_client/relationships/relations"
4
+ autoload :TopLevelRelations, "json_api_client/relationships/top_level_relations"
5
+ end
6
+ end
@@ -0,0 +1,55 @@
1
+ module JsonApiClient
2
+ module Relationships
3
+ class Relations
4
+ include Helpers::DynamicAttributes
5
+ include Helpers::Dirty
6
+ include ActiveModel::Serialization
7
+
8
+ attr_reader :record_class
9
+ delegate :key_formatter, to: :record_class
10
+
11
+ def initialize(record_class, relations)
12
+ @record_class = record_class
13
+ self.attributes = relations
14
+ end
15
+
16
+ def present?
17
+ attributes.present?
18
+ end
19
+
20
+ def as_json_api
21
+ Hash[attributes_for_serialization.map do |k, v|
22
+ [k, v.slice("data")] if v.has_key?("data")
23
+ end.compact]
24
+ end
25
+
26
+ def as_json
27
+ Hash[attributes.map do |k, v|
28
+ [k, v.slice("data")] if v.has_key?("data")
29
+ end.compact]
30
+ end
31
+
32
+ def attributes_for_serialization
33
+ attributes.slice(*changed)
34
+ end
35
+
36
+ protected
37
+
38
+ def set_attribute(name, value)
39
+ value = case value
40
+ when JsonApiClient::Resource
41
+ {data: value.as_relation}
42
+ when Array
43
+ {data: value.map(&:as_relation)}
44
+ when NilClass
45
+ {data: nil}
46
+ else
47
+ value
48
+ end
49
+ attribute_will_change!(name) if value != attributes[name]
50
+ attributes[name] = value
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,30 @@
1
+ module JsonApiClient
2
+ module Relationships
3
+ class TopLevelRelations
4
+
5
+ attr_reader :relations, :record_class
6
+
7
+ def initialize(record_class, relations)
8
+ @relations = relations
9
+ @record_class = record_class
10
+ end
11
+
12
+ def respond_to_missing?(method, include_private = false)
13
+ relations.has_key?(method.to_s) || super
14
+ end
15
+
16
+ def method_missing(method, *args)
17
+ if respond_to_missing?(method)
18
+ fetch_relation(method)
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ def fetch_relation(relation_name)
25
+ link_definition = relations.fetch(relation_name.to_s)
26
+ record_class.requestor.linked(link_definition)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,57 @@
1
+ module JsonApiClient
2
+ class RequestParams
3
+ attr_reader :klass, :includes, :fields
4
+
5
+ def initialize(klass, includes: [], fields: {})
6
+ @klass = klass
7
+ @includes = includes
8
+ @fields = fields
9
+ end
10
+
11
+ def add_includes(includes)
12
+ Utils.parse_includes(klass, *includes).each do |name|
13
+ name = name.to_sym
14
+ self.includes.push(name) unless self.includes.include?(name)
15
+ end
16
+ end
17
+
18
+ def reset_includes!
19
+ @includes = []
20
+ end
21
+
22
+ def set_fields(type, field_names)
23
+ self.fields[type.to_sym] = field_names.map(&:to_sym)
24
+ end
25
+
26
+ def remove_fields(type)
27
+ self.fields.delete(type.to_sym)
28
+ end
29
+
30
+ def field_types
31
+ self.fields.keys
32
+ end
33
+
34
+ def clear
35
+ reset_includes!
36
+ @fields = {}
37
+ end
38
+
39
+ def to_params
40
+ return nil if field_types.empty? && includes.empty?
41
+ parsed_fields.merge(parsed_includes)
42
+ end
43
+
44
+ private
45
+
46
+ def parsed_includes
47
+ return {} if includes.empty?
48
+ {include: includes.join(",")}
49
+ end
50
+
51
+ def parsed_fields
52
+ return {} if field_types.empty?
53
+ {fields: fields.map { |type, names| [type, names.join(",")] }.to_h}
54
+ end
55
+
56
+ end
57
+ end