module Sequel::Plugins::Bitemporal
Constants
- THREAD_NOW_KEY
- THREAD_POINT_IN_TIME_KEY
Public Class Methods
as_we_knew_it(time) { || ... }
click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 7 def self.as_we_knew_it(time) previous = Thread.current[THREAD_POINT_IN_TIME_KEY] raise ArgumentError, "requires a block" unless block_given? Thread.current[THREAD_POINT_IN_TIME_KEY] = time.to_datetime yield ensure Thread.current[THREAD_POINT_IN_TIME_KEY] = previous end
at(time) { || ... }
click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 21 def self.at(time) previous = Thread.current[THREAD_NOW_KEY] raise ArgumentError, "requires a block" unless block_given? Thread.current[THREAD_NOW_KEY] = time.to_datetime yield ensure Thread.current[THREAD_NOW_KEY] = previous end
bitemporal_excluded_columns(master = nil)
click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 44 def self.bitemporal_excluded_columns(master = nil) [:id, *bitemporal_version_columns(master)] end
bitemporal_version_columns(master = nil)
click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 40 def self.bitemporal_version_columns(master = nil) [*version_foreign_keys(master), :valid_from, :valid_to, :created_at, :expired_at] end
configure(master, opts = {})
click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 48 def self.configure(master, opts = {}) version = opts[:version_class] raise Error, "please specify version class to use for bitemporal plugin" unless version return version.db.log_info("Version table does not exist for #{version.name}") unless version.db.table_exists?(version.table_name) missing = bitemporal_version_columns(master) - version.columns raise Error, "bitemporal plugin requires the following missing column#{"s" if missing.size>1} on version class: #{missing.join(", ")}" unless missing.empty? if Sequel::Plugins::Bitemporal.jdbc?(master.db) master.plugin :typecast_on_load, *master.columns end # Sequel::Model.def_dataset_method has been deprecated and moved to the # def_dataset_method plugin in Sequel 4.46.0, it was not loaded by # default from 5.0 begin master.plugin :def_dataset_method rescue LoadError end # Sequel::Model#set_all has been deprecated and moved to the whitelist # security plugin in Sequel 4.46.0, it was not loaded by default from 5.0 begin master.plugin :whitelist_security version.plugin :whitelist_security rescue LoadError end master.instance_eval do @version_class = version base_alias = opts.fetch :base_alias do name ? underscore(demodulize(name)) : table_name end @versions_alias = "#{base_alias}_versions".to_sym @current_version_alias = "#{base_alias}_current_version".to_sym @audit_class = opts[:audit_class] @audit_updated_by_method = opts.fetch(:audit_updated_by_method){ :updated_by } @propagate_per_column = opts.fetch(:propagate_per_column, false) @version_uses_string_nilifier = version.plugins.map(&:to_s).include? "Sequel::Plugins::StringNilifier" @excluded_columns = Sequel::Plugins::Bitemporal.bitemporal_excluded_columns(master) @excluded_columns += Array opts[:excluded_columns] if opts[:excluded_columns] @use_ranges = if opts[:ranges] db = self.db unless db.database_type==:postgres && db.server_version >= 90200 raise "Ranges require PostgreSQL 9.2" end true else false end end master.class_eval do def self.current_versions_dataset t = ::Sequel::Plugins::Bitemporal.point_in_time n = ::Sequel::Plugins::Bitemporal.now version_class.where do (created_at <= t) & (Sequel.|({expired_at=>nil}, expired_at > t)) & (valid_from <= n) & (valid_to > n) end end end master.one_to_many :versions, class: version, key: version_foreign_keys(master), graph_alias_base: master.versions_alias master.one_to_one :current_version, class: version, key: version_foreign_keys(master), graph_alias_base: master.current_version_alias, :graph_block=>(proc do |j, lj, js| t = Sequel.delay{ ::Sequel::Plugins::Bitemporal.point_in_time } n = Sequel.delay{ ::Sequel::Plugins::Bitemporal.now } if master.use_ranges master.existence_range_contains(t, j) & master.validity_range_contains(n, j) else e = Sequel.qualify j, :expired_at (Sequel.qualify(j, :created_at) <= t) & (Sequel.|({e=>nil}, e > t)) & (Sequel.qualify(j, :valid_from) <= n) & (Sequel.qualify(j, :valid_to) > n) end end) do |ds| t = Sequel.delay{ ::Sequel::Plugins::Bitemporal.point_in_time } n = Sequel.delay{ ::Sequel::Plugins::Bitemporal.now } if master.use_ranges ds.where(master.existence_range_contains(t) & master.validity_range_contains(n)) else ds.where do (created_at <= t) & (Sequel.|({expired_at=>nil}, expired_at > t)) & (valid_from <= n) & (valid_to > n) end end end master.def_dataset_method :with_current_version do eager_graph(:current_version).where( Sequel.negate( Sequel.qualify(model.current_version_alias, :id) => nil ) ) end master.one_to_many :current_or_future_versions, class: version, key: version_foreign_keys(master), :graph_block=>(proc do |j, lj, js| t = Sequel.delay{ ::Sequel::Plugins::Bitemporal.point_in_time } n = Sequel.delay{ ::Sequel::Plugins::Bitemporal.now } if master.use_ranges master.existence_range_contains(t, j) & (Sequel.qualify(j, :valid_to) > n) & (Sequel.qualify(j, :valid_from) != Sequel.qualify(j, :valid_to)) else e = Sequel.qualify j, :expired_at (Sequel.qualify(j, :created_at) <= t) & Sequel.|({e=>nil}, e > t) & (Sequel.qualify(j, :valid_to) > n) & (Sequel.qualify(j, :valid_from) != Sequel.qualify(j, :valid_to)) end end) do |ds| t = Sequel.delay{ ::Sequel::Plugins::Bitemporal.point_in_time } n = Sequel.delay{ ::Sequel::Plugins::Bitemporal.now } if master.use_ranges existence_conditions = master.existence_range_contains t ds.where{ existence_conditions & (:valid_to > n) & (:valid_from != :valid_to) } else ds.where do (created_at <= t) & Sequel.|({expired_at=>nil}, expired_at > t) & (valid_to > n) & (valid_from != valid_to) end end end master.def_dataset_method :with_current_or_future_versions do eager_graph(:current_or_future_versions).where( Sequel.negate(Sequel.qualify(:current_or_future_versions, :id) => nil) ) end version.many_to_one :master, class: master, key: version_foreign_keys(master) version.class_eval do if Sequel::Plugins::Bitemporal.jdbc?(master.db) plugin :typecast_on_load, *columns end def current? t = ::Sequel::Plugins::Bitemporal.point_in_time n = ::Sequel::Plugins::Bitemporal.now !new? && created_at.to_datetime<=t && (expired_at.nil? || expired_at.to_datetime>t) && valid_from.to_datetime<=n && valid_to.to_datetime>n end def destroy(opts={}) expand_previous_version = opts.fetch(:expand_previous_version){ valid_from.to_datetime>::Sequel::Plugins::Bitemporal.now } master.destroy_version self, expand_previous_version end end unless opts[:delegate]==false (version.columns-master.columns-master.excluded_columns).each do |column| master.class_eval <<-EOS, __FILE__, __LINE__ + 1 def #{column} pending_or_current_version.#{column} if pending_or_current_version end EOS end end if opts[:writers] (version.columns-master.columns-master.excluded_columns).each do |column| master.class_eval <<-EOS, __FILE__, __LINE__ + 1 def #{column}=(value) self.attributes = {"#{column}" => value} end EOS end end end
current_versions_dataset()
click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 98 def self.current_versions_dataset t = ::Sequel::Plugins::Bitemporal.point_in_time n = ::Sequel::Plugins::Bitemporal.now version_class.where do (created_at <= t) & (Sequel.|({expired_at=>nil}, expired_at > t)) & (valid_from <= n) & (valid_to > n) end end
jdbc?(db)
click to toggle source
# File lib/sequel_bitemporal.rb, line 10 def self.jdbc?(db) db.adapter_scheme==:jdbc end
jruby?()
click to toggle source
# File lib/sequel_bitemporal.rb, line 6 def self.jruby? (defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby') || defined?(JRUBY_VERSION) end
now()
click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 30 def self.now Thread.current[THREAD_NOW_KEY] || DateTime.now end
point_in_time()
click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 16 def self.point_in_time Thread.current[THREAD_POINT_IN_TIME_KEY] || DateTime.now end
version_foreign_keys(master = nil)
click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 34 def self.version_foreign_keys(master = nil) return :master_id unless master primary_key = [*master.primary_key] primary_key.size > 1 ? primary_key : :master_id end
Public Instance Methods
current?()
click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 184 def current? t = ::Sequel::Plugins::Bitemporal.point_in_time n = ::Sequel::Plugins::Bitemporal.now !new? && created_at.to_datetime<=t && (expired_at.nil? || expired_at.to_datetime>t) && valid_from.to_datetime<=n && valid_to.to_datetime>n end
destroy(opts={})
click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 193 def destroy(opts={}) expand_previous_version = opts.fetch(:expand_previous_version){ valid_from.to_datetime>::Sequel::Plugins::Bitemporal.now } master.destroy_version self, expand_previous_version end