inventory_refresh 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +47 -0
- data/.gitignore +13 -0
- data/.rspec +4 -0
- data/.rspec_ci +4 -0
- data/.rubocop.yml +4 -0
- data/.rubocop_cc.yml +5 -0
- data/.rubocop_local.yml +2 -0
- data/.travis.yml +12 -0
- data/.yamllint +12 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +6 -0
- data/LICENSE +202 -0
- data/README.md +35 -0
- data/Rakefile +47 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/inventory_refresh.gemspec +34 -0
- data/lib/inventory_refresh.rb +11 -0
- data/lib/inventory_refresh/application_record_iterator.rb +56 -0
- data/lib/inventory_refresh/application_record_reference.rb +15 -0
- data/lib/inventory_refresh/graph.rb +157 -0
- data/lib/inventory_refresh/graph/topological_sort.rb +66 -0
- data/lib/inventory_refresh/inventory_collection.rb +1175 -0
- data/lib/inventory_refresh/inventory_collection/data_storage.rb +178 -0
- data/lib/inventory_refresh/inventory_collection/graph.rb +170 -0
- data/lib/inventory_refresh/inventory_collection/index/proxy.rb +230 -0
- data/lib/inventory_refresh/inventory_collection/index/type/base.rb +80 -0
- data/lib/inventory_refresh/inventory_collection/index/type/data.rb +26 -0
- data/lib/inventory_refresh/inventory_collection/index/type/local_db.rb +286 -0
- data/lib/inventory_refresh/inventory_collection/index/type/skeletal.rb +116 -0
- data/lib/inventory_refresh/inventory_collection/reference.rb +96 -0
- data/lib/inventory_refresh/inventory_collection/references_storage.rb +106 -0
- data/lib/inventory_refresh/inventory_collection/scanner.rb +117 -0
- data/lib/inventory_refresh/inventory_collection/serialization.rb +140 -0
- data/lib/inventory_refresh/inventory_object.rb +303 -0
- data/lib/inventory_refresh/inventory_object_lazy.rb +151 -0
- data/lib/inventory_refresh/save_collection/base.rb +38 -0
- data/lib/inventory_refresh/save_collection/recursive.rb +52 -0
- data/lib/inventory_refresh/save_collection/saver/base.rb +390 -0
- data/lib/inventory_refresh/save_collection/saver/batch.rb +17 -0
- data/lib/inventory_refresh/save_collection/saver/concurrent_safe.rb +71 -0
- data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +632 -0
- data/lib/inventory_refresh/save_collection/saver/default.rb +57 -0
- data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +85 -0
- data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +120 -0
- data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +196 -0
- data/lib/inventory_refresh/save_collection/topological_sort.rb +38 -0
- data/lib/inventory_refresh/save_inventory.rb +38 -0
- data/lib/inventory_refresh/target.rb +73 -0
- data/lib/inventory_refresh/target_collection.rb +80 -0
- data/lib/inventory_refresh/version.rb +3 -0
- data/tools/ci/create_db_user.sh +3 -0
- metadata +207 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
require "inventory_refresh/save_collection/saver/batch"
|
2
|
+
require "inventory_refresh/save_collection/saver/concurrent_safe"
|
3
|
+
require "inventory_refresh/save_collection/saver/concurrent_safe_batch"
|
4
|
+
require "inventory_refresh/save_collection/saver/default"
|
5
|
+
|
6
|
+
module InventoryRefresh::SaveCollection
|
7
|
+
class Base
|
8
|
+
class << self
|
9
|
+
# Saves one InventoryCollection object into the DB.
|
10
|
+
#
|
11
|
+
# @param ems [ExtManagementSystem] manger owning the InventoryCollection object
|
12
|
+
# @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object we want to save
|
13
|
+
def save_inventory_object_inventory(ems, inventory_collection)
|
14
|
+
#_log.debug("Saving collection #{inventory_collection} of size #{inventory_collection.size} to"\
|
15
|
+
# " the database, for the manager: '#{ems.name}'...")
|
16
|
+
|
17
|
+
if inventory_collection.custom_save_block.present?
|
18
|
+
#_log.debug("Saving collection #{inventory_collection} using a custom save block")
|
19
|
+
inventory_collection.custom_save_block.call(ems, inventory_collection)
|
20
|
+
else
|
21
|
+
save_inventory(inventory_collection)
|
22
|
+
end
|
23
|
+
#_log.debug("Saving collection #{inventory_collection}, for the manager: '#{ems.name}'...Complete")
|
24
|
+
inventory_collection.saved = true
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Saves one InventoryCollection object into the DB using a configured saver_strategy class.
|
30
|
+
#
|
31
|
+
# @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object we want to save
|
32
|
+
def save_inventory(inventory_collection)
|
33
|
+
saver_class = "InventoryRefresh::SaveCollection::Saver::#{inventory_collection.saver_strategy.to_s.camelize}"
|
34
|
+
saver_class.constantize.new(inventory_collection).save_inventory_collection!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "inventory_refresh/save_collection/base"
|
2
|
+
|
3
|
+
module InventoryRefresh::SaveCollection
|
4
|
+
class Recursive < InventoryRefresh::SaveCollection::Base
|
5
|
+
class << self
|
6
|
+
# Saves the passed InventoryCollection objects by recursively passing the graph
|
7
|
+
#
|
8
|
+
# @param ems [ExtManagementSystem] manager owning the inventory_collections
|
9
|
+
# @param inventory_collections [Array<InventoryRefresh::InventoryCollection>] array of InventoryCollection objects
|
10
|
+
# for saving
|
11
|
+
def save_collections(ems, inventory_collections)
|
12
|
+
graph = InventoryRefresh::InventoryCollection::Graph.new(inventory_collections)
|
13
|
+
graph.build_directed_acyclic_graph!
|
14
|
+
|
15
|
+
graph.nodes.each do |inventory_collection|
|
16
|
+
save_collection(ems, inventory_collection, [])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Saves the one passed InventoryCollection object
|
23
|
+
#
|
24
|
+
# @param ems [ExtManagementSystem] manager owning the inventory_collections
|
25
|
+
# @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object for saving
|
26
|
+
# @param traversed_collections [Array<InventoryRefresh::InventoryCollection>] array of traversed InventoryCollection
|
27
|
+
# objects, that we use for detecting possible cycle
|
28
|
+
def save_collection(ems, inventory_collection, traversed_collections)
|
29
|
+
unless inventory_collection.kind_of?(::InventoryRefresh::InventoryCollection)
|
30
|
+
raise "A InventoryRefresh::SaveInventory needs a InventoryCollection object, it got: #{inventory_collection.inspect}"
|
31
|
+
end
|
32
|
+
|
33
|
+
return if inventory_collection.saved?
|
34
|
+
|
35
|
+
traversed_collections << inventory_collection
|
36
|
+
|
37
|
+
unless inventory_collection.saveable?
|
38
|
+
inventory_collection.dependencies.each do |dependency|
|
39
|
+
next if dependency.saved?
|
40
|
+
if traversed_collections.include?(dependency)
|
41
|
+
raise "Edge from #{inventory_collection} to #{dependency} creates a cycle"
|
42
|
+
end
|
43
|
+
save_collection(ems, dependency, traversed_collections)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
#_log.debug("Saving #{inventory_collection} of size #{inventory_collection.size}")
|
48
|
+
save_inventory_object_inventory(ems, inventory_collection)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,390 @@
|
|
1
|
+
require "inventory_refresh/application_record_iterator"
|
2
|
+
require "inventory_refresh/save_collection/saver/sql_helper"
|
3
|
+
require "active_support/core_ext/module/delegation"
|
4
|
+
|
5
|
+
module InventoryRefresh::SaveCollection
|
6
|
+
module Saver
|
7
|
+
class Base
|
8
|
+
include InventoryRefresh::SaveCollection::Saver::SqlHelper
|
9
|
+
|
10
|
+
# @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object we will be saving
|
11
|
+
def initialize(inventory_collection)
|
12
|
+
@inventory_collection = inventory_collection
|
13
|
+
# TODO(lsmola) do I need to reload every time? Also it should be enough to clear the associations.
|
14
|
+
inventory_collection.parent.reload if inventory_collection.parent
|
15
|
+
@association = inventory_collection.db_collection_for_comparison
|
16
|
+
|
17
|
+
# Private attrs
|
18
|
+
@model_class = inventory_collection.model_class
|
19
|
+
@table_name = @model_class.table_name
|
20
|
+
@q_table_name = get_connection.quote_table_name(@table_name)
|
21
|
+
@primary_key = @model_class.primary_key
|
22
|
+
@arel_primary_key = @model_class.arel_attribute(@primary_key)
|
23
|
+
@unique_index_keys = inventory_collection.unique_index_keys
|
24
|
+
@unique_index_keys_to_s = inventory_collection.manager_ref_to_cols.map(&:to_s)
|
25
|
+
@select_keys = [@primary_key] + @unique_index_keys_to_s
|
26
|
+
@unique_db_primary_keys = Set.new
|
27
|
+
@unique_db_indexes = Set.new
|
28
|
+
|
29
|
+
# Right now ApplicationRecordIterator in association is used for targeted refresh. Given the small amount of
|
30
|
+
# records flowing through there, we probably don't need to optimize that association to fetch a pure SQL.
|
31
|
+
@pure_sql_records_fetching = !inventory_collection.use_ar_object? && !@association.kind_of?(InventoryRefresh::ApplicationRecordIterator)
|
32
|
+
|
33
|
+
@batch_size_for_persisting = inventory_collection.batch_size_pure_sql
|
34
|
+
|
35
|
+
@batch_size = @pure_sql_records_fetching ? @batch_size_for_persisting : inventory_collection.batch_size
|
36
|
+
@record_key_method = @pure_sql_records_fetching ? :pure_sql_record_key : :ar_record_key
|
37
|
+
@select_keys_indexes = @select_keys.each_with_object({}).with_index { |(key, obj), index| obj[key.to_s] = index }
|
38
|
+
@pg_types = @model_class.attribute_names.each_with_object({}) do |key, obj|
|
39
|
+
obj[key.to_sym] = inventory_collection.model_class.columns_hash[key]
|
40
|
+
.try(:sql_type_metadata)
|
41
|
+
.try(:instance_values)
|
42
|
+
.try(:[], "sql_type")
|
43
|
+
end
|
44
|
+
|
45
|
+
@serializable_keys = {}
|
46
|
+
@deserializable_keys = {}
|
47
|
+
@model_class.attribute_names.each do |key|
|
48
|
+
attribute_type = @model_class.type_for_attribute(key.to_s)
|
49
|
+
pg_type = @pg_types[key.to_sym]
|
50
|
+
|
51
|
+
if inventory_collection.use_ar_object?
|
52
|
+
# When using AR object, lets make sure we type.serialize(value) every value, so we have a slow but always
|
53
|
+
# working way driven by a configuration
|
54
|
+
@serializable_keys[key.to_sym] = attribute_type
|
55
|
+
@deserializable_keys[key.to_sym] = attribute_type
|
56
|
+
elsif attribute_type.respond_to?(:coder) ||
|
57
|
+
attribute_type.type == :int4range ||
|
58
|
+
attribute_type.type == :jsonb ||
|
59
|
+
pg_type == "text[]" ||
|
60
|
+
pg_type == "character varying[]"
|
61
|
+
# Identify columns that needs to be encoded by type.serialize(value), it's a costy operations so lets do
|
62
|
+
# do it only for columns we need it for.
|
63
|
+
# TODO: should these set @deserializable_keys too?
|
64
|
+
@serializable_keys[key.to_sym] = attribute_type
|
65
|
+
elsif attribute_type.type == :decimal
|
66
|
+
# Postgres formats decimal columns with fixed number of digits e.g. '0.100'
|
67
|
+
# Need to parse and let Ruby format the value to have a comparable string.
|
68
|
+
@serializable_keys[key.to_sym] = attribute_type
|
69
|
+
@deserializable_keys[key.to_sym] = attribute_type
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Saves the InventoryCollection
|
75
|
+
def save_inventory_collection!
|
76
|
+
# If we have a targeted InventoryCollection that wouldn't do anything, quickly skip it
|
77
|
+
return if inventory_collection.noop?
|
78
|
+
# If we want to use delete_complement strategy using :all_manager_uuids attribute, we are skipping any other
|
79
|
+
# job. We want to do 1 :delete_complement job at 1 time, to keep to memory down.
|
80
|
+
return delete_complement if inventory_collection.all_manager_uuids.present?
|
81
|
+
|
82
|
+
save!(association)
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
attr_reader :inventory_collection, :association
|
88
|
+
|
89
|
+
delegate :build_stringified_reference, :build_stringified_reference_for_record, :to => :inventory_collection
|
90
|
+
|
91
|
+
# Applies serialize method for each relevant attribute, which will cast the value to the right type.
|
92
|
+
#
|
93
|
+
# @param all_attribute_keys [Symbol] attribute keys we want to process
|
94
|
+
# @param attributes [Hash] attributes hash
|
95
|
+
# @return [Hash] modified hash from parameter attributes with casted values
|
96
|
+
def values_for_database!(all_attribute_keys, attributes)
|
97
|
+
all_attribute_keys.each do |key|
|
98
|
+
next unless attributes.key?(key)
|
99
|
+
|
100
|
+
if (type = serializable_keys[key])
|
101
|
+
attributes[key] = type.serialize(attributes[key])
|
102
|
+
end
|
103
|
+
end
|
104
|
+
attributes
|
105
|
+
end
|
106
|
+
|
107
|
+
def transform_to_hash!(all_attribute_keys, hash)
|
108
|
+
if inventory_collection.use_ar_object?
|
109
|
+
record = inventory_collection.model_class.new(hash)
|
110
|
+
values_for_database!(all_attribute_keys,
|
111
|
+
record.attributes.slice(*record.changed_attributes.keys).symbolize_keys)
|
112
|
+
elsif serializable_keys?
|
113
|
+
values_for_database!(all_attribute_keys,
|
114
|
+
hash)
|
115
|
+
else
|
116
|
+
hash
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
attr_reader :unique_index_keys, :unique_index_keys_to_s, :select_keys, :unique_db_primary_keys, :unique_db_indexes,
|
123
|
+
:primary_key, :arel_primary_key, :record_key_method, :pure_sql_records_fetching, :select_keys_indexes,
|
124
|
+
:batch_size, :batch_size_for_persisting, :model_class, :serializable_keys, :deserializable_keys, :pg_types, :table_name,
|
125
|
+
:q_table_name
|
126
|
+
|
127
|
+
# Saves the InventoryCollection
|
128
|
+
#
|
129
|
+
# @param association [Symbol] An existing association on manager
|
130
|
+
def save!(association)
|
131
|
+
attributes_index = {}
|
132
|
+
inventory_objects_index = {}
|
133
|
+
inventory_collection.each do |inventory_object|
|
134
|
+
attributes = inventory_object.attributes(inventory_collection)
|
135
|
+
index = build_stringified_reference(attributes, unique_index_keys)
|
136
|
+
|
137
|
+
attributes_index[index] = attributes
|
138
|
+
inventory_objects_index[index] = inventory_object
|
139
|
+
end
|
140
|
+
|
141
|
+
#_log.debug("Processing #{inventory_collection} of size #{inventory_collection.size}...")
|
142
|
+
# Records that are in the DB, we will be updating or deleting them.
|
143
|
+
ActiveRecord::Base.transaction do
|
144
|
+
association.find_each do |record|
|
145
|
+
index = build_stringified_reference_for_record(record, unique_index_keys)
|
146
|
+
|
147
|
+
next unless assert_distinct_relation(record.id)
|
148
|
+
next unless assert_unique_record(record, index)
|
149
|
+
|
150
|
+
inventory_object = inventory_objects_index.delete(index)
|
151
|
+
hash = attributes_index.delete(index)
|
152
|
+
|
153
|
+
if inventory_object.nil?
|
154
|
+
# Record was found in the DB but not sent for saving, that means it doesn't exist anymore and we should
|
155
|
+
# delete it from the DB.
|
156
|
+
delete_record!(record) if inventory_collection.delete_allowed?
|
157
|
+
else
|
158
|
+
# Record was found in the DB and sent for saving, we will be updating the DB.
|
159
|
+
update_record!(record, hash, inventory_object) if assert_referential_integrity(hash)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
unless inventory_collection.custom_reconnect_block.nil?
|
165
|
+
inventory_collection.custom_reconnect_block.call(inventory_collection, inventory_objects_index, attributes_index)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Records that were not found in the DB but sent for saving, we will be creating these in the DB.
|
169
|
+
if inventory_collection.create_allowed?
|
170
|
+
ActiveRecord::Base.transaction do
|
171
|
+
inventory_objects_index.each do |index, inventory_object|
|
172
|
+
hash = attributes_index.delete(index)
|
173
|
+
|
174
|
+
create_record!(hash, inventory_object) if assert_referential_integrity(hash)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
#_log.debug("Processing #{inventory_collection}, "\
|
179
|
+
# "created=#{inventory_collection.created_records.count}, "\
|
180
|
+
# "updated=#{inventory_collection.updated_records.count}, "\
|
181
|
+
# "deleted=#{inventory_collection.deleted_records.count}...Complete")
|
182
|
+
rescue => e
|
183
|
+
#_log.error("Error when saving #{inventory_collection} with #{inventory_collection_details}. Message: #{e.message}")
|
184
|
+
raise e
|
185
|
+
end
|
186
|
+
|
187
|
+
# @return [String] a string for logging purposes
|
188
|
+
def inventory_collection_details
|
189
|
+
"strategy: #{inventory_collection.strategy}, saver_strategy: #{inventory_collection.saver_strategy}, targeted: #{inventory_collection.targeted?}"
|
190
|
+
end
|
191
|
+
|
192
|
+
# @param record [ApplicationRecord] ApplicationRecord object
|
193
|
+
# @param key [Symbol] A key that is an attribute of the AR object
|
194
|
+
# @return [Object] Value of attribute name :key on the :record
|
195
|
+
def record_key(record, key)
|
196
|
+
record.public_send(key)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Deletes a complement of referenced data
|
200
|
+
def delete_complement
|
201
|
+
return unless inventory_collection.delete_allowed?
|
202
|
+
|
203
|
+
all_manager_uuids_size = inventory_collection.all_manager_uuids.size
|
204
|
+
|
205
|
+
#_log.debug("Processing :delete_complement of #{inventory_collection} of size "\
|
206
|
+
# "#{all_manager_uuids_size}...")
|
207
|
+
deleted_counter = 0
|
208
|
+
|
209
|
+
inventory_collection.db_collection_for_comparison_for_complement_of(
|
210
|
+
inventory_collection.all_manager_uuids
|
211
|
+
).find_in_batches do |batch|
|
212
|
+
ActiveRecord::Base.transaction do
|
213
|
+
batch.each do |record|
|
214
|
+
record.public_send(inventory_collection.delete_method)
|
215
|
+
deleted_counter += 1
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
#_log.debug("Processing :delete_complement of #{inventory_collection} of size "\
|
221
|
+
# "#{all_manager_uuids_size}, deleted=#{deleted_counter}...Complete")
|
222
|
+
end
|
223
|
+
|
224
|
+
# Deletes/soft-deletes a given record
|
225
|
+
#
|
226
|
+
# @param [ApplicationRecord] record we want to delete
|
227
|
+
def delete_record!(record)
|
228
|
+
record.public_send(inventory_collection.delete_method)
|
229
|
+
inventory_collection.store_deleted_records(record)
|
230
|
+
end
|
231
|
+
|
232
|
+
# @return [TrueClass] always return true, this method is redefined in default saver
|
233
|
+
def assert_unique_record(_record, _index)
|
234
|
+
# TODO(lsmola) can go away once we indexed our DB with unique indexes
|
235
|
+
true
|
236
|
+
end
|
237
|
+
|
238
|
+
# Check if relation provided is distinct, i.e. the relation should not return the same primary key value twice.
|
239
|
+
#
|
240
|
+
# @param primary_key_value [Bigint] primary key value
|
241
|
+
# @raise [Exception] if env is not production and relation is not distinct
|
242
|
+
# @return [Boolean] false if env is production and relation is not distinct
|
243
|
+
def assert_distinct_relation(primary_key_value)
|
244
|
+
if unique_db_primary_keys.include?(primary_key_value) # Include on Set is O(1)
|
245
|
+
# Change the InventoryCollection's :association or :arel parameter to return distinct results. The :through
|
246
|
+
# relations can return the same record multiple times. We don't want to do SELECT DISTINCT by default, since
|
247
|
+
# it can be very slow.
|
248
|
+
if false # TODO: Rails.env.production?
|
249
|
+
#_log.warn("Please update :association or :arel for #{inventory_collection} to return a DISTINCT result. "\
|
250
|
+
# " The duplicate value is being ignored.")
|
251
|
+
return false
|
252
|
+
else
|
253
|
+
raise("Please update :association or :arel for #{inventory_collection} to return a DISTINCT result. ")
|
254
|
+
end
|
255
|
+
else
|
256
|
+
unique_db_primary_keys << primary_key_value
|
257
|
+
end
|
258
|
+
true
|
259
|
+
end
|
260
|
+
|
261
|
+
# Check that the needed foreign key leads to real value. This check simulates NOT NULL and FOREIGN KEY constraints
|
262
|
+
# we should have in the DB. The needed foreign keys are identified as fixed_foreign_keys, which are the foreign
|
263
|
+
# keys needed for saving of the record.
|
264
|
+
#
|
265
|
+
# @param hash [Hash] data we want to save
|
266
|
+
# @raise [Exception] if env is not production and a foreign_key is missing
|
267
|
+
# @return [Boolean] false if env is production and a foreign_key is missing
|
268
|
+
def assert_referential_integrity(hash)
|
269
|
+
inventory_collection.fixed_foreign_keys.each do |x|
|
270
|
+
next unless hash[x].nil?
|
271
|
+
subject = "#{hash} of #{inventory_collection} because of missing foreign key #{x} for "\
|
272
|
+
"#{inventory_collection.parent.class.name}:"\
|
273
|
+
"#{inventory_collection.parent.try(:id)}"
|
274
|
+
if false # TODO: Rails.env.production?
|
275
|
+
#_log.warn("Referential integrity check violated, ignoring #{subject}")
|
276
|
+
return false
|
277
|
+
else
|
278
|
+
raise("Referential integrity check violated for #{subject}")
|
279
|
+
end
|
280
|
+
end
|
281
|
+
true
|
282
|
+
end
|
283
|
+
|
284
|
+
# @return [Time] A rails friendly time getting config from ActiveRecord::Base.default_timezone (can be :local
|
285
|
+
# or :utc)
|
286
|
+
def time_now
|
287
|
+
if ActiveRecord::Base.default_timezone == :utc
|
288
|
+
Time.now.utc
|
289
|
+
else
|
290
|
+
Time.zone.now
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Enriches data hash with timestamp columns
|
295
|
+
#
|
296
|
+
# @param hash [Hash] data hash
|
297
|
+
# @param update_time [Time] data hash
|
298
|
+
def assign_attributes_for_update!(hash, update_time)
|
299
|
+
hash[:type] = model_class.name if supports_sti? && hash[:type].nil?
|
300
|
+
hash[:updated_on] = update_time if supports_updated_on?
|
301
|
+
hash[:updated_at] = update_time if supports_updated_at?
|
302
|
+
end
|
303
|
+
|
304
|
+
# Enriches data hash with timestamp and type columns
|
305
|
+
#
|
306
|
+
# @param hash [Hash] data hash
|
307
|
+
# @param create_time [Time] data hash
|
308
|
+
def assign_attributes_for_create!(hash, create_time)
|
309
|
+
hash[:created_on] = create_time if supports_created_on?
|
310
|
+
hash[:created_at] = create_time if supports_created_at?
|
311
|
+
assign_attributes_for_update!(hash, create_time)
|
312
|
+
end
|
313
|
+
|
314
|
+
def internal_columns
|
315
|
+
@internal_columns ||= inventory_collection.internal_columns
|
316
|
+
end
|
317
|
+
|
318
|
+
# Finds an index that fits the list of columns (keys) the best
|
319
|
+
#
|
320
|
+
# @param keys [Array<Symbol>]
|
321
|
+
# @raise [Exception] if the unique index for the columns was not found
|
322
|
+
# @return [ActiveRecord::ConnectionAdapters::IndexDefinition] unique index fitting the keys
|
323
|
+
def unique_index_for(keys)
|
324
|
+
inventory_collection.unique_index_for(keys)
|
325
|
+
end
|
326
|
+
|
327
|
+
# @return [Array<Symbol>] all columns that are part of the best fit unique index
|
328
|
+
def unique_index_columns
|
329
|
+
@unique_index_columns ||= inventory_collection.unique_index_columns
|
330
|
+
end
|
331
|
+
|
332
|
+
# @return [Array<String>] all columns that are part of the best fit unique index
|
333
|
+
def unique_index_columns_to_s
|
334
|
+
return @unique_index_columns_to_s if @unique_index_columns_to_s
|
335
|
+
|
336
|
+
@unique_index_columns_to_s = unique_index_columns.map(&:to_s)
|
337
|
+
end
|
338
|
+
|
339
|
+
# @return [Boolean] true if the model_class supports STI
|
340
|
+
def supports_sti?
|
341
|
+
@supports_sti_cache ||= inventory_collection.supports_sti?
|
342
|
+
end
|
343
|
+
|
344
|
+
# @return [Boolean] true if the model_class has created_on column
|
345
|
+
def supports_created_on?
|
346
|
+
@supports_created_on_cache ||= inventory_collection.supports_created_on?
|
347
|
+
end
|
348
|
+
|
349
|
+
# @return [Boolean] true if the model_class has updated_on column
|
350
|
+
def supports_updated_on?
|
351
|
+
@supports_updated_on_cache ||= inventory_collection.supports_updated_on?
|
352
|
+
end
|
353
|
+
|
354
|
+
# @return [Boolean] true if the model_class has created_at column
|
355
|
+
def supports_created_at?
|
356
|
+
@supports_created_at_cache ||= inventory_collection.supports_created_at?
|
357
|
+
end
|
358
|
+
|
359
|
+
# @return [Boolean] true if the model_class has updated_at column
|
360
|
+
def supports_updated_at?
|
361
|
+
@supports_updated_at_cache ||= inventory_collection.supports_updated_at?
|
362
|
+
end
|
363
|
+
|
364
|
+
# @return [Boolean] true if any serializable keys are present
|
365
|
+
def serializable_keys?
|
366
|
+
@serializable_keys_bool_cache ||= serializable_keys.present?
|
367
|
+
end
|
368
|
+
|
369
|
+
# @return [Boolean] true if the model_class has resource_timestamp column
|
370
|
+
def supports_remote_data_timestamp?(all_attribute_keys)
|
371
|
+
all_attribute_keys.include?(:resource_timestamp) # include? on Set is O(1)
|
372
|
+
end
|
373
|
+
|
374
|
+
# @return [Boolean] true if the model_class has resource_version column
|
375
|
+
def supports_remote_data_version?(all_attribute_keys)
|
376
|
+
all_attribute_keys.include?(:resource_version) # include? on Set is O(1)
|
377
|
+
end
|
378
|
+
|
379
|
+
# @return [Boolean] true if the model_class has resource_timestamps column
|
380
|
+
def supports_resource_timestamps_max?
|
381
|
+
@supports_resource_timestamps_max_cache ||= inventory_collection.supports_resource_timestamps_max?
|
382
|
+
end
|
383
|
+
|
384
|
+
# @return [Boolean] true if the model_class has resource_versions column
|
385
|
+
def supports_resource_versions_max?
|
386
|
+
@supports_resource_versions_max_cache ||= inventory_collection.supports_resource_versions_max?
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|