console_kit 1.0.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e392416a4b0f32756b184aefa7431d71ce45a733479e477f9c54f9e14affcd6
4
- data.tar.gz: 3b15e7006400dc93794f84e7e14828f573639c67452c0ccb4922ece8fcab9c89
3
+ metadata.gz: acd47ead1008fc3bcdc25b0d995b735f69d1e6f5a8d5f8442c67dc5ee86bb9a3
4
+ data.tar.gz: 55bd88158a92c6741eab85bda17d4b61828dd101e94cd5ab02097688f8994302
5
5
  SHA512:
6
- metadata.gz: c6f6b2d49d2c2e2d776af957764804dfbebb397a7fbb52449013a77bed92d1ff817672154d546a304bbc92c9c385f14e5402bee250046e8b001564169f52881d
7
- data.tar.gz: e9c24b447c674eb693a05b9552484e293a483b547e04620e78145f9c865010c27b45f487ab16b453bce8bd9a7bb8c4c180c9464abef3cda20f7e5678fdc932bd
6
+ metadata.gz: 9d1214289e7859342493f2930557554e05ad3ccb3200e041d3ade9b2f95a710b6f111eaea9bfc40f0192e98cabd1010f7be6c71469066d2fd702a74d70a33132
7
+ data.tar.gz: 4564b76383af8ab6d60c6ee79644fc78a1be8633520ffe1c74a26a69936ebbacb7db8b20dd84c28c2433f532198c46c1c140ff7d31cd323d613559624fabb713
data/CHANGELOG.md CHANGED
@@ -6,6 +6,22 @@ This project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ---
8
8
 
9
+ ## [1.1.0] - 2026-03-14
10
+ ### Added
11
+ - **Redis Connection Handler:** Automatic Redis DB selection per tenant via `Redis.current.select`, with graceful fallback for Redis v5+ where `Redis.current` is deprecated.
12
+ - **Elasticsearch Connection Handler:** Sets a per-tenant Elasticsearch index name prefix via thread-local storage and `Elasticsearch::Model.index_name_prefix=` (when available).
13
+ - **Console Helpers:** New `switch_tenant`, `tenant_info`, and `tenants` methods available in the Rails console for quick tenant management.
14
+ - **Custom Console Prompt:** IRB and Pry prompts now display the active tenant name (e.g., `[acme] main:001>`).
15
+ - **Tenant Banner:** On successful tenant initialization, a banner now shows the tenant name, environment safety warnings (production in red, staging in yellow), and a summary of active connections.
16
+ - **Environment Safety Warnings:** Production and staging environments are flagged with color-coded warnings at tenant setup time.
17
+ - New tenant configuration keys: `redis_db`, `elasticsearch_prefix`, and `environment`.
18
+
19
+ ### Changed
20
+ - `TenantConfigurator` now manages `tenant_redis_db` and `tenant_elasticsearch_prefix` context attributes alongside existing ones.
21
+ - Generator template updated with examples for the new configuration keys.
22
+
23
+ ---
24
+
9
25
  ## [1.0.0] - 2026-03-01
10
26
  ### Added
11
27
  - **Global Configuration Persistence:** ConsoleKit settings now persist across the entire session and across multiple threads.
@@ -83,6 +99,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
83
99
  - Tenant-specific database configuration.
84
100
  - Colorized console output for improved UX.
85
101
 
102
+ [1.1.0]: https://github.com/Soumyadeep-ai/console_kit/releases/tag/v1.1.0
86
103
  [1.0.0]: https://github.com/Soumyadeep-ai/console_kit/releases/tag/v1.0.0
87
104
  [0.1.5]: https://github.com/Soumyadeep-ai/console_kit/releases/tag/v0.1.5
88
105
  [0.1.4]: https://github.com/Soumyadeep-ai/console_kit/releases/tag/v0.1.4
data/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  A simple and flexible multi-tenant console setup toolkit for Rails applications.
10
10
 
11
- ConsoleKit helps you manage tenant-specific database connections and context configuration via an easy CLI interface and Rails integration.
11
+ ConsoleKit helps you manage tenant-specific database connections (SQL, MongoDB, Redis, Elasticsearch) and context configuration via an easy CLI interface and Rails integration.
12
12
 
13
13
  ## Installation
14
14
 
