sequelizer 0.1.4 → 0.2.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/.beads/.gitignore +54 -0
  3. data/.beads/.jsonl.lock +0 -0
  4. data/.beads/.migration-hint-ts +1 -0
  5. data/.beads/README.md +81 -0
  6. data/.beads/config.yaml +42 -0
  7. data/.beads/issues.jsonl +20 -0
  8. data/.beads/metadata.json +7 -0
  9. data/.coderabbit.yaml +94 -0
  10. data/.devcontainer/.p10k.zsh +1713 -0
  11. data/.devcontainer/.zshrc +29 -0
  12. data/.devcontainer/Dockerfile +137 -0
  13. data/.devcontainer/copy-claude-credentials.sh +32 -0
  14. data/.devcontainer/devcontainer.json +102 -0
  15. data/.devcontainer/init-firewall.sh +123 -0
  16. data/.devcontainer/setup-credentials.sh +95 -0
  17. data/.github/dependabot.yml +18 -0
  18. data/.github/workflows/dependabot-auto-merge.yml +36 -0
  19. data/.github/workflows/test.yml +44 -9
  20. data/.gitignore +6 -1
  21. data/.overcommit.yml +73 -0
  22. data/.rubocop.yml +167 -0
  23. data/AGENTS.md +126 -0
  24. data/CHANGELOG.md +41 -0
  25. data/CLAUDE.md +230 -0
  26. data/Gemfile +6 -2
  27. data/Gemfile.lock +189 -0
  28. data/Guardfile +1 -1
  29. data/Rakefile +28 -3
  30. data/config/platforms/base.csv +5 -0
  31. data/config/platforms/rdbms/athena.csv +4 -0
  32. data/config/platforms/rdbms/postgres.csv +3 -0
  33. data/config/platforms/rdbms/snowflake.csv +1 -0
  34. data/config/platforms/rdbms/spark.csv +3 -0
  35. data/lib/sequel/extensions/cold_col.rb +436 -0
  36. data/lib/sequel/extensions/db_opts.rb +65 -4
  37. data/lib/sequel/extensions/funky.rb +136 -0
  38. data/lib/sequel/extensions/make_readyable.rb +146 -30
  39. data/lib/sequel/extensions/more_sql.rb +76 -0
  40. data/lib/sequel/extensions/platform.rb +301 -0
  41. data/lib/sequel/extensions/settable.rb +64 -0
  42. data/lib/sequel/extensions/sql_recorder.rb +85 -0
  43. data/lib/sequel/extensions/unionize.rb +169 -0
  44. data/lib/sequel/extensions/usable.rb +30 -1
  45. data/lib/sequelizer/cli.rb +61 -18
  46. data/lib/sequelizer/connection_maker.rb +54 -72
  47. data/lib/sequelizer/env_config.rb +6 -6
  48. data/lib/sequelizer/gemfile_modifier.rb +23 -21
  49. data/lib/sequelizer/monkey_patches/database_in_after_connect.rb +7 -5
  50. data/lib/sequelizer/options.rb +102 -19
  51. data/lib/sequelizer/options_hash.rb +2 -0
  52. data/lib/sequelizer/version.rb +3 -1
  53. data/lib/sequelizer/yaml_config.rb +9 -4
  54. data/lib/sequelizer.rb +65 -9
  55. data/sequelizer.gemspec +20 -12
  56. data/test/lib/sequel/extensions/test_cold_col.rb +251 -0
  57. data/test/lib/sequel/extensions/test_db_opts.rb +10 -8
  58. data/test/lib/sequel/extensions/test_make_readyable.rb +198 -28
  59. data/test/lib/sequel/extensions/test_more_sql.rb +132 -0
  60. data/test/lib/sequel/extensions/test_platform.rb +222 -0
  61. data/test/lib/sequel/extensions/test_settable.rb +109 -0
  62. data/test/lib/sequel/extensions/test_sql_recorder.rb +231 -0
  63. data/test/lib/sequel/extensions/test_unionize.rb +76 -0
  64. data/test/lib/sequel/extensions/test_usable.rb +5 -2
  65. data/test/lib/sequelizer/test_connection_maker.rb +21 -17
  66. data/test/lib/sequelizer/test_env_config.rb +5 -2
  67. data/test/lib/sequelizer/test_gemfile_modifier.rb +7 -6
  68. data/test/lib/sequelizer/test_options.rb +42 -9
  69. data/test/lib/sequelizer/test_yaml_config.rb +13 -12
  70. data/test/test_helper.rb +37 -8
  71. metadata +196 -39
  72. data/lib/sequel/extensions/sqls.rb +0 -31
