hoardable 0.14.2 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,47 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails/generators'
3
+ require "rails/generators"
4
4
 
5
5
  module Hoardable
6
6
  # Generates an initializer file for {Hoardable} configuration and a migration with a PostgreSQL
7
7
  # function.
8
8
  class InstallGenerator < Rails::Generators::Base
9
- source_root File.expand_path('templates', __dir__)
9
+ source_root File.expand_path("templates", __dir__)
10
10
  include Rails::Generators::Migration
11
- delegate :supports_schema_enums?, to: :class
12
11
 
13
12
  def create_initializer_file
14
- create_file(
15
- 'config/initializers/hoardable.rb',
16
- <<~TEXT
17
- # Hoardable configuration defaults are below. Learn more at https://github.com/waymondo/hoardable#configuration
18
- #
19
- # Hoardable.enabled = true
20
- # Hoardable.version_updates = true
21
- # Hoardable.save_trash = true
22
- TEXT
23
- )
24
- end
25
-
26
- def change_schema_format_to_sql
27
- return if supports_schema_enums?
28
-
29
- application 'config.active_record.schema_format = :sql'
13
+ create_file("config/initializers/hoardable.rb", <<~TEXT)
14
+ # Hoardable configuration defaults are below. Learn more at https://github.com/waymondo/hoardable#configuration
15
+ #
16
+ # Hoardable.enabled = true
17
+ # Hoardable.version_updates = true
18
+ # Hoardable.save_trash = true
19
+ TEXT
30
20
  end
31
21
 
32
22
  def create_migration_file
33
- migration_template 'install.rb.erb', 'db/migrate/install_hoardable.rb'
23
+ migration_template "install.rb.erb", "db/migrate/install_hoardable.rb"
34
24
  end
35
25
 
36
26
  def create_functions
37
- Dir.glob(File.join(__dir__, 'functions', '*.sql')).each do |file_path|
38
- file_name = file_path.match(%r{([^/]+)\.sql})[1]
39
- template file_path, "db/functions/#{file_name}_v01.sql"
40
- end
27
+ Dir
28
+ .glob(File.join(__dir__, "functions", "*.sql"))
29
+ .each do |file_path|
30
+ file_name = file_path.match(%r{([^/]+)\.sql})[1]
31
+ template file_path, "db/functions/#{file_name}_v01.sql"
32
+ end
41
33
  end
42
34
 
43
- def self.supports_schema_enums?
44
- ActiveRecord.version >= ::Gem::Version.new('7.0.0')
35
+ no_tasks do
36
+ def postgres_version
37
+ ActiveRecord::Base
38
+ .connection
39
+ .select_value("SELECT VERSION()")
40
+ .match(/[0-9]{1,2}([,.][0-9]{1,2})?/)[
41
+ 0
42
+ ].to_f
43
+ end
45
44
  end
46
45
 
47
46
  def self.next_migration_number(dir)
@@ -1,23 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails/generators'
4
- require 'rails/generators/active_record/migration/migration_generator'
3
+ require "rails/generators"
4
+ require "rails/generators/active_record/migration/migration_generator"
5
5
 
6
6
  module Hoardable
7
7
  # Generates a migration to create an inherited uni-temporal table of a model including
8
8
  # {Hoardable::Model}, for the storage of +versions+.
9
9
  class MigrationGenerator < ActiveRecord::Generators::Base
10
- source_root File.expand_path('templates', __dir__)
10
+ source_root File.expand_path("templates", __dir__)
11
11
  include Rails::Generators::Migration
12
12
  class_option(
13
13
  :foreign_key_type,
14
14
  type: :string,
15
15
  optional: true,
16
- desc: 'explictly set / override the foreign key type of the versions table'
16
+ desc: "explictly set / override the foreign key type of the versions table"
17
17
  )
18
18
 
19
19
  def create_versions_table
20
- migration_template 'migration.rb.erb', "db/migrate/create_#{singularized_table_name}_versions.rb"
20
+ migration_template(
21
+ "migration.rb.erb",
22
+ "db/migrate/create_#{singularized_table_name}_versions.rb"
23
+ )
21
24
  end