@@ -50,10 +50,24 @@ Then, edit config/initializers/console_kit.rb to define your tenants and context
50
50
  ConsoleKit.configure do |config|
51
51
  config.tenants = {
52
52
  tenant_one: {
53
- constants: { shard: :tenant_one_db, mongo_db: :tenant_one_mongo, partner_code: 'partnerA' }
53
+ constants: {
54
+ shard: :tenant_one_db,
55
+ mongo_db: :tenant_one_mongo,
56
+ partner_code: 'partnerA',
57
+ redis_db: 1,
58
+ elasticsearch_prefix: 'tenant_one',
59
+ environment: 'production'
60
+ }
54
61
  },
55
62
  tenant_two: {
56
- constants: { shard: :tenant_two_db, mongo_db: :tenant_two_mongo, partner_code: 'partnerB' }
63
+ constants: {
64
+ shard: :tenant_two_db,
65
+ mongo_db: :tenant_two_mongo,
66
+ partner_code: 'partnerB',
67
+ redis_db: 2,
68
+ elasticsearch_prefix: 'tenant_two',
69
+ environment: 'staging'
70
+ }
57
71
  }
58
72
  }
59
73
 
@@ -64,39 +78,72 @@ ConsoleKit.configure do |config|
64
78
  end
65
79
  ```
66
80
 
81
+ ## Supported Connections
82
+
83
+ ConsoleKit automatically detects and manages connections for:
84
+
85
+ | Connection | Gem Required | Config Key | Behavior |
86
+ |-----------------|-----------------|--------------------------|------------------------------------------------|
87
+ | SQL (ActiveRecord) | `activerecord` | `shard` | Calls `establish_connection` on your base class |
88
+ | MongoDB | `mongoid` | `mongo_db` | Calls `Mongoid.override_database` |
89
+ | Redis | `redis` | `redis_db` | Calls `Redis.current.select(db)` |
90
+ | Elasticsearch | `elasticsearch` | `elasticsearch_prefix` | Sets `Elasticsearch::Model.index_name_prefix=` |
91
+
92
+ Handlers are only activated when their corresponding gem is loaded.
93
+
67
94
  ## Console Usage
68
95
 
69
- When launching the Rails console, ConsoleKit will prompt you to select a tenant (if multiple tenants are configured).
96
+ When launching the Rails console, ConsoleKit will prompt you to select a tenant (if multiple tenants are configured). On selection, a tenant banner is displayed showing the tenant name, environment safety warnings, and active connections.
70
97
 
71
98
  ### Selection Options:
72
99
  - **Number or Name:** Select a tenant by its index or name (case-insensitive).
73
100
  - **0 (Skip):** Load the console without any tenant configuration.
74
101
  - **exit / quit:** Immediately terminate the console session.
75
102
 
76
- You can also manually interact with it:
103
+ ### Console Helpers
77
104
 
78
- ### Get Current Tenant
79
- ```ruby
80
- ConsoleKit.current_tenant
81
- # => :tenant_one
82
- ```
105
+ The following helper methods are available in your Rails console:
83
106
 
84
- ### Reset Current Tenant
85
107
  ```ruby
86
- ConsoleKit.reset_current_tenant
87
- # => nil
108
+ # Switch to a different tenant
109
+ switch_tenant
110
+
111
+ # Print details about the current tenant
112
+ tenant_info
113
+
114
+ # List all available tenants
115
+ tenants
88
116
  ```
89
117
 
90
- ### Manually Enable Pretty Output
91
- ```ruby
92
- ConsoleKit.enable_pretty_output
118
+ ### Custom Prompt
119
+
120
+ ConsoleKit automatically sets your IRB/Pry prompt to show the active tenant:
121
+
122
+ ```
123
+ [tenant_one] main:001>
93
124
  ```
94
125
 
95
- ### Manually Disable Pretty Output
126
+ ### Other Methods
127
+
96
128
  ```ruby
129
+ # Get current tenant
130
+ ConsoleKit.current_tenant
131
+ # => :tenant_one
132
+
133
+ # Reset and re-select tenant
134
+ ConsoleKit.reset_current_tenant
135
+
136
+ # Toggle pretty output
137
+ ConsoleKit.enable_pretty_output
97
138
  ConsoleKit.disable_pretty_output
98
139
  ```
99
140
 