@@ -3,39 +3,75 @@ require 'pp'
3
3
  require_relative 'gemfile_modifier'
4
4
 
5
5
  module Sequelizer
6
+ # = CLI
7
+ #
8
+ # Command line interface for Sequelizer gem using Thor.
9
+ # Provides commands for:
10
+ #
11
+ # * Updating Gemfile with database adapters
12
+ # * Initializing .env files with database configuration
13
+ # * Displaying current configuration
14
+ #
15
+ # @example
16
+ # sequelizer update_gemfile
17
+ # sequelizer init_env --adapter postgres --host localhost
18
+ # sequelizer config
6
19
  class CLI < Thor
7
- desc 'update_gemfile', 'adds or replaces a line in your Gemfile to include the correct database adapter to work with Sequel'
20
+
21
+ desc 'update_gemfile',
22
+ 'adds or replaces a line in your Gemfile to include the correct database adapter to work with Sequel'
8
23
  option 'dry-run', type: :boolean, desc: 'Only prints out what it would do, but makes no changes'
9
24
  option 'skip-bundle', type: :boolean, desc: "Don't run `bundle install` after modifying Gemfile"
25
+ # Updates the Gemfile to include the appropriate database adapter gem.
26
+ #
27
+ # This command analyzes your current database configuration and adds or updates
28
+ # the corresponding database adapter gem in your Gemfile. It supports various
29
+ # adapters including PostgreSQL, MySQL, SQLite, and JDBC-based adapters.
30
+ #
31
+ # @option options [Boolean] dry-run Only prints what would be done without making changes
32
+ # @option options [Boolean] skip-bundle Skip running `bundle install` after modification
10
33
  def update_gemfile
11
34
  GemfileModifier.new(options).modify
12
35
  end
13
36
 
14
37
  desc 'init_env', 'creates a .env file with the parameters listed'
15
38
  option :adapter,
16
- aliases: :a,
17
- desc: 'adapter for database'
39
+ aliases: :a,
40
+ desc: 'adapter for database'
18
41
  option :host,
19
- aliases: :h,
20
- banner: 'localhost',
21
- desc: 'host for database'
42
+ aliases: :h,
43
+ banner: 'localhost',
44
+ desc: 'host for database'
22
45
  option :username,
23
- aliases: :u,
24
- desc: 'username for database'
46
+ aliases: :u,
47
+ desc: 'username for database'
25
48
  option :password,
26
- aliases: :P,
27
- desc: 'password for database'
49
+ aliases: :P,
50
+ desc: 'password for database'
28
51
  option :port,
29
- aliases: :p,
30
- type: :numeric,
31
- banner: '5432',
32
- desc: 'port for database'
52
+ aliases: :p,
53
+ type: :numeric,
54
+ banner: '5432',
55
+ desc: 'port for database'
33
56
  option :database,
34
- aliases: :d,
35
- desc: 'database for database'
57
+ aliases: :d,
58
+ desc: 'database for database'
36
59
  option :search_path,