22
25
 
23
26
  def create_triggers
@@ -34,17 +37,23 @@ module Hoardable
34
37
  end
35
38
 
36
39
  no_tasks do
40
+ def table_name
41
+ class_name.singularize.constantize.table_name
42
+ rescue StandardError
43
+ super
44
+ end
45
+
37
46
  def foreign_key_type
38
47
  options[:foreign_key_type] ||
39
- class_name.singularize.constantize.columns.find { |col| col.name == 'id' }.sql_type
48
+ class_name.singularize.constantize.columns.find { |col| col.name == primary_key }.sql_type
40
49
  rescue StandardError
41
- 'bigint'
50
+ "bigint"
42
51
  end
43
52
 
44
53
  def primary_key
45
54
  options[:primary_key] || class_name.singularize.constantize.primary_key
46
55
  rescue StandardError
47
- 'id'
56
+ "id"
48
57
  end
49
58
 
50
59
  def singularized_table_name
@@ -2,33 +2,10 @@
2
2
 
3
3
  class InstallHoardable < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
4
4
  def change
5
- create_function :hoardable_prevent_update_id
5
+ <% if postgres_version < 13 %>enable_extension :pgcrypto
6
+ <% end %>create_function :hoardable_prevent_update_id
6
7
  create_function :hoardable_source_set_id
7
8
  create_function :hoardable_version_prevent_update
8
- <% if supports_schema_enums? %>
9
9
  create_enum :hoardable_operation, %w[update delete insert]
10
- <% else %>
11
- reversible do |dir|
12
- dir.up do
13
- execute(
14
- <<~SQL.squish
15
- DO $$
16
- BEGIN
17
- IF NOT EXISTS (
18
- SELECT 1 FROM pg_type t WHERE t.typname = 'hoardable_operation'
19
- ) THEN
20
- CREATE TYPE hoardable_operation AS ENUM ('update', 'delete', 'insert');
21
- END IF;
22
- END
23
- $$;
24
- SQL
25
- )
26
- end
27
-
28
- dir.down do
29
- execute('DROP TYPE IF EXISTS hoardable_operation;')
30
- end
31
- end
32
- <% end %>
33
10
  end
34
11
  end
@@ -4,7 +4,11 @@ class Create<%= class_name.singularize.delete(':') %>Versions < ActiveRecord::Mi
4
4
  def change
5
5
  add_column :<%= table_name %>, :hoardable_id, :<%= foreign_key_type %>
6
6
  add_index :<%= table_name %>, :hoardable_id
7
- create_table :<%= singularized_table_name %>_versions, id: false, options: 'INHERITS (<%= table_name %>)' do |t|
7
+ create_table(
8
+ :<%= singularized_table_name %>_versions,
9
+ id: false,
10
+ options: 'INHERITS (<%= table_name %>)',
11
+ ) do |t|
8
12
  t.jsonb :_data
9
13
  t.tsrange :_during, null: false
10
14
  t.uuid :_event_uuid, null: false, index: true
@@ -12,6 +16,8 @@ class Create<%= class_name.singularize.delete(':') %>Versions < ActiveRecord::Mi
12
16
  end
13
17
  reversible do |dir|
14
18
  dir.up do
19
+ execute('ALTER TABLE <%= singularized_table_name %>_versions ADD PRIMARY KEY (<%= primary_key %>);')
20
+ # remove the following line if you plan on seeding +hoardable_id+ outside the migration
15
21
  execute('UPDATE <%= table_name %> SET hoardable_id = <%= primary_key %>;')
16
22
  end
17
23
  end
