activerecord-materialized 0.1.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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +37 -0
  3. data/LICENSE +21 -0
  4. data/README.md +526 -0
  5. data/lib/activerecord/materialized/aggregate_analysis.rb +132 -0
  6. data/lib/activerecord/materialized/async_refresher.rb +105 -0
  7. data/lib/activerecord/materialized/cache_table_schema.rb +67 -0
  8. data/lib/activerecord/materialized/cold_read.rb +60 -0
  9. data/lib/activerecord/materialized/configuration.rb +80 -0
  10. data/lib/activerecord/materialized/delta_maintainer.rb +74 -0
  11. data/lib/activerecord/materialized/dependency_registry.rb +107 -0
  12. data/lib/activerecord/materialized/dependency_trackable.rb +48 -0
  13. data/lib/activerecord/materialized/incremental_maintainer.rb +58 -0
  14. data/lib/activerecord/materialized/maintenance_delta.rb +82 -0
  15. data/lib/activerecord/materialized/maintenance_delta_builder.rb +62 -0
  16. data/lib/activerecord/materialized/maintenance_store.rb +82 -0
  17. data/lib/activerecord/materialized/metadata/maintenance_payload.rb +33 -0
  18. data/lib/activerecord/materialized/metadata/schema.rb +84 -0
  19. data/lib/activerecord/materialized/metadata/timestamps.rb +31 -0
  20. data/lib/activerecord/materialized/metadata.rb +138 -0
  21. data/lib/activerecord/materialized/metadata_record.rb +28 -0
  22. data/lib/activerecord/materialized/migration_builder.rb +38 -0
  23. data/lib/activerecord/materialized/module_api.rb +82 -0
  24. data/lib/activerecord/materialized/partition_record.rb +27 -0
  25. data/lib/activerecord/materialized/partition_state.rb +127 -0
  26. data/lib/activerecord/materialized/query_expressions.rb +83 -0
  27. data/lib/activerecord/materialized/railtie.rb +16 -0
  28. data/lib/activerecord/materialized/refresh_callbacks.rb +62 -0
  29. data/lib/activerecord/materialized/refresh_job.rb +22 -0
  30. data/lib/activerecord/materialized/refresh_result.rb +40 -0
  31. data/lib/activerecord/materialized/refresh_scheduler.rb +54 -0
  32. data/lib/activerecord/materialized/refresher.rb +139 -0
  33. data/lib/activerecord/materialized/registry.rb +74 -0
  34. data/lib/activerecord/materialized/relation_cache_writer.rb +137 -0
  35. data/lib/activerecord/materialized/schema_verifier.rb +64 -0
  36. data/lib/activerecord/materialized/summary_delta.rb +76 -0
  37. data/lib/activerecord/materialized/summary_delta_builder.rb +58 -0
  38. data/lib/activerecord/materialized/table_model_registry.rb +43 -0
  39. data/lib/activerecord/materialized/tasks.rb +79 -0
  40. data/lib/activerecord/materialized/type_reexports.rb +14 -0
  41. data/lib/activerecord/materialized/version.rb +9 -0
  42. data/lib/activerecord/materialized/view.rb +79 -0
  43. data/lib/activerecord/materialized/view_class.rb +8 -0
  44. data/lib/activerecord/materialized/view_configuration_class_methods.rb +103 -0
  45. data/lib/activerecord/materialized/view_definition.rb +133 -0
  46. data/lib/activerecord/materialized/view_incremental_class_methods.rb +142 -0
  47. data/lib/activerecord/materialized/view_query_access_class_methods.rb +160 -0
  48. data/lib/activerecord/materialized/view_refresh_policy_class_methods.rb +109 -0
  49. data/lib/activerecord/materialized/write_change.rb +69 -0
  50. data/lib/activerecord/materialized.rb +55 -0
  51. data/lib/activerecord_materialized_types.rb +18 -0
  52. data/lib/generators/activerecord_materialized/install/templates/README +55 -0
  53. data/lib/generators/activerecord_materialized/install/templates/create_ar_materialized_view_metadata.rb.erb +30 -0
  54. data/lib/generators/activerecord_materialized/install_generator.rb +32 -0
  55. data/lib/generators/activerecord_materialized/migration_generator.rb +51 -0
  56. data/lib/generators/activerecord_materialized/templates/materialized_view.rb.erb +17 -0
  57. data/lib/generators/activerecord_materialized/templates/materialized_view_migration.rb.erb +11 -0
  58. data/lib/generators/activerecord_materialized/view_generator.rb +18 -0
  59. metadata +162 -0
