hover-ruby-client 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ruby.yml +30 -0
- data/.gitignore +64 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +161 -0
- data/README.md +50 -0
- data/Rakefile +14 -0
- data/hover-ruby-client.gemspec +38 -0
- data/lib/hover/client/basic_auth.rb +18 -0
- data/lib/hover/client/hmac.rb +20 -0
- data/lib/hover/client/hover.rb +29 -0
- data/lib/hover/client/http.rb +141 -0
- data/lib/hover/client/manowar.rb +17 -0
- data/lib/hover/client/midas.rb +120 -0
- data/lib/hover/client/static.rb +89 -0
- data/lib/hover/decoder/json_stream.rb +25 -0
- data/lib/hover/encoder/json_stream.rb +35 -0
- data/lib/hover/importer/active_record.rb +134 -0
- data/lib/hover/jobs/concerns/retry_network_errors.rb +45 -0
- data/lib/hover/version.rb +3 -0
- data/test/app/Gemfile +1 -0
- data/test/files/archive.zip +0 -0
- data/test/files/encoded.json +106 -0
- data/test/test_helper.rb +80 -0
- data/test/unit/client/basic_auth_test.rb +21 -0
- data/test/unit/client/hmac_test.rb +277 -0
- data/test/unit/client/hover_test.rb +62 -0
- data/test/unit/client/manowar_test.rb +34 -0
- data/test/unit/client/midas_test.rb +187 -0
- data/test/unit/client/static_test.rb +169 -0
- data/test/unit/decoder/json_stream_test.rb +8 -0
- data/test/unit/encoder/json_stream_test.rb +135 -0
- data/test/unit/importer/active_record_test.rb +354 -0
- data/test/unit/jobs/concerns/retry_network_errors_test.rb +23 -0
- metadata +286 -0
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'api-auth'
|
2
|
+
require 'uri'
|
3
|
+
require 'net/http'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Hover
|
7
|
+
module Client
|
8
|
+
class HTTP
|
9
|
+
attr_accessor :prefix, :site
|
10
|
+
|
11
|
+
def initialize(site = 'http://localhost:3000', prefix = 'api/v1')
|
12
|
+
@prefix = prefix
|
13
|
+
@site = site
|
14
|
+
end
|
15
|
+
|
16
|
+
def authenticate(request)
|
17
|
+
end
|
18
|
+
|
19
|
+
def make_uri(path, parameters = {})
|
20
|
+
parts = [site, prefix, path]
|
21
|
+
uri = URI.parse(parts.map(&:presence).compact.join('/'))
|
22
|
+
uri = URI.parse("#{uri.to_s}?#{URI.encode_www_form(parameters)}") unless parameters.empty?
|
23
|
+
|
24
|
+
uri
|
25
|
+
end
|
26
|
+
|
27
|
+
def make_request(request, uri)
|
28
|
+
authenticate(request)
|
29
|
+
|
30
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
31
|
+
http.use_ssl = true if 'https' == uri.scheme
|
32
|
+
|
33
|
+
response = http.request(request)
|
34
|
+
|
35
|
+
raise_errors_for_cloudfront_response(response)
|
36
|
+
|
37
|
+
response
|
38
|
+
end
|
39
|
+
|
40
|
+
def get(path, parameters = {})
|
41
|
+
uri = make_uri(path, parameters)
|
42
|
+
request = Net::HTTP::Get.new(uri)
|
43
|
+
response = make_request(request, uri)
|
44
|
+
|
45
|
+
response
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_redirect_location(path, parameters = {}, &block)
|
49
|
+
uri = make_uri(path, parameters)
|
50
|
+
request = Net::HTTP::Get.new(uri)
|
51
|
+
redirect_response = make_request(request, uri)
|
52
|
+
|
53
|
+
if redirect_response.is_a?(Net::HTTPRedirection) && redirect_response['location']
|
54
|
+
redirect_response['location']
|
55
|
+
else
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def post(path, parameters = {})
|
61
|
+
uri = make_uri(path)
|
62
|
+
request = Net::HTTP::Post.new(uri)
|
63
|
+
make_form_request(request, uri, parameters)
|
64
|
+
end
|
65
|
+
|
66
|
+
def make_form_request(request, uri, parameters)
|
67
|
+
request.set_form_data(parameters)
|
68
|
+
response = make_request(request, uri)
|
69
|
+
response
|
70
|
+
end
|
71
|
+
|
72
|
+
def put(path, parameters = {})
|
73
|
+
uri = make_uri(path)
|
74
|
+
request = Net::HTTP::Put.new(uri)
|
75
|
+
make_form_request(request, uri, parameters)
|
76
|
+
end
|
77
|
+
|
78
|
+
def patch(path, parameters = {})
|
79
|
+
uri = make_uri(path)
|
80
|
+
request = Net::HTTP::Patch.new(uri)
|
81
|
+
make_form_request(request, uri, parameters)
|
82
|
+
end
|
83
|
+
|
84
|
+
def delete(path, parameters = {})
|
85
|
+
uri = make_uri(path)
|
86
|
+
request = Net::HTTP::Delete.new(uri)
|
87
|
+
make_form_request(request, uri, parameters)
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_response(response)
|
91
|
+
raise_errors(response)
|
92
|
+
|
93
|
+
case response.code.to_i
|
94
|
+
when 204 # no content
|
95
|
+
{}
|
96
|
+
else
|
97
|
+
::JSON.parse(response.body)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def raise_errors_for_cloudfront_response(response)
|
102
|
+
#
|
103
|
+
# If the server can't be reached cloudfront will respond with an
|
104
|
+
# error message of it's own. We need to catch those so that we can retry
|
105
|
+
# the requests.
|
106
|
+
#
|
107
|
+
|
108
|
+
return unless (response.header['Server'] =~ /Cloudfront/i)
|
109
|
+
|
110
|
+
raise ::Hover::Client::Unavailable
|
111
|
+
end
|
112
|
+
|
113
|
+
def raise_errors(response)
|
114
|
+
message = "(#{response.code}): - #{response.body}"
|
115
|
+
|
116
|
+
case response.code.to_i
|
117
|
+
when 400
|
118
|
+
raise BadRequest, message
|
119
|
+
when 401
|
120
|
+
raise Unauthorized, message
|
121
|
+
when 403
|
122
|
+
raise General, message
|
123
|
+
when 404
|
124
|
+
raise NotFound, message
|
125
|
+
when 500
|
126
|
+
raise InternalError, "Internal error: #{message}"
|
127
|
+
when 502..503
|
128
|
+
raise Unavailable, message
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class BadRequest < StandardError; end
|
134
|
+
class Unauthorized < StandardError; end
|
135
|
+
class General < StandardError; end
|
136
|
+
class Unavailable < StandardError; end
|
137
|
+
class InternalError < StandardError; end
|
138
|
+
class NotFound < StandardError; end
|
139
|
+
class AloomaReportingError < StandardError; end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'hover/client/hover'
|
2
|
+
|
3
|
+
module Hover
|
4
|
+
module Client
|
5
|
+
class Manowar < ::Hover::Client::Hover
|
6
|
+
|
7
|
+
def submit_order(parameters = {})
|
8
|
+
json_post('orders.json', parameters)
|
9
|
+
end
|
10
|
+
|
11
|
+
def order_results(order_id)
|
12
|
+
json_get("orders/#{order_id}/results.json")
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'zip'
|
4
|
+
|
5
|
+
require 'hover/client/hover'
|
6
|
+
|
7
|
+
module Hover
|
8
|
+
module Client
|
9
|
+
class Midas < ::Hover::Client::Hover
|
10
|
+
|
11
|
+
#
|
12
|
+
# Users
|
13
|
+
#
|
14
|
+
|
15
|
+
def me
|
16
|
+
json_get('me.json')
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Orders
|
21
|
+
#
|
22
|
+
|
23
|
+
def orders(parameters = {})
|
24
|
+
json_get('orders.json', parameters)
|
25
|
+
end
|
26
|
+
|
27
|
+
def order(id, parameters = {})
|
28
|
+
json_get("orders/#{id}.json", parameters)
|
29
|
+
end
|
30
|
+
|
31
|
+
def order_complete_work(order_id)
|
32
|
+
put("orders/#{order_id}/work_complete.json")
|
33
|
+
end
|
34
|
+
|
35
|
+
#def update_order(id, order_attributes)
|
36
|
+
# put("orders/#{id}.json", order_parameters(order_attributes))
|
37
|
+
#end
|
38
|
+
|
39
|
+
def create_order(params = {})
|
40
|
+
json_post('orders.json', params)
|
41
|
+
end
|
42
|
+
|
43
|
+
def complete_order_upload(order_id)
|
44
|
+
patch("orders/#{order_id}/archive_uploading_complete.json")
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Images
|
49
|
+
#
|
50
|
+
|
51
|
+
def images(parameters = {})
|
52
|
+
json_get("images.json", parameters)
|
53
|
+
end
|
54
|
+
|
55
|
+
def image(id)
|
56
|
+
json_get("images/#{id}.json")
|
57
|
+
end
|
58
|
+
|
59
|
+
def download_image(image, parameters = {}, &block)
|
60
|
+
return unless url = get_redirect_location("images/#{image["id"]}.jpg", parameters)
|
61
|
+
|
62
|
+
Tempfile.open(["image-download", ".jpg"]) do |output|
|
63
|
+
output.write(Kernel.open(url).read)
|
64
|
+
output.rewind
|
65
|
+
|
66
|
+
yield output
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def upload_image(image, io)
|
71
|
+
raise "Already Uploaded" unless upload_url = image["image"].try(:[], "upload_url")
|
72
|
+
|
73
|
+
file = (io.is_a?(String) ? File.open(io) : io)
|
74
|
+
|
75
|
+
uri = URI.parse(upload_url)
|
76
|
+
|
77
|
+
request = Net::HTTP::Put.new(uri.request_uri)
|
78
|
+
request.body_stream = file
|
79
|
+
request.content_length = file.size
|
80
|
+
request['Content-Type'] = ''
|
81
|
+
|
82
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
83
|
+
http.use_ssl = true
|
84
|
+
response = http.request(request)
|
85
|
+
end
|
86
|
+
|
87
|
+
def image_upload_urls(order_id)
|
88
|
+
json_get("images/upload_urls.json", order_id: order_id)
|
89
|
+
end
|
90
|
+
|
91
|
+
def upload_images_for_order(order_id, input_path)
|
92
|
+
image_array = image_upload_urls(order_id).map { |upload_url| {"image" => {"upload_url" => upload_url}} }
|
93
|
+
file_paths = Dir.glob(File.join(input_path, "*.jp*"))
|
94
|
+
|
95
|
+
if file_paths.size > image_array.size
|
96
|
+
raise("Trying to upload #{file_paths.size} files. #{image_array.size} is the maximum.")
|
97
|
+
end
|
98
|
+
|
99
|
+
file_paths.each do |file_path|
|
100
|
+
image = image_array.pop
|
101
|
+
upload_image(image, file_path)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def upload_images_from_prometheus_export(order_id, export_zip_path)
|
106
|
+
Zip::File.open(export_zip_path) do |zip|
|
107
|
+
Dir.mktmpdir do |directory_path|
|
108
|
+
zip.entries.select { |entry| entry.name =~ /original_image\.jp(g|eg)$/ }.each do |entry|
|
109
|
+
basename = File.basename(entry.name)
|
110
|
+
next if basename =~ /^\./
|
111
|
+
entry.extract(File.join(directory_path, basename)) { true }
|
112
|
+
end
|
113
|
+
|
114
|
+
upload_images_for_order(order_id, directory_path)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'hover/client/hover'
|
2
|
+
|
3
|
+
module Hover
|
4
|
+
module Client
|
5
|
+
class Static < ::Hover::Client::Hover
|
6
|
+
|
7
|
+
attr_accessor :application, :environment
|
8
|
+
|
9
|
+
def initialize(application, environment, client_id, client_secret, site = 'http://localhost:3000', prefix = 'api/v1')
|
10
|
+
super(client_id, client_secret, site, prefix)
|
11
|
+
|
12
|
+
self.application = application
|
13
|
+
self.environment = environment
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_metric(name:, value:, happened_at: Time.now.utc, tags: {}, remote_record_id: nil)
|
17
|
+
return unless alooma_url
|
18
|
+
|
19
|
+
parameters = {
|
20
|
+
"metric[name]" => name,
|
21
|
+
'metric[value]' => value,
|
22
|
+
'metric[happened_at]' => happened_at.to_s,
|
23
|
+
'metric[environment]' => environment,
|
24
|
+
'metric[application]' => application
|
25
|
+
}.merge(self.class.tags_to_params(tags))
|
26
|
+
parameters['metric[remote_record_id]'] = remote_record_id if remote_record_id
|
27
|
+
add_unique_identifier(parameters)
|
28
|
+
|
29
|
+
post_to_alooma(parameters, alooma_url)
|
30
|
+
end
|
31
|
+
|
32
|
+
def alooma_url
|
33
|
+
@alooma_url ||= if Rails.env.staging?
|
34
|
+
"https://inputs.alooma.com/rest/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnROYW1lIjoiaG92ZXItc2YtMSIsImlucHV0TGFiZWwiOiJzdGF0aWNfc3RhZ2luZyIsImlucHV0VHlwZSI6IlJFU1RBUEkifQ.Iba3VM9GaOjzJEmUFPuHDA6oaQT7TBe9A0iMxfYW0x0"
|
35
|
+
elsif Rails.env.production?
|
36
|
+
"https://inputs.alooma.com/rest/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnROYW1lIjoiaG92ZXItc2YtMSIsImlucHV0TGFiZWwiOiJzdGF0aWNfcHJvZHVjdGlvbiIsImlucHV0VHlwZSI6IlJFU1RBUEkifQ.jiuWVLDPDvZSv4bmiy54GVEpOre19bDqr9ZC7Y-HKwo"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def post_to_alooma(parameters, url)
|
41
|
+
uri = URI.parse(url)
|
42
|
+
request = Net::HTTP::Post.new(uri)
|
43
|
+
request.set_form_data(parameters)
|
44
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
45
|
+
http.use_ssl = true if 'https' == uri.scheme
|
46
|
+
response = http.request(request)
|
47
|
+
|
48
|
+
unless (200 .. 299).include?(response.code.to_i)
|
49
|
+
raise AloomaReportingError.new("Error reporting to alooma. #{response.inspect}")
|
50
|
+
end
|
51
|
+
|
52
|
+
response
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_unique_identifier(parameters)
|
56
|
+
sorted_array = parameters.to_a.sort_by(&:first)
|
57
|
+
metric_identifier = Digest::SHA256.hexdigest(sorted_array.to_json)
|
58
|
+
|
59
|
+
parameters['metric[identifier]'] = metric_identifier
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Class Methods
|
64
|
+
#
|
65
|
+
|
66
|
+
def self.tags_to_params(tags, parent_keys = ['metric', 'tags'])
|
67
|
+
params = {}
|
68
|
+
|
69
|
+
tags.each do |key, value|
|
70
|
+
if value.is_a?(Hash)
|
71
|
+
params.merge!(tags_to_params(value, (parent_keys + [key])))
|
72
|
+
elsif value.is_a?(Array)
|
73
|
+
params["#{param_name(*parent_keys, key)}[]"] = value
|
74
|
+
else
|
75
|
+
params[param_name(*parent_keys, key)] = value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
params
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.param_name(*args)
|
83
|
+
first = args.shift
|
84
|
+
"#{first}" + args.map{ |arg| "[#{arg}]" }.join
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'yajl'
|
2
|
+
|
3
|
+
module Hover
|
4
|
+
module Decoder
|
5
|
+
class JSONStream
|
6
|
+
|
7
|
+
def initialize(s3_object, block = ->(object) {})
|
8
|
+
@parser = Yajl::Parser.new
|
9
|
+
@s3_object = s3_object
|
10
|
+
|
11
|
+
@parser.on_parse_complete = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
bucket_name = @s3_object.bucket.name
|
16
|
+
key = @s3_object.key
|
17
|
+
|
18
|
+
@s3_object.client.get_object(bucket: bucket_name, key: key) do |chunk|
|
19
|
+
@parser << chunk
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Hover
|
2
|
+
module Encoder
|
3
|
+
class JSONStream
|
4
|
+
attr_accessor :file
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
end
|
8
|
+
|
9
|
+
def open(path)
|
10
|
+
self.file = File.open(path, 'w')
|
11
|
+
end
|
12
|
+
|
13
|
+
def close
|
14
|
+
self.file.close
|
15
|
+
self.file = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def append(attributes, klass)
|
19
|
+
self.file.puts(JSON.dump(attributes.merge('class' => klass.name)))
|
20
|
+
end
|
21
|
+
|
22
|
+
def append_each(attributes_sets, klass)
|
23
|
+
attributes_sets.each do |attributes|
|
24
|
+
append(attributes, klass)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def append_records(scope)
|
29
|
+
scope.find_each do |record|
|
30
|
+
append(record.attributes.as_json, record.class)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Hover
|
2
|
+
module Importer
|
3
|
+
class ActiveRecord
|
4
|
+
attr_accessor :remote_local_map, :update_finder, :column_changes
|
5
|
+
|
6
|
+
def initialize(remote_local_map, update_finder = nil, column_changes = {})
|
7
|
+
self.remote_local_map = Hash.new { |hash, key| hash[key] = {} }
|
8
|
+
self.remote_local_map.merge!(remote_local_map)
|
9
|
+
self.update_finder = update_finder
|
10
|
+
self.column_changes = column_changes
|
11
|
+
end
|
12
|
+
|
13
|
+
def change_columns(attributes)
|
14
|
+
attributes.inject({}) do |hash, key_value|
|
15
|
+
hash[(column_changes[key_value[0]] || key_value[0])] = key_value[1]
|
16
|
+
hash
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def update(table_name, records_attributes)
|
21
|
+
klass = table_name.singularize.camelize.constantize
|
22
|
+
records_attributes.collect { |attributes| update_record(klass, attributes) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def update_record(klass, attributes)
|
26
|
+
return unless record = find_record_to_update(klass, attributes)
|
27
|
+
|
28
|
+
attributes = change_columns(attributes.dup)
|
29
|
+
s3_file_attributes = s3_file_keys(klass, attributes)
|
30
|
+
|
31
|
+
attributes.delete(klass.primary_key)
|
32
|
+
attributes.reject! { |key, value| !klass.column_names.include?(key) }
|
33
|
+
attributes.merge!(s3_file_attributes) if s3_file_attributes.is_a?(Hash)
|
34
|
+
|
35
|
+
record.assign_attributes(attributes)
|
36
|
+
record.save!(validate: false)
|
37
|
+
|
38
|
+
[klass, record.id]
|
39
|
+
end
|
40
|
+
|
41
|
+
def s3_file_keys(klass, attributes)
|
42
|
+
klass.try(:s3_file_versions).try(:keys).try(:inject, {}) do |output, attribute|
|
43
|
+
if value = attributes.delete(attribute.to_s)
|
44
|
+
output["#{attribute}_s3_keys"] = value
|
45
|
+
end
|
46
|
+
|
47
|
+
output
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_record_to_update(klass, attributes)
|
52
|
+
self.update_finder.call(self, klass, attributes)
|
53
|
+
end
|
54
|
+
|
55
|
+
def import_results(results)
|
56
|
+
class_id_sets = []
|
57
|
+
|
58
|
+
results.each do |table_name, records_attributes|
|
59
|
+
if table_class_id_sets = import_table(table_name, records_attributes)
|
60
|
+
class_id_sets.concat(table_class_id_sets)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class_id_sets
|
65
|
+
end
|
66
|
+
|
67
|
+
def import_table(table_name, records_attributes)
|
68
|
+
klass = table_name.singularize.camelize.constantize
|
69
|
+
|
70
|
+
records_attributes.collect { |attributes|
|
71
|
+
import_record(klass, attributes) unless attributes.nil?
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def import_record(klass, attributes)
|
76
|
+
attributes = change_columns(attributes.dup)
|
77
|
+
external_primary_key = attributes.delete(klass.primary_key)
|
78
|
+
s3_file_attributes = s3_file_keys(klass, attributes)
|
79
|
+
|
80
|
+
attributes.reject! { |key, value| !klass.column_names.include?(key) }
|
81
|
+
attributes.merge!(s3_file_attributes) if s3_file_attributes.is_a?(Hash)
|
82
|
+
|
83
|
+
record = klass.new(attributes)
|
84
|
+
return unless last_insert_id = insert_record(record)
|
85
|
+
|
86
|
+
remote_local_map[klass.table_name][external_primary_key] = last_insert_id
|
87
|
+
|
88
|
+
[klass, last_insert_id]
|
89
|
+
end
|
90
|
+
|
91
|
+
# see https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/persistence.rb#L729-L741
|
92
|
+
def insert_record(record)
|
93
|
+
attribute_names = record.class.column_names
|
94
|
+
attributes_values = record.send(:attributes_with_values_for_create, attribute_names)
|
95
|
+
|
96
|
+
primary_key = record.class._insert_record(attributes_values)
|
97
|
+
|
98
|
+
record[record.class.primary_key] = primary_key
|
99
|
+
end
|
100
|
+
|
101
|
+
def new_foreign_keys(record)
|
102
|
+
record.class.reflect_on_all_associations.inject({}) do |hash, association|
|
103
|
+
method = "new_foreign_keys_for_#{association.macro}"
|
104
|
+
method += "_polymorphic" if association.options[:polymorphic]
|
105
|
+
|
106
|
+
if respond_to?(method) && before_after = send(method, record, association)
|
107
|
+
hash.store(*before_after)
|
108
|
+
end
|
109
|
+
|
110
|
+
hash
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def new_foreign_keys_for_belongs_to(record, association)
|
115
|
+
return unless id = self.remote_local_map[association.klass.table_name].try(:[], record[association.foreign_key])
|
116
|
+
[association.foreign_key, id]
|
117
|
+
end
|
118
|
+
|
119
|
+
def new_foreign_keys_for_belongs_to_polymorphic(record, association)
|
120
|
+
return unless id = self.remote_local_map[record[association.foreign_type].tableize].try(:[], record[association.foreign_key])
|
121
|
+
[association.foreign_key, id]
|
122
|
+
end
|
123
|
+
|
124
|
+
def update_foreign_keys(record)
|
125
|
+
foreign_key_attributes = new_foreign_keys(record)
|
126
|
+
return if foreign_key_attributes.empty?
|
127
|
+
|
128
|
+
record.class.unscoped.where(id: record.id).update_all(foreign_key_attributes)
|
129
|
+
|
130
|
+
[record.reload.class, record.id]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|