@@ -0,0 +1,57 @@
1
+ module Hoardable
2
+ # This is a monkey patch of JOIN related {Arel::Visitors} for PostgreSQL so that they can append
3
+ # the ONLY clause when known to be operating on a {Hoardable::Model}. Ideally, {Arel} itself would
4
+ # provide a mechanism to support this keyword.
5
+ module ArelVisitors
6
+ def visit_Arel_Nodes_FullOuterJoin(o, collector)
7
+ collector << "FULL OUTER JOIN "
8
+ hoardable_maybe_add_only(o, collector)
9
+ collector = visit o.left, collector
10
+ collector << " "
11
+ visit o.right, collector
12
+ end
13
+
14
+ def visit_Arel_Nodes_OuterJoin(o, collector)
15
+ collector << "LEFT OUTER JOIN "
16
+ hoardable_maybe_add_only(o, collector)
17
+ collector = visit o.left, collector
18
+ collector << " "
19
+ visit o.right, collector
20
+ end
21
+
22
+ def visit_Arel_Nodes_RightOuterJoin(o, collector)
23
+ collector << "RIGHT OUTER JOIN "
24
+ hoardable_maybe_add_only(o, collector)
25
+ collector = visit o.left, collector
26
+ collector << " "
27
+ visit o.right, collector
28
+ end
29
+
30
+ def visit_Arel_Nodes_InnerJoin(o, collector)
31
+ collector << "INNER JOIN "
32
+ hoardable_maybe_add_only(o, collector)
33
+ collector = visit o.left, collector
34
+ if o.right
35
+ collector << " "
36
+ visit(o.right, collector)
37
+ else
38
+ collector
39
+ end
40
+ end
41
+
42
+ private def hoardable_maybe_add_only(o, collector)
43
+ left = o.left
44
+
45
+ if left.is_a?(Arel::Nodes::TableAlias)
46
+ hoardable_maybe_add_only(left, collector)
47
+ else
48
+ return unless left.instance_variable_get("@klass").in?(Hoardable::REGISTRY)
49
+ return if Hoardable.instance_variable_get("@at")
50
+
51
+ collector << "ONLY "
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ Arel::Visitors::PostgreSQL.prepend Hoardable::ArelVisitors
@@ -13,9 +13,13 @@ module Hoardable
13
13
  delegate :version_class, to: :source_record
14
14
 
15
15
  def insert_hoardable_version(operation, &block)
16
- version = version_class.insert(initialize_version_attributes(operation), returning: source_primary_key.to_sym)
16
+ version =
17
+ version_class.insert(
18
+ initialize_version_attributes(operation),
19
+ returning: source_primary_key.to_sym
20
+ )
17
21
  version_id = version[0][source_primary_key]
18
- source_record.instance_variable_set('@hoardable_version', version_class.find(version_id))
22
+ source_record.instance_variable_set("@hoardable_version", version_class.find(version_id))
19
23
  source_record.run_callbacks(:versioned, &block)
20
24
  end
21
25
 
@@ -24,40 +28,50 @@ module Hoardable
24
28
  end
25
29
 
26
30
  def find_or_initialize_hoardable_event_uuid
27
- Thread.current[:hoardable_event_uuid] ||= ActiveRecord::Base.connection.query('SELECT gen_random_uuid();')[0][0]
31
+ Thread.current[:hoardable_event_uuid] ||= (
32
+ ActiveRecord::Base.connection.query("SELECT gen_random_uuid();")[0][0]
33
+ )
28
34
  end
29
35
 
30
36
  def initialize_version_attributes(operation)
31
37
  source_attributes_without_primary_key.merge(
32
38
  source_record.changes.transform_values { |h| h[0] },
33
39
  {
34
- 'hoardable_id' => source_record.id,
35
- '_event_uuid' => find_or_initialize_hoardable_event_uuid,
36
- '_operation' => operation,
37
- '_data' => initialize_hoardable_data.merge(changes: source_record.changes),
38
- '_during' => initialize_temporal_range
40
+ "hoardable_id" => source_record.id,
41
+ "_event_uuid" => find_or_initialize_hoardable_event_uuid,
42
+ "_operation" => operation,
43
+ "_data" => initialize_hoardable_data.merge(changes: source_record.changes),
44
+ "_during" => initialize_temporal_range
39
45
  }
40
46
  )
41
47
  end
42
48
 