@@ -0,0 +1,69 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module ActiveRecord
5
+ module Materialized
6
+ # A committed write to a dependency table, captured as full before/after
7
+ # attribute snapshots (string-keyed). Snapshots are complete — not just the
8
+ # changed columns — so maintenance can compute deltas even when the GROUP BY
9
+ # key or a summed column did not change.
10
+ class WriteChange
11
+ extend T::Sig
12
+
13
+ Operation = T.type_alias { T.any(Symbol, String) }
14
+ Attributes = T.type_alias { T::Hash[String, T.untyped] }
15
+
16
+ sig { returns(String) }
17
+ attr_reader :table_name
18
+
19
+ sig { returns(Operation) }
20
+ attr_reader :operation
21
+
22
+ sig { returns(Attributes) }
23
+ attr_reader :before
24
+
25
+ sig { returns(Attributes) }
26
+ attr_reader :after
27
+
28
+ sig { params(table_name: String, operation: Operation, before: Attributes, after: Attributes).void }
29
+ def initialize(table_name:, operation:, before:, after:)
30
+ @table_name = table_name
31
+ @operation = operation
32
+ @before = before
33
+ @after = after
34
+ end
35
+
36
+ sig { params(record: ::ActiveRecord::Base, operation: Operation).returns(WriteChange) }
37
+ def self.from_record(record, operation)
38
+ table_name = record.class.table_name
39
+ case operation.to_sym
40
+ when :create
41
+ new(table_name: table_name, operation: :create, before: {}, after: stringify_keys(record.attributes))
42
+ when :update
43
+ new(table_name: table_name, operation: :update, before: old_attributes(record),
44
+ after: stringify_keys(record.attributes))
45
+ when :destroy
46
+ # attributes_in_database is emptied once the row is gone; use in-memory attributes.
47
+ new(table_name: table_name, operation: :destroy, before: stringify_keys(record.attributes), after: {})
48
+ else
49
+ raise ArgumentError, "unsupported write operation: #{operation}"
50
+ end
51
+ end
52
+
53
+ # Full pre-update attributes: current values with changed columns reverted.
54
+ sig { params(record: ::ActiveRecord::Base).returns(Attributes) }
55
+ def self.old_attributes(record)
56
+ attributes = stringify_keys(record.attributes)
57
+ record.saved_changes.each_pair { |column, (old_value, _new_value)| attributes[column.to_s] = old_value }
58
+ attributes
59
+ end
60
+
61
+ sig { params(values: T::Hash[T.any(String, Symbol), T.untyped]).returns(Attributes) }
62
+ def self.stringify_keys(values)
63
+ values.each_with_object({}) { |(key, value), result| result[key.to_s] = value }
64
+ end
65
+
66
+ private_class_method :old_attributes, :stringify_keys
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,55 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "active_record"
6
+ require "active_support/concern"
7
+ require "active_support/core_ext/string/inflections"
8
+ require "active_support/core_ext/module/delegation"
9
+ require "active_support/core_ext/integer/time"
10
+ require "active_support/core_ext/object/blank"
11
+ require "securerandom"
12
+
13
+ require_relative "../activerecord_materialized_types"
14
+ require_relative "materialized/type_reexports"
15
+ require_relative "materialized/configuration"
16
+ require_relative "materialized/module_api"
17
+ require_relative "materialized/refresh_callbacks"
18
+ require_relative "materialized/write_change"
19
+ require_relative "materialized/maintenance_delta_builder"
20
+ require_relative "materialized/table_model_registry"
21
+ require_relative "materialized/dependency_trackable"
22
+ require_relative "materialized/dependency_registry"
23
+ require_relative "materialized/async_refresher"
24
+ require_relative "materialized/refresh_scheduler"
25
+ require_relative "materialized/registry"
26
+ require_relative "materialized/metadata_record"
27
+ require_relative "materialized/view_incremental_class_methods"
28
+ require_relative "materialized/view_refresh_policy_class_methods"
29
+ require_relative "materialized/view_configuration_class_methods"
30
+ require_relative "materialized/view_query_access_class_methods"
31
+ require_relative "materialized/view"
32
+ require_relative "materialized/view_class"
33
+ require_relative "materialized/refresh_result"
34
+ require_relative "materialized/refresher"
35
+ require_relative "materialized/maintenance_delta"
36
+ require_relative "materialized/view_definition"
37
+ require_relative "materialized/aggregate_analysis"
38
+ require_relative "materialized/summary_delta"
39
+ require_relative "materialized/summary_delta_builder"
40
+ require_relative "materialized/query_expressions"
41
+ require_relative "materialized/cache_table_schema"
42
+ require_relative "materialized/migration_builder"
43
+ require_relative "materialized/schema_verifier"
44
+ require_relative "materialized/cold_read"
45
+ require_relative "materialized/relation_cache_writer"
46
+ require_relative "materialized/maintenance_store"
47
+ require_relative "materialized/incremental_maintainer"
48
+ require_relative "materialized/delta_maintainer"
49
+ require_relative "materialized/metadata"
50
+ require_relative "materialized/partition_record"
51
+ require_relative "materialized/partition_state"
52
+
53
+ require_relative "materialized/refresh_job" if defined?(ActiveJob::Base)
54
+
55
+ require_relative "materialized/railtie" if defined?(Rails::Railtie)
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module ActiveRecordMaterializedTypes
5
+ DebounceInterval = T.type_alias { T.any(Integer, Float, ::ActiveSupport::Duration) }
6
+ StalenessDuration = T.type_alias { T.any(Integer, ::ActiveSupport::Duration) }
7
+ SourceDefinition = T.type_alias do
8
+ T.any(
9
+ ::ActiveRecord::Relation,
10
+ Proc,
11
+ T.nilable(T.proc.returns(::ActiveRecord::Relation))
12
+ )
13
+ end
14
+ RefreshMode = T.type_alias { Symbol }
15
+ RefreshCallbackName = T.type_alias { T.any(Symbol, Proc) }
16
+ Connection = T.type_alias { ::ActiveRecord::ConnectionAdapters::AbstractAdapter }
17
+ Timestamp = T.type_alias { T.any(::Time, ::ActiveSupport::TimeWithZone) }
18
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ After installing activerecord-materialized, create materialized views as standard
4
+ ActiveRecord models inheriting from ActiveRecord::Materialized::View.
5
+
6
+ Example:
7
+
8
+ class SalesSummary < ActiveRecord::Materialized::View
9
+ self.table_name = "mv_sales_summary"
10
+
11
+ materialized_from do
12
+ LineItem.group(:category).select(
13
+ LineItem.arel_table[:category],
14
+ ActiveRecord::Materialized::QueryExpressions.sum_as(LineItem.arel_table[:amount], as: :total)
15
+ )
16
+ end
17
+
18
+ depends_on :line_items
19
+ max_staleness 12.hours
20
+ # cold_read :read_through # default; also :serve_stale or :raise
21
+ # warm_up { [where(region: "us")] } # materialize hot partitions at deploy
22
+ end
23
+
24
+ Provision the (empty) cache table with a migration generated from the
25
+ relation, so the table exists at deploy time:
26
+
27
+ bin/rails generate activerecord_materialized:migration SalesSummary
28
+ bin/rails db:migrate
29
+
30
+ Verify the cache tables still match their relations (e.g. in CI / on deploy):
31
+
32
+ bin/rails materialized:verify
33
+
34
+ Build the view once (the only full-scan path — never happens implicitly; runs
35
+ in the database via INSERT ... SELECT):
36
+
37
+ SalesSummary.rebuild!(confirm: true)
38
+
39
+ Or warm just the hot partitions at deploy time (leaving the rest to read
40
+ through on demand):
41
+
42
+ bin/rails materialized:warm_up
43
+
44
+ Until a view is built, reads transparently fall through to the source query
45
+ (cold_read :read_through). After it is built, writes to depends_on tables
46
+ incrementally maintain it; refresh! applies that maintenance (it never rebuilds):
47
+
48
+ SalesSummary.refresh! # incremental maintenance only
49
+ SalesSummary.refresh_if_stale!
50
+
51
+ Or via Rake:
52
+
53
+ bin/rails materialized:rebuild # intentional full materialization
54
+ bin/rails materialized:refresh_all # incremental maintenance pass
55
+ bin/rails materialized:refresh_stale
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateArMaterializedViewMetadata < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
4
+ def change
5
+ create_table :ar_materialized_view_metadata do |t|
6
+ t.string :view_name, null: false
7
+ t.datetime :last_refreshed_at
8
+ t.boolean :refreshing, null: false, default: false
9
+ t.boolean :dirty, null: false, default: true
10
+ t.boolean :warm, null: false, default: false
11
+ t.integer :row_count
12
+ t.integer :refresh_duration_ms
13
+ t.text :last_error
14
+ t.text :maintenance_payload
15
+
16
+ t.timestamps
17
+ end
18
+
19
+ add_index :ar_materialized_view_metadata, :view_name, unique: true
20
+
21
+ create_table :ar_materialized_view_partitions do |t|
22
+ t.string :view_name, null: false
23
+ t.string :partition_key, null: false
24
+
25
+ t.datetime :created_at, null: false
26
+ end
27
+
28
+ add_index :ar_materialized_view_partitions, %i[view_name partition_key], unique: true
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "rails/generators"
5
+ require "rails/generators/migration"
6
+
7
+ module ActiverecordMaterialized
8
+ # `rails generate activerecord_materialized:install` — installs the metadata-table migration.
9
+ class InstallGenerator < ::Rails::Generators::Base
10
+ extend T::Sig
11
+ include Rails::Generators::Migration
12
+
13
+ source_root File.expand_path("install", __dir__)
14
+
15
+ sig { params(dirname: String).returns(String) }
16
+ def self.next_migration_number(dirname)
17
+ next_migration_number = T.unsafe(self).current_migration_number(dirname) + 1
18
+ ::ActiveRecord::Migration.next_migration_number(next_migration_number)
19
+ end
20
+
21
+ sig { void }
22
+ def copy_migration
23
+ migration_template "create_ar_materialized_view_metadata.rb.erb",
24
+ "db/migrate/create_ar_materialized_view_metadata.rb"
25
+ end
26
+
27
+ sig { void }
28
+ def show_readme
29
+ readme "README" if behavior == :invoke
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,51 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "rails/generators"
5
+ require "rails/generators/migration"
6
+
7
+ module ActiverecordMaterialized
8
+ # Generates a migration that provisions a materialized view's (empty) cache
9
+ # table, with columns inferred from the view's source relation:
10
+ #
11
+ # bin/rails generate activerecord_materialized:migration SalesSummary
12
+ #
13
+ # Run after the view's `materialized_from` is defined, then `db:migrate`.
14
+ class MigrationGenerator < ::Rails::Generators::NamedBase
15
+ extend T::Sig
16
+ include ::Rails::Generators::Migration
17
+
18
+ source_root File.expand_path("templates", __dir__)
19
+
20
+ sig { params(dirname: String).returns(String) }
21
+ def self.next_migration_number(dirname)
22
+ ::ActiveRecord::Migration.next_migration_number(T.unsafe(self).current_migration_number(dirname) + 1)
23
+ end
24
+
25
+ sig { void }
26
+ def create_migration_file
27
+ migration_template "materialized_view_migration.rb.erb",
28
+ File.join("db", "migrate", "create_#{builder.table_name}.rb")
29
+ end
30
+
31
+ private
32
+
33
+ sig { returns(::ActiveRecord::Materialized::MigrationBuilder) }
34
+ def builder
35
+ @builder ||= T.let(build_builder, T.nilable(::ActiveRecord::Materialized::MigrationBuilder))
36
+ end
37
+
38
+ # Resolve via the registry (after eager-load) rather than constantize.
39
+ sig { returns(::ActiveRecord::Materialized::MigrationBuilder) }
40
+ def build_builder
41
+ T.unsafe(::Rails).application.eager_load!
42
+ view_class = ::ActiveRecord::Materialized::Registry.for_class_name(class_name)
43
+ if view_class.nil?
44
+ Kernel.raise ::Thor::Error,
45
+ "Unknown materialized view: #{class_name}. Define it before generating its migration."
46
+ end
47
+
48
+ ::ActiveRecord::Materialized::MigrationBuilder.new(view_class)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= class_name %> < ActiveRecord::Materialized::View
4
+ self.table_name = "mv_<%= file_name %>"
5
+
6
+ # Replace ApplicationRecord.none with your ActiveRecord::Relation source, e.g.:
7
+ # Order.group(:status).select(
8
+ # Order.arel_table[:status],
9
+ # ActiveRecord::Materialized::QueryExpressions.count_as(Order.arel_table[:id], as: :order_count)
10
+ # )
11
+ materialized_from { ApplicationRecord.none }
12
+
13
+ depends_on :application_record
14
+ refresh_on_change :async
15
+ refresh_debounce 2.seconds
16
+ max_staleness 12.hours
17
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= builder.migration_class_name %> < ActiveRecord::Migration[<%= builder.migration_version %>]
4
+ def change
5
+ create_table :<%= builder.table_name %> do |t|
6
+ <% builder.column_definitions.each do |column| -%>
7
+ t.<%= column.type %> :<%= column.name %>
8
+ <% end -%>
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "rails/generators"
5
+
6
+ module ActiverecordMaterialized
7
+ # `rails generate activerecord_materialized:view NAME` — scaffolds a materialized view class.
8
+ class ViewGenerator < ::Rails::Generators::NamedBase
9
+ extend T::Sig
10
+
11
+ source_root File.expand_path("templates", __dir__)
12
+
13
+ sig { void }
14
+ def create_model
15
+ template "materialized_view.rb.erb", File.join("app/models", class_path, "#{file_name}.rb")
16
+ end
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-materialized
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Avrukin
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activerecord
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '8.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: activesupport
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '8.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '8.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: railties
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '8.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '8.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: sorbet-runtime
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0.5'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0.5'
68
+ description: |
69
+ Provides transparent materialized view semantics for Rails applications
70
+ on databases that do not support native materialized views. Precomputes
71
+ expensive analytical queries into cache tables with scheduled or on-demand refresh.
72
+ email:
73
+ - michael@avrukin.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - CHANGELOG.md
79
+ - LICENSE
80
+ - README.md
81
+ - lib/activerecord/materialized.rb
82
+ - lib/activerecord/materialized/aggregate_analysis.rb
83
+ - lib/activerecord/materialized/async_refresher.rb
84
+ - lib/activerecord/materialized/cache_table_schema.rb
85
+ - lib/activerecord/materialized/cold_read.rb
86
+ - lib/activerecord/materialized/configuration.rb
87
+ - lib/activerecord/materialized/delta_maintainer.rb
88
+ - lib/activerecord/materialized/dependency_registry.rb
89
+ - lib/activerecord/materialized/dependency_trackable.rb
90
+ - lib/activerecord/materialized/incremental_maintainer.rb
91
+ - lib/activerecord/materialized/maintenance_delta.rb
92
+ - lib/activerecord/materialized/maintenance_delta_builder.rb
93
+ - lib/activerecord/materialized/maintenance_store.rb
94
+ - lib/activerecord/materialized/metadata.rb
95
+ - lib/activerecord/materialized/metadata/maintenance_payload.rb
96
+ - lib/activerecord/materialized/metadata/schema.rb
97
+ - lib/activerecord/materialized/metadata/timestamps.rb
98
+ - lib/activerecord/materialized/metadata_record.rb
99
+ - lib/activerecord/materialized/migration_builder.rb
100
+ - lib/activerecord/materialized/module_api.rb
101
+ - lib/activerecord/materialized/partition_record.rb
102
+ - lib/activerecord/materialized/partition_state.rb
103
+ - lib/activerecord/materialized/query_expressions.rb
104
+ - lib/activerecord/materialized/railtie.rb
105
+ - lib/activerecord/materialized/refresh_callbacks.rb
106
+ - lib/activerecord/materialized/refresh_job.rb
107
+ - lib/activerecord/materialized/refresh_result.rb
108
+ - lib/activerecord/materialized/refresh_scheduler.rb
109
+ - lib/activerecord/materialized/refresher.rb
110
+ - lib/activerecord/materialized/registry.rb
111
+ - lib/activerecord/materialized/relation_cache_writer.rb
112
+ - lib/activerecord/materialized/schema_verifier.rb
113
+ - lib/activerecord/materialized/summary_delta.rb
114
+ - lib/activerecord/materialized/summary_delta_builder.rb
115
+ - lib/activerecord/materialized/table_model_registry.rb
116
+ - lib/activerecord/materialized/tasks.rb
117
+ - lib/activerecord/materialized/type_reexports.rb
118
+ - lib/activerecord/materialized/version.rb
119
+ - lib/activerecord/materialized/view.rb
120
+ - lib/activerecord/materialized/view_class.rb
121
+ - lib/activerecord/materialized/view_configuration_class_methods.rb
122
+ - lib/activerecord/materialized/view_definition.rb
123
+ - lib/activerecord/materialized/view_incremental_class_methods.rb
124
+ - lib/activerecord/materialized/view_query_access_class_methods.rb
125
+ - lib/activerecord/materialized/view_refresh_policy_class_methods.rb
126
+ - lib/activerecord/materialized/write_change.rb
127
+ - lib/activerecord_materialized_types.rb
128
+ - lib/generators/activerecord_materialized/install/templates/README
129
+ - lib/generators/activerecord_materialized/install/templates/create_ar_materialized_view_metadata.rb.erb
130
+ - lib/generators/activerecord_materialized/install_generator.rb
131
+ - lib/generators/activerecord_materialized/migration_generator.rb
132
+ - lib/generators/activerecord_materialized/templates/materialized_view.rb.erb
133
+ - lib/generators/activerecord_materialized/templates/materialized_view_migration.rb.erb
134
+ - lib/generators/activerecord_materialized/view_generator.rb
135
+ homepage: https://github.com/mavrukin/activerecord-materialized
136
+ licenses:
137
+ - MIT
138
+ metadata:
139
+ allowed_push_host: https://rubygems.org
140
+ source_code_uri: https://github.com/mavrukin/activerecord-materialized
141
+ changelog_uri: https://github.com/mavrukin/activerecord-materialized/blob/main/CHANGELOG.md
142
+ bug_tracker_uri: https://github.com/mavrukin/activerecord-materialized/issues
143
+ documentation_uri: https://rubydoc.info/gems/activerecord-materialized
144
+ rubygems_mfa_required: 'true'
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 3.4.0
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubygems_version: 4.0.15
160
+ specification_version: 4
161
+ summary: Application-level materialized views for ActiveRecord
162
+ test_files: []