pg_rls 0.2.4 → 1.0.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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +55 -17
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +19 -2
  5. data/CODE_OF_CONDUCT.md +77 -29
  6. data/Guardfile +44 -0
  7. data/README.md +247 -83
  8. data/Rakefile +5 -12
  9. data/Steepfile +29 -0
  10. data/UPGRADE.md +106 -0
  11. data/app/models/pg_rls/admin.rb +24 -0
  12. data/app/models/pg_rls/current.rb +48 -0
  13. data/app/models/pg_rls/record.rb +13 -0
  14. data/app/models/pg_rls/tenant/searchable.rb +60 -0
  15. data/app/models/pg_rls/tenant/securable.rb +67 -0
  16. data/app/models/pg_rls/tenant/switchable.rb +40 -0
  17. data/app/models/pg_rls/tenant.rb +9 -0
  18. data/assets/logo.svg +8 -0
  19. data/docker-compose.yml +14 -0
  20. data/lib/generators/pg_rls/active_record/active_record_generator.rb +62 -65
  21. data/lib/generators/pg_rls/install/install_generator.rb +38 -0
  22. data/lib/generators/pg_rls/pg_rls_generator.rb +2 -1
  23. data/lib/generators/pg_rls/templates/USAGE +28 -0
  24. data/lib/generators/pg_rls/templates/app/models/abstract_base_class.rb.tt +7 -0
  25. data/lib/generators/pg_rls/templates/app/models/model.rb.tt +22 -0
  26. data/lib/generators/pg_rls/templates/config/initializers/pg_rls.rb.tt +58 -0
  27. data/lib/generators/pg_rls/templates/db/migrate/backport_pg_rls_table.rb.tt +14 -0
  28. data/lib/generators/pg_rls/templates/db/migrate/convert_to_pg_rls_table.rb.tt +5 -0
  29. data/lib/generators/pg_rls/templates/db/migrate/convert_to_pg_rls_tenant_table.rb.tt +5 -0
  30. data/lib/generators/pg_rls/templates/db/migrate/create_pg_rls_table.rb.tt +29 -0
  31. data/lib/generators/pg_rls/templates/db/migrate/create_pg_rls_tenant_table.rb.tt +29 -0
  32. data/lib/pg_rls/active_record/connection_adapters/connection_pool.rb +31 -0
  33. data/lib/pg_rls/active_record/connection_adapters/postgre_sql/check_rls_user_privileges.rb +207 -0
  34. data/lib/pg_rls/active_record/connection_adapters/postgre_sql/errors.rb +17 -0
  35. data/lib/pg_rls/active_record/connection_adapters/postgre_sql/grant_rls_user_privileges.rb +167 -0
  36. data/lib/pg_rls/active_record/connection_adapters/postgre_sql/rls_functions.rb +91 -0
  37. data/lib/pg_rls/active_record/connection_adapters/postgre_sql/rls_policies.rb +56 -0
  38. data/lib/pg_rls/active_record/connection_adapters/postgre_sql/rls_triggers.rb +95 -0
  39. data/lib/pg_rls/active_record/connection_adapters/postgre_sql/rls_user_statements.rb +127 -0
  40. data/lib/pg_rls/active_record/connection_adapters/postgre_sql/schema_dumper.rb +71 -0
  41. data/lib/pg_rls/active_record/connection_adapters/postgre_sql/schema_statements.rb +120 -0
  42. data/lib/pg_rls/active_record/connection_adapters/postgre_sql/sql_helper_method.rb +30 -0
  43. data/lib/pg_rls/active_record/connection_adapters/postgre_sql.rb +36 -0
  44. data/lib/pg_rls/active_record/connection_adapters.rb +12 -0
  45. data/lib/pg_rls/active_record/database_shards.rb +74 -0
  46. data/lib/pg_rls/active_record/migration/command_recorder.rb +28 -0
  47. data/lib/pg_rls/active_record/migration.rb +11 -0
  48. data/lib/pg_rls/active_record/test_databases.rb +19 -0
  49. data/lib/pg_rls/active_record.rb +11 -0
  50. data/lib/pg_rls/active_support/string_ext.rb +17 -0
  51. data/lib/pg_rls/active_support.rb +9 -0
  52. data/lib/pg_rls/connection_config.rb +61 -0
  53. data/lib/pg_rls/deprecation.rb +14 -0
  54. data/lib/pg_rls/engine.rb +8 -0
  55. data/lib/pg_rls/error.rb +10 -0
  56. data/lib/pg_rls/generators/.keep +0 -0
  57. data/lib/pg_rls/multi_tenancy.rb +1 -1
  58. data/lib/pg_rls/railtie.rb +1 -11
  59. data/lib/pg_rls/tasks/.keep +0 -0
  60. data/lib/pg_rls/version.rb +3 -1
  61. data/lib/pg_rls.rb +67 -151
  62. data/rbs_collection.lock.yaml +132 -0
  63. data/rbs_collection.yaml +127 -0
  64. data/review_code.sh +33 -0
  65. data/sig/generators/pg_rls/active_record/active_record_generator.rbs +43 -0
  66. data/sig/generators/pg_rls/install/install_generator.rbs +20 -0
  67. data/sig/generators/pg_rls/pg_rls_generator.rbs +9 -0
  68. data/sig/pg_rls/active_record/connection_adapters/postgre_sql/check_rls_user_privileges.rbs +53 -0
  69. data/sig/pg_rls/active_record/connection_adapters/postgre_sql/errors.rbs +24 -0
  70. data/sig/pg_rls/active_record/connection_adapters/postgre_sql/grant_rls_user_privileges.rbs +55 -0
  71. data/sig/pg_rls/active_record/connection_adapters/postgre_sql/rls_functions.rbs +31 -0
  72. data/sig/pg_rls/active_record/connection_adapters/postgre_sql/rls_policies.rbs +28 -0
  73. data/sig/pg_rls/active_record/connection_adapters/postgre_sql/rls_triggers.rbs +35 -0
  74. data/sig/pg_rls/active_record/connection_adapters/postgre_sql/rls_user_statements.rbs +48 -0
  75. data/sig/pg_rls/active_record/connection_adapters/postgre_sql/schema_dumper.rbs +38 -0
  76. data/sig/pg_rls/active_record/connection_adapters/postgre_sql/schema_statements.rbs +67 -0
  77. data/sig/pg_rls/active_record/connection_adapters/postgre_sql/sql_helper_method.rbs +21 -0
  78. data/sig/pg_rls/active_record/connection_adapters/postgresql.rbs +10 -0
  79. data/sig/pg_rls/active_record/connection_adapters.rbs +50 -0
  80. data/sig/pg_rls/active_record/database_shards.rbs +34 -0
  81. data/sig/pg_rls/active_record/migration/command_recorder.rbs +14 -0
  82. data/sig/pg_rls/active_record/migration.rbs +8 -0
  83. data/sig/pg_rls/active_record.rbs +7 -0
  84. data/sig/pg_rls/active_support/hash_ext.rbs +11 -0
  85. data/sig/pg_rls/active_support/string_ext.rbs +27 -0
  86. data/sig/pg_rls/active_support.rbs +7 -0
  87. data/sig/pg_rls/app/models/pg_rls/record.rbs +4 -0
  88. data/sig/pg_rls/connection_config.rbs +16 -0
  89. data/sig/pg_rls/deprecation.rbs +9 -0
  90. data/sig/pg_rls/engine.rbs +7 -0
  91. data/sig/pg_rls/errors.rbs +14 -0
  92. data/sig/pg_rls/railtie.rbs +6 -0
  93. data/sig/pg_rls/tenant_test_helper.rbs +14 -0
  94. data/sig/pg_rls.rbs +60 -0
  95. data/sig/support/active_record.rbs +86 -0
  96. data/sig/support/active_support.rbs +7 -0
  97. data/sig/support/fowardable.rbs +2 -0
  98. data/sig/support/pg.rbs +12 -0
  99. data/sig/support/rails.rbs +38 -0
  100. data/start.sh +30 -0
  101. metadata +167 -12
  102. data/Gemfile +0 -21
  103. data/Gemfile.lock +0 -300
  104. data/bin/console +0 -15
  105. data/bin/setup +0 -8
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgRls
4
+ class Tenant
5
+ # Searchable Class
6
+ class Searchable
7
+ SEARCH_METHOD_BY_COLUMN_TYPE = PgRls::Tenant.columns.each_with_object({}) do |column, hash|
8
+ type = column.sql_type_metadata.type
9
+ hash[type] ||= []
10
+ hash[type] << column.name
11
+ end
12
+
13
+ def self.by_rls_object(tenant)
14
+ case tenant
15
+ when Tenant then tenant
16
+ when String, Symbol, Integer then Searchable.by_rls_methods(tenant)
17
+ when PgRls.main_model then tenant.becomes(Tenant)
18
+ else raise PgRls::Error::InvalidSearchInput, "Invalid search input: #{tenant}"
19
+ end
20
+ end
21
+
22
+ def self.by_rls_methods(search_input)
23
+ new(search_input).search_methods.each do |search_method|
24
+ tenant = PgRls::Tenant.find_by(search_method => search_input)
25
+ return tenant if tenant.present?
26
+ end
27
+
28
+ raise PgRls::Error::TenantNotFound, "No tenant found for #{search_input}"
29
+ end
30
+
31
+ def initialize(search_input)
32
+ @search_input = search_input
33
+ end
34
+
35
+ def search_methods
36
+ SEARCH_METHOD_BY_COLUMN_TYPE[target_type] || []
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :search_input
42
+
43
+ def target_type
44
+ case search_input
45
+ when Integer then :integer
46
+ when String, Symbol then target_type_by_string
47
+ else raise PgRls::Error::InvalidSearchInput, "Invalid search input: #{search_input}"
48
+ end
49
+ end
50
+
51
+ def target_type_by_string
52
+ case search_input
53
+ when /\A\d+\z/ then :integer
54
+ when /\A[0-9a-fA-F\-]{36}\z/ then :uuid
55
+ else :string
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgRls
4
+ class Tenant
5
+ # Securable Module
6
+ module Securable
7
+ extend ::ActiveSupport::Concern
8
+
9
+ included do
10
+ self.table_name = PgRls.table_name
11
+
12
+ self.ignored_columns = column_names.reject do |column|
13
+ PgRls.search_methods.map(&:to_s).include?(column)
14
+ end
15
+ end
16
+
17
+ class_methods do
18
+ def reset_rls_used_connections(connection = PgRls::Record.connection)
19
+ return connection if rls_connection_object_cache_by_thread.nil?
20
+
21
+ connection.exec_query("SET rls.tenant_id TO DEFAULT")
22
+ self.rls_connection_object_cache_by_thread = nil
23
+ connection
24
+ end
25
+
26
+ def rls_connection_object_cache_by_thread=(value)
27
+ ::ActiveSupport::IsolatedExecutionState[:active_record_rls_used_connections] = value
28
+ end
29
+
30
+ def rls_connection_object_cache_by_thread
31
+ ::ActiveSupport::IsolatedExecutionState[:active_record_rls_used_connections]
32
+ end
33
+ end
34
+
35
+ def set_rls(connection = PgRls::Record.connection)
36
+ self.class.reset_rls_used_connections if new_tenant?
37
+ return self if reused_connection?(connection)
38
+
39
+ connection.exec_query("SET rls.tenant_id = '#{tenant_id}'")
40
+
41
+ self
42
+ end
43
+
44
+ def readonly?
45
+ true
46
+ end
47
+
48
+ private
49
+
50
+ def new_tenant?
51
+ !rls_used_connections.add?(tenant_id).nil?
52
+ end
53
+
54
+ def reused_connection?(conn)
55
+ rls_used_connections.add?(conn.object_id).nil?
56
+ end
57
+
58
+ def rls_used_connections
59
+ if self.class.rls_connection_object_cache_by_thread.nil?
60
+ self.class.rls_connection_object_cache_by_thread = Set.new([tenant_id])
61
+ end
62
+
63
+ self.class.rls_connection_object_cache_by_thread
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgRls
4
+ class Tenant
5
+ # Switchable Module
6
+ module Switchable
7
+ extend ::ActiveSupport::Concern
8
+
9
+ def switch(tenant)
10
+ set_rls!(tenant)
11
+ rescue PgRls::Error::TenantNotFound, PgRls::Error::InvalidSearchInput
12
+ nil
13
+ end
14
+
15
+ def run_within(tenant)
16
+ set_rls!(tenant)
17
+ yield(PgRls::Current.tenant).presence
18
+ ensure
19
+ PgRls::Tenant.reset_rls
20
+ end
21
+
22
+ def with_tenant!(...)
23
+ PgRls::Deprecation.warn("This method is deprecated and will be removed in future versions. " \
24
+ "please use PgRls::Tenant.run_within instead.")
25
+ run_within(...)
26
+ end
27
+
28
+ def set_rls!(tenant)
29
+ PgRls::Current.tenant = Searchable.by_rls_object(tenant)
30
+ raise PgRls::Error::TenantNotFound, "No tenant found for #{tenant}" unless PgRls::Current.tenant.present?
31
+
32
+ PgRls::Current.tenant
33
+ end
34
+
35
+ def reset_rls
36
+ PgRls::Current.reset
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgRls
4
+ # Tenant model
5
+ class Tenant < Record
6
+ include Securable
7
+ extend Switchable
8
+ end
9
+ end
data/assets/logo.svg ADDED
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
2
+ <rect width="200" height="200" fill="#f8f8f8"/>
3
+ <g transform="translate(20, 20)">
4
+ <path d="M80 0 L160 40 L160 120 L80 160 L0 120 L0 40 Z" fill="#ff4444"/>
5
+ <path d="M80 20 L140 52 L140 108 L80 140 L20 108 L20 52 Z" fill="#ffffff"/>
6
+ <text x="80" y="95" font-family="Arial, sans-serif" font-size="36" font-weight="bold" text-anchor="middle" fill="#ff4444">PgRls</text>
7
+ </g>
8
+ </svg>
@@ -0,0 +1,14 @@
1
+ services:
2
+ postgres:
3
+ image: postgres:latest
4
+ environment:
5
+ POSTGRES_PASSWORD: password
6
+ healthcheck:
7
+ test: pg_isready -U postgres
8
+ ports:
9
+ - "5432:5432"
10
+ volumes:
11
+ - postgres:/var/lib/postgresql/data
12
+
13
+ volumes:
14
+ postgres:
@@ -1,115 +1,112 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails/generators/active_record/model/model_generator'
4
- require File.join(File.dirname(__FILE__), '../base')
3
+ require "rails/generators/active_record/model/model_generator"
4
+ require_relative "../install/install_generator"
5
5
 
