pg_rls 0.0.1.beta → 0.0.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eff1edae4e2b864b6d1c50dbe73330ddd3092314fc0f0654ce631dd3f2381e73
4
- data.tar.gz: f61c086a2319def269009a8acd4bb712457913cb23703d4f592486c1adc76161
3
+ metadata.gz: 54636ddf6a708016930a9f737f9a45b9cc13c157bc4adef11e6bb11d72d9490c
4
+ data.tar.gz: 9a8185e0f13e3f021850372c6a4faf44b0c7c4f331eebbeb65e280aefc88812c
5
5
  SHA512:
6
- metadata.gz: 88eaa010df64b8ba78630841f3ac886c44dca7929e43016e2dcbe6b4000a06ad41d0606e94fe7457eb3c46fe0e3c341d2208bf6b5fd64553fcd9eedbaee59c7a
7
- data.tar.gz: 70aef5056d562fb96a2189d580f3b3cc31b1efb4ef71433969f8896268cdf0286bc5450b9b855166e48580d12aed451d0771c672611c3a934f7f046be2ea8dc9
6
+ metadata.gz: 6fc47ebe73cf24b6a8171442d971e3f255e46036af47ba8db4d8127639b321ca2f464be096adc320a2c3405157effdea56c4c7969969dbdf0633179257718ebc
7
+ data.tar.gz: 1c317ccb4d8aea566f8f334309aa0ed785f1256ad450c4b97f484efdf79b75d3269e259404dd7a379225ac40803f93981c277211f40438d30810bf22142fad83
data/Gemfile CHANGED
@@ -13,5 +13,3 @@ gem 'rake'
13
13
  gem 'rspec'
14
14
 
15
15
  gem 'rubocop'
16
-
17
- gem 'rspec-rails'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pg_rls (0.0.1.alpha)
4
+ pg_rls (0.0.1)
5
5
  bundler (>= 2.2.10)
6
6
 
7
7
  GEM
@@ -83,7 +83,7 @@ GEM
83
83
  mini_mime (>= 0.1.1)
84
84
  marcel (1.0.2)
85
85
  method_source (1.0.0)
86
- mini_mime (1.1.1)
86
+ mini_mime (1.1.2)
87
87
  minitest (5.14.4)
88
88
  nio4r (2.5.8)
89
89
  nokogiri (1.12.5-x86_64-linux)
@@ -137,14 +137,6 @@ GEM
137
137
  rspec-mocks (3.10.2)
138
138
  diff-lcs (>= 1.2.0, < 2.0)
139
139
  rspec-support (~> 3.10.0)
140
- rspec-rails (5.0.2)
141
- actionpack (>= 5.2)
142
- activesupport (>= 5.2)
143
- railties (>= 5.2)
144
- rspec-core (~> 3.10)
145
- rspec-expectations (~> 3.10)
146
- rspec-mocks (~> 3.10)
147
- rspec-support (~> 3.10)
148
140
  rspec-support (3.10.2)
149
141
  rubocop (1.22.1)
150
142
  parallel (~> 1.10)
@@ -182,7 +174,6 @@ DEPENDENCIES
182
174
  rails (~> 6.1.4, >= 6.1.4.1)
183
175
  rake
184
176
  rspec
185
- rspec-rails
186
177
  rubocop
187
178
 
188
179
  RUBY VERSION
