inventory_refresh 0.0.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/.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
|