37
- aliases: :s,
38
- desc: 'schema for database (PostgreSQL only)'
60
+ aliases: :s,
61
+ desc: 'schema for database (PostgreSQL only)'
62
+ # Creates a .env file with database configuration parameters.
63
+ #
64
+ # This command generates a .env file with SEQUELIZER_* environment variables
65
+ # based on the provided options. It will not overwrite an existing .env file.
66
+ #
67
+ # @option options [String] :adapter Database adapter (e.g., 'postgres', 'mysql2')
68
+ # @option options [String] :host Database host (default: 'localhost')
69
+ # @option options [String] :username Database username
70
+ # @option options [String] :password Database password
71
+ # @option options [Integer] :port Database port (default: 5432 for PostgreSQL)
72
+ # @option options [String] :database Database name
73
+ # @option options [String] :search_path PostgreSQL schema search path
74
+ # @raise [SystemExit] if .env file already exists
39
75
  def init_env
40
76
  if File.exist?('.env')
41
77
  puts ".env already exists! I'm too cowardly to overwrite it!"
@@ -49,6 +85,11 @@ module Sequelizer
49
85
  end
50
86
 
51
87
  desc 'config', 'prints out the connection parameters'
88
+ # Displays the current database configuration and extensions.
89
+ #
90
+ # This command shows the resolved configuration options that would be used
91
+ # for database connections, including all merged sources and any Sequel
92
+ # extensions that would be loaded.
52
93
  def config
53
94
  opts = Options.new
54
95
  pp opts.to_hash
@@ -56,10 +97,12 @@ module Sequelizer
56
97
  end
57
98
 
58
99
  private
100
+
59
101
  def make_env(options)
60
102
  options.map do |key, value|
61
103
  "SEQUELIZER_#{key.upcase}=#{value}"
62
104
  end.join("\n")
63
105
  end
106
+
64
107
  end
65
108
  end
@@ -1,97 +1,79 @@
1
1
  require 'sequel'
2
- require 'cgi'
3
2
  require_relative 'options'
4
3
 
5
4
  module Sequelizer
6
- # Class that handles loading/interpretting the database options and
7
- # creates the Sequel connection
5
+ # = ConnectionMaker
6
+ #
7
+ # Class that handles loading/interpreting the database options and
8
+ # creates the Sequel connection. This class is responsible for:
9
+ #
10
+ # * Loading configuration from multiple sources
11
+ # * Creating standard Sequel database connections
12
+ #
13
+ # @example Basic usage
14
+ # maker = ConnectionMaker.new(adapter: 'postgres', host: 'localhost')
15
+ # db = maker.connection
8
16
  class ConnectionMaker
9
- # The options for Sequel.connect
10
- attr :options
11
17
 
12
- # Accepts an optional set of database options
18
+ # @!attribute [r] options
19
+ # @return [Options] the database connection options
20
+ attr_reader :options
21
+
22
+ # Creates a new ConnectionMaker instance.
13
23
  #
14
- # If no options are provided, attempts to read options from
15
- # config/database.yml
24
+ # If no options are provided, attempts to read options from multiple sources
25
+ # in order of precedence:
26
+ # 1. .env file
27
+ # 2. Environment variables
28
+ # 3. config/database.yml
29
+ # 4. ~/.config/sequelizer/database.yml
16
30
  #
17
- # If config/database.yml doesn't exist, Dotenv is used to try to load a
18
- # .env file, then uses any SEQUELIZER_* environment variables as
19
- # database options
31
+ # @param options [Hash, nil] database connection options
32
+ # @option options [String] :adapter database adapter (e.g., 'postgres', 'mysql2')
33
+ # @option options [String] :host database host
34
+ # @option options [Integer] :port database port
35
+ # @option options [String] :database database name
36
+ # @option options [String] :username database username
37
+ # @option options [String] :password database password
38
+ # @option options [String] :search_path PostgreSQL schema search path
20
39
  def initialize(options = nil)
21
40
  @options = Options.new(options)
22
41
  end
23
42
 
24
- # Returns a Sequel connection to the database
43
+ # Returns a Sequel connection to the database.
44
+ #
45
+ # This method creates a standard Sequel database connection
46
+ # using the configured options.
47
+ #
48
+ # @return [Sequel::Database] configured database connection
49
+ # @raise [Sequel::Error] if connection fails
50
+ #
51
+ # @example
52
+ # connection = maker.connection
53
+ # users = connection[:users].all
25
54
  def connection