data/README.md CHANGED
@@ -1,10 +1,48 @@
1
- # PgRls
2
-
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/pg_rls`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
6
-
7
- ## Installation
1
+ [![Contributors][contributors-shield]][contributors-url]
2
+ [![Forks][forks-shield]][forks-url]
3
+ [![Stargazers][stars-shield]][stars-url]
4
+ [![Issues][issues-shield]][issues-url]
5
+ [![LinkedIn][linkedin-shield2]][linkedin-url2]
6
+ [![Hireable][hireable]][hireable-url]
7
+ [![Donate][donate]][paypal-donate-code]
8
+
9
+ <!-- PROJECT LOGO -->
10
+ <br />
11
+ <p align="center">
12
+ <h1 align="center">PgRls<h2 align="center">PostgreSQL Row Level Security<br />The Rails right way to do multitenancy</h2></h1>
13
+
14
+ <p align="center">
15
+ <br />
16
+ <a href="https://github.com/Dandush03/pg_rls/wiki"><strong>Explore the docs »</strong></a>
17
+ <br />
18
+ <br />
19
+ <a href="https://github.com/Dandush03/pg_rls/issues">Report Bug</a>
20
+ ·
21
+ <a href="https://github.com/Dandush03/pg_rls/issues">Request Feature</a>
22
+ ·
23
+ <a href="https://github.com/Dandush03/pg_rls">API Repo</a>
24
+ </p>
25
+
26
+ </p>
27
+
28
+ ### Table of Contents
29
+ * [Required Installations](#required-Installations)
30
+ * [Installing](#installing)
31
+ * [Instructions](#instructions)
32
+ * [Testing](#Testing)
33
+ * [Development](#testing)
34
+ * [Contact](#contact)
35
+ * [Contributing](#contributing)
36
+ * [License](#license)
37
+ * [Code of Conduct](#Code-of-Conduct)
38
+ * [Show your support](#Show-your-support)
39
+
40
+ ### It's time we start doing multitenancy right! You can avoid creating a separate Postgres schema/databases for each customer or trying to ensure the WHERE clause of every single query includes the particular company. Just integrate PgRls seamlessly to your application.
41
+
42
+ ### This gem will integrate PostgreSQL RLS to help you develop a great multitenancy application.
43
+
44
+ ## Required Installation
45
+ ### Installing
8
46
 
9
47
  Add this line to your application's Gemfile:
10
48
 
@@ -16,23 +54,51 @@ And then execute:
16
54
 
17
55
  $ bundle install
18
56
 
19
- Or install it yourself as:
57
+ Or install it yourself with:
20
58
 
21
59
  $ gem install pg_rls
22
60
 
23
- ## Usage
61
+ ### Instructions
62
+
63
+ ```bash
64
+ rails generate pg_rls:install company #=> where company eq tenant model name
65
+ ```
66
+ You can change company to anything you'd like, for example, `tenant`
67
+ This will generate the model and inject all the required code
68
+
69
+ For any new model that needs to be under rls, you can generate it by writing
70
+
71
+ ```bash
72
+ rails generate pg_rls user #=> where user eq model name
73
+ ```
74
+ and it will generate all the necesary information for you.
24
75
 
25
- RUN `rails generate pg_rls:install company`
76
+ You can swtich to another tenant by using
77
+ ```ruby
78
+ PgRls::Tenant.switch :app #=> where app eq tenant name
79
+ ```
80
+ Don't forget to update how you want `PgRls` to find your tenant, you can set multiple options by modifying `api/config/initializers/pg_rls.rb` `search_methods`
81
+ ### Testing
26
82
 
27
- You can change company to anything you'd like like for example `tenant`
28
- this will generate the model and inject all the required code
83
+ Many application uses some sort of database cleaner before running thair spec so on each test that we run we'll have an empty state. Usually, those gems clear our user configuration for the database. To solve this issue, we must implement the following:
29
84
 
30
- for any new model that required to be under rls you can generate it by writing
85
+ ```ruby
86
+ # spec/rails_helper.rb
31
87
 
32
- `rails generate pg_rls user`
33
- and it will generate all the necesary information for you
88
+ ...
89
+ # some database cleaning strategy
34
90
 
