hoardable 0.14.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.streerc +1 -0
- data/.tool-versions +2 -2
- data/CHANGELOG.md +15 -0
- data/Gemfile +9 -10
- data/README.md +157 -132
- data/Rakefile +22 -8
- data/lib/generators/hoardable/install_generator.rb +25 -26
- data/lib/generators/hoardable/migration_generator.rb +11 -8
- data/lib/generators/hoardable/templates/install.rb.erb +2 -25
- data/lib/generators/hoardable/templates/migration.rb.erb +7 -1
- data/lib/hoardable/arel_visitors.rb +51 -0
- data/lib/hoardable/database_client.rb +41 -23
- data/lib/hoardable/engine.rb +32 -33
- data/lib/hoardable/error.rb +4 -7
- data/lib/hoardable/finder_methods.rb +1 -3
- data/lib/hoardable/has_many.rb +6 -10
- data/lib/hoardable/has_rich_text.rb +14 -7
- data/lib/hoardable/model.rb +19 -16
- data/lib/hoardable/schema_dumper.rb +25 -0
- data/lib/hoardable/schema_statements.rb +33 -0
- data/lib/hoardable/scopes.rb +22 -29
- data/lib/hoardable/source_model.rb +6 -5
- data/lib/hoardable/version.rb +1 -1
- data/lib/hoardable/version_model.rb +30 -31
- data/lib/hoardable.rb +21 -18
- data/sig/hoardable.rbs +37 -12
- metadata +14 -11
- data/.rubocop.yml +0 -21
@@ -1,47 +1,46 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
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(
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
23
|
+
migration_template "install.rb.erb", "db/migrate/install_hoardable.rb"
|
34
24
|
end
|
35
25
|
|
36
26
|
def create_functions
|
37
|
-
Dir
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
44
|
-
|
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
|
4
|
-
require
|
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(
|
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:
|
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
|
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
|
@@ -36,15 +39,15 @@ module Hoardable
|
|
36
39
|
no_tasks do
|
37
40
|
def foreign_key_type
|
38
41
|
options[:foreign_key_type] ||
|
39
|
-
class_name.singularize.constantize.columns.find { |col| col.name ==
|
42
|
+
class_name.singularize.constantize.columns.find { |col| col.name == primary_key }.sql_type
|
40
43
|
rescue StandardError
|
41
|
-
|
44
|
+
"bigint"
|
42
45
|
end
|
43
46
|
|
44
47
|
def primary_key
|
45
48
|
options[:primary_key] || class_name.singularize.constantize.primary_key
|
46
49
|
rescue StandardError
|
47
|
-
|
50
|
+
"id"
|
48
51
|
end
|
49
52
|
|
50
53
|
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
|
-
|
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
|
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,51 @@
|
|
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
|
+
return unless o.left.instance_variable_get("@klass").in?(Hoardable::REGISTRY)
|
44
|
+
return if Hoardable.instance_variable_get("@at")
|
45
|
+
|
46
|
+
collector << "ONLY "
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
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 =
|
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(
|
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] ||=
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
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(
|
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
|
59
|
-
|
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 ||=
|
71
|
-
|
72
|
-
|
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
|
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(
|
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(
|
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?(
|
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
|
data/lib/hoardable/engine.rb
CHANGED
@@ -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 =
|
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 =
|
21
|
+
DURING_QUERY = "_during @> ?::timestamp"
|
20
22
|
private_constant :DURING_QUERY
|
21
23
|
|
22
|
-
HOARDABLE_CALLBACKS_ENABLED =
|
23
|
-
|
24
|
-
|
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 =
|
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 =
|
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(
|
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
|
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)
|
51
|
-
@config[key]
|
52
|
-
end
|
47
|
+
define_method(key) { @config[key] }
|
53
48
|
|
54
|
-
define_method("#{key}=")
|
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)
|
61
|
-
@context[key]
|
62
|
-
end
|
53
|
+
define_method(key) { @context[key] }
|
63
54
|
|
64
|
-
define_method("#{key}=")
|
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
|
94
|
+
initializer "hoardable.action_text" do
|
106
95
|
ActiveSupport.on_load(:action_text_rich_text) do
|
107
|
-
require_relative
|
108
|
-
require_relative
|
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
|
data/lib/hoardable/error.rb
CHANGED
@@ -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
|
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
|
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
|
data/lib/hoardable/has_many.rb
CHANGED
@@ -12,15 +12,9 @@ module Hoardable
|
|
12
12
|
@scope ||= hoardable_scope
|
13
13
|
end
|
14
14
|
|
15
|
-
private
|
16
|
-
|
17
|
-
|
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(
|
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
|
@@ -6,16 +6,23 @@ module Hoardable
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
class_methods do
|
9
|
-
def has_rich_text(name,
|
10
|
-
|
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
|
-
|
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
|
data/lib/hoardable/model.rb
CHANGED
@@ -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
|
55
|
-
|
54
|
+
TracePoint
|
55
|
+
.new(:end) do |trace|
|
56
|
+
next unless self == trace.self
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
71
|
-
|
72
|
+
trace.disable
|
73
|
+
end
|
74
|
+
.enable
|
72
75
|
end
|
73
76
|
end
|
74
77
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hoardable
|
4
|
+
module SchemaDumper
|
5
|
+
def ignored?(table_name)
|
6
|
+
super || @connection.inherited_table?(table_name)
|
7
|
+
end
|
8
|
+
|
9
|
+
def tables(stream)
|
10
|
+
super
|
11
|
+
dump_inherited_tables(stream)
|
12
|
+
empty_line(stream)
|
13
|
+
triggers(stream)
|
14
|
+
end
|
15
|
+
|
16
|
+
private def dump_inherited_tables(stream)
|
17
|
+
sorted_tables = @connection.tables.filter { |table| @connection.inherited_table?(table) }.sort
|
18
|
+
sorted_tables.each do |table_name|
|
19
|
+
table(table_name, stream)
|
20
|
+
foreign_keys(table_name, stream)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
private_constant :SchemaDumper
|
25
|
+
end
|