26
55
  opts = options.to_hash
27
56
  extensions = options.extensions
28
57
 
29
- conn = if url = (opts.delete(:uri) || opts.delete(:url))
30
- Sequel.connect(url, opts)
31
- else
32
- # Kerberos related options
33
- realm = opts[:realm]
34
- host_fqdn = opts[:host_fqdn] || opts[:host]
35
- principal = opts[:principal]
36
-
37
- adapter = opts[:adapter]
38
- if adapter =~ /\Ajdbc_/
39
- user = opts[:user]
40
- password = opts[:password]
41
- end
42
-
43
- case opts[:adapter] && opts[:adapter].to_sym
44
- when :jdbc_hive2
45
- opts[:adapter] = :jdbc
46
- auth = if realm
47
- ";principal=#{e principal}/#{e host_fqdn}@#{e realm}"
48
- elsif user
49
- ";user=#{e user};password=#{e password}"
50
- else
51
- ';auth=noSasl'
52
- end
53
- opts[:uri] = "jdbc:hive2://#{e opts[:host]}:#{opts.fetch(:port, 21050).to_i}/#{e(opts[:database] || 'default')}#{auth}"
54
- when :jdbc_impala
55
- opts[:adapter] = :jdbc
56
- auth = if realm
57
- ";AuthMech=1;KrbServiceName=#{e principal};KrbAuthType=2;KrbHostFQDN=#{e host_fqdn};KrbRealm=#{e realm}"
58
- elsif user
59
- if password
60
- ";AuthMech=3;UID=#{e user};PWD=#{e password}"
61
- else
62
- ";AuthMech=2;UID=#{e user}"
63
- end
64
- end
65
- opts[:uri] = "jdbc:impala://#{e opts[:host]}:#{opts.fetch(:port, 21050).to_i}/#{e(opts[:database] || 'default')}#{auth}"
66
- when :jdbc_postgres
67
- opts[:adapter] = :jdbc
68
- auth = "?user=#{user}#{"&password=#{password}" if password}" if user
69
- opts[:uri] = "jdbc:postgresql://#{e opts[:host]}:#{opts.fetch(:port, 5432).to_i}/#{e(opts[:database])}#{auth}"
70
- when :impala
71
- opts[:database] ||= 'default'
72
- opts[:port] ||= 21000
73
- if principal
74
- # realm doesn't seem to be used?
75
- opts[:transport] = :sasl
76
- opts[:sasl_params] = {
77
- mechanism: "GSSAPI",
78
- remote_host: host_fqdn,
79
- remote_principal: principal
80
- }
81
- end
82
- end
83
-
84
- Sequel.connect(opts)
85
- end
58
+ conn = create_sequel_connection(opts)
86
59
  conn.extension(*extensions)
87
60
  conn
88
61
  end
89
62
 
90
63
  private
91
64
 
92
- def e(v)
93
- CGI.escape(v.to_s)
65
+ def create_sequel_connection(opts)
66
+ if (url = opts.delete(:uri) || opts.delete(:url))
67
+ Sequel.connect(url, opts)
68
+ else
69
+ configure_adapter_specific_options(opts)
70
+ Sequel.connect(opts)
71
+ end
94
72
  end
73
+
74
+ def configure_adapter_specific_options(opts)
75
+ # No adapter-specific configuration needed currently
76
+ end
77
+
95
78
  end
96
79
  end
97
-
@@ -4,25 +4,25 @@ module Sequelizer
4
4
  # Creates a set of database configuration options from environment
5
5
  # variables
6
6
  class EnvConfig
7
+
7
8
  # Any environment variables in the .env file are loaded and then
8
9
  # any environment variable starting with SEQUELIZER_ will be used
9
10
  # as an option for the database
10
11
  def options
11
12
  Dotenv.load
12
13
 
