any_query 0.1.1
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 +7 -0
- data/lib/any_query/adapters/base.rb +173 -0
- data/lib/any_query/adapters/csv.rb +61 -0
- data/lib/any_query/adapters/fixed_length.rb +71 -0
- data/lib/any_query/adapters/http.rb +213 -0
- data/lib/any_query/adapters/sql.rb +100 -0
- data/lib/any_query/adapters.rb +13 -0
- data/lib/any_query/config.rb +12 -0
- data/lib/any_query/field.rb +7 -0
- data/lib/any_query/query.rb +70 -0
- data/lib/any_query/version.rb +5 -0
- data/lib/any_query.rb +58 -0
- data/spec/any_query/csv_spec.rb +44 -0
- data/spec/any_query/fixed_length_spec.rb +47 -0
- data/spec/any_query/http_spec.rb +161 -0
- data/spec/any_query/sql_spec.rb +58 -0
- data/spec/fixtures/sample.csv +3 -0
- data/spec/fixtures/sample.txt +2 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/schema.rb +146 -0
- metadata +137 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4acc0ba4d4783462b9c1d7b985395b44ff91f857c519252379d0a9f4f2bbcc63
|
4
|
+
data.tar.gz: 71b6ed3cbceef45d8a4f696075faa680249e28b995bf3ea1406416c7b0be6334
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 519287d516d95e87749cab7de90425622bab52466f5bc08de5c721bdab167222299452da647bb9d6e746ed24f28c1e3f5b3e07470f579326aa6db68450ec8e7f
|
7
|
+
data.tar.gz: bfadab83910a87cb40c0948ff5e71a7a4ce634b561e0295ffacaf5cc8919664d4305fdea553f1e724128cf6773b06506b0ca353b8caa902fb12e34a67215b5fd
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnyQuery
|
4
|
+
module Adapters
|
5
|
+
# @api private
|
6
|
+
class Base
|
7
|
+
def initialize(config)
|
8
|
+
@config = config.to_h
|
9
|
+
end
|
10
|
+
|
11
|
+
def load(model, select:, joins:, where:, limit:)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_single(model, id, joins)
|
16
|
+
load(model, select: [], joins:, where: [{ id: }], limit: 1).first
|
17
|
+
end
|
18
|
+
|
19
|
+
def instantiate_model(model, record)
|
20
|
+
instance = model.new
|
21
|
+
attrs = instance.instance_variable_get(:@attributes)
|
22
|
+
record.each do |key, value|
|
23
|
+
attrs.send("#{key}=", value)
|
24
|
+
end
|
25
|
+
instance
|
26
|
+
end
|
27
|
+
|
28
|
+
def resolve_path(data, path)
|
29
|
+
if path.is_a?(Proc)
|
30
|
+
path.call(data)
|
31
|
+
elsif path.is_a?(Array)
|
32
|
+
data.dig(*path)
|
33
|
+
else
|
34
|
+
data[path]
|
35
|
+
end
|
36
|
+
rescue StandardError => e
|
37
|
+
AnyQuery::Config.logger.error "Failed to resolve path #{path} on #{data.inspect}"
|
38
|
+
raise e
|
39
|
+
end
|
40
|
+
|
41
|
+
def fallback_where(data, wheres)
|
42
|
+
data.filter do |row|
|
43
|
+
wheres.all? do |where|
|
44
|
+
where.all? do |key, value|
|
45
|
+
resolve_path(row, key) == value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolve_join(data, join)
|
52
|
+
AnyQuery::Config.logger.debug "Joining #{join[:model]} on #{join[:primary_key]} = #{join[:foreign_key]}"
|
53
|
+
foreign_keys = data.map { |row| resolve_path(row, join[:foreign_key]) }.compact.uniq
|
54
|
+
result = run_external_join(join, foreign_keys)
|
55
|
+
|
56
|
+
result = group_join_data(result, join)
|
57
|
+
|
58
|
+
data.each do |row|
|
59
|
+
row[join[:into]] = result[resolve_path(row, join[:foreign_key])] || (join[:as] == :list ? [] : nil)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def group_join_data(data, join)
|
64
|
+
if join[:as] == :list
|
65
|
+
data.group_by { |e| resolve_path(e, join[:primary_key]) }
|
66
|
+
else
|
67
|
+
data.index_by { |e| resolve_path(e, join[:primary_key]) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def run_external_join(join, foreign_keys)
|
72
|
+
case join[:strategy]
|
73
|
+
when Proc
|
74
|
+
join[:strategy].call(foreign_keys)
|
75
|
+
when :single
|
76
|
+
map_multi_threaded(foreign_keys.uniq) { |key| join[:model].find(key) }
|
77
|
+
when :full_scan
|
78
|
+
join[:model].to_a
|
79
|
+
else
|
80
|
+
join[:model].where(id: foreign_keys).to_a
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def resolve_select(chain, select)
|
85
|
+
chain.map do |record|
|
86
|
+
select.map do |field|
|
87
|
+
resolve_path(record, field)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def map_multi_threaded(list, concurrency = 50)
|
93
|
+
list.each_slice(concurrency).flat_map do |slice|
|
94
|
+
slice
|
95
|
+
.map { |data| Thread.new { yield(data) } }
|
96
|
+
.each(&:join)
|
97
|
+
.map(&:value)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def parse_field_type(field, line)
|
102
|
+
method_name = "parse_field_type_#{field[:type]}"
|
103
|
+
|
104
|
+
if respond_to?(method_name)
|
105
|
+
send(method_name, field, line)
|
106
|
+
else
|
107
|
+
line.strip
|
108
|
+
end
|
109
|
+
rescue StandardError => e
|
110
|
+
AnyQuery::Config.logger.error "Failed to parse field \"#{line}\" with type #{field.inspect}: #{e.message}"
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
def parse_field_type_integer(_, line)
|
115
|
+
line.to_i
|
116
|
+
end
|
117
|
+
|
118
|
+
def parse_field_type_date(field, line)
|
119
|
+
if field[:format]
|
120
|
+
Date.strptime(line.strip, field[:format])
|
121
|
+
else
|
122
|
+
Date.parse(line)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def parse_field_type_datetime(field, line)
|
127
|
+
if field[:format]
|
128
|
+
DateTime.strptime(line.strip, field[:format])
|
129
|
+
else
|
130
|
+
DateTime.parse(line)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def parse_field_type_float(_, line)
|
135
|
+
line.to_f
|
136
|
+
end
|
137
|
+
|
138
|
+
def parse_field_type_decimal(_, line)
|
139
|
+
BigDecimal(line)
|
140
|
+
end
|
141
|
+
|
142
|
+
def parse_field_type_string(_, line)
|
143
|
+
line.strip
|
144
|
+
end
|
145
|
+
|
146
|
+
def parse_field_type_boolean(_, line)
|
147
|
+
line.strip == 'true'
|
148
|
+
end
|
149
|
+
|
150
|
+
# @abstract
|
151
|
+
class Config
|
152
|
+
def initialize(&block)
|
153
|
+
instance_eval(&block)
|
154
|
+
end
|
155
|
+
|
156
|
+
def url(url)
|
157
|
+
@url = url
|
158
|
+
end
|
159
|
+
|
160
|
+
def primary_key(key)
|
161
|
+
@primary_key = key
|
162
|
+
end
|
163
|
+
|
164
|
+
def to_h
|
165
|
+
{
|
166
|
+
url: @url,
|
167
|
+
primary_key: @primary_key
|
168
|
+
}
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
module AnyQuery
|
6
|
+
module Adapters
|
7
|
+
class Csv < Base
|
8
|
+
class Config < Base::Config
|
9
|
+
def to_h
|
10
|
+
{
|
11
|
+
url: @url,
|
12
|
+
primary_key: @primary_key,
|
13
|
+
table: @table
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_fields(model)
|
19
|
+
CSV.foreach(url, headers: true).map do |line|
|
20
|
+
result = {}
|
21
|
+
model.fields.each do |name, field|
|
22
|
+
result[name] = parse_field(field, line[field[:source] || name.to_s])
|
23
|
+
end
|
24
|
+
result
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def url
|
29
|
+
@config[:url]
|
30
|
+
end
|
31
|
+
|
32
|
+
def load(model, select:, joins:, where:, limit:)
|
33
|
+
chain = parse_fields(model)
|
34
|
+
chain = fallback_where(chain, where) if where.present?
|
35
|
+
chain = chain.first(limit) if limit.present?
|
36
|
+
chain = resolve_joins(chain, joins) if joins.present?
|
37
|
+
|
38
|
+
chain.map! { |row| instantiate_model(model, row) }
|
39
|
+
chain = resolve_select(chain, select) if select.present?
|
40
|
+
|
41
|
+
chain
|
42
|
+
end
|
43
|
+
|
44
|
+
def resolve_joins(data, joins)
|
45
|
+
joins.map do |join|
|
46
|
+
resolve_join(data, join)
|
47
|
+
end
|
48
|
+
data
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse_field(field, line)
|
52
|
+
result = parse_field_type(field, line)
|
53
|
+
if field[:transform]
|
54
|
+
field[:transform].call(result)
|
55
|
+
else
|
56
|
+
result
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnyQuery
|
4
|
+
module Adapters
|
5
|
+
# @api private
|
6
|
+
class FixedLength < Base
|
7
|
+
# @api private
|
8
|
+
class Config < Base::Config
|
9
|
+
def to_h
|
10
|
+
{
|
11
|
+
url: @url,
|
12
|
+
primary_key: @primary_key,
|
13
|
+
table: @table
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(config)
|
19
|
+
super(config)
|
20
|
+
@file = File.open(url)
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_fields(model)
|
24
|
+
@file.each_line.map do |line|
|
25
|
+
result = {}
|
26
|
+
last_index = 0
|
27
|
+
model.fields.each do |name, field|
|
28
|
+
raw_value = line[last_index...(last_index + field[:length])]
|
29
|
+
result[name] = parse_field(field, raw_value)
|
30
|
+
last_index += field[:length]
|
31
|
+
end
|
32
|
+
result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def url
|
37
|
+
@config[:url]
|
38
|
+
end
|
39
|
+
|
40
|
+
def load(model, select:, joins:, where:, limit:)
|
41
|
+
@file.rewind
|
42
|
+
|
43
|
+
chain = parse_fields(model)
|
44
|
+
chain = fallback_where(chain, where) if where.present?
|
45
|
+
chain = chain.first(limit) if limit.present?
|
46
|
+
chain = resolve_joins(chain, joins) if joins.present?
|
47
|
+
|
48
|
+
chain.map! { |row| instantiate_model(model, row) }
|
49
|
+
chain = resolve_select(chain, select) if select.present?
|
50
|
+
|
51
|
+
chain
|
52
|
+
end
|
53
|
+
|
54
|
+
def resolve_joins(data, joins)
|
55
|
+
joins.map do |join|
|
56
|
+
resolve_join(data, join)
|
57
|
+
end
|
58
|
+
data
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_field(field, line)
|
62
|
+
result = parse_field_type(field, line)
|
63
|
+
if field[:transform]
|
64
|
+
field[:transform].call(result)
|
65
|
+
else
|
66
|
+
result
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnyQuery
|
4
|
+
module Adapters
|
5
|
+
# @api private
|
6
|
+
class Http < Base
|
7
|
+
MAX_ITERATIONS = 1000
|
8
|
+
# @api private
|
9
|
+
class Config < Base::Config
|
10
|
+
def endpoint(name, method, path, options = {})
|
11
|
+
@endpoints ||= {}
|
12
|
+
@endpoints[name] = { method:, path:, options: }
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
{
|
17
|
+
url: @url,
|
18
|
+
primary_key: @primary_key,
|
19
|
+
wrapper: @wrapper,
|
20
|
+
endpoints: @endpoints
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def load(model, select:, joins:, where:, limit:)
|
26
|
+
data = run_http_list_query(where)
|
27
|
+
|
28
|
+
data = resolve_joins(data, joins) if joins.present?
|
29
|
+
data = data.first(limit) if limit.present?
|
30
|
+
|
31
|
+
parse_response(model, select, data)
|
32
|
+
end
|
33
|
+
|
34
|
+
def load_single(model, id, joins)
|
35
|
+
data = run_http_single_query(id, {})
|
36
|
+
|
37
|
+
data = resolve_joins(data, joins) if joins.present?
|
38
|
+
|
39
|
+
instantiate_model(model, data)
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_response(model, select, data)
|
43
|
+
data = data.map do |record|
|
44
|
+
instantiate_model(model, record)
|
45
|
+
end
|
46
|
+
data = resolve_select(data, select) if select.present?
|
47
|
+
|
48
|
+
data
|
49
|
+
end
|
50
|
+
|
51
|
+
# FIXME: Use common method
|
52
|
+
def load_single_from_list(data)
|
53
|
+
data.each_slice(50).flat_map do |slice|
|
54
|
+
slice
|
55
|
+
.map { |data| Thread.new { run_http_single_query(data[:id], {}) } }
|
56
|
+
.each(&:join)
|
57
|
+
.map(&:value)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def resolve_joins(data, joins)
|
62
|
+
data = load_single_from_list(data) if joins.any? { |j| j[:model] == :show }
|
63
|
+
|
64
|
+
joins.each do |join|
|
65
|
+
next if join[:model] == :show
|
66
|
+
|
67
|
+
resolve_join(data, join)
|
68
|
+
end
|
69
|
+
data
|
70
|
+
end
|
71
|
+
|
72
|
+
def build_filters(where)
|
73
|
+
{
|
74
|
+
query: (where || {}).inject({}) do |memo, object|
|
75
|
+
memo.merge(object)
|
76
|
+
end
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
def run_http_single_query(id, params)
|
81
|
+
endpoint = @config[:endpoints][:show]
|
82
|
+
url = build_url(endpoint, params, id:)
|
83
|
+
params = (endpoint[:options][:default_params] || {}).merge(params)
|
84
|
+
AnyQuery::Config.logger.debug "Starting request to #{url} with params #{params.inspect}"
|
85
|
+
|
86
|
+
data = run_http_request(endpoint, url, params)
|
87
|
+
data = data.dig(*endpoint[:options][:wrapper]) if endpoint[:options][:wrapper]
|
88
|
+
AnyQuery::Config.logger.debug 'Responded with single record.'
|
89
|
+
data
|
90
|
+
end
|
91
|
+
|
92
|
+
def run_http_list_query(raw_params)
|
93
|
+
endpoint = @config[:endpoints][:list]
|
94
|
+
url = build_url(endpoint, raw_params)
|
95
|
+
params = build_filters(raw_params)
|
96
|
+
results = Set.new
|
97
|
+
previous_response = nil
|
98
|
+
MAX_ITERATIONS.times do |i|
|
99
|
+
params = merge_params(endpoint, params, i, previous_response)
|
100
|
+
|
101
|
+
AnyQuery::Config.logger.debug "Starting request to #{url} with params #{params.inspect}"
|
102
|
+
|
103
|
+
data = run_http_request(endpoint, url, params)
|
104
|
+
break if previous_response == data
|
105
|
+
|
106
|
+
previous_response = data
|
107
|
+
|
108
|
+
data = unwrap(endpoint, data)
|
109
|
+
|
110
|
+
AnyQuery::Config.logger.debug "Responded with #{data&.size || 0} records"
|
111
|
+
break if !data || data.empty?
|
112
|
+
|
113
|
+
previous_count = results.size
|
114
|
+
results += data
|
115
|
+
break if results.size == previous_count
|
116
|
+
|
117
|
+
break if endpoint.dig(:options, :pagination, :type) == :none
|
118
|
+
end
|
119
|
+
results.to_a
|
120
|
+
end
|
121
|
+
|
122
|
+
def merge_params(endpoint, params, iteration, previous_response)
|
123
|
+
(endpoint[:options][:default_params] || {})
|
124
|
+
.deep_merge(params)
|
125
|
+
.deep_merge(handle_pagination(endpoint, iteration, previous_response))
|
126
|
+
end
|
127
|
+
|
128
|
+
def build_url(endpoint, params, id: nil)
|
129
|
+
output = (@config[:url] + endpoint[:path])
|
130
|
+
|
131
|
+
output.gsub!('{id}', id.to_s) if id
|
132
|
+
|
133
|
+
if output.include?('{')
|
134
|
+
output.gsub!(/\{([^}]+)\}/) do |match|
|
135
|
+
key = Regexp.last_match(1).to_sym
|
136
|
+
hash = params.find { |h| h[Regexp.last_match(1).to_sym] }
|
137
|
+
hash&.delete(key) || match
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
output
|
142
|
+
end
|
143
|
+
|
144
|
+
def unwrap(endpoint, data)
|
145
|
+
data = unwrap_list(endpoint, data)
|
146
|
+
unwrap_single(endpoint, data)
|
147
|
+
end
|
148
|
+
|
149
|
+
def unwrap_list(endpoint, data)
|
150
|
+
wrapper = endpoint[:options][:wrapper]
|
151
|
+
return data unless wrapper
|
152
|
+
|
153
|
+
if wrapper.is_a?(Proc)
|
154
|
+
wrapper.call(data)
|
155
|
+
else
|
156
|
+
data.dig(*wrapper)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def unwrap_single(endpoint, data)
|
161
|
+
return data unless endpoint[:options][:single_wrapper]
|
162
|
+
|
163
|
+
data.map! do |row|
|
164
|
+
row.dig(*endpoint[:options][:single_wrapper])
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def run_http_request(endpoint, url, params)
|
169
|
+
response = HTTParty.public_send(endpoint[:method], url, params)
|
170
|
+
|
171
|
+
raise response.inspect unless response.success?
|
172
|
+
|
173
|
+
if response.parsed_response.is_a?(Array)
|
174
|
+
response.parsed_response.map(&:deep_symbolize_keys)
|
175
|
+
else
|
176
|
+
response.parsed_response&.deep_symbolize_keys
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def handle_pagination(endpoint, index, previous_response = nil)
|
181
|
+
pagination = endpoint.dig(:options, :pagination) || {}
|
182
|
+
method_name = "handle_pagination_#{pagination[:type]}"
|
183
|
+
if respond_to?(method_name, true)
|
184
|
+
send(method_name, pagination, index, previous_response)
|
185
|
+
else
|
186
|
+
AnyQuery::Config.logger.warn "Unknown pagination type #{pagination[:type]}"
|
187
|
+
{ query: { page: index } }
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def handle_pagination_page(pagination, index, _previous_response)
|
192
|
+
starts_from = pagination[:starts_from] || 0
|
193
|
+
{ query: { (pagination.dig(:params, :number) || :page) => starts_from + index } }
|
194
|
+
end
|
195
|
+
|
196
|
+
def handle_pagination_skip(_pagination, _index, _previous_response)
|
197
|
+
raise 'TODO: Implement skip pagination'
|
198
|
+
end
|
199
|
+
|
200
|
+
def handle_pagination_cursor(pagination, _index, previous_response)
|
201
|
+
return {} unless previous_response
|
202
|
+
|
203
|
+
cursor_parameter = pagination.dig(:params, :cursor) || :cursor
|
204
|
+
cursor = previous_response[cursor_parameter]
|
205
|
+
{ query: { (pagination.dig(:params, :cursor) || :cursor) => cursor } }
|
206
|
+
end
|
207
|
+
|
208
|
+
def handle_pagination_none(_pagination, _index, _previous_response)
|
209
|
+
{}
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnyQuery
|
4
|
+
module Adapters
|
5
|
+
class Sql < Base
|
6
|
+
class Config < Base::Config
|
7
|
+
def table(name)
|
8
|
+
@table = name
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_h
|
12
|
+
{
|
13
|
+
url: @url,
|
14
|
+
primary_key: @primary_key,
|
15
|
+
table: @table
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(config)
|
21
|
+
super(config)
|
22
|
+
ActiveRecord::Base.establish_connection(@config[:url])
|
23
|
+
table_name = @config[:table]
|
24
|
+
|
25
|
+
@rails_model = declare_model!
|
26
|
+
@rails_model.table_name = table_name
|
27
|
+
@rails_model.inheritance_column = :_sti_disabled
|
28
|
+
Object.const_set("AnyQuery#{table_name.classify}", @rails_model)
|
29
|
+
end
|
30
|
+
|
31
|
+
def declare_model!
|
32
|
+
Class.new(ActiveRecord::Base) do
|
33
|
+
def dig(key, *other)
|
34
|
+
data = public_send(key)
|
35
|
+
return data if other.empty?
|
36
|
+
|
37
|
+
return unless data.respond_to?(:dig)
|
38
|
+
|
39
|
+
data.dig(*other)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :rails_model
|
45
|
+
|
46
|
+
def url
|
47
|
+
@config[:url]
|
48
|
+
end
|
49
|
+
|
50
|
+
def load(_model, select:, joins:, where:, limit:)
|
51
|
+
declare_required_associations!(joins)
|
52
|
+
chain = @rails_model.all
|
53
|
+
chain = chain.where(*where) if where.present?
|
54
|
+
chain = chain.limit(limit) if limit.present?
|
55
|
+
chain = resolve_joins(chain, joins) if joins.present?
|
56
|
+
|
57
|
+
chain = resolve_select(chain, select) if select.present?
|
58
|
+
|
59
|
+
chain
|
60
|
+
end
|
61
|
+
|
62
|
+
def declare_required_associations!(joins)
|
63
|
+
joins&.each do |join|
|
64
|
+
next if join[:model]._adapter.url != @config[:url]
|
65
|
+
|
66
|
+
relation = join_relation_name(join)
|
67
|
+
|
68
|
+
if join[:as] == :list
|
69
|
+
@rails_model.has_many(relation, class_name: join[:model]._adapter.rails_model.to_s,
|
70
|
+
foreign_key: join[:foreign_key], primary_key: join[:primary_key])
|
71
|
+
else
|
72
|
+
@rails_model.belongs_to(relation, class_name: join[:model]._adapter.rails_model.to_s,
|
73
|
+
foreign_key: join[:foreign_key], primary_key: join[:primary_key])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def resolve_joins(data, joins)
|
79
|
+
joins.map do |join|
|
80
|
+
if join[:model]._adapter.url == @config[:url]
|
81
|
+
relation = join_relation_name(join)
|
82
|
+
|
83
|
+
data = data.eager_load(relation)
|
84
|
+
else
|
85
|
+
resolve_join(data, join)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
data
|
89
|
+
end
|
90
|
+
|
91
|
+
def join_relation_name(join)
|
92
|
+
if join[:as] == :list
|
93
|
+
join[:model].table_name.pluralize.to_sym
|
94
|
+
else
|
95
|
+
join[:model].table_name.singularize.to_sym
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|