carwow-json_api_client 1.19.0

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