13
- seq_config = ENV.keys.select { |key| key =~ /^SEQUELIZER_/ }.inject({}) do |config, key|
14
+ seq_config = ENV.keys.grep(/^SEQUELIZER_/).each_with_object({}) do |key, config|
14
15
  new_key = key.gsub(/^SEQUELIZER_/, '').downcase
15
- config[new_key] = ENV[key]
16
- config
16
+ config[new_key] = ENV.fetch(key, nil)
17
17
  end
18
18
 
19
- db_config = ENV.keys.select { |key| key =~ /_DB_OPT_/ }.inject({}) do |config, key|
19
+ db_config = ENV.keys.grep(/_DB_OPT_/).each_with_object({}) do |key, config|
20
20
  new_key = key.downcase
21
- config[new_key] = ENV[key]
22
- config
21
+ config[new_key] = ENV.fetch(key, nil)
23
22
  end
24
23
 
25
24
  db_config.merge(seq_config)
26
25
  end
26
+
27
27
  end
28
28
  end
@@ -2,7 +2,8 @@ require_relative 'options'
2
2
 
3
3
  module Sequelizer
4
4
  class GemfileModifier
5
- attr :options
5
+
6
+ attr_reader :options
6
7
 
7
8
  def initialize(options = {})
8
9
  @options = options
@@ -14,14 +15,14 @@ module Sequelizer
14
15
  modify_gemfile
15
16
  run_bundle unless options['skip-bundle']
16
17
  else
17
- puts "Gemfile needs no modification"
18
+ puts 'Gemfile needs no modification'
18
19
  end
19
20
  end
20
21
 
21
22
  private
22
23
 
23
24
  def modify_gemfile
24
- puts %Q|Adding "#{gem_line}" to Gemfile|
25
+ puts %(Adding "#{gem_line}" to Gemfile)
25
26
  return if options['dry-run']
26
27
 
27
28
  File.write(gemfile, modified_lines.join("\n"))
@@ -30,21 +31,21 @@ module Sequelizer
30
31
  def proper_gem
31
32
  opts = Options.new
32
33
  @proper_gem ||= case opts.adapter
33
- when 'postgres'
34
- 'pg'
35
- when 'sqlite'
36
- 'sqlite3'
37
- when 'mysql'
38
- 'mysql2'
39
- when 'tinytds'
40
- 'tiny_tds'
41
- when 'oracle'
42
- 'ruby-oci8'
43
- when nil
44
- raise "No database adapter defined in your Sequelizer configuration"
45
- else
46
- raise "Don't know which database gem to use with adapter: #{opts.adapter}"
47
- end
34
+ when 'postgres'
35
+ 'pg'
36
+ when 'sqlite'
37
+ 'sqlite3'
38
+ when 'mysql'
39
+ 'mysql2'
40
+ when 'tinytds'
41
+ 'tiny_tds'
42
+ when 'oracle'
43
+ 'ruby-oci8'
44
+ when nil
45
+ raise 'No database adapter defined in your Sequelizer configuration'
46
+ else
47
+ raise "Don't know which database gem to use with adapter: #{opts.adapter}"
48
+ end
48
49
  end
49
50
 
50
51
  def gem_line
@@ -68,22 +69,23 @@ module Sequelizer
68
69
  end
69
70
 
70
71
  def modified_lines
71
- gemfile_lines.select { |l| l !~ Regexp.new(gem_line_comment) } + [full_gem_line]
72
+ gemfile_lines.grep_v(Regexp.new(gem_line_comment)) + [full_gem_line]
72
73
  end
73
74
 
74
75
  def check_for_gemfile
75
76
  return if gemfile.exist?
77
+
76
78
  raise "Could not find Gemfile in current directory: #{Pathname.pwd}"
77
79
  end
78
80
 
79
81
  def run_bundle
80
- puts "Running `bundle install` to update dependencies"
82
+ puts 'Running `bundle install` to update dependencies'
81
83
  system('bundle install')
82
84
  end
83
85
 
84
86
  def gemfile
85
87
  @gemfile ||= Pathname.new('Gemfile')
86
88
  end