141
+ ### Environment Warnings
142
+
143
+ When a tenant has an `environment` key in its constants:
144
+ - **production**: A red warning is displayed at setup time.
145
+ - **staging**: A yellow warning is displayed at setup time.
146
+
100
147
  ## Development
101
148
 
102
149
  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.
data/SECURITY.md CHANGED
@@ -8,7 +8,8 @@ Once a new version is released, the previous version is branched and locked, and
8
8
 
9
9
  | Version | Supported |
10
10
  | ------- | ------------------ |
11
- | 1.0.0 | :white_check_mark: |
11
+ | 1.1.0 | :white_check_mark: |
12
+ | 1.0.0 | :x: |
12
13
  | 0.1.5 | :x: |
13
14
  | 0.1.4 | :x: |
14
15
  | 0.1.3 | :x: |
@@ -14,7 +14,13 @@ module ConsoleKit
14
14
 
15
15
  def initialize(context) = @context = context
16
16
  def connect = raise NotImplementedError, "#{self.class} must implement #connect"
17
- def available? = false
17
+ def available? = raise NotImplementedError, "#{self.class} must implement #available?"
18
+
19
+ private
20
+
21
+ def context_attribute(name)
22
+ @context.respond_to?(name, true) ? @context.send(name) : nil
23
+ end
18
24
  end
19
25
  end
20
26
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require_relative 'sql_connection_handler'
4
4
  require_relative 'mongo_connection_handler'
5
+ require_relative 'redis_connection_handler'
6
+ require_relative 'elasticsearch_connection_handler'
5
7
 
6
8
  module ConsoleKit
7
9
  module Connections
@@ -12,6 +14,8 @@ module ConsoleKit
12
14
  handler_classes.filter_map do |klass|
13
15
  handler = klass.new(context)
14
16
  handler if handler.available?
17
+ rescue NotImplementedError
18
+ nil
15
19
  end
16
20
  end
17
21
 
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_connection_handler'
4
+
5
+ module ConsoleKit
6
+ module Connections
7
+ # Handles Elasticsearch connections
8
+ class ElasticsearchConnectionHandler < BaseConnectionHandler
9
+ def connect
10
+ prefix = context_attribute(:tenant_elasticsearch_prefix).presence
11
+ Output.print_info(switch_message(prefix))
12
+ Thread.current[:console_kit_elasticsearch_prefix] = prefix
13
+ apply_model_index_prefix(prefix)
14
+ end
15
+
16
+ def available? = defined?(Elasticsearch)
17
+
18
+ private
19
+
20
+ def apply_model_index_prefix(prefix)
21
+ return unless defined?(Elasticsearch::Model) && Elasticsearch::Model.respond_to?(:index_name_prefix=)
22
+
23
+ Elasticsearch::Model.index_name_prefix = prefix
24
+ end
25
+
26
+ def switch_message(prefix)
27
+ prefix ? "Setting Elasticsearch index prefix: #{prefix}" : 'Resetting Elasticsearch index prefix to default'
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,22 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
3
  require_relative 'base_connection_handler'
5
4
 
6
5
  module ConsoleKit
7
6
  module Connections
8
7
  # Handles MongoDB connections
9
8
  class MongoConnectionHandler < BaseConnectionHandler
10
- extend Forwardable
11
-
12
- def_delegator :@context, :tenant_mongo_db
13
-
14
9
  def connect
15
- db = tenant_mongo_db.presence
10
+ db = context_attribute(:tenant_mongo_db).presence
16
11
  Output.print_info(switch_message(db))
17
- Mongoid.override_client(db)
12
+ Mongoid.override_database(db)
18
13
  rescue NoMethodError
19
- Output.print_warning('Mongoid.override_client is not defined.')
14
+ Output.print_warning('Mongoid.override_database is not available in this version of Mongoid.')
20
15
  end
21
16
 
