Class: LowCardTables::LowCardTable::RowManager

Inherits:
Object
  • Object
show all
Defined in:
lib/low_card_tables/low_card_table/row_manager.rb

Overview

In many ways, the RowManager is the beating heart of low_card_tables. It is responsible for finding and creating rows in low-card tables, as well as maintaining the unique index across all columns in the table and dealing with any needs from migrations.

Because this class is quite complex, some pieces of functionality have been broken out into other classes. The TableUniqueIndex is responsible for maintaining the unique index across all columns in the table, and the RowCollapser handles the case where rows need to be collapsed (unified) because a column was removed from the low-card table.

Cache Notifications

This class uses the ActiveSupport::Notifications interface to notify anyone who’s interested of cache-related events. In particular, it fires the following events with the following payloads:

low_card_tables.cache_load

{ :low_card_model => <ActiveRecord model class> }; this is fired when the cache is loaded from the database, whether that’s the first time after startup or after a cache flush.

low_card_tables.cache_flush

{ :low_card_model => <ActiveRecord model class>, :reason => <some reason> }; this is fired when there’s a cache that is flushed. Additional payload depends on the :reason.

Reasons for low_card_tables.cache_flush include:

:manually_requested

You called low_card_flush_cache! on the low-card model.

:id_not_found

You requested a low-card row by ID, and we didn’t find that ID in the cache. We assume that the ID is likely valid and that it’s simply been created since we retrieved the cache from the database, so we flush the cache and try again. :ids is present in the payload, mapping to an array of one or more IDs – the ID or IDs that weren’t found in the cache.

:collapse_rows_and_update_referrers

The low-card table has been migrated and has had a column removed; we’ve collapsed any now-duplicate rows properly. As such, we need to flush the cache.

:schema_change

We have detected that the schema of the low-card table has changed, and need to flush the cache.

:creating_rows

We’re about to create one or more new rows in the low-card table, because a set of attributes that has never been seen before was asked for. Before we actually go try to create them, we lock the table and flush the cache, so that, in the case where some other process has already created them, we simply pick them up now. Then, after we create them, we flush the cache again to pick up the newly-created rows. :context is present in the payload, mapped to either :before_import or :after_import (corresponding to the two situations above). :new_rows is also present in the payload, mapped to an array of one or more Hashes, each of which represents a unique combination of attributes to be created.

:stale

By far the most common case – the cache is simply stale based upon the current cache-expiration policy, and needs to be reloaded. The payload will contain :loaded, which is the time that the cache was loaded, and :now, which is the time at which the cache was checked for validity. (:now will always be very close to, but not after, the current time; any delay is just due to the time it took to receive the notification via ActiveSupport::Notifications.)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(low_card_model) ⇒ RowManager

Creates a new instance for the given low-card model.



58
59
60
61
62
63
64
65
66
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 58

def initialize(low_card_model)
  unless low_card_model.respond_to?(:is_low_card_table?) && low_card_model.is_low_card_table?
    raise ArgumentError, "You must supply a low-card AR model class, not: #{low_card_model.inspect}"
  end

  @low_card_model = low_card_model
  @table_unique_index = LowCardTables::LowCardTable::TableUniqueIndex.new(low_card_model)
  @referring_models = [ ]
end

Instance Attribute Details

#low_card_modelObject (readonly)

Returns the value of attribute low_card_model.



55
56
57
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 55

def low_card_model
  @low_card_model
end

#referring_modelsObject (readonly)

Returns the value of attribute referring_models.



68
69
70
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 68

def referring_models
  @referring_models
end

Instance Method Details

#all_rowsObject

Returns all rows in the low-card table. This behaves semantically identically to simply calling ActiveRecord’s #all method on the low-card table itself, but it returns the data from cache.



92
93
94
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 92

def all_rows
  cache.all_rows
end

#collapse_rows_and_update_referrers!(low_card_options = { }) ⇒ Object

Iterates through this table, finding duplicate rows and collapsing them. See RowCollapser for far more information.



197
198
199
200
201
202
203
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 197

def collapse_rows_and_update_referrers!(low_card_options = { })
  collapser = LowCardTables::LowCardTable::RowCollapser.new(@low_card_model, low_card_options)
  collapse_map = collapser.collapse!

  flush!(:collapse_rows_and_update_referrers)
  collapse_map
end

#column_information_reset!Object

Tells us that someone called #reset_column_information on the low-card table; we’ll inform all referring models of that fact.



86
87
88
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 86

def column_information_reset!
  @referring_models.each { |m| m._low_card_associations_manager.low_card_column_information_reset!(@low_card_model) }
end

#ensure_has_unique_index!(create_if_needed = false) ⇒ Object

If this table already has the correct unique index across all value columns, does nothing.

If this table does not have the correct unique index, and create_if_needed is truthy, then creates the index. If this table does not have the correct unique index, and create_if_needed is falsy, then raises LowCardTables::Errors::LowCardNoUniqueIndexError.



210
211
212
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 210

def ensure_has_unique_index!(create_if_needed = false)
  @table_unique_index.ensure_present!(create_if_needed)
end

#find_ids_for(hash_hashes_object_or_objects) ⇒ Object

Behaves identically to #find_rows_for, except that it returns IDs instead of rows.



174
175
176
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 174

def find_ids_for(hash_hashes_object_or_objects)
  row_map_to_id_map(find_rows_for(hash_hashes_object_or_objects))
end

#find_or_create_ids_for(hash_hashes_object_or_objects) ⇒ Object

Behaves identically to #find_or_create_rows_for, except that it returns IDs instead of rows.