89
+
87
90
  end
88
91
  end
89
-
@@ -1,11 +1,12 @@
1
1
  module Sequel
2
2
  class ConnectionPool
3
+
3
4
  # Return a new connection by calling the connection proc with the given server name,
4
5
  # and checking for connection errors.
5
6
  def make_new(server)
6
7
  begin
7
8
  conn = @db.connect(server)
8
- if ac = @after_connect
9
+ if (ac = @after_connect)
9
10
  case ac.arity
10
11
  when 3
11
12
  ac.call(conn, server, @db)
@@ -15,12 +16,13 @@ module Sequel
15
16
  ac.call(conn)
16
17
  end
17
18
  end
18
- rescue Exception=>exception
19
- raise Sequel.convert_exception_class(exception, Sequel::DatabaseConnectionError)
19
+ rescue StandardError => e
20
+ raise Sequel.convert_exception_class(e, Sequel::DatabaseConnectionError)
20
21
  end
21
- raise(Sequel::DatabaseConnectionError, "Connection parameters not valid") unless conn
22
+ raise(Sequel::DatabaseConnectionError, 'Connection parameters not valid') unless conn
23
+
22
24
  conn
23
25
  end
26
+
24
27
  end
25
28
  end
26
-
@@ -1,20 +1,64 @@
1
+ require 'uri'
1
2
  require_relative 'yaml_config'
2
3
  require_relative 'env_config'
3
4
  require_relative 'options_hash'
4
5
 
5
6
  module Sequelizer
7
+ # = Options
8
+ #
9
+ # Manages database connection options from multiple configuration sources.
10
+ # This class is responsible for:
11
+ #
12
+ # * Loading configuration from various sources (YAML files, environment variables, .env files)
13
+ # * Applying precedence rules for configuration sources
14
+ # * Processing adapter-specific options (especially PostgreSQL schema handling)
15
+ # * Managing Sequel extensions
16
+ # * Setting up after_connect callbacks
17
+ #
18
+ # == Configuration Sources (in order of precedence)
19
+ #
20
+ # 1. Passed options (highest priority)
21
+ # 2. .env file
22
+ # 3. Environment variables
23
+ # 4. config/database.yml
24
+ # 5. ~/.config/sequelizer/database.yml (lowest priority)
25
+ #
26
+ # @example Basic usage
27
+ # options = Options.new(adapter: 'postgres', host: 'localhost')
28
+ # hash = options.to_hash
29
+ #
30
+ # @example Loading from environment
31
+ # ENV['SEQUELIZER_ADAPTER'] = 'postgres'
32
+ # options = Options.new
33
+ # puts options.adapter # => 'postgres'
6
34
  class Options
7
- attr :extensions
35
+
36
+ # @!attribute [r] extensions
37
+ # @return [Array<Symbol>] list of Sequel extensions to load
38
+ attr_reader :extensions
39
+
40
+ # Creates a new Options instance, processing configuration from multiple sources.
41
+ #
42
+ # @param options [Hash, String, nil] database connection options or connection URL
43
+ # If a Hash is provided, it will be merged with configuration from other sources.
44
+ # If a String is provided, it's treated as a database URL and returned as-is.
45
+ # If nil, configuration is loaded entirely from external sources.
8
46
  def initialize(options = nil)
9
47
  opts = fix_options(options)
10
48
  @options, @extensions = filter_extensions(opts)
11
49
  end
12
50
 
51
+ # Returns the processed options as a hash suitable for Sequel.connect.
52
+ #
53
+ # @return [Hash] the database connection options
13
54
  def to_hash
14
55
  @options
15
56
  end
16
57
 
17
- %w(adapter database username password search_path).each do |name|
58
+ # Define accessor methods for common database options
59
+ %w[adapter database username password search_path].each do |name|
60
+ # @!method #{name}
61
+ # @return [String, nil] the #{name} option value
18
62
  define_method(name) do
19
63
  @options[name]
20
64
  end
@@ -22,9 +66,14 @@ module Sequelizer
22
66
 