22
17
  def available? = defined?(Mongoid)
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_connection_handler'
4
+
5
+ module ConsoleKit
6
+ module Connections
7
+ # Handles Redis connections
8
+ class RedisConnectionHandler < BaseConnectionHandler
9
+ DEFAULT_REDIS_DB = 0
10
+
11
+ def connect
12
+ db = context_attribute(:tenant_redis_db)
13
+ Output.print_info(switch_message(db))
14
+ select_redis_db(db.nil? ? DEFAULT_REDIS_DB : db)
15
+ end
16
+
17
+ def available? = defined?(Redis)
18
+
19
+ private
20
+
21
+ def select_redis_db(db)
22
+ if Redis.respond_to?(:current) && Redis.current
23
+ Redis.current.select(db)
24
+ elsif defined?(RedisClient) && db != DEFAULT_REDIS_DB
25
+ Output.print_warning("Redis DB #{db} configured but auto-select not supported with RedisClient. " \
26
+ 'Ensure your Redis configuration sets the correct DB.')
27
+ end
28
+ rescue NoMethodError
29
+ Output.print_warning('Redis.current is not available (deprecated in Redis v5+).')
30
+ end
31
+
32
+ def switch_message(db)
33
+ db ? "Switching to Redis DB: #{db}" : 'Resetting Redis connection to default'
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,22 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
3
  require_relative 'base_connection_handler'
5
4
 
6
5
  module ConsoleKit
7
6
  module Connections
8
7
  # Handles SQL connections
9
8
  class SqlConnectionHandler < BaseConnectionHandler
10
- extend Forwardable
11
-
12
- def_delegator :@context, :tenant_shard
13
-
14
9
  def connect
15
- shard = tenant_shard.presence&.to_sym
16
- msg = shard ? "Establishing SQL connection to shard: #{shard}" : 'Resetting SQL connection to default'
17
-
18
- Output.print_info("#{msg} via #{base_class}")
19
- base_class.establish_connection(shard)
10
+ shard = context_attribute(:tenant_shard).presence&.to_sym
11
+ Output.print_info("#{connection_message(shard)} via #{base_class}")
12
+ shard ? base_class.establish_connection(shard) : base_class.establish_connection
20
13
  end
21
14
 
22
15
  def available? = sql_base_class_name.to_s.safe_constantize.present?
@@ -30,6 +23,10 @@ module ConsoleKit
30
23
  raise Error, "ConsoleKit: sql_base_class '#{sql_base_class_name}' could not be found."
31
24
  end
32
25
 
26
+ def connection_message(shard)
27
+ shard ? "Establishing SQL connection to shard: #{shard}" : 'Resetting SQL connection to default'
28
+ end
29
+
33
30
  def sql_base_class_name = ConsoleKit.configuration.sql_base_class
34
31
  end
35
32
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConsoleKit
4
+ # Helper methods available in the Rails console
5
+ module ConsoleHelpers
6
+ def switch_tenant = ConsoleKit.reset_current_tenant
7
+
8
+ def tenant_info
9
+ tenant = ConsoleKit::Setup.current_tenant
10
+ unless tenant
11
+ ConsoleKit::Output.print_warning('No tenant is currently configured.')
12
+ return
13
+ end
14
+
15
+ constants = ConsoleKit.configuration.tenants[tenant]&.[](:constants) || {}
16
+ print_tenant_details(tenant, constants)
17
+ end
18
+
19
+ def tenants
20
+ names = ConsoleKit.configuration.tenants&.keys || []
21
+ ConsoleKit::Output.print_list(names, header: 'Available Tenants')
22
+ names
23
+ end
24
+
25
+ private
26
+
27
+ def print_tenant_details(tenant, constants)
28
+ ConsoleKit::Output.print_header("Tenant: #{tenant}")
29
+ {
30
+ 'Partner' => :partner_code, 'Shard' => :shard, 'Mongo DB' => :mongo_db,
31
+ 'Redis DB' => :redis_db, 'ES Prefix' => :elasticsearch_prefix, 'Environment' => :environment
32
+ }.each do |label, key|
33
+ ConsoleKit::Output.print_info(" #{label.ljust(13)}#{constants[key]}") unless constants[key].nil?
34
+ end
35
+ nil
36
+ end
37
+ end
38
+ end
@@ -34,7 +34,7 @@ module ConsoleKit
34
34
  return if silent
35
35
 
36
36
  formatted = (type == :header ? "\n--- #{text} ---" : text)
37
- print_with(type, formatted, timestamp, newline: newline)
37
+ print_with(type, formatted, timestamp, { newline: newline })
38
38
  end
39
39
  end
40
40
 
@@ -55,15 +55,15 @@ module ConsoleKit
55
55
  def print_backtrace(exception)
56
56
  return if silent
57
57
 