6
6
  module PgRls
7
7
  module Generators
8
8
  # Active Record Generator
9
9
  class ActiveRecordGenerator < ::ActiveRecord::Generators::ModelGenerator
10
- include ::PgRls::Base
10
+ source_root File.expand_path("../templates", __dir__.to_s)
11
11
 
12
- source_root File.expand_path('./templates', __dir__)
12
+ class_option :parent, type: :string, default: "ApplicationRecord",
13
+ desc: "The parent class for the generated model"
13
14
 
14
- def check_class_collision; end
15
+ class_option :rls_parent, type: :string, default: "PgRls::Record",
16
+ desc: "The parent class for the rls generated model"
15
17
 
16
- def create_migration_file; end
17
-
18
- def migration_exist?
19
- @migration_exist ||= Dir.glob("#{migration_path}/*create_#{table_name}.rb").present?
18
+ # Need to override so it will not check for class collision
19
+ def check_class_collision
20
+ super
21
+ @class_coalescing = false
22
+ rescue Rails::Generators::Error
23
+ @class_coalescing = true
20
24
  end
21
25
 
22
- def create_tenant_migration_file
23
- return unless creating?
26
+ def create_model_file
27
+ return if class_coalescing?
24
28
 
25
- migration_template(create_migration_template_path,
26
- "#{migration_path}/#{create_file_sub_name}_#{table_name}.rb",
27
- migration_version:)
29
+ generate_abstract_class if database && !custom_parent?
30
+ template("app/models/model.rb", model_path, parent_class_name: parent_class_name)
28
31
  end