23
67
  private
24
68
 
69
+ # Creates an after_connect callback proc that handles custom callbacks
70
+ # and applies database-specific options.
71
+ #
72
+ # @param opts [Hash] options hash that may contain an :after_connect callback
73
+ # @return [Proc] callback to be executed after database connection
25
74
  def make_ac(opts)
26
- Proc.new do |conn, server, db|
27
- if ac = opts[:after_connect]
75
+ proc do |conn, server, db|
76
+ if (ac = opts[:after_connect])
28
77
  ac.arity == 2 ? ac.call(conn, server) : ac.call(conn)
29
78
  end
30
79
  db.extension :db_opts
@@ -32,19 +81,30 @@ module Sequelizer
32
81
  end
33
82
  end
34
83
 
84
+ # Processes and fixes the passed options, handling various input types
85
+ # and applying adapter-specific transformations.
86
+ #
35
87
  # If passed a hash, scans hash for certain options and sets up hash
36
- # to be fed to Sequel.connect
88
+ # to be fed to Sequel.connect. Handles PostgreSQL schema setup and
89
+ # timeout conversion.
90
+ #
91
+ # If passed anything else (like a string that represents a database URL),
92
+ # the value is returned without modification.
37
93
  #
38
- # If fed anything, like a string that represents the URL for a DB,
39
- # the string is returned without modification
94
+ # @param passed_options [Hash, String, nil] the options to process
95
+ # @return [Hash, String] processed options or original string
40
96
  def fix_options(passed_options)
41
97
  return passed_options unless passed_options.nil? || passed_options.is_a?(Hash)
98
+
42
99
  opts = OptionsHash.new(passed_options || {}).to_hash
43
100
  sequelizer_options = db_config(opts).merge(opts)
44
101
 
45
- if sequelizer_options[:adapter] =~ /^postgres/
102
+ adapter = sequelizer_options[:adapter]
103
+ adapter ||= URI.parse(sequelizer_options[:url].to_s).scheme if sequelizer_options[:url]
104
+
105
+ if adapter =~ /^postgres/
46
106
  sequelizer_options[:adapter] = 'postgres'
47
- paths = %w(search_path schema_search_path schema).map { |key| sequelizer_options.delete(key) }.compact
107
+ paths = %w[search_path schema_search_path schema].map { |key| sequelizer_options.delete(key) }.compact
48
108
 
49
109
  unless paths.empty?
50
110
  sequelizer_options[:search_path] = paths.first
@@ -62,10 +122,14 @@ module Sequelizer
62
122
  sequelizer_options.merge(after_connect: make_ac(sequelizer_options))
63
123
  end
64
124
 
65
- # Grabs the database options from
66
- # - ~/.config/sequelizer.yml if it exists
67
- # - config/database.yml if it exists
68
- # - environment variables (also reads from .env)
125
+ # Loads database configuration from external sources in order of precedence.
126
+ # Sources checked (in order):
127
+ # - ~/.config/sequelizer.yml (if it exists and not ignored)
128
+ # - config/database.yml (if it exists and not ignored)
129
+ # - environment variables (including .env file if not ignored)
130
+ #
131
+ # @param opts [Hash] base options that may contain ignore flags
132
+ # @return [OptionsHash] merged configuration from all sources
69
133
  def db_config(opts)
70
134
  @db_config ||= begin
71
135
  opts = OptionsHash.new(opts)
@@ -77,14 +141,19 @@ module Sequelizer
77
141
  end
78
142
 
79
143
  # Returns a proc that should be executed after Sequel connects to the
80
- # datebase.
144
+ # database.
145
+ #
146
+ # For PostgreSQL connections with a search_path defined, this proc will:
147
+ # 1. Create each schema in the search path if it doesn't exist
148
+ # 2. Set the search_path for the connection
81
149
  #