58
- exception&.backtrace&.each { |line| print_with(:trace, " #{line}", true, newline: true) }
58
+ exception&.backtrace&.each { |line| print_with(:trace, " #{line}", true, { newline: true }) }
59
59
  end
60
60
 
61
61
  private
62
62
 
63
- def print_with(type, text, timestamp, newline: true)
63
+ def print_with(type, text, timestamp, opts = {})
64
64
  meta = TYPES.fetch(type)
65
65
  message = build_message(text, meta[:symbol], timestamp)
66
- output(message, meta[:color], newline: newline)
66
+ emit(message, meta[:color], opts.fetch(:newline, true))
67
67
  end
68
68
 
69
69
  def build_message(text, symbol, timestamp)
@@ -74,11 +74,10 @@ module ConsoleKit
74
74
  def timestamp_prefix(timestamp) = prefix_for(timestamp) { Time.current.strftime('[%Y-%m-%d %H:%M:%S] ') }
75
75
  def symbol_prefix(symbol) = prefix_for(symbol) { |sym| "#{sym} " }
76
76
 
77
- def output(message, color, newline: true)
78
- method = newline ? :puts : :print
79
- return send(method, message) unless ConsoleKit.configuration.pretty_output && color
80
-
81
- send(method, "\e[#{color}m#{message}\e[0m")
77
+ def emit(message, color, newline)
78
+ writer = newline ? :puts : :print
79
+ formatted = ConsoleKit.configuration.pretty_output && color ? "\e[#{color}m#{message}\e[0m" : message
80
+ send(writer, formatted)
82
81
  end
83
82
  end
