json_api_client 1.5.2 → 1.22.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.
- checksums.yaml +5 -5
- data/README.md +206 -9
- data/lib/json_api_client/associations/base_association.rb +7 -0
- data/lib/json_api_client/associations/belongs_to.rb +11 -10
- data/lib/json_api_client/associations/has_many.rb +0 -8
- data/lib/json_api_client/associations/has_one.rb +6 -9
- data/lib/json_api_client/connection.rb +8 -3
- data/lib/json_api_client/error_collector.rb +19 -10
- data/lib/json_api_client/errors.rb +86 -17
- data/lib/json_api_client/helpers/associatable.rb +88 -0
- data/lib/json_api_client/helpers/dirty.rb +5 -1
- data/lib/json_api_client/helpers/dynamic_attributes.rb +19 -11
- data/lib/json_api_client/helpers.rb +1 -0
- data/lib/json_api_client/included_data.rb +24 -12
- data/lib/json_api_client/middleware/json_request.rb +16 -1
- data/lib/json_api_client/middleware/status.rb +32 -6
- data/lib/json_api_client/paginating/nested_param_paginator.rb +140 -0
- data/lib/json_api_client/paginating/paginator.rb +1 -1
- data/lib/json_api_client/paginating.rb +2 -1
- data/lib/json_api_client/query/builder.rb +77 -49
- data/lib/json_api_client/query/requestor.rb +21 -13
- data/lib/json_api_client/relationships/relations.rb +0 -1
- data/lib/json_api_client/request_params.rb +57 -0
- data/lib/json_api_client/resource.rb +196 -46
- data/lib/json_api_client/schema.rb +3 -3
- data/lib/json_api_client/utils.rb +26 -1
- data/lib/json_api_client/version.rb +1 -1
- data/lib/json_api_client.rb +2 -1
- metadata +44 -16
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module JsonApiClient
|
|
2
|
+
module Helpers
|
|
3
|
+
module Associatable
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
class_attribute :associations, instance_accessor: false
|
|
8
|
+
self.associations = []
|
|
9
|
+
attr_accessor :__cached_associations
|
|
10
|
+
attr_accessor :__belongs_to_params
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ClassMethods
|
|
14
|
+
def _define_association(attr_name, association_klass, options = {})
|
|
15
|
+
attr_name = attr_name.to_sym
|
|
16
|
+
association = association_klass.new(attr_name, self, options)
|
|
17
|
+
self.associations += [association]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def _define_relationship_methods(attr_name)
|
|
21
|
+
attr_name = attr_name.to_sym
|
|
22
|
+
|
|
23
|
+
define_method(attr_name) do
|
|
24
|
+
_cached_relationship(attr_name) do
|
|
25
|
+
relationship_definition = relationship_definition_for(attr_name)
|
|
26
|
+
return unless relationship_definition
|
|
27
|
+
relationship_data_for(attr_name, relationship_definition)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
define_method("#{attr_name}=") do |value|
|
|
32
|
+
_clear_cached_relationship(attr_name)
|
|
33
|
+
relationships.public_send("#{attr_name}=", value)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def belongs_to(attr_name, options = {})
|
|
38
|
+
_define_association(attr_name, JsonApiClient::Associations::BelongsTo::Association, options)
|
|
39
|
+
|
|
40
|
+
param = associations.last.param
|
|
41
|
+
define_method(param) do
|
|
42
|
+
_belongs_to_params[param]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
define_method(:"#{param}=") do |value|
|
|
46
|
+
_belongs_to_params[param] = value
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def has_many(attr_name, options = {})
|
|
51
|
+
_define_association(attr_name, JsonApiClient::Associations::HasMany::Association, options)
|
|
52
|
+
_define_relationship_methods(attr_name)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def has_one(attr_name, options = {})
|
|
56
|
+
_define_association(attr_name, JsonApiClient::Associations::HasOne::Association, options)
|
|
57
|
+
_define_relationship_methods(attr_name)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def _belongs_to_params
|
|
62
|
+
self.__belongs_to_params ||= {}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def _clear_belongs_to_params
|
|
66
|
+
self.__belongs_to_params = {}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def _cached_associations
|
|
70
|
+
self.__cached_associations ||= {}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def _clear_cached_relationships
|
|
74
|
+
self.__cached_associations = {}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def _clear_cached_relationship(attr_name)
|
|
78
|
+
_cached_associations.delete(attr_name)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def _cached_relationship(attr_name)
|
|
82
|
+
return _cached_associations[attr_name] if _cached_associations.has_key?(attr_name)
|
|
83
|
+
_cached_associations[attr_name] = yield
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -18,6 +18,10 @@ module JsonApiClient
|
|
|
18
18
|
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
def forget_change!(attr)
|
|
22
|
+
@changed_attributes.delete(attr.to_s)
|
|
23
|
+
end
|
|
24
|
+
|
|
21
25
|
def set_all_attributes_dirty
|
|
22
26
|
attributes.each do |k, v|
|
|
23
27
|
set_attribute_was(k, v)
|
|
@@ -68,4 +72,4 @@ module JsonApiClient
|
|
|
68
72
|
|
|
69
73
|
end
|
|
70
74
|
end
|
|
71
|
-
end
|
|
75
|
+
end
|
|
@@ -24,7 +24,7 @@ module JsonApiClient
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def respond_to_missing?(method, include_private = false)
|
|
27
|
-
if (method
|
|
27
|
+
if has_attribute?(method) || method.to_s.end_with?('=')
|
|
28
28
|
true
|
|
29
29
|
else
|
|
30
30
|
super
|
|
@@ -38,16 +38,14 @@ module JsonApiClient
|
|
|
38
38
|
protected
|
|
39
39
|
|
|
40
40
|
def method_missing(method, *args, &block)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if normalized_method
|
|
48
|
-
set_attribute(
|
|
49
|
-
elsif has_attribute?(method)
|
|
50
|
-
attributes[method]
|
|
41
|
+
if has_attribute?(method)
|
|
42
|
+
return attributes[method]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
normalized_method = safe_key_formatter.unformat(method.to_s)
|
|
46
|
+
|
|
47
|
+
if normalized_method.end_with?('=')
|
|
48
|
+
set_attribute(normalized_method[0..-2], args.first)
|
|
51
49
|
else
|
|
52
50
|
super
|
|
53
51
|
end
|
|
@@ -61,10 +59,20 @@ module JsonApiClient
|
|
|
61
59
|
attributes[name] = value
|
|
62
60
|
end
|
|
63
61
|
|
|
62
|
+
def safe_key_formatter
|
|
63
|
+
@safe_key_formatter ||= (key_formatter || DefaultKeyFormatter.new)
|
|
64
|
+
end
|
|
65
|
+
|
|
64
66
|
def key_formatter
|
|
65
67
|
self.class.respond_to?(:key_formatter) && self.class.key_formatter
|
|
66
68
|
end
|
|
67
69
|
|
|
70
|
+
class DefaultKeyFormatter
|
|
71
|
+
def unformat(method)
|
|
72
|
+
method.to_s
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
68
76
|
end
|
|
69
77
|
end
|
|
70
78
|
end
|
|
@@ -5,16 +5,29 @@ module JsonApiClient
|
|
|
5
5
|
def initialize(result_set, data)
|
|
6
6
|
record_class = result_set.record_class
|
|
7
7
|
grouped_data = data.group_by{|datum| datum["type"]}
|
|
8
|
-
|
|
8
|
+
grouped_included_set = grouped_data.each_with_object({}) do |(type, records), h|
|
|
9
9
|
klass = Utils.compute_type(record_class, record_class.key_formatter.unformat(type).singularize.classify)
|
|
10
|
-
h[type] = records.map do |
|
|
11
|
-
params = klass.parser.parameters_from_resource(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
end
|
|
16
|
-
|
|
10
|
+
h[type] = records.map do |record|
|
|
11
|
+
params = klass.parser.parameters_from_resource(record)
|
|
12
|
+
klass.load(params).tap do |resource|
|
|
13
|
+
resource.last_result_set = result_set
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if record_class.search_included_in_result_set
|
|
19
|
+
# deep_merge overrides the nested Arrays o_O
|
|
20
|
+
# {a: [1,2]}.deep_merge(a: [3,4]) # => {a: [3,4]}
|
|
21
|
+
grouped_included_set.merge!(result_set.group_by(&:type)) do |_, resources1, resources2|
|
|
22
|
+
resources1 + resources2
|
|
23
|
+
end
|
|
17
24
|
end
|
|
25
|
+
|
|
26
|
+
grouped_included_set.each do |type, resources|
|
|
27
|
+
grouped_included_set[type] = resources.index_by { |resource| resource.attributes[:id] }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
@data = grouped_included_set
|
|
18
31
|
end
|
|
19
32
|
|
|
20
33
|
def data_for(method_name, definition)
|
|
@@ -23,9 +36,7 @@ module JsonApiClient
|
|
|
23
36
|
|
|
24
37
|
if data.is_a?(Array)
|
|
25
38
|
# has_many link
|
|
26
|
-
data.map
|
|
27
|
-
record_for(link_def)
|
|
28
|
-
end
|
|
39
|
+
data.map(&method(:record_for)).compact
|
|
29
40
|
else
|
|
30
41
|
# has_one link
|
|
31
42
|
record_for(data)
|
|
@@ -40,7 +51,8 @@ module JsonApiClient
|
|
|
40
51
|
|
|
41
52
|
# should return a resource record of some type for this linked document
|
|
42
53
|
def record_for(link_def)
|
|
43
|
-
data[link_def["type"]]
|
|
54
|
+
record = data[link_def["type"]]
|
|
55
|
+
record[link_def["id"]] if record
|
|
44
56
|
end
|
|
45
57
|
end
|
|
46
58
|
end
|
|
@@ -2,10 +2,25 @@ module JsonApiClient
|
|
|
2
2
|
module Middleware
|
|
3
3
|
class JsonRequest < Faraday::Middleware
|
|
4
4
|
def call(environment)
|
|
5
|
+
accept_header = update_accept_header(environment[:request_headers])
|
|
6
|
+
|
|
5
7
|
environment[:request_headers]["Content-Type"] = 'application/vnd.api+json'
|
|
6
|
-
environment[:request_headers]["Accept"] =
|
|
8
|
+
environment[:request_headers]["Accept"] = accept_header
|
|
7
9
|
@app.call(environment)
|
|
8
10
|
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def update_accept_header(headers)
|
|
15
|
+
return 'application/vnd.api+json' if headers["Accept"].nil?
|
|
16
|
+
accept_params = headers["Accept"].split(",")
|
|
17
|
+
|
|
18
|
+
unless accept_params.include?('application/vnd.api+json')
|
|
19
|
+
accept_params.unshift('application/vnd.api+json')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
accept_params.join(",")
|
|
23
|
+
end
|
|
9
24
|
end
|
|
10
25
|
end
|
|
11
26
|
end
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
module JsonApiClient
|
|
2
2
|
module Middleware
|
|
3
3
|
class Status < Faraday::Middleware
|
|
4
|
+
def initialize(app, options)
|
|
5
|
+
super(app)
|
|
6
|
+
@options = options
|
|
7
|
+
end
|
|
8
|
+
|
|
4
9
|
def call(environment)
|
|
5
10
|
@app.call(environment).on_complete do |env|
|
|
6
11
|
handle_status(env[:status], env)
|
|
@@ -11,13 +16,20 @@ module JsonApiClient
|
|
|
11
16
|
handle_status(code, env)
|
|
12
17
|
end
|
|
13
18
|
end
|
|
14
|
-
rescue Faraday::ConnectionFailed, Faraday::TimeoutError
|
|
15
|
-
raise Errors::ConnectionError,
|
|
19
|
+
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
|
|
20
|
+
raise Errors::ConnectionError.new environment, e.to_s
|
|
16
21
|
end
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def custom_handler_for(code)
|
|
26
|
+
@options.fetch(:custom_handlers, {})[code]
|
|
27
|
+
end
|
|
19
28
|
|
|
20
29
|
def handle_status(code, env)
|
|
30
|
+
custom_handler = custom_handler_for(code)
|
|
31
|
+
return custom_handler.call(env) if custom_handler.present?
|
|
32
|
+
|
|
21
33
|
case code
|
|
22
34
|
when 200..399
|
|
23
35
|
when 401
|
|
@@ -25,12 +37,26 @@ module JsonApiClient
|
|
|
25
37
|
when 403
|
|
26
38
|
raise Errors::AccessDenied, env
|
|
27
39
|
when 404
|
|
28
|
-
raise Errors::NotFound, env
|
|
40
|
+
raise Errors::NotFound, env
|
|
41
|
+
when 408
|
|
42
|
+
raise Errors::RequestTimeout, env
|
|
29
43
|
when 409
|
|
30
44
|
raise Errors::Conflict, env
|
|
45
|
+
when 422
|
|
46
|
+
# Allow to proceed as resource errors will be populated
|
|
47
|
+
when 429
|
|
48
|
+
raise Errors::TooManyRequests, env
|
|
31
49
|
when 400..499
|
|
32
|
-
|
|
33
|
-
when 500
|
|
50
|
+
raise Errors::ClientError, env
|
|
51
|
+
when 500
|
|
52
|
+
raise Errors::InternalServerError, env
|
|
53
|
+
when 502
|
|
54
|
+
raise Errors::BadGateway, env
|
|
55
|
+
when 503
|
|
56
|
+
raise Errors::ServiceUnavailable, env
|
|
57
|
+
when 504
|
|
58
|
+
raise Errors::GatewayTimeout, env
|
|
59
|
+
when 501..599
|
|
34
60
|
raise Errors::ServerError, env
|
|
35
61
|
else
|
|
36
62
|
raise Errors::UnexpectedStatus.new(code, env[:url])
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
module JsonApiClient
|
|
2
|
+
module Paginating
|
|
3
|
+
# An alternate, more consistent Paginator that always wraps
|
|
4
|
+
# pagination query string params in a top-level wrapper_name,
|
|
5
|
+
# e.g. page[offset]=2, page[limit]=10.
|
|
6
|
+
class NestedParamPaginator
|
|
7
|
+
DEFAULT_WRAPPER_NAME = "page".freeze
|
|
8
|
+
DEFAULT_PAGE_PARAM = "page".freeze
|
|
9
|
+
DEFAULT_PER_PAGE_PARAM = "per_page".freeze
|
|
10
|
+
|
|
11
|
+
# Define class accessors as methods to enforce standard way
|
|
12
|
+
# of defining pagination related query string params.
|
|
13
|
+
class << self
|
|
14
|
+
|
|
15
|
+
def wrapper_name
|
|
16
|
+
@_wrapper_name ||= DEFAULT_WRAPPER_NAME
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def wrapper_name=(param = DEFAULT_WRAPPER_NAME)
|
|
20
|
+
raise ArgumentError, "don't wrap wrapper_name" unless valid_param?(param)
|
|
21
|
+
|
|
22
|
+
@_wrapper_name = param.to_s
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def page_param
|
|
26
|
+
@_page_param ||= DEFAULT_PAGE_PARAM
|
|
27
|
+
"#{wrapper_name}[#{@_page_param}]"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def page_param=(param = DEFAULT_PAGE_PARAM)
|
|
31
|
+
raise ArgumentError, "don't wrap page_param" unless valid_param?(param)
|
|
32
|
+
|
|
33
|
+
@_page_param = param.to_s
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def per_page_param
|
|
37
|
+
@_per_page_param ||= DEFAULT_PER_PAGE_PARAM
|
|
38
|
+
"#{wrapper_name}[#{@_per_page_param}]"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def per_page_param=(param = DEFAULT_PER_PAGE_PARAM)
|
|
42
|
+
raise ArgumentError, "don't wrap per_page_param" unless valid_param?(param)
|
|
43
|
+
|
|
44
|
+
@_per_page_param = param
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def valid_param?(param)
|
|
50
|
+
!(param.nil? || param.to_s.include?("[") || param.to_s.include?("]"))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
attr_reader :params, :result_set, :links
|
|
56
|
+
|
|
57
|
+
def initialize(result_set, data)
|
|
58
|
+
@params = params_for_uri(result_set.uri)
|
|
59
|
+
@result_set = result_set
|
|
60
|
+
@links = data["links"]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def next
|
|
64
|
+
result_set.links.fetch_link("next")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def prev
|
|
68
|
+
result_set.links.fetch_link("prev")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def first
|
|
72
|
+
result_set.links.fetch_link("first")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def last
|
|
76
|
+
result_set.links.fetch_link("last")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def total_pages
|
|
80
|
+
if links["last"]
|
|
81
|
+
uri = result_set.links.link_url_for("last")
|
|
82
|
+
last_params = params_for_uri(uri)
|
|
83
|
+
last_params.fetch(page_param, &method(:current_page)).to_i
|
|
84
|
+
else
|
|
85
|
+
current_page
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# this is an estimate, not necessarily an exact count
|
|
90
|
+
def total_entries
|
|
91
|
+
per_page * total_pages
|
|
92
|
+
end
|
|
93
|
+
def total_count; total_entries; end
|
|
94
|
+
|
|
95
|
+
def offset
|
|
96
|
+
per_page * (current_page - 1)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def per_page
|
|
100
|
+
params.fetch(per_page_param) do
|
|
101
|
+
result_set.length
|
|
102
|
+
end.to_i
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def current_page
|
|
106
|
+
params.fetch(page_param, 1).to_i
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def out_of_bounds?
|
|
110
|
+
current_page > total_pages
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def previous_page
|
|
114
|
+
current_page > 1 ? (current_page - 1) : nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def next_page
|
|
118
|
+
current_page < total_pages ? (current_page + 1) : nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def page_param
|
|
122
|
+
self.class.page_param
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def per_page_param
|
|
126
|
+
self.class.per_page_param
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
alias limit_value per_page
|
|
130
|
+
|
|
131
|
+
protected
|
|
132
|
+
|
|
133
|
+
def params_for_uri(uri)
|
|
134
|
+
return {} unless uri
|
|
135
|
+
uri = Addressable::URI.parse(uri)
|
|
136
|
+
( uri.query_values || {} ).with_indifferent_access
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'active_support/all'
|
|
2
|
+
|
|
1
3
|
module JsonApiClient
|
|
2
4
|
module Query
|
|
3
5
|
class Builder
|
|
@@ -5,60 +7,55 @@ module JsonApiClient
|
|
|
5
7
|
attr_reader :klass
|
|
6
8
|
delegate :key_formatter, to: :klass
|
|
7
9
|
|
|
8
|
-
def initialize(klass)
|
|
9
|
-
@klass
|
|
10
|
-
@primary_key
|
|
11
|
-
@pagination_params = {}
|
|
12
|
-
@path_params
|
|
13
|
-
@additional_params = {}
|
|
14
|
-
@filters
|
|
15
|
-
@includes
|
|
16
|
-
@orders
|
|
17
|
-
@fields
|
|
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, [] )
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def where(conditions = {})
|
|
21
23
|
# pull out any path params here
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 )
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
def order(*args)
|
|
28
|
-
|
|
29
|
-
self
|
|
31
|
+
_new_scope( orders: parse_orders(*args) )
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def includes(*tables)
|
|
33
|
-
|
|
34
|
-
self
|
|
35
|
+
_new_scope( includes: parse_related_links(*tables) )
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def select(*fields)
|
|
38
|
-
|
|
39
|
-
self
|
|
39
|
+
_new_scope( fields: parse_fields(*fields) )
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def paginate(conditions = {})
|
|
43
|
-
scope =
|
|
43
|
+
scope = _new_scope
|
|
44
44
|
scope = scope.page(conditions[:page]) if conditions[:page]
|
|
45
45
|
scope = scope.per(conditions[:per_page]) if conditions[:per_page]
|
|
46
46
|
scope
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def page(number)
|
|
50
|
-
|
|
51
|
-
self
|
|
50
|
+
_new_scope( pagination_params: { klass.paginator.page_param => number || 1 } )
|
|
52
51
|
end
|
|
53
52
|
|
|
54
53
|
def per(size)
|
|
55
|
-
|
|
56
|
-
self
|
|
54
|
+
_new_scope( pagination_params: { klass.paginator.per_page_param => size } )
|
|
57
55
|
end
|
|
58
56
|
|
|
59
57
|
def with_params(more_params)
|
|
60
|
-
|
|
61
|
-
self
|
|
58
|
+
_new_scope( additional_params: more_params )
|
|
62
59
|
end
|
|
63
60
|
|
|
64
61
|
def first
|
|
@@ -69,8 +66,12 @@ module JsonApiClient
|
|
|
69
66
|
paginate(page: 1, per_page: 1).pages.last.to_a.last
|
|
70
67
|
end
|
|
71
68
|
|
|
72
|
-
def build
|
|
73
|
-
klass.new(
|
|
69
|
+
def build(attrs = {})
|
|
70
|
+
klass.new @path_params.merge(attrs.with_indifferent_access)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def create(attrs = {})
|
|
74
|
+
klass.create @path_params.merge(attrs.with_indifferent_access)
|
|
74
75
|
end
|
|
75
76
|
|
|
76
77
|
def params
|
|
@@ -85,27 +86,63 @@ module JsonApiClient
|
|
|
85
86
|
end
|
|
86
87
|
|
|
87
88
|
def to_a
|
|
88
|
-
@to_a ||=
|
|
89
|
+
@to_a ||= _fetch
|
|
89
90
|
end
|
|
90
91
|
alias all to_a
|
|
91
92
|
|
|
92
93
|
def find(args = {})
|
|
94
|
+
if klass.raise_on_blank_find_param && args.blank?
|
|
95
|
+
raise Errors::NotFound, nil, 'blank .find param'
|
|
96
|
+
end
|
|
97
|
+
|
|
93
98
|
case args
|
|
94
99
|
when Hash
|
|
95
|
-
where(args)
|
|
100
|
+
scope = where(args)
|
|
96
101
|
else
|
|
97
|
-
|
|
102
|
+
scope = _new_scope( primary_key: args )
|
|
98
103
|
end
|
|
99
104
|
|
|
100
|
-
|
|
105
|
+
scope._fetch
|
|
101
106
|
end
|
|
102
107
|
|
|
103
108
|
def method_missing(method_name, *args, &block)
|
|
104
109
|
to_a.send(method_name, *args, &block)
|
|
105
110
|
end
|
|
106
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
|
+
|
|
107
132
|
private
|
|
108
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.deep_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
|
+
|
|
109
146
|
def path_params
|
|
110
147
|
@path_params.empty? ? {} : {path: @path_params}
|
|
111
148
|
end
|
|
@@ -123,7 +160,13 @@ module JsonApiClient
|
|
|
123
160
|
end
|
|
124
161
|
|
|
125
162
|
def pagination_params
|
|
126
|
-
|
|
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
|
|
127
170
|
end
|
|
128
171
|
|
|
129
172
|
def includes_params
|
|
@@ -159,22 +202,7 @@ module JsonApiClient
|
|
|
159
202
|
end
|
|
160
203
|
|
|
161
204
|
def parse_related_links(*tables)
|
|
162
|
-
|
|
163
|
-
case table
|
|
164
|
-
when Hash
|
|
165
|
-
table.map do |k, v|
|
|
166
|
-
parse_related_links(*v).map do |sub|
|
|
167
|
-
"#{k}.#{sub}"
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
when Array
|
|
171
|
-
table.map do |v|
|
|
172
|
-
parse_related_links(*v)
|
|
173
|
-
end
|
|
174
|
-
else
|
|
175
|
-
key_formatter.format(table)
|
|
176
|
-
end
|
|
177
|
-
end.flatten
|
|
205
|
+
Utils.parse_includes(klass, *tables)
|
|
178
206
|
end
|
|
179
207
|
|
|
180
208
|
def parse_orders(*args)
|