82
- # Right now, the only thing that happens is if we're connecting to
83
- # PostgreSQL and the schema_search_path is defined, each schema
84
- # is created if it doesn't exist, then the search_path is set for
85
- # the connection.
150
+ # @param search_path [String] comma-separated list of PostgreSQL schemas
151
+ # @return [Proc] callback to execute after connection
152
+ # @example
153
+ # callback = after_connect('public,app_schema')
154
+ # # When called, creates schemas and sets search_path
86
155
  def after_connect(search_path)
87
- Proc.new do |conn|
156
+ proc do |conn|
88
157
  search_path.split(',').map(&:strip).each do |schema|
89
158
  conn.execute("CREATE SCHEMA IF NOT EXISTS #{schema}")
90
159
  end
@@ -92,6 +161,19 @@ module Sequelizer
92
161
  end
93
162
  end
94
163
 
164
+ # Extracts Sequel extension configuration from options.
165
+ #
166
+ # Looks for keys starting with 'extension_' and converts them to
167
+ # extension names. The extension keys are removed from the options
168
+ # hash and returned separately.
169
+ #
170
+ # @param options [Hash] options hash that may contain extension keys
171
+ # @return [Array<Hash, Array>] tuple of [filtered_options, extensions]
172
+ # @example
173
+ # opts = { adapter: 'postgres', extension_pg_json: true, extension_pg_array: true }
174
+ # filtered_opts, exts = filter_extensions(opts)
175
+ # # filtered_opts => { adapter: 'postgres' }
176
+ # # exts => [:pg_json, :pg_array]
95
177
  def filter_extensions(options)
96
178
  extension_regexp = /^extension_/
97
179
  extension_keys = options.keys.select { |k| k.to_s =~ extension_regexp }
@@ -101,5 +183,6 @@ module Sequelizer
101
183
  end
102
184
  [options, extensions]
103
185
  end
186
+
104
187
  end
105
188
  end
@@ -1,7 +1,9 @@
1
1
  require 'hashie'
2
2
  module Sequelizer
3
3
  class OptionsHash < Hash
4
+
4
5
  include Hashie::Extensions::IndifferentAccess
5
6
  include Hashie::Extensions::MergeInitializer
7
+
6
8
  end
7
9
  end
@@ -1,4 +1,6 @@
1
1
  module Sequelizer
2
+
2
3
  # Version for the gem
3
- VERSION = "0.1.4"
4
+ VERSION = '0.2.0'.freeze
5
+
4
6
  end
@@ -1,12 +1,13 @@
1
1
  require 'psych'
2
- require 'pathname'
3
2
  require 'erb'
4
3
 
5
4
  module Sequelizer
6
5
  class YamlConfig
6
+
7
7
  attr_reader :config_file_path
8
8
 
9
9
  class << self
10
+
10
11
  def local_config
11
12
  new
12
13
  end
@@ -16,19 +17,22 @@ module Sequelizer
16
17
  end
17
18
 
18
19
  def user_config_path
19
- return nil unless ENV['HOME']
20
- Pathname.new(ENV['HOME']) + ".config" + "sequelizer" + "database.yml"
20
+ return nil unless Dir.home
21
+
22
+ Pathname.new(Dir.home).join('.config', 'sequelizer', 'database.yml')
21
23
  end
24
+
22
25
  end
23
26
 
24
27
  def initialize(config_file_path = nil)
25
- @config_file_path = Pathname.new(config_file_path || Pathname.pwd + "config" + "sequelizer.yml").expand_path
28
+ @config_file_path = Pathname.new(config_file_path || Pathname.pwd.join('config', 'sequelizer.yml')).expand_path
26
29
  end
27
30
 
28
31
  # Returns a set of options pulled from config/database.yml
29
32
  # or +nil+ if config/database.yml doesn't exist
30
33
  def options
31
34
  return {} unless config_file_path.exist?
35
+
32
36
  config[environment] || config
33
37
  end
34
38
 
@@ -51,5 +55,6 @@ module Sequelizer
51
55
  def config
52
56
  @config ||= Psych.load(ERB.new(File.read(config_file_path)).result)
53
57
  end
58
+
54
59
  end
55
60
  end