84
83
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConsoleKit
4
+ # Sets the console prompt to show the current tenant
5
+ module Prompt
6
+ class << self
7
+ def apply
8
+ apply_irb_prompt if defined?(IRB)
9
+ apply_pry_prompt if defined?(Pry)
10
+ end
11
+
12
+ private
13
+
14
+ def tenant_label
15
+ tenant = ConsoleKit::Setup.current_tenant
16
+ tenant ? "[#{tenant}]" : '[no-tenant]'
17
+ end
18
+
19
+ def apply_irb_prompt
20
+ conf = IRB.conf
21
+ conf[:PROMPT] ||= {}
22
+ conf[:PROMPT][:CONSOLE_KIT] = {
23
+ PROMPT_I: "#{tenant_label} %N(%m):%03n> ",
24
+ PROMPT_S: "#{tenant_label} %N(%m):%03n%l ",
25
+ PROMPT_C: "#{tenant_label} %N(%m):%03n* ",
26
+ RETURN: "=> %s\n"
27
+ }
28
+ conf[:PROMPT_MODE] = :CONSOLE_KIT
29
+ end
30
+
31
+ def apply_pry_prompt
32
+ Pry.config.prompt = Pry::Prompt.new(
33
+ 'console_kit',
34
+ 'ConsoleKit tenant prompt',
35
+ [
36
+ proc { |obj, nest_level, _pry_instance| "#{Prompt.send(:tenant_label)} (#{obj}):#{nest_level}> " },
37
+ proc { |obj, nest_level, _pry_instance| "#{Prompt.send(:tenant_label)} (#{obj}):#{nest_level}* " }
38
+ ]
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
@@ -3,7 +3,15 @@
3
3
  module ConsoleKit
4
4
  # Railtie for integrating ConsoleKit with Rails console.
5
5
  class Railtie < Rails::Railtie
6
- console { ConsoleKit::Setup.setup }
6
+ console do
7
+ ConsoleKit::Setup.setup
8
+ ConsoleKit::Prompt.apply
9
+ if defined?(Pry)
10
+ TOPLEVEL_BINDING.receiver.extend(ConsoleKit::ConsoleHelpers)
11
+ elsif defined?(IRB::ExtendCommandBundle)
12
+ IRB::ExtendCommandBundle.include(ConsoleKit::ConsoleHelpers)
13
+ end
14
+ end
7
15
 
8
16
  config.to_prepare { ConsoleKit::Setup.reapply if defined?(Rails::Console) }
9
17
  end
@@ -9,6 +9,11 @@ module ConsoleKit
9
9
  # Does the initial setup
10
10
  module Setup
11
11
  class << self
12
+ ENVIRONMENT_WARNINGS = {
13
+ 'production' => -> { Output.print_error('WARNING: You are connected to a PRODUCTION environment!') },
14
+ 'staging' => -> { Output.print_warning('You are connected to a staging environment.') }
15
+ }.freeze
16
+
12
17
  def current_tenant = Thread.current[:console_kit_current_tenant]
13
18
 
14
19
  def current_tenant=(val)
@@ -27,11 +32,13 @@ module ConsoleKit
27
32
  def reset_current_tenant
28
33
  return warn_no_tenants unless tenants?
29
34
 
30
- warn_reset if current_tenant
31
- TenantConfigurator.clear if current_tenant
35
+ key = select_tenant_key
36
+ return cancel_switch if key == :abort || key.blank?
32
37
 
33
- self.current_tenant = nil
34
- setup
38
+ clear_current_tenant
39
+ return skip_tenant_message if %i[exit none].include?(key)
40
+
41
+ configure(key)
35
42
  end
36
43
 
37
44
  private
@@ -40,7 +47,6 @@ module ConsoleKit
40
47
  return if tenant_setup_successful?
41
48
 
42
49
  ConsoleKit.configuration.validate!
43
-
44
50
  select_and_configure
45
51
  rescue StandardError => e
46
52
  handle_error(e)
@@ -54,19 +60,17 @@ module ConsoleKit
54
60
  end
55
61
 
56
62
  def handle_selection_result(key)
57
- exit_on_key(key) if key == :exit
63
+ exit_on_key if %i[exit abort].include?(key)
58
64
 
59
65
  case key
60
- when :abort, :none
66
+ when :none
61
67
  Output.print_info('No tenant selected. Loading without tenant configuration.')
62
68
  when nil, ''
63
69
  Output.print_error('Tenant selection failed. Loading without tenant configuration.')
64
70
  end
65
71
  end
66
72
 
67
- def exit_on_key(key)
68
- return unless key == :exit
69
-
73
+ def exit_on_key
70
74
  Output.print_info('Exiting console...')
71
75
  Kernel.exit
72
76
  end
@@ -76,7 +80,31 @@ module ConsoleKit
76
80
  return unless TenantConfigurator.configuration_success
77
81
 
78
82
  self.current_tenant = key
83
+ Prompt.apply
84
+ print_tenant_banner(key)
85
+ end
86
+
87
+ def print_tenant_banner(key)
88
+ constants = ConsoleKit.configuration.tenants[key]&.[](:constants) || {}
89
+ env = constants[:environment]&.to_s&.downcase
79
90
  Output.print_success("Tenant initialized: #{key}")
91
+ print_environment_warning(env) if env
92
+ print_active_connections
93
+ end
94
+
95
+ def print_environment_warning(env) = ENVIRONMENT_WARNINGS[env]&.call
96
+
97
+ def print_active_connections
98
+ names = active_connection_names
99
+ Output.print_info("Active connections: #{names.join(', ')}") unless names.empty?
100
+ end
101
+
102
+ def active_connection_names
103
+ ctx = context_class
104
+ return [] unless ctx
105
+
106
+ handlers = ConsoleKit::Connections::ConnectionManager.available_handlers(ctx)
107
+ handlers.map { |h| h.class.name.demodulize.delete_suffix('ConnectionHandler') }
80
108
  end
81
109
 
82
110
  def tenants = ConsoleKit.configuration.tenants
@@ -89,6 +117,16 @@ module ConsoleKit
89
117
  def non_interactive? = !$stdin.tty?
90
118
  def warn_no_tenants = Output.print_warning('Cannot reset tenant: No tenants configured.')
91
119
  def warn_reset = Output.print_warning("Resetting tenant: #{current_tenant}")
120
+ def cancel_switch = Output.print_warning('Tenant switch cancelled.')
121
+ def skip_tenant_message = Output.print_info('No tenant selected. Loading without tenant configuration.')
122
+
123
+ def clear_current_tenant
124
+ if current_tenant
125
+ warn_reset
126
+ TenantConfigurator.clear
127
+ end
128
+ self.current_tenant = nil
129
+ end
92
130
 
93
131
  def handle_error(error)
94
132
  Output.print_error("Error setting up tenant: #{error.message}")
@@ -7,6 +7,13 @@ module ConsoleKit
7
7
  # For tenant configuration
8
8
  module TenantConfigurator
9
9
  class << self
10
+ HANDLER_ATTRIBUTES = {
11
+ Connections::SqlConnectionHandler => :tenant_shard,
12
+ Connections::MongoConnectionHandler => :tenant_mongo_db,
13
+ Connections::RedisConnectionHandler => :tenant_redis_db,
14
+ Connections::ElasticsearchConnectionHandler => :tenant_elasticsearch_prefix
15
+ }.freeze
16
+
10
17
  def configuration_success = Thread.current[:console_kit_configuration_success]
11
18
 
12
19
  def configuration_success=(val)
@@ -39,7 +46,7 @@ module ConsoleKit
39
46
  end
40
47
 
41
48
  def reset_context_attributes(ctx)
42
- %i[tenant_shard tenant_mongo_db partner_identifier].each do |attr|
49
+ available_context_attributes(ctx).each do |attr|
43
50
  ctx.public_send("#{attr}=", nil)
44
51
  end
45
52
  end
@@ -60,31 +67,38 @@ module ConsoleKit
60
67
  configure_success(key)
61
68
  end
62
69
 
63
- def validate_context_interface!(ctx)
64
- missing = required_interface_methods.reject { |s| ctx.respond_to?(s) }
65
- return if missing.empty?
66
-
67
- raise Error, "Context class #{ctx} does not implement the required interface. " \
68
- "Missing methods: #{missing.join(', ')}"
70
+ def handler_available?(handler_class)
71
+ handler_class.new(nil).available?
72
+ rescue NotImplementedError, StandardError
73
+ false
69
74
  end
70
75
 
71
- def required_interface_methods
72
- attributes = %i[tenant_shard tenant_mongo_db partner_identifier]
73
- attributes + attributes.map { |a| "#{a}=" }
76
+ def available_context_attributes(ctx)
77
+ attributes = %i[partner_identifier]
78
+ HANDLER_ATTRIBUTES.each do |handler, attr|
79
+ attributes << attr if handler_available?(handler) && ctx.respond_to?("#{attr}=")
80
+ end
81
+ attributes.select { |attr| ctx.respond_to?("#{attr}=") }
74
82
  end
75
83
 
76
84
  def apply_context(constant)
77
85
  ctx = ConsoleKit.configuration.context_class
78
- validate_context_interface!(ctx)
79
-
80
86
  assign_context_attributes(ctx, constant)
81
87
  setup_connections(ctx)
82
88
  end
83
89
 
84
90
  def assign_context_attributes(ctx, constant)
85
- ctx.tenant_shard = constant[:shard]
86
- ctx.tenant_mongo_db = constant[:mongo_db]
87
- ctx.partner_identifier = constant[:partner_code]
91
+ attribute_to_constant = {
92
+ partner_identifier: :partner_code,
93
+ tenant_shard: :shard,
94
+ tenant_mongo_db: :mongo_db,
95
+ tenant_redis_db: :redis_db,
96
+ tenant_elasticsearch_prefix: :elasticsearch_prefix
97
+ }
98
+
99
+ available_context_attributes(ctx).each do |attr|
100
+ ctx.public_send("#{attr}=", constant[attribute_to_constant[attr]])
101
+ end
88
102
  end
89
103
 
90
104
  def setup_connections(context)
@@ -9,36 +9,36 @@ module ConsoleKit
9
9
  DEFAULT_SELECTION = '1'
10
10
 
11
11
  class << self
12
- def select = attempt_selection(RETRY_LIMIT)
12
+ def select
13
+ RETRY_LIMIT.times do
14
+ result = attempt_selection
15
+ return result unless result == :retry
16
+ end
17
+ nil
18
+ end
13
19
 
14
20
  private
15
21
 
16
- def attempt_selection(retries_left)
17
- return nil if retries_left.zero?
18
-
22
+ def attempt_selection
19
23
  print_tenant_selection_menu
20
- process_selection(retries_left)
21
- end
22
-
23
- def process_selection(retries_left)
24
24
  selection = parse_user_selection
25
25
  return :abort if selection == :abort
26
- return attempt_selection(retries_left - 1) unless selection
26
+ return :retry unless selection
27
27
 
28
28
  selection.is_a?(Integer) ? resolve_selection(selection) : selection
29
29
  end
30
30
 
31
31
  def print_tenant_selection_menu
32
32
  Output.print_header('Multiple tenants detected. Please choose one:')
33
+ Output.print_list(menu_items)
34
+ end
33
35
 
34
- items = []
35
- items << '0. Skip (load without tenant configuration)'
36
-
36
+ def menu_items
37
+ items = ['0. Skip (load without tenant configuration)']
37
38
  ConsoleKit.tenants.keys.each_with_index do |key, index|
38
39
  items << "#{index + 1}. #{key} (partner: #{tenant_partner(key)})"
39
40
  end
40
-
41
- Output.print_list(items)
41
+ items
42
42
  end
43
43
 
44
44
  def tenant_partner(key) = ConsoleKit.tenants.dig(key, :constants, :partner_code) || 'N/A'
@@ -69,9 +69,10 @@ module ConsoleKit
69
69
 
70
70
  def read_input_with_default
71
71
  Output.print_prompt("Selection (number or name) [#{DEFAULT_SELECTION}]: ")
72
-
73
72
  raw_input = $stdin.gets
74
73
  raw_input ? normalize_input(raw_input) : :abort
74
+ rescue Interrupt
75
+ :abort
75
76
  end
76
77
 
77
78
  def normalize_input(raw_input)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ConsoleKit
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
data/lib/console_kit.rb CHANGED
@@ -7,6 +7,8 @@ require 'active_support/core_ext/string/inflections'
7
7
  require_relative 'console_kit/version'
8
8
  require_relative 'console_kit/configuration'
9
9
  require_relative 'console_kit/setup'
10
+ require_relative 'console_kit/console_helpers'
11
+ require_relative 'console_kit/prompt'
10
12
  require_relative 'console_kit/railtie' if defined?(Rails::Railtie)
11
13
 
12
14
  # Main module for ConsoleKit
@@ -21,7 +23,7 @@ module ConsoleKit
21
23
  def reset_configuration!
22
24
  @configuration = nil
23
25
  Setup.current_tenant = nil
24
- TenantConfigurator.configuration_success = false
26
+ TenantConfigurator.configuration_success = false if defined?(TenantConfigurator)
25
27
  end
26
28
 
27
29
  def pretty_output = configuration.pretty_output
@@ -11,14 +11,20 @@ Rails.application.config.after_initialize do
11
11
  # constants: {
12
12
  # shard: :shard_1,
13
13
  # mongo_db: 'mongo_db_1',
14
- # partner_code: 'partner_a'
14
+ # partner_code: 'partner_a',
15
+ # redis_db: 1,
16
+ # elasticsearch_prefix: 'tenant_a',
17
+ # environment: 'production'
15
18
  # }
16
19
  # },
17
20
  # tenant_b: {
18
21
  # constants: {
19
22
  # shard: :shard_2,
20
23
  # mongo_db: 'mongo_db_2',
21
- # partner_code: 'partner_b'
24
+ # partner_code: 'partner_b',
25
+ # redis_db: 2,
26
+ # elasticsearch_prefix: 'tenant_b',
27
+ # environment: 'staging'
22
28
  # }
23
29
  # }
24
30
  # }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: console_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Soumyadeep Pal
@@ -9,20 +9,6 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: mongoid
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - ">="
17
- - !ruby/object:Gem::Version
18
- version: '0'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - ">="
24
- - !ruby/object:Gem::Version
25
- version: '0'
26
12
  - !ruby/object:Gem::Dependency
27
13
  name: rails
28
14
  requirement: !ruby/object:Gem::Requirement
@@ -53,9 +39,13 @@ files:
53
39
  - lib/console_kit/configuration.rb
54
40
  - lib/console_kit/connections/base_connection_handler.rb
55
41
  - lib/console_kit/connections/connection_manager.rb
42
+ - lib/console_kit/connections/elasticsearch_connection_handler.rb
56
43
  - lib/console_kit/connections/mongo_connection_handler.rb
44
+ - lib/console_kit/connections/redis_connection_handler.rb
57
45
  - lib/console_kit/connections/sql_connection_handler.rb
46
+ - lib/console_kit/console_helpers.rb
58
47
  - lib/console_kit/output.rb
48
+ - lib/console_kit/prompt.rb
59
49
  - lib/console_kit/railtie.rb
60
50
  - lib/console_kit/setup.rb
61
51
  - lib/console_kit/tenant_configurator.rb