179
180
181
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 179

def find_or_create_ids_for(hash_hashes_object_or_objects)
  row_map_to_id_map(find_or_create_rows_for(hash_hashes_object_or_objects))
end

#find_or_create_rows_for(hash_hashes_object_or_objects) ⇒ Object

Given a single Hash specifying values for every column in the low-card table, returns an instance of the low-card table for that combination of values. The row in question will be created if it doesn’t already exist.

Given an array of Hashes, each specifying values for every column in the low-card table, returns a Hash mapping each of those Hashes to an instance of the low-card table for that combination of values. Rows for any missing combinations of values will be created. (Creation is done in bulk, using activerecord_import, so this method will be fast no matter how many rows need to be created.)



169
170
171
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 169

def find_or_create_rows_for(hash_hashes_object_or_objects)
  do_find_or_create(hash_hashes_object_or_objects, true)
end

#find_rows_for(hash_hashes_object_or_objects) ⇒ Object

Given a single Hash specifying values for every column in the low-card table, returns an instance of the low-card table, already existing in the database, for that combination of values.

Given an array of Hashes, each specifying values for every column in the low-card table, returns a Hash mapping each of those Hashes to an instance of the low-card table, already existing in the database, for that combination of values.

If you request an instance for a combination of values that doesn’t exist in the table, it will simply be mapped to nil. Under no circumstances will rows be added to the database.



157
158
159
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 157

def find_rows_for(hash_hashes_object_or_objects)
  do_find_or_create(hash_hashes_object_or_objects, false)
end

#flush_cache!Object

Flushes the cache immediately (assuming we have any cached data at all).



97
98
99
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 97

def flush_cache!
  flush!(:manually_requested)
end

#ids_matching(hash_or_hashes = nil, &block) ⇒ Object

Given a single Hash specifying zero or more constraints for low-card rows (i.e., mapping zero or more columns of the low-card table to specific values for those columns), returns a (possibly empty) Array of IDs of low-card rows that match those constraints.

Given an array of one or more Hashes, each of which specify zero or more constraints for low-card rows, returns a Hash mapping each of those Hashes to a (possibly empty) Array of IDs of low-card rows that match each Hash.

Given a block (in which case no hashes may be passed), returns an Array of IDs of low-card rows that match the block. The block is passed an instance of the low-card model class, and the return value of the block (truthy or falsy) determines whether the ID of that row is included in the return value or not.



129
130
131
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 129

def ids_matching(hash_or_hashes = nil, &block)
  do_matching(hash_or_hashes, block, :ids_matching)
end

#referred_to_by(referring_model_class) ⇒ Object

Tells us that the low-card model we’re operating on behalf of is referenced by the given referring_model_class. This referring_model_class should be an ActiveRecord class that has declared ‘has_low_card_table’ on this low-card table.

We keep track of this and expose it for a few reasons:

  • If we need to collapse the rows in this low-card table because a column has been removed, we use this list of referring models to know which columns have a foreign key to this table;

  • When someone calls #reset_column_information on the low-card table, we re-compute (and re-install) the set of delegated methods from all models that refer to this low-card table.



80
81
82
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 80

def referred_to_by(referring_model_class)
  @referring_models |= [ referring_model_class ]
end

#remove_unique_index!Object

If this table currently has a unique index across all value columns, removes it.



215
216
217
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 215

def remove_unique_index!
  @table_unique_index.remove!
end

#row_for_id(id) ⇒ Object

A synonym for #rows_for_ids.



114
115
116
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 114

def row_for_id(id)
  rows_for_ids(id)
end

#rows_for_ids(id_or_ids) ⇒ Object

Given a single primary-key ID of a low-card row, returns the row for that ID. Given an Array of one or more primary-key IDs, returns a Hash mapping each of those IDs to the corresponding row. Properly flushes the cache and tries again if given an ID that doesn’t exist in cache.



104
105
106
107
108
109
110
111
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 104

def rows_for_ids(id_or_ids)
  begin
    cache.rows_for_ids(id_or_ids)
  rescue LowCardTables::Errors::LowCardIdNotFoundError => lcinfe
    flush!(:id_not_found, :ids => lcinfe.ids)
    cache.rows_for_ids(id_or_ids)
  end
end

#rows_matching(hash_or_hashes = nil, &block) ⇒ Object

Given a single Hash specifying zero or more constraints for low-card rows (i.e., mapping zero or more columns of the low-card table to specific values for those columns), returns a (possibly empty) Array of low-card rows that match those constraints.

Given an array of one or more Hashes, each of which specify zero or more constraints for low-card rows, returns a Hash mapping each of those Hashes to a (possibly empty) Array of low-card rows that match each Hash.

Given a block (in which case no hashes may be passed), returns an Array of low-card rows that match the block. The block is passed an instance of the low-card model class, and the return value of the block (truthy or falsy) determines whether that row is included in the return value or not.



144
145
146
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 144

def rows_matching(hash_or_hashes = nil, &block)
  do_matching(hash_or_hashes, block, :rows_matching)
end

#value_column_namesObject

Returns the set of columns on the low-card table that we should consider “value columns” – i.e., those that contain data values, rather than metadata, like the primary key, created_at/updated_at, and so on.

Columns that are excluded:

  • The primary key

  • created_at and updated_at

  • Any additional columns specified using the :exclude_column_names option when declaring is_low_card_table.



191
192
193
# File 'lib/low_card_tables/low_card_table/row_manager.rb', line 191

def value_column_names
  value_columns.map(&:name)
end