29
32
 
30
- def convert_tenant_migration_file
31
- unless creating?
32
- migration_template(convert_migration_template_path,
33
- "#{migration_path}/#{convert_file_sub_name}_#{table_name}.rb",
34
- migration_version:)
35
- end
36
-
37
- return unless installation_in_progress?
33
+ def upgrade_model_file
34
+ return unless class_coalescing? && model_file_exists?
38
35
 
39
- migration_template('convert_migration_backport.rb.tt',
40
- "#{migration_path}/pg_rls_backport_#{table_name}.rb",
41
- migration_version:)
36
+ gsub_file(model_path, /< ApplicationRecord/, "< #{parent_class_name}")
42
37
  end
43
38
 
44
- def create_model_file
45
- return if migration_exist?
46
-
47
- generate_abstract_class if database && !parent
39
+ def create_migration_file
40
+ return if skip_migration_creation? || class_coalescing?
48
41
 
49
- template model_template_path, model_file
42
+ clean_indexes_attributes!
43
+ migration_template "db/migrate/create_#{migration_template_prefix}_table.rb",
44
+ "db/migrate/create_#{migration_template_prefix}_#{table_name}.rb"
50
45
  end
51
46
 
52
- def inject_method_to_model
53
- return unless installation_in_progress?
54
-
55
- gsub_file(model_file, /Class #{class_name} < #{parent_class_name.classify}/mi) do |match|
56
- "#{match}\n def self.current\n PgRls::Tenant.fetch\n end\n"
57
- end
58
- end
47
+ def upgrade_migration_file
48
+ return if skip_migration_creation? || !class_coalescing?
59
49
 
60
- def model_file
61
- File.join('app/models', class_path, "#{file_name}.rb")
50
+ migration_template "db/migrate/convert_to_#{migration_template_prefix}_table.rb",
51
+ "db/migrate/convert_to_#{migration_template_prefix}_#{table_name}.rb"
62
52
  end
63
53
 
64
- def create_migration_template_path
65
- return 'init_migration.rb.tt' if installation_in_progress?
54
+ def backport_migration_file
55
+ return if skip_migration_creation? || installing? || !class_coalescing?
66
56
 
67
- 'migration.rb.tt'
57
+ migration_template "db/migrate/backport_pg_rls_table.rb",
58
+ "db/migrate/backport_pg_rls_to_#{table_name}.rb"
68
59
  end
69
60
 
70
- def convert_migration_template_path
71
- return 'init_convert_migration.rb.tt' if installation_in_progress?
61
+ private
72
62
 
73
- 'convert_migration.rb.tt'
63
+ def installing?
64
+ Kernel.const_defined?("PgRls::InstallGenerator")
74
65
  end
75
66
 
76
- def model_template_path
77
- return 'init_model.rb.tt' if installation_in_progress?
67
+ def parent_class_name
68
+ return rls_parent unless installing?
78
69
 
79
- 'model.rb.tt'
70
+ super
80
71
  end
81
72
 
82
- def create_file_sub_name
83
- return 'pg_rls_create_tenant' if installation_in_progress?
73
+ def rls_parent
74
+ options[:rls_parent]
75
+ end
84
76
 
85
- 'pg_rls_create'
77
+ def migration_template_prefix
78
+ installing? ? "pg_rls_tenant" : "pg_rls"
86
79
  end
87
80
 
88
- def convert_file_sub_name
89
- return 'pg_rls_convert_tenant' if installation_in_progress?
81
+ def generate_abstract_class
82
+ return if File.exist?(generate_abstract_class_path)
90
83
 
91
- 'pg_rls_convert'
84
+ template "app/models/abstract_base_class.rb", generate_abstract_class_path
92
85
  end
93
86
 
94
- def installation_in_progress?
95
- shell.base.class.name.include?('Install')
87
+ def model_path
88
+ File.join("app/models", class_path, "#{file_name}.rb")
96
89
  end
97
90
 
98
- def migration_version
99
- "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
91
+ def generate_abstract_class_path
92
+ File.join("app/models", "#{database.underscore}_record.rb")
100
93
  end
101
94
 
102
- def migration_path
103
- db_migrate_path
95
+ def class_coalescing?
96
+ @class_coalescing
104
97
  end
105
98
 
106
- def creating?
107
- @creating ||= !migration_exist?
108
- end
99
+ def clean_indexes_attributes!
100
+ return unless options[:indexes] == false
109
101
 
110
- protected
102
+ attributes.each do |a|
103
+ a.attr_options.delete(:index) if a.reference? && !a.has_index?
104
+ end
105
+ end
111
106
 
112
- def migration_action = 'add'
107
+ def model_file_exists?
108
+ File.exist?(File.join(destination_root, model_path))
109
+ end
113
110
  end
114
111
  end
115
112
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgRls
4
+ # Generator to install the gem
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("../templates", __dir__.to_s)
7
+
8
+ attr_reader :attributes
9
+
10
+ def initialize(args, *options)
11
+ pg_rls_config(args.first)
12
+ super
13
+ end
14
+
15
+ def create_install_config
16
+ template "config/initializers/pg_rls.rb"
17
+ end
18
+
19
+ hook_for :orm, required: true
20
+
21
+ def show_readme
22
+ readme "USAGE" if invoke?
23
+ end
24
+
25
+ private
26
+
27
+ def pg_rls_config(tenant_model_or_table)
28
+ raise "Tenant model or table name is required" if tenant_model_or_table.blank?
29
+
30
+ PgRls.class_name = tenant_model_or_table.capitalize.singularize.to_sym
31
+ PgRls.table_name = tenant_model_or_table.underscore.pluralize.to_sym
32
+ end
33
+
34
+ def invoke?
35
+ behavior == :invoke
36
+ end
37
+ end
38
+ end
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative File.join(File.dirname(__FILE__), 'active_record/active_record_generator')
3
+ require_relative File.join(File.dirname(__FILE__), "active_record/active_record_generator")
4
4
 
5
5
  module PgRls
6
6
  module Generators
7
+ # PgRls Generator
7
8
  class PgRlsGenerator < ::Rails::Generators::NamedBase
8
9
  # override ModelGenerator
9
10
  hook_for :orm, required: true
@@ -0,0 +1,28 @@
1
+ ===============================================================================
2
+
3
+ 🎉 Congratulations! The PgRls gem has been successfully installed.
4
+
5
+ To generate a model with secure Row-Level Security (RLS), run the following command:
6
+
7
+ rails g pg_rls ModelName
8
+
9
+ or
10
+
11
+ rails generate pg_rls ModelName
12
+
13
+ 📌 Remember:
14
+
15
+ - Replace `ModelName` with the actual name of your model (e.g., `User`, `Post`).
16
+ - This will set up the necessary RLS policies and table configurations for multi-tenant support.
17
+
18
+ 🛠 Next Steps:
19
+
20
+ 1. After generating the model, review the generated migration and model files.
21
+ 2. Ensure your database is configured to support Row-Level Security.
22
+ 3. Run `rails db:migrate` to apply the generated migration.
23
+ 4. Customize the generated policies to fit your application's security needs.
24
+
25
+ 💡 Tip: Check the documentation at <https://github.com/Dandush03/pg_rls>
26
+ for more details on how to fine-tune RLS policies.
27
+
28
+ ===============================================================================
@@ -0,0 +1,7 @@
1
+ <% module_namespacing do -%>
2
+ class <%= abstract_class_name %> < <%= parent_class_name.classify %>
3
+ self.abstract_class = true
4
+
5
+ connects_to database: { <%= ActiveRecord.writing_role %>: :<%= database -%> }
6
+ end
7
+ <% end -%>
@@ -0,0 +1,22 @@
1
+ <% module_namespacing do -%>
2
+ class <%= class_name %> < <%= parent_class_name.classify %>
3
+ <% attributes.select(&:reference?).each do |attribute| -%>
4
+ belongs_to :<%= attribute.name %><%= ", polymorphic: true" if attribute.polymorphic? %>
5
+ <% end -%>
6
+ <% attributes.select(&:rich_text?).each do |attribute| -%>
7
+ has_rich_text :<%= attribute.name %>
8
+ <% end -%>
9
+ <% attributes.select(&:attachment?).each do |attribute| -%>
10
+ has_one_attached :<%= attribute.name %>
11
+ <% end -%>
12
+ <% attributes.select(&:attachments?).each do |attribute| -%>
13
+ has_many_attached :<%= attribute.name %>
14
+ <% end -%>
15
+ <% attributes.select(&:token?).each do |attribute| -%>
16
+ has_secure_token<% if attribute.name != "token" %> :<%= attribute.name %><% end %>
17
+ <% end -%>
18
+ <% if attributes.any?(&:password_digest?) -%>
19
+ has_secure_password
20
+ <% end -%>
21
+ end
22
+ <% end -%>
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # PgRls Configuration
4
+ # ----------------------
5
+ # Use this file to configure PgRls to fit your application's needs.
6
+ # Below are the default settings and examples of how to customize them.
7
+ #
8
+ # You can modify these settings as needed and restart your application.
9
+
10
+ PgRls.setup do |config|
11
+ # The main class name used for the RLS tenant model.
12
+ # This should be the base model representing your tenant (e.g., :organization, :account, etc.).
13
+ # Default: Derived from your tenant model specified during installation.
14
+ config.class_name = :<%= PgRls.class_name %>
15
+
16
+ # The name of the database table where tenant information is stored.
17
+ # Ensure this table is properly configured with RLS enabled.
18
+ # Default: Derived from your tenant model.
19
+ config.table_name = :<%= PgRls.table_name %>
20
+
21
+ # The methods used to search for the tenant. These methods should exist on the tenant model.
22
+ # Default: Search methods specified during installation.
23
+ config.search_methods = <%= "%i[#{PgRls.search_methods.join(' ')}]" %>
24
+
25
+ # The attributes that should be tracked in the request context.
26
+ # This allows you to specify which attributes PgRls::Current should manage dynamically.
27
+ # By default, this is an empty array, as only the current tenant is tracked for RLS purposes.
28
+ # You can add additional attributes when needed, such as theme, branch, etc.
29
+ config.current_attributes = []
30
+
31
+ # If you need to track additional attributes dynamically, use the `__` convention.
32
+ # This allows calling nested models with a double underscore, inspired by Stimulus controllers.
33
+ # Example: If `organization__branch` is added, it will resolve to Organization::Branch.first
34
+ # config.current_attributes = %i[organization__branch]
35
+
36
+ # Database credentials for the RLS user (if needed).
37
+ # If using Rails credentials, ensure they are correctly stored in config/credentials.yml.enc.
38
+ # Uncomment and update these lines if you need to provide explicit credentials.
39
+ # config.username = Rails.application.credentials.dig(:database, :username)
40
+ # config.password = Rails.application.credentials.dig(:database, :password)
41
+
42
+ # The base class from which PgRls::Record should inherit.
43
+ # This allows your PgRls models to inherit from the same base class as your application models.
44
+ # For Rails 5+ applications that use ApplicationRecord, this is particularly useful.
45
+ # Default: 'ActiveRecord::Base'
46
+ # config.abstract_base_record_class = 'ApplicationRecord'
47
+
48
+ # The database role group used for RLS operations.
49
+ # This role acts as a base role, allowing multiple users to be assigned this role for consistent access control.
50
+ # Default: "rls_group"
51
+ # config.rls_role_group = "rls_group"
52
+
53
+ # The database schema where your tenant tables are located.
54
+ # Default: "public"
55
+ # config.schema = "public"
56
+
57
+ # You can add additional custom configurations here if needed.
58
+ end
@@ -0,0 +1,14 @@
1
+ class BackportPgRlsTo<%= table_name.camelize %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def up
3
+ # Suggested Code:
4
+ # PgRls.on_each_tenant do |tenant|
5
+ # <%= class_name.camelize %>.where(identifier_reference_for_tenant: tenant.id)
6
+ # .in_batches.update_all(tenant_id: tenant.tenant_id)
7
+ # end
8
+ end
9
+
10
+ def down
11
+ # Suggested Code:
12
+ # raise ActiveRecord::IrreversibleMigration, 'This migration is irreversible, please restore from backup.'
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ class ConvertToPgRls<%= table_name.camelize %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ convert_to_rls_table :<%= table_name %>
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class ConvertToPgRlsTenant<%= table_name.camelize %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ convert_to_rls_tenant_table :<%= table_name %>
4
+ end
5
+ end
@@ -0,0 +1,29 @@
1
+ class CreatePgRls<%= table_name.camelize %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_rls_table :<%= table_name %><%= primary_key_type %> do |t|
4
+ <% attributes.each do |attribute| -%>
5
+ <% if attribute.password_digest? -%>
6
+ t.string :password_digest<%= attribute.inject_options %>
7
+ <% elsif attribute.token? -%>
8
+ t.string :<%= attribute.name %><%= attribute.inject_options %>
9
+ <% elsif attribute.reference? -%>
10
+ t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %><%= foreign_key_type %>
11
+ <% elsif !attribute.virtual? -%>
12
+ t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
13
+ <% end -%>
14
+ <% end -%>
15
+ <% unless attributes.empty? -%>
16
+
17
+ <% end -%>
18
+ <% if options[:timestamps] -%>
19
+ t.timestamps
20
+ <% end -%>
21
+ end
22
+ <% attributes.select(&:token?).each do |attribute| -%>
23
+ add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true
24
+ <% end -%>
25
+ <% attributes_with_index.each do |attribute| -%>
26
+ add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
27
+ <% end -%>
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ class CreatePgRlsTenant<%= table_name.camelize %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_rls_tenant_table :<%= table_name %><%= primary_key_type %> do |t|
4
+ <% attributes.each do |attribute| -%>
5
+ <% if attribute.password_digest? -%>
6
+ t.string :password_digest<%= attribute.inject_options %>
7
+ <% elsif attribute.token? -%>
8
+ t.string :<%= attribute.name %><%= attribute.inject_options %>
9
+ <% elsif attribute.reference? -%>
10
+ t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %><%= foreign_key_type %>
11
+ <% elsif !attribute.virtual? -%>
12
+ t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
13
+ <% end -%>
14
+ <% end -%>
15
+ <% unless attributes.empty? -%>
16
+
17
+ <% end -%>
18
+ <% if options[:timestamps] -%>
19
+ t.timestamps
20
+ <% end -%>
21
+ end
22
+ <% attributes.select(&:token?).each do |attribute| -%>
23
+ add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true
24
+ <% end -%>
25
+ <% attributes_with_index.each do |attribute| -%>
26
+ add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
27
+ <% end -%>
28
+ end
29
+ end