35
- enjoy the gem :)
91
+ config.before(:suite) do
92
+ # Create A Default Tenant and Grant Test User Credentials
93
+ PgRls::Database::Prepared.grant_user_credentials
94
+ # Create the tenant which in this example is company and we are using FactoryBot
95
+ FactoryBot.create(:company, subdomain: 'app')
96
+ # In this default case our initializer is set to search by subdomain so will use it
97
+ PgRls::Tenant.switch :app
98
+ end
99
+
100
+ ...
101
+ ```
36
102
  ## Development
37
103
 
38
104
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -50,3 +116,33 @@ The gem is available as open source under the terms of the [MIT License](https:/
50
116
  ## Code of Conduct
51
117
 
52
118
  Everyone interacting in the PgRls project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/dandush03/pg_rls/blob/master/CODE_OF_CONDUCT.md).
119
+
120
+ ## Note
121
+ Currently we only support subdomain as a searcher but will soon integrate slug/domain and cookies support
122
+
123
+ ## Show your support
124
+
125
+ Give a ⭐️ if you like this project!
126
+
127
+ If this project help you reduce time to develop, you can give me a cup of coffee :)
128
+
129
+ [![paypal][paypal-url]][paypal-donate-code]
130
+
131
+ <!-- MARKDOWN LINKS & IMAGES -->
132
+ [contributors-shield]: https://img.shields.io/github/contributors/Dandush03/React-Calculator.svg?style=flat-square
133
+ [contributors-url]: https://github.com/Dandush03/pg_rls/graphs/contributors
134
+ [forks-shield]: https://img.shields.io/github/forks/Dandush03/pg_rls.svg?style=flat-square
135
+ [forks-url]: https://github.com/Dandush03/pg_rls/network/members
136
+ [stars-shield]: https://img.shields.io/github/stars/Dandush03/pg_rls.svg?style=flat-square
137
+ [stars-url]: https://github.com/Dandush03/pg_rls/stargazers
138
+ [issues-shield]: https://img.shields.io/github/issues/Dandush03/pg_rls.svg?style=flat-square
139
+ [issues-url]: https://github.com/Dandush03/pg_rls/issues
140
+ [license-shield]: https://img.shields.io/github/license/Dandush03/pg_rls.svg?style=flat-square
141
+ [license-url]: https://github.com/Dandush03/pg_rls/blob/master/LICENSE.txt
142
+ [linkedin-shield2]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555
143
+ [linkedin-url2]: https://www.linkedin.com/in/daniel-laloush/
144
+ [hireable]: https://cdn.rawgit.com/hiendv/hireable/master/styles/flat/yes.svg
145
+ [paypal-url]: https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif
146
+ [paypal-donate-code]: https://www.paypal.com/donate?hosted_button_id=QKZFZAMQNC8JL
147
+ [hireable-url]: https://www.linkedin.com/in/daniel-laloush/
148
+ [donate]: https://img.shields.io/badge/Donate-PayPal-blue.svg
@@ -11,36 +11,86 @@ module PgRls
11
11
 
12
12
  source_root File.expand_path('./templates', __dir__)
13
13
 
14
- hook_for :test_framework
14
+ def check_class_collision; end
15
15
 
16
- def create_migration_file
17
- migration_template migration_template_path, "#{migration_path}/#{file_sub_name}_#{table_name}.rb",
16
+ def create_migration_file; end
17
+
18
+ def migration_exist?
19
+ @migration_exist ||= Dir.glob("#{migration_path}/*create_#{table_name}.rb").present?
20
+ end
21
+
22
+ def create_tenant_migration_file
23
+ return if migration_exist?
24
+
25
+ migration_template create_migration_template_path,
26
+ "#{migration_path}/#{create_file_sub_name}_#{table_name}.rb",
27
+ migration_version: migration_version
28
+ end
29
+
30
+ def convert_tenant_migration_file
31
+ return unless migration_exist?
32
+
33
+ migration_template convert_migration_template_path,
34
+ "#{migration_path}/#{convert_file_sub_name}_#{table_name}.rb",
35
+ migration_version: migration_version
36
+
37
+ return if installation_in_progress?
38
+
39
+ migration_template 'convert_migration_backport.rb.tt',
40
+ "#{migration_path}/pg_rls_backport_#{table_name}.rb",
18
41
  migration_version: migration_version
19
42
  end
20
43
 
21
44
  def create_model_file
45
+ return if migration_exist?
46
+
22
47
  generate_abstract_class if database && !parent
23
- template model_template_path, File.join('app/models', class_path, "#{file_name}.rb")
48
+
49
+ template model_template_path, model_file
50
+ end
51
+
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
59
+
60
+ def model_file
61
+ File.join('app/models', class_path, "#{file_name}.rb")
24
62
  end
25
63
 
26
- def migration_template_path
64
+ def create_migration_template_path
27
65
  return 'init_migration.rb.tt' if installation_in_progress?
28
66
 
29
67
  'migration.rb.tt'
30
68
  end
31
69
 
70
+ def convert_migration_template_path
71
+ return 'init_convert_migration.rb.tt' if installation_in_progress?
72
+
73
+ 'convert_migration.rb.tt'
74
+ end
75
+
32
76
  def model_template_path
33
77
  return 'init_model.rb.tt' if installation_in_progress?
34
78
 
35
79
  'model.rb.tt'
36
80
  end
37
81
 
38
- def file_sub_name
39
- return 'pg_rls_tenant_create' if installation_in_progress?
82
+ def create_file_sub_name
83
+ return 'pg_rls_create_tenant' if installation_in_progress?
40
84
 
41
85
  'pg_rls_create'
42
86
  end
43
87
 
88
+ def convert_file_sub_name
89
+ return 'pg_rls_convert_tenant' if installation_in_progress?
90
+
91
+ 'pg_rls_convert'
92
+ end
93
+
44
94
  def installation_in_progress?
45
95
  shell.base.class.name.include?('Install')
46
96
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PgRlsConvert<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
4
+ def up
5
+ convert_to_rls_table :<%= table_name %>
6
+ end
7
+
8
+ def down
9
+ revert_rls_table :<%= table_name %>
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PgRlsBackport<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
4
+ def up
5
+ PgRls.establish_default_connection
6
+
7
+ # Suggested Code:
8
+ # PgRls.all_tenants do |tenant|
9
+ # tenant.<%= table_name %>.in_batches(of: 100) do |<%= table_name %>|
10
+ # <%= table_name %>.each { |<%= table_name.singularize %>| <%= table_name.singularize %>.update_attribute('tenant_id', tenant.tenant_id) }
11
+ # end
12
+ # end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PgRlsConvertTenant<%= PgRls.table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
4
+ def up
5
+ convert_to_rls_tenant_table :<%= table_name %>
6
+ end
7
+
8
+ def down
9
+ revert_rls_tenant_table :<%= table_name %>
10
+ end
11
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class PgRlsTenantCreate<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
3
+ class PgRlsCreateTenant<%= PgRls.table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
4
4
  def up
5
5
  create_rls_tenant_table :<%= table_name %>, id: :uuid do |t|
6
6
  t.string :name
@@ -13,13 +13,13 @@ class PgRlsTenantCreate<%= table_name.camelize %> < ActiveRecord::Migration<%= m
13
13
  t.timestamps
14
14
  end
15
15
 
16
- add_index :companies, :name, unique: true
17
- add_index :companies, :identification, unique: true
18
- add_index :companies, :domain, unique: true
19
- add_index :companies, :subdomain, unique: true
16
+ add_index :<%= table_name %>, :name, unique: true
17
+ add_index :<%= table_name %>, :identification, unique: true
18
+ add_index :<%= table_name %>, :domain, unique: true
19
+ add_index :<%= table_name %>, :subdomain, unique: true
20
20
  end
21
21
 
22
22
  def down
23
- drop_rls_tenant_table :companies
23
+ drop_rls_tenant_table :<%= table_name %>
24
24
  end
25
25
  end
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  <% module_namespacing do -%>
4
- class <%= class_name %> < <%= parent_class_name.classify %>
4
+ class <%= PgRls.class_name.camelize %> < <%= parent_class_name.classify %>
5
5
  def self.current
6
- find_by_tenant_id(connection.execute("SELECT current_setting('rls.tenant_id')").getvalue(0, 0))
7
- rescue ActiveRecord::StatementInvalid
8
- 'no tenant is selected'
6
+ PgRls::Tenant.fetch
9
7
  end
10
8
  <% attributes.select(&:reference?).each do |attribute| -%>
11
9
  belongs_to :<%= attribute.name %><%= ", polymorphic: true" if attribute.polymorphic? %>
@@ -8,14 +8,25 @@ module PgRls
8
8
  MissingORMError = Class.new(Thor::Error)
9
9
  # Installer Generator
10
10
  class InstallGenerator < Rails::Generators::Base
11
+ def initialize(*args)
12
+ tenant_model_or_table = args.first
13
+ if tenant_model_or_table.present?
14
+ PgRls.table_name = tenant_model_or_table.first.pluralize
15
+ PgRls.class_name = tenant_model_or_table.first.singularize
16
+ end
17
+ super
18
+ end
11
19
  APPLICATION_RECORD_LINE = 'class ApplicationRecord < ActiveRecord::Base'
12
20
  APPLICATION_RECORD_PATH = 'app/models/application_record.rb'
13
21
  APPLICATION_CONTROLLER_LINE = 'class ApplicationController < ActionController::Base'
14
22
  APPLICATION_CONTROLLER_PATH = 'app/controllers/application_controller.rb'
23
+
15
24
  source_root File.expand_path('../templates', __dir__)
16
25
 
17
26
  desc 'Creates a PgRls initializer and copy locale files to your application.'
18
27
 
28
+ hook_for :orm, required: true
29
+
19
30
  def orm_error_message
20
31
  <<-ERROR.strip_heredoc
21
32
  An ORM must be set to install PgRls in your application.
@@ -30,10 +41,9 @@ module PgRls
30
41
  def copy_initializer
31
42
  raise MissingORMError, orm_error_message unless options[:orm]
32
43
 
33
- template 'pg_rls.rb.tt', 'config/initializers/pg_rls.rb'
34
-
35
44
  inject_include_to_application_record
36
45
  inject_include_to_application_controller
46
+ template 'pg_rls.rb.tt', 'config/initializers/pg_rls.rb'
37
47
  end
38
48
 
39
49
  def inject_include_to_application_record
@@ -68,8 +78,6 @@ module PgRls
68
78
  def show_readme
69
79
  readme 'README' if behavior == :invoke
70
80
  end
71
-
72
- hook_for :orm, required: true
73
81
  end
74
82
  end
75
83
  end
@@ -2,6 +2,11 @@
2
2
 
3
3
  require 'pg_rls'
4
4
 
5
- PgRls.setup do |_config|
5
+ PgRls.setup do |config|
6
6
  ActiveRecord::ConnectionAdapters::AbstractAdapter.include PgRls::Schema::Statements
7
+
8
+ # Do not remove this value after initialization
9
+ config.class_name = :<%= PgRls.class_name %>
10
+ config.table_name = :<%= PgRls.table_name %>
11
+ config.search_methods = <%= PgRls.search_methods %>
7
12
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgRls
4
+ module Database
5
+ # Prepare database for test unit
6
+ module Prepared
7
+ class << self
8
+ def grant_user_credentials(name: PgRls::SECURE_USERNAME)
9
+ return unless Rails.env.test?
10
+
11
+ PgRls.execute <<-SQL
12
+ GRANT USAGE, SELECT
13
+ ON ALL SEQUENCES IN SCHEMA public
14
+ TO #{name};
15
+ GRANT SELECT, INSERT, UPDATE, DELETE
16
+ ON ALL TABLES IN SCHEMA public
17
+ TO #{name};
18
+ SQL
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -5,7 +5,13 @@ module PgRls
5
5
  # Down Schema Statements
6
6
  module DownStatements
7
7
  def drop_rls_user
8
- ActiveRecord::Migration.execute "DROP USER #{PgRls::SECURE_USERNAME};"
8
+ ActiveRecord::Migration.execute <<~SQL
9
+ DROP OWNED BY #{PgRls::SECURE_USERNAME};
10
+ REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM #{PgRls::SECURE_USERNAME};
11
+ REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM #{PgRls::SECURE_USERNAME};
12
+ REVOKE ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public FROM #{PgRls::SECURE_USERNAME};
13
+ DROP USER #{PgRls::SECURE_USERNAME};
14
+ SQL
9
15
  end
10
16
 
11
17
  def drop_rls_blocking_function
@@ -27,11 +27,11 @@ module PgRls
27
27
  end
28
28
 
29
29
  def drop_rls_tenant_table(table_name)
30
- drop_rls_user
31
30
  drop_rls_setter_function
32
31
  detach_blocking_function(table_name)
33
32
  drop_table(table_name)
34
33
  drop_rls_blocking_function
34
+ drop_rls_user
35
35
  end
36
36
 
37
37
  def drop_rls_table(table_name)
@@ -39,6 +39,34 @@ module PgRls
39
39
  drop_rls_policy(table_name)
40
40
  drop_table(table_name)
41
41
  end
42
+
43
+ def convert_to_rls_tenant_table(table_name, **_options)
44
+ create_rls_user(password: PgRls.database_configuration['password'])
45
+ create_rls_setter_function
46
+ create_rls_blocking_function
47
+ add_rls_column_to_tenant_table(table_name)
48
+ append_blocking_function(table_name)
49
+ end
50
+
51
+ def revert_rls_tenant_table(table_name)
52
+ drop_rls_setter_function
53
+ detach_blocking_function(table_name)
54
+ drop_rls_blocking_function
55
+ drop_rls_user
56
+ drop_rls_column(table_name)
57
+ end
58
+
59
+ def convert_to_rls_table(table_name)
60
+ add_rls_column(table_name)
61
+ create_rls_policy(table_name)
62
+ append_trigger_function(table_name)
63
+ end
64
+
65
+ def revert_rls_table(table_name)
66
+ detach_trigger_function(table_name)
67
+ drop_rls_policy(table_name)
68
+ drop_rls_column(table_name)
69
+ end
42
70
  end
43
71
  end
44
72
  end
@@ -12,6 +12,12 @@ module PgRls
12
12
  ALTER DEFAULT PRIVILEGES IN SCHEMA public
13
13
  GRANT SELECT, INSERT, UPDATE, DELETE
14
14
  ON TABLES TO #{name};
15
+ GRANT SELECT, INSERT, UPDATE, DELETE
16
+ ON ALL TABLES IN SCHEMA public
17
+ TO #{name};
18
+ GRANT USAGE, SELECT
19
+ ON ALL SEQUENCES IN SCHEMA public
20
+ TO #{name};
15
21
  SQL
16
22
  end
17
23
 
@@ -64,9 +70,9 @@ module PgRls
64
70
  ActiveRecord::Migration.execute <<-SQL
65
71
  ALTER TABLE #{table_name}
66
72
  ADD COLUMN IF NOT EXISTS tenant_id uuid,
67
- ADD CONSTRAINT fk_companies
73
+ ADD CONSTRAINT fk_#{PgRls.table_name}
68
74
  FOREIGN KEY (tenant_id)
69
- REFERENCES companies(tenant_id)
75
+ REFERENCES #{PgRls.table_name}(tenant_id)
70
76
  ON DELETE CASCADE;
71
77
  SQL
72
78
  end
@@ -12,6 +12,8 @@ module PgRls
12
12
  private
13
13
 
14
14
  def establish_secure_connection
15
+ return if PgRls.default_connection?
16
+
15
17
  return if secure_connection_established?
16
18
 
17
19
  PgRls.establish_new_connection
data/lib/pg_rls/tenant.rb CHANGED
@@ -4,20 +4,37 @@ module PgRls
4
4
  # Tenant Controller
5
5
  module Tenant
6
6
  class << self
7
- SET_COMPANY_ID_SQL = 'SET rls.tenant_id = %s'
8
7
  def switch(resource)
9
8
  connection_adapter = PgRls.connection_class
10
- tenant = tenant_by_subdomain_uuid_or_tenant_id(resource)
11
- connection_adapter.connection.execute(format(SET_COMPANY_ID_SQL,
9
+ find_tenant(resource)
10
+ connection_adapter.connection.execute(format('SET rls.tenant_id = %s',
12
11
  connection_adapter.connection.quote(tenant.tenant_id)))
13
- "RLS changed to '#{tenant}'"
12
+ "RLS changed to '#{tenant.name}'"
14
13
  rescue StandardError => e
15
14
  puts 'connection was not made'
16
- puts e
15
+ puts @error || e
17
16
  end
18
17
 
19
- def tenant_by_subdomain_uuid_or_tenant_id(resource)
20
- Company.find_by_subdomain(resource) || Company.find_by_id(resource) || Company.find_by_tenant_id(resource)
18
+ attr_reader :tenant
19
+
20
+ def fetch
21
+ @fetch ||= tenant.find_by_tenant_id(
22
+ PgRls.connection_class.connection.execute(
23
+ "SELECT current_setting('rls.tenant_id')"
24
+ ).getvalue(0, 0)
25
+ )
26
+ rescue ActiveRecord::StatementInvalid
27
+ 'no tenant is selected'
28
+ end
29
+
30
+ def find_tenant(resource)
31
+ @tenant = nil
32
+
33
+ PgRls.search_methods.each do |method|
34
+ @tenant ||= PgRls.main_model.send("find_by_#{method}", resource)
35
+ rescue NoMethodError => e
36
+ @error = e
37
+ end
21
38
  end
22
39
  end
23
40
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgRls
4
- VERSION = '0.0.1.beta'
4
+ VERSION = '0.0.1.3'
5
5
  end
data/lib/pg_rls.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require 'active_record'
4
4
  require 'forwardable'
5
5
  require_relative 'pg_rls/version'
6
+ require_relative 'pg_rls/database/prepared'
6
7
  require_relative 'pg_rls/schema/statements'
7
8
  require_relative 'pg_rls/tenant'
8
9
  require_relative 'pg_rls/secure_connection'
@@ -11,14 +12,19 @@ require_relative 'pg_rls/multi_tenancy'
11
12
  # PostgreSQL Row Level Security
12
13
  module PgRls
13
14
  class Error < StandardError; end
14
- SECURE_USERNAME = 'app_user'
15
+ SECURE_USERNAME = "#{Rails.env}_app_user".freeze
15
16
 
16
17
  class << self
17
18
  extend Forwardable
18
19
 
19
- WRITER_METHODS = %i[].freeze
20
- READER_METHODS = %i[connection_class database_configuration execute].freeze
21
- DELEGATORS_METHODS = %i[connection_class database_configuration execute].freeze
20
+ WRITER_METHODS = %i[table_name class_name search_methods].freeze
21
+ READER_METHODS = %i[
22
+ connection_class database_configuration execute table_name class_name search_methods
23
+ ].freeze
24
+ DELEGATORS_METHODS = %i[
25
+ connection_class database_configuration execute table_name search_methods
26
+ class_name all_tenants main_model establish_default_connection
27
+ ].freeze
22
28
 
23
29
  attr_writer(*WRITER_METHODS)
24
30
  attr_reader(*READER_METHODS)
@@ -45,8 +51,29 @@ module PgRls
45
51
  )
46
52
  end
47
53
 
54
+ def establish_default_connection
55
+ @default_connection = true
56
+ end
57
+
58
+ def default_connection?
59
+ @default_connection
60
+ end
61
+
62
+ def main_model
63
+ class_name.to_s.camelize.constantize
64
+ end
65
+
66
+ def all_tenants
67
+ main_model.all.each do |tenant|
68
+ allowed_search_fields = search_methods.map(&:to_s).intersection(main_model.column_names)
69
+ Tenant.switch tenant.send(allowed_search_fields.first)
70
+
71
+ yield(tenant) if block_given?
72
+ end
73
+ end
74
+
48
75
  def current_connection_username
49
- PgRls.connection_class.connection_db_config.configuration_hash[:username]
76
+ connection_class.connection_db_config.configuration_hash[:username]
50
77
  end
51
78
 
52
79
  def execute(query)
@@ -59,4 +86,12 @@ module PgRls
59
86
  end
60
87
  end
61
88
  end
89
+ mattr_accessor :table_name
90
+ @@table_name = 'companies'
91
+
92
+ mattr_accessor :class_name
93
+ @@class_name = 'Company'
94
+
95
+ mattr_accessor :search_methods
96
+ @@search_methods = %i[subdomain id tenant_id]
62
97
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_rls
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.beta
4
+ version: 0.0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Laloush
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-10-10 00:00:00.000000000 Z
11
+ date: 2022-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -24,7 +24,9 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 2.2.10
27
- description: Write a longer description or delete this line.
27
+ description: |2
28
+ This gem will help you to integrate PostgreSQL RLS to help you develop a great multitenancy application
29
+ checkout the repository at https://github.com/Dandush03/pg_rls
28
30
  email:
29
31
  - daniel.laloush@influitive.com
30
32
  executables: []
@@ -45,6 +47,9 @@ files:
45
47
  - lib/generators/pg_rls.rb
46
48
  - lib/generators/pg_rls/active_record/active_record_generator.rb
47
49
  - lib/generators/pg_rls/active_record/templates/abstract_base_class.rb.tt
50
+ - lib/generators/pg_rls/active_record/templates/convert_migration.rb.tt
51
+ - lib/generators/pg_rls/active_record/templates/convert_migration_backport.rb.tt
52
+ - lib/generators/pg_rls/active_record/templates/init_convert_migration.rb.tt
48
53
  - lib/generators/pg_rls/active_record/templates/init_migration.rb.tt
49
54
  - lib/generators/pg_rls/active_record/templates/init_model.rb.tt
50
55
  - lib/generators/pg_rls/active_record/templates/migration.rb.tt
@@ -55,6 +60,7 @@ files:
55
60
  - lib/generators/templates/README
56
61
  - lib/generators/templates/pg_rls.rb.tt
57
62
  - lib/pg_rls.rb
63
+ - lib/pg_rls/database/prepared.rb
58
64
  - lib/pg_rls/multi_tenancy.rb
59
65
  - lib/pg_rls/schema/down_statements.rb
60
66
  - lib/pg_rls/schema/statements.rb
@@ -65,7 +71,8 @@ files:
65
71
  homepage: https://github.com/Dandush03/pg_rls
66
72
  licenses:
67
73
  - MIT
68
- metadata: {}
74
+ metadata:
75
+ rubygems_mfa_required: 'true'
69
76
  post_install_message:
70
77
  rdoc_options: []
71
78
  require_paths:
@@ -77,11 +84,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
84
  version: 3.0.0
78
85
  required_rubygems_version: !ruby/object:Gem::Requirement
79
86
  requirements:
80
- - - ">"
87
+ - - ">="
81
88
  - !ruby/object:Gem::Version
82
- version: 1.3.1
89
+ version: '0'
83
90
  requirements: []
84
- rubygems_version: 3.2.27
91
+ rubygems_version: 3.2.22
85
92
  signing_key:
86
93
  specification_version: 4
87
94
  summary: Write a short summary, because RubyGems requires one.