class RnDB::Table
Attributes
Public Class Methods
Create a new record wit the given ID.
# File lib/rndb/table.rb, line 10 def initialize(id) _validate! @id = id end
Private Class Methods
Return a new record corresponding to the specified index.
# File lib/rndb/table.rb, line 79 def [](index) _validate! new(index) if index < count end
# File lib/rndb/table.rb, line 224 def _db Thread.current[:rndb_database] end
# File lib/rndb/table.rb, line 310 def _get_state(id, attribute) _schema[:state][id] && _schema[:state][id][attribute] end
# File lib/rndb/table.rb, line 272 def _migrate(size) raise "table already migrated" unless _schema[:class].nil? ids = Thicket.new(0...size) _schema[:columns].each_value do |column| distribution = column[:distribution] next if distribution.nil? if distribution.is_a?(Array) distribution.each do |context| thicket = ids & _query(context[:where], size) _migrate_column(column, thicket, context[:stats]) end else raise "distribution must sum to unity" unless distribution.values.sum == 1 _migrate_column(column, ids, distribution) end ids = column[:mapping].values.reduce(Thicket.new) do |thicket, other| thicket | other end end _schema[:size] = size _schema[:class] = self end
# File lib/rndb/table.rb, line 262 def _migrate_column(column, ids, distribution) min = 0.0 distribution.each do |value, probability| max = min + probability column[:mapping][value] ||= Thicket.new column[:mapping][value] |= ids * (min..max) min = max end end
# File lib/rndb/table.rb, line 249 def _query(constraints, size) ids = Thicket.new(0...size) constraints.each do |attribute, values| column = _schema[:columns][attribute] raise "no mapping for column" if column[:mapping].empty? other = Array(values).reduce(Thicket.new) do |thicket, value| thicket | column[:mapping][value] end ids &= other end ids end
# File lib/rndb/table.rb, line 228 def _schema Thread.current[:rndb_tables] ||= Hash.new do |tables, name| tables[name] = { class: nil, size: 0, columns: Hash.new do |columns, key| columns[key] = { distribution: nil, mapping: {}, generator: nil } end, associations: Hash.new do |associations, key| associations[key] = nil end, state: {} } end Thread.current[:rndb_tables][table_name] end
# File lib/rndb/table.rb, line 296 def _seed_prng(id, attribute) tuple = [_db.seed, table_name, attribute, id].join('-') digest = Digest::SHA256.hexdigest(tuple) value = digest.to_i(16) % 18_446_744_073_709_551_616 _db.prng.srand(value) Faker::Config.random = _db.prng value end
# File lib/rndb/table.rb, line 314 def _set_state(id, attribute, value) _schema[:state][id] ||= {} _schema[:state][id][attribute] = value end
# File lib/rndb/table.rb, line 305 def _validate! @valid ||= (self == _schema[:class]) raise "table not added to database" unless @valid end
Return all records.
# File lib/rndb/table.rb, line 91 def all where end
Add an association between two Table
models.
# File lib/rndb/table.rb, line 150 def association(attribute, *args) args.each do |arg| _schema[:associations][attribute] = arg end define_method("#{attribute}_id".to_sym) do _generate_association_id(attribute) end define_method(attribute) do _generate_association(attribute) end end
Add a new column to the Table
model.
# File lib/rndb/table.rb, line 121 def column(attribute, *args) column = _schema[:columns][attribute] args.each do |arg| index = case arg when Hash, Array :distribution when Proc :generator else raise "unsupported column parameter" end column[index] = arg end if column[:generator] && column[:distribution] define_method("#{attribute}_key") do _generate_column_key(attribute) end end define_method(attribute) do _generate_column(attribute) end define_method("#{attribute}=".to_sym) do |value| @_attributes&.delete(attribute) self.class.send(:_set_state, id, attribute, value) end end
Count all records, delegating this to the all Query
.
# File lib/rndb/table.rb, line 96 def count all.count end
Iterate over all records, delegating this to the all Query
# File lib/rndb/table.rb, line 106 def each(&block) all.each(&block) end
# File lib/rndb/table.rb, line 211 def get(attribute) raise unless @current if _schema[:columns].key?(attribute) value(@current, attribute) elsif _schema[:associations].key?(attribute) join(@current, attribute) else raise "no such attribute" end end
Return the instance joined to the current ID.
# File lib/rndb/table.rb, line 202 def join(id, name) @current = id _schema[:associations][name].each do |context| next unless (index = where(context[:where]).index(id)) return where(context[:joins])[index] end nil end
Retrieve the key that can be queried on for generated attributes.
# File lib/rndb/table.rb, line 170 def key(id, attribute) @current = id _validate! column = _schema[:columns][attribute] return if column[:distribution].nil? column[:mapping].find do |_, ids| ids.include?(id) end&.first end
Return the last record, to be consistent with first, which we get by magic.
# File lib/rndb/table.rb, line 101 def last all.last end
Pluck specified attributes from all records, delegating this to the all query.
# File lib/rndb/table.rb, line 111 def pluck(*args) all.pluck(args) end
Generate a random number, intended to be used in lambdas. The number will have been seeded appropriately to ensure determinism.
# File lib/rndb/table.rb, line 164 def rand(*args) _validate! _db.prng.rand(*args) end
Return a Querty that contains a random sampling of records.
# File lib/rndb/table.rb, line 116 def sample(limit=1) all.sample(limit) end
Return the name of the table, which is derived from the class name.
# File lib/rndb/table.rb, line 74 def table_name name.downcase.to_sym end
Retrieve the value of the given attribute for the given ID.
# File lib/rndb/table.rb, line 181 def value(id, attribute) @current = id _validate! return id if attribute == :id override = _get_state(id, attribute) return override unless override.nil? column = _schema[:columns][attribute] value = key(id, attribute) unless column[:generator].nil? _seed_prng(id, attribute) value = if column[:distribution].nil? column[:generator].call else column[:generator].call(value) end end value end
Return a Query
that matches the supplied constraints
# File lib/rndb/table.rb, line 85 def where(constraints={}) _validate! Query.new(self, _query(constraints, _schema[:size])) end
Public Instance Methods
Generate all attributes, which may be expensive.
# File lib/rndb/table.rb, line 16 def attributes _generate_all end
Return the attributes as a hash.
# File lib/rndb/table.rb, line 21 def to_h attributes end
Return a stringified version of the attributes hash.
# File lib/rndb/table.rb, line 26 def to_s to_h.to_s end
Private Instance Methods
# File lib/rndb/table.rb, line 32 def _generate_all _schema[:columns].each do |name, column| _generate_column_key(name) if column[:generator] && column[:distribution] _generate_column(name) end _schema[:associations].each_key do |name| _generate_association_id(name) end @_attributes end
# File lib/rndb/table.rb, line 58 def _generate_association(name) self.class.join(@id, name) end
# File lib/rndb/table.rb, line 53 def _generate_association_id(name) @_attributes ||= { id: @id } @_attributes["#{name}_id".to_sym] ||= _generate_association(name)&.id end
# File lib/rndb/table.rb, line 48 def _generate_column(name) @_attributes ||= { id: @id } @_attributes[name] ||= self.class.value(@id, name) end
# File lib/rndb/table.rb, line 43 def _generate_column_key(name) @_attributes ||= { id: @id } @_attributes["#{name}_key".to_sym] ||= self.class.key(@id, name) end
# File lib/rndb/table.rb, line 66 def _schema self.class.send(:_schema) end
# File lib/rndb/table.rb, line 62 def _validate! self.class.send(:_validate!) end