43
49
  def has_one_find_conditions(reflection)
44
50
  {
45
- reflection.type => source_record.class.name.sub(/Version$/, ''),
51
+ reflection.type => source_record.class.name.sub(/Version$/, ""),
46
52
  reflection.foreign_key => source_record.hoardable_id,
47
- 'name' => (reflection.name.to_s.sub(/^rich_text_/, '') if reflection.class_name.match?(/RichText$/))
53
+ "name" =>
54
+ (reflection.name.to_s.sub(/^rich_text_/, "") if reflection.class_name.match?(/RichText$/))
48
55
  }.reject { |k, v| k.nil? || v.nil? }
49
56
  end
50
57
 
51
58
  def has_one_at_timestamp
52
- Hoardable.instance_variable_get('@at') || source_record.updated_at
59
+ Hoardable.instance_variable_get("@at") || source_record.updated_at
53
60
  rescue NameError
54
61
  raise(UpdatedAtColumnMissingError, source_record.class.table_name)
55
62
  end
56
63
 
57
64
  def source_attributes_without_primary_key
58
- source_record.attributes.without(source_primary_key, *generated_column_names).merge(
59
- source_record.class.select(refreshable_column_names).find(source_record.id).slice(refreshable_column_names)
60
- )
65
+ source_record
66
+ .attributes
67
+ .without(source_primary_key, *generated_column_names)
68
+ .merge(
69
+ source_record
70
+ .class
71
+ .select(refreshable_column_names)
72
+ .find(source_record.id)
73
+ .slice(refreshable_column_names)
74
+ )
61
75
  end
62
76
 
63
77
  def generated_column_names
@@ -67,9 +81,15 @@ module Hoardable
67
81
  end
68
82
 
69
83
  def refreshable_column_names
70
- @refreshable_column_names ||= source_record.class.columns.select(&:default_function).reject do |column|
71
- column.name == source_primary_key || column.name.in?(generated_column_names)
72
- end.map(&:name)
84
+ @refreshable_column_names ||=
85
+ source_record
86
+ .class
87
+ .columns
88
+ .select(&:default_function)
89
+ .reject do |column|
90
+ column.name == source_primary_key || column.name.in?(generated_column_names)
91
+ end
92
+ .map(&:name)
73
93
  end
74
94
 
75
95
  def initialize_temporal_range
@@ -77,9 +97,7 @@ module Hoardable
77
97
  end
78
98
 
79
99
  def initialize_hoardable_data
80
- DATA_KEYS.to_h do |key|
81
- [key, assign_hoardable_context(key)]
82
- end
100
+ DATA_KEYS.to_h { |key| [key, assign_hoardable_context(key)] }
83
101
  end
84
102
 
85
103
  def assign_hoardable_context(key)
@@ -89,18 +107,18 @@ module Hoardable
89
107
  end
90
108
 
91
109
  def unset_hoardable_version_and_event_uuid
92
- source_record.instance_variable_set('@hoardable_version', nil)
110
+ source_record.instance_variable_set("@hoardable_version", nil)
93
111
  return if source_record.class.connection.transaction_open?
94
112
 
95
113
  Thread.current[:hoardable_event_uuid] = nil
96
114
  end
97
115
 
98
116
  def previous_temporal_tsrange_end
99
- source_record.versions.only_most_recent.pluck('_during').first&.end
117
+ source_record.versions.only_most_recent.pluck("_during").first&.end
100
118
  end
101
119
 
102
120
  def hoardable_source_epoch
103
- return source_record.created_at if source_record.class.column_names.include?('created_at')
121
+ return source_record.created_at if source_record.class.column_names.include?("created_at")
104
122
 
105
123
  raise CreatedAtColumnMissingError, source_record.class.table_name
106
124
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  # An +ActiveRecord+ extension for keeping versions of records in uni-temporal inherited tables.
4
4
  module Hoardable
5
+ REGISTRY = Set.new
6
+
5
7
  # Symbols for use with setting contextual data, when creating versions. See
