# frozen_string_literal: true module SuperSettings module Storage # ActiveRecord implementation of the SuperSettings::Storage model. # # To use this model, you must run the migration included with the gem. If the gem # is mounted as an engine in a Rails applicationThe migration can be installed with # # @example # rake app:super_settings:install:migrations class ActiveRecordStorage # Base class that the models extend from. class ApplicationRecord < ActiveRecord::Base self.abstract_class = true end class Model < ApplicationRecord self.table_name = "super_settings" has_many :history_items, class_name: "SuperSettings::Storage::ActiveRecordStorage::HistoryModel", foreign_key: :key, primary_key: :key end class HistoryModel < ApplicationRecord self.table_name = "super_settings_histories" # Since these models are created automatically on a callback, ensure that the data will # fit into the database columns since we can't handle any validation errors. before_validation do self.changed_by = changed_by.to_s[0, 150] if changed_by.present? end end include Storage class << self def all if Model.table_exists? Model.all.collect { |model| new(model) } else [] end end def active if Model.table_exists? Model.where(deleted: false).collect { |model| new(model) } else [] end end def updated_since(time) if Model.table_exists? Model.where("updated_at > ?", time).collect { |model| new(model) } else [] end end def find_by_key(key) model = Model.where(deleted: false).find_by(key: key) if Model.table_exists? new(model) if model end def last_updated_at if Model.table_exists? Model.maximum(:updated_at) end end def with_connection(&block) Model.connection_pool.with_connection(&block) end def transaction(&block) Model.transaction(&block) end protected # Only load settings asynchronously if there is an extra database connection left in the # connection pool or if the configuration has explicitly allowed it. def default_load_asynchronous? Model.connection_pool.size > Thread.list.size end end delegate :key, :key=, :raw_value, :raw_value=, :value_type, :value_type=, :description, :description=, :deleted?, :deleted=, :updated_at, :updated_at=, :created_at, :created_at=, :persisted?, to: :@model def initialize(attributes = {}) @model = if attributes.is_a?(Model) attributes else Model.new(attributes) end end def save! begin @model.save! rescue ActiveRecord::RecordNotUnique => e # Gracefully handle duplicate key constraint on the database; in this case the existing # record should be updated. duplicate = @model.class.find_by(key: @model.key) raise e if duplicate == @model duplicate.raw_value = @model.raw_value duplicate.value_type = @model.value_type duplicate.description = @model.description duplicate.deleted = false @model.transaction do if @model.persisted? @model.reload.update!(deleted: true) end duplicate.save! end @model = duplicate end end def history(limit: nil, offset: 0) finder = @model.history_items.order(id: :desc).offset(offset) finder = finder.limit(limit) if limit finder.collect do |record| HistoryItem.new(key: key, value: record.value, changed_by: record.changed_by, created_at: record.created_at, deleted: record.deleted?) end end def create_history(changed_by:, created_at:, value: nil, deleted: false) history_attributes = {value: value, deleted: deleted, changed_by: changed_by, created_at: created_at} if @model.persisted? @model.history_items.create!(history_attributes) else @model.history_items.build(history_attributes) end end end end end