6
8
  # {file:README.md#tracking-contextual-data README} for more.
7
9
  DATA_KEYS = %i[meta whodunit event_uuid].freeze
@@ -10,60 +12,47 @@ module Hoardable
10
12
  # README} for more.
11
13
  CONFIG_KEYS = %i[enabled version_updates save_trash].freeze
12
14
 
13
- VERSION_CLASS_SUFFIX = 'Version'
15
+ VERSION_CLASS_SUFFIX = "Version"
14
16
  private_constant :VERSION_CLASS_SUFFIX
15
17
 
16
18
  VERSION_TABLE_SUFFIX = "_#{VERSION_CLASS_SUFFIX.tableize}"
17
19
  private_constant :VERSION_TABLE_SUFFIX
18
20
 
19
- DURING_QUERY = '_during @> ?::timestamp'
21
+ DURING_QUERY = "_during @> ?::timestamp"
20
22
  private_constant :DURING_QUERY
21
23
 
22
- HOARDABLE_CALLBACKS_ENABLED = proc do |source_model|
23
- source_model.class.hoardable_config[:enabled] && !source_model.class.name.end_with?(VERSION_CLASS_SUFFIX)
24
- end.freeze
24
+ HOARDABLE_CALLBACKS_ENABLED =
25
+ proc do |source_model|
26
+ source_model.class.hoardable_config[:enabled] &&
27
+ !source_model.class.name.end_with?(VERSION_CLASS_SUFFIX)
28
+ end.freeze
25
29
  private_constant :HOARDABLE_CALLBACKS_ENABLED
26
30
 
27
- HOARDABLE_SAVE_TRASH = proc do |source_model|
28
- source_model.class.hoardable_config[:save_trash]
29
- end.freeze
31
+ HOARDABLE_SAVE_TRASH =
32
+ proc { |source_model| source_model.class.hoardable_config[:save_trash] }.freeze
30
33
  private_constant :HOARDABLE_SAVE_TRASH
31
34
 
32
- HOARDABLE_VERSION_UPDATES = proc do |source_model|
33
- source_model.class.hoardable_config[:version_updates]
34
- end.freeze
35
+ HOARDABLE_VERSION_UPDATES =
36
+ proc { |source_model| source_model.class.hoardable_config[:version_updates] }.freeze
35
37
  private_constant :HOARDABLE_VERSION_UPDATES
36
38
 
37
- SUPPORTS_ENCRYPTED_ACTION_TEXT = ActiveRecord.version >= ::Gem::Version.new('7.0.4')
39
+ SUPPORTS_ENCRYPTED_ACTION_TEXT = ActiveRecord.version >= ::Gem::Version.new("7.0.4")
38
40
  private_constant :SUPPORTS_ENCRYPTED_ACTION_TEXT
39
41
 
40
- SUPPORTS_VIRTUAL_COLUMNS = ActiveRecord.version >= ::Gem::Version.new('7.0.0')
41
- private_constant :SUPPORTS_VIRTUAL_COLUMNS
42
-
43
42
  @context = {}
44
- @config = CONFIG_KEYS.to_h do |key|
45
- [key, true]
46
- end
43
+ @config = CONFIG_KEYS.to_h { |key| [key, true] }
47
44
 
48
45
  class << self
49
46
  CONFIG_KEYS.each do |key|
50
- define_method(key) do
51
- @config[key]
52
- end
47
+ define_method(key) { @config[key] }
53
48
 
54
- define_method("#{key}=") do |value|
55
- @config[key] = value
56
- end
49
+ define_method("#{key}=") { |value| @config[key] = value }
57
50
  end
58
51
 
59
52
  DATA_KEYS.each do |key|
60
- define_method(key) do
61
- @context[key]
62
- end
53
+ define_method(key) { @context[key] }
63
54
 
64
- define_method("#{key}=") do |value|
65
- @context[key] = value
66
- end
55
+ define_method("#{key}=") { |value| @context[key] = value }
67
56
  end
68
57
 
69
58
  # This is a general use method for setting {file:README.md#tracking-contextual-data Contextual
@@ -102,10 +91,20 @@ module Hoardable
102
91
  class Engine < ::Rails::Engine
103
92
  isolate_namespace Hoardable
104
93
 
105
- initializer 'hoardable.action_text' do
94
+ initializer "hoardable.action_text" do
106
95
  ActiveSupport.on_load(:action_text_rich_text) do
107
- require_relative 'rich_text'
108
- require_relative 'encrypted_rich_text' if SUPPORTS_ENCRYPTED_ACTION_TEXT
96
+ require_relative "rich_text"
97
+ require_relative "encrypted_rich_text" if SUPPORTS_ENCRYPTED_ACTION_TEXT
98
+ end
99
+ end
100
+
101
+ initializer "hoardable.schema_statements" do
102
+ ActiveSupport.on_load(:active_record_postgresqladapter) do
103
+ # We need to control the table dumping order of tables, so revert these to just +super+
104
+ Fx::SchemaDumper::Trigger.module_eval("def tables(streams); super; end")
105
+
106
+ ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaDumper.prepend(SchemaDumper)
107
+ ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements.prepend(SchemaStatements)
109
108
  end
110
109
  end
111
110
  end
@@ -2,29 +2,26 @@
2
2
 
3
3
  module Hoardable
4
4
  # A subclass of +StandardError+ for general use within {Hoardable}.
5
- class Error < StandardError; end
5
+ class Error < StandardError
6
+ end
6
7
 
7
8
  # An error to be raised when 'created_at' columns are missing for {Hoardable::Model}s.
8
9
  class CreatedAtColumnMissingError < Error
9
10
  def initialize(source_table_name)
10
- super(
11
- <<~LOG
11
+ super(<<~LOG)
12
12
  '#{source_table_name}' does not have a 'created_at' column, so the start of the first
13
13
  version’s temporal period cannot be known. Add a 'created_at' column to '#{source_table_name}'.
14
14
  LOG
15
- )
16
15
  end
17
16
  end
18
17
 
19
18
  # An error to be raised when 'updated_at' columns are missing for {Hoardable::Model}s.
20
19
  class UpdatedAtColumnMissingError < Error
21
20
  def initialize(source_table_name)
22
- super(
23
- <<~LOG
21
+ super(<<~LOG)
24
22
  '#{source_table_name}' does not have an 'updated_at' column, so Hoardable cannot look up
25
23
  associated record versions with it. Add an 'updated_at' column to '#{source_table_name}'.
26
24
  LOG
27
- )
28
25
  end
29
26
  end
30
27
  end
@@ -17,9 +17,7 @@ module Hoardable
17
17
  private
18
18
 
19
19
  def hoardable_ids(ids)
20
- ids.map do |id|
21
- version_class.where(hoardable_id: id).select(primary_key).ids[0] || id
22
- end
20
+ ids.map { |id| version_class.where(hoardable_id: id).select(primary_key).ids[0] || id }
23
21
  end
24
22
  end
25
23
  end
@@ -12,15 +12,9 @@ module Hoardable
12
12
  @scope ||= hoardable_scope
13
13
  end
14
14
 
15
- private
16
-
17
- def hoardable_scope
18
- if Hoardable.instance_variable_get('@at') && (hoardable_id = @association.owner.hoardable_id)
19
- if @association.reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
20
- @association.reflection.source_reflection.instance_variable_set(
21
- '@active_record_primary_key', 'hoardable_id'
22
- )
23
- end
15
+ private def hoardable_scope
16
+ if Hoardable.instance_variable_get("@at") &&
17
+ (hoardable_id = @association.owner.hoardable_id)
24
18
  @association.scope.rewhere(@association.reflection.foreign_key => hoardable_id)
25
19
  else
26
20
  @association.scope
@@ -32,7 +26,9 @@ module Hoardable
32
26
  class_methods do
33
27
  def has_many(*args, &block)
34
28
  options = args.extract_options!
35
- options[:extend] = Array(options[:extend]).push(HasManyExtension) if options.delete(:hoardable)
29
+ options[:extend] = Array(options[:extend]).push(HasManyExtension) if options.delete(
30
+ :hoardable
31
+ )
36
32
  super(*args, **options, &block)
37
33
 
38
34
  # This hack is needed to force Rails to not use any existing method cache so that the
@@ -9,13 +9,13 @@ module Hoardable
9
9
  def has_one(*args)
10
10
  options = args.extract_options!
11
11
  hoardable = options.delete(:hoardable)
12
- association = super(*args, **options)
13
12
  name = args.first
14
- return unless hoardable || association[name.to_s].options[:class_name].match?(/RichText$/)
13
+ association = super(*args, **options).symbolize_keys[name]
14
+ return unless hoardable || (association.options[:class_name].match?(/RichText$/))
15
15
 
16
16
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
17
17
  def #{name}
18
- reflection = _reflections['#{name}']
18
+ reflection = _reflections.symbolize_keys[:#{name}]
19
19
  return super if reflection.klass.name.match?(/^ActionText/)
20
20
  return super unless (timestamp = hoardable_client.has_one_at_timestamp)
21
21
 
@@ -6,16 +6,23 @@ module Hoardable
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  class_methods do
9
- def has_rich_text(name, encrypted: false, hoardable: false)
10
- if SUPPORTS_ENCRYPTED_ACTION_TEXT
11
- super(name, encrypted: encrypted)
12
- else
13
- super(name)
14
- end
9
+ def has_rich_text(name, hoardable: false, **opts)
10
+ super(name, **opts)
15
11
  return unless hoardable
16
12
 
17
13
  reflection_options = reflections["rich_text_#{name}"].options
18
- reflection_options[:class_name] = reflection_options[:class_name].sub(/ActionText/, 'Hoardable')
14
+
15
+ # load the +ActionText+ class if it hasn’t been already
16
+ reflection_options[:class_name].constantize
17
+
18
+ reflection_options[:class_name] = reflection_options[:class_name].sub(
19
+ /^ActionText/,
20
+ "Hoardable"
21
+ )
22
+ end
23
+
24
+ def has_hoardable_rich_text(name, **opts)
25
+ has_rich_text(name, hoardable: true, **opts)
19
26
  end
20
27
  end
21
28
  end
@@ -51,24 +51,27 @@ module Hoardable
51
51
  define_model_callbacks :reverted, only: :after
52
52
  define_model_callbacks :untrashed, only: :after
53
53
 
54
- TracePoint.new(:end) do |trace|
55
- next unless self == trace.self
54
+ TracePoint
55
+ .new(:end) do |trace|
56
+ next unless self == trace.self
56
57
 
57
- full_version_class_name = "#{name}#{VERSION_CLASS_SUFFIX}"
58
- if (namespace_match = full_version_class_name.match(/(.*)::(.*)/))
59
- object_namespace = namespace_match[1].constantize
60
- version_class_name = namespace_match[2]
61
- else
62
- object_namespace = Object
63
- version_class_name = full_version_class_name
64
- end
65
- unless Object.const_defined?(full_version_class_name)
66
- object_namespace.const_set(version_class_name, Class.new(self) { include VersionModel })
67
- end
68
- include SourceModel
58
+ full_version_class_name = "#{name}#{VERSION_CLASS_SUFFIX}"
59
+ if (namespace_match = full_version_class_name.match(/(.*)::(.*)/))
60
+ object_namespace = namespace_match[1].constantize
61
+ version_class_name = namespace_match[2]
62
+ else
63
+ object_namespace = Object
64
+ version_class_name = full_version_class_name
65
+ end
66
+ unless Object.const_defined?(full_version_class_name)
67
+ object_namespace.const_set(version_class_name, Class.new(self) { include VersionModel })
68
+ end
69
+ include SourceModel
70
+ REGISTRY.add(self)
69
71
 
70
- trace.disable
71
- end.enable
72
+ trace.disable
73
+ end
74
+ .enable
72
75
  end
73
76
  end
74
77
  end