pg_objects 1.4.8 → 1.5.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -1
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +13 -2
  5. data/CHANGELOG.md +78 -0
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +71 -54
  8. data/README.md +174 -3
  9. data/Rakefile +2 -0
  10. data/bin/benchmark +42 -7
  11. data/bin/console +1 -0
  12. data/lib/generators/pg_objects/install/install_generator.rb +2 -0
  13. data/lib/pg_objects/config.rb +53 -2
  14. data/lib/pg_objects/db_object.rb +6 -1
  15. data/lib/pg_objects/db_object_factory.rb +11 -3
  16. data/lib/pg_objects/logger.rb +34 -3
  17. data/lib/pg_objects/manager.rb +66 -15
  18. data/lib/pg_objects/parsed_object/aggregate.rb +9 -1
  19. data/lib/pg_objects/parsed_object/base.rb +38 -0
  20. data/lib/pg_objects/parsed_object/base_type.rb +17 -0
  21. data/lib/pg_objects/parsed_object/conversion.rb +9 -1
  22. data/lib/pg_objects/parsed_object/domain.rb +16 -0
  23. data/lib/pg_objects/parsed_object/enum_type.rb +16 -0
  24. data/lib/pg_objects/parsed_object/event_trigger.rb +3 -1
  25. data/lib/pg_objects/parsed_object/extension.rb +10 -0
  26. data/lib/pg_objects/parsed_object/function.rb +9 -1
  27. data/lib/pg_objects/parsed_object/index.rb +17 -0
  28. data/lib/pg_objects/parsed_object/materialized_view.rb +9 -1
  29. data/lib/pg_objects/parsed_object/operator.rb +9 -1
  30. data/lib/pg_objects/parsed_object/operator_class.rb +9 -1
  31. data/lib/pg_objects/parsed_object/policy.rb +10 -0
  32. data/lib/pg_objects/parsed_object/range_type.rb +16 -0
  33. data/lib/pg_objects/parsed_object/rule.rb +10 -0
  34. data/lib/pg_objects/parsed_object/sequence.rb +16 -0
  35. data/lib/pg_objects/parsed_object/table.rb +9 -1
  36. data/lib/pg_objects/parsed_object/text_search_parser.rb +9 -1
  37. data/lib/pg_objects/parsed_object/text_search_template.rb +9 -1
  38. data/lib/pg_objects/parsed_object/trigger.rb +3 -1
  39. data/lib/pg_objects/parsed_object/type.rb +9 -1
  40. data/lib/pg_objects/parsed_object/view.rb +9 -1
  41. data/lib/pg_objects/parsed_object.rb +11 -0
  42. data/lib/pg_objects/parsed_object_factory.rb +52 -90
  43. data/lib/pg_objects/parser.rb +28 -12
  44. data/lib/pg_objects/railtie.rb +2 -0
  45. data/lib/pg_objects/version.rb +3 -1
  46. data/lib/pg_objects/yaml_configurable.rb +25 -7
  47. data/lib/pg_objects.rb +54 -4
  48. data/lib/tasks/pg_objects_tasks.rake +33 -8
  49. data/pg_objects.gemspec +5 -3
  50. metadata +16 -20
  51. data/.github/copilot-instructions.md +0 -131
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # OPERATOR CLASS object representation
3
5
  #
4
6
  class PgObjects::ParsedObject::OperatorClass < PgObjects::ParsedObject::Base
5
7
  def name
6
- stmt.create_op_class_stmt.opclassname[0].string.sval
8
+ extract_name { stmt.create_op_class_stmt.opclassname.last.string.sval }
9
+ end
10
+
11
+ private
12
+
13
+ def schema
14
+ extract_name { qualifier(stmt.create_op_class_stmt.opclassname) }
7
15
  end
8
16
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # POLICY object representation
5
+ #
6
+ class PgObjects::ParsedObject::Policy < PgObjects::ParsedObject::Base
7
+ def name
8
+ extract_name { stmt.create_policy_stmt.policy_name }
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # RANGE TYPE object representation (CREATE TYPE ... AS RANGE)
5
+ #
6
+ class PgObjects::ParsedObject::RangeType < PgObjects::ParsedObject::Base
7
+ def name
8
+ extract_name { stmt.create_range_stmt.type_name.last.string.sval }
9
+ end
10
+
11
+ private
12
+
13
+ def schema
14
+ extract_name { qualifier(stmt.create_range_stmt.type_name) }
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # RULE object representation
5
+ #
6
+ class PgObjects::ParsedObject::Rule < PgObjects::ParsedObject::Base
7
+ def name
8
+ extract_name { stmt.rule_stmt.rulename }
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # SEQUENCE object representation
5
+ #
6
+ class PgObjects::ParsedObject::Sequence < PgObjects::ParsedObject::Base
7
+ def name
8
+ extract_name { stmt.create_seq_stmt.sequence.relname }
9
+ end
10
+
11
+ private
12
+
13
+ def schema
14
+ extract_name { stmt.create_seq_stmt.sequence.schemaname }
15
+ end
16
+ end
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # TABLE object representation
3
5
  #
4
6
  class PgObjects::ParsedObject::Table < PgObjects::ParsedObject::Base
5
7
  def name
6
- stmt.create_stmt.relation.relname
8
+ extract_name { stmt.create_stmt.relation.relname }
9
+ end
10
+
11
+ private
12
+
13
+ def schema
14
+ extract_name { stmt.create_stmt.relation.schemaname }
7
15
  end
8
16
  end
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # TEXT SEARCH PARSER object representation
3
5
  #
4
6
  class PgObjects::ParsedObject::TextSearchParser < PgObjects::ParsedObject::Base
5
7
  def name
6
- stmt.define_stmt.defnames[0].string.sval
8
+ extract_name { stmt.define_stmt.defnames.last.string.sval }
9
+ end
10
+
11
+ private
12
+
13
+ def schema
14
+ extract_name { qualifier(stmt.define_stmt.defnames) }
7
15
  end
8
16
  end
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # TEXT SEARCH TEMPLATE object representation
3
5
  #
4
6
  class PgObjects::ParsedObject::TextSearchTemplate < PgObjects::ParsedObject::Base
5
7
  def name
6
- stmt.define_stmt.defnames[0].string.sval
8
+ extract_name { stmt.define_stmt.defnames.last.string.sval }
9
+ end
10
+
11
+ private
12
+
13
+ def schema
14
+ extract_name { qualifier(stmt.define_stmt.defnames) }
7
15
  end
8
16
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # TRIGGER object representation
3
5
  #
4
6
  class PgObjects::ParsedObject::Trigger < PgObjects::ParsedObject::Base
5
7
  def name
6
- stmt.create_trig_stmt.trigname
8
+ extract_name { stmt.create_trig_stmt.trigname }
7
9
  end
8
10
  end
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # TYPE object representation
3
5
  #
4
6
  class PgObjects::ParsedObject::Type < PgObjects::ParsedObject::Base
5
7
  def name
6
- stmt.composite_type_stmt.typevar.relname
8
+ extract_name { stmt.composite_type_stmt.typevar.relname }
9
+ end
10
+
11
+ private
12
+
13
+ def schema
14
+ extract_name { stmt.composite_type_stmt.typevar.schemaname }
7
15
  end
8
16
  end
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # VIEW object representation
3
5
  #
4
6
  class PgObjects::ParsedObject::View < PgObjects::ParsedObject::Base
5
7
  def name
6
- stmt.view_stmt.view.relname
8
+ extract_name { stmt.view_stmt.view.relname }
9
+ end
10
+
11
+ private
12
+
13
+ def schema
14
+ extract_name { stmt.view_stmt.view.schemaname }
7
15
  end
8
16
  end
@@ -1,14 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PgObjects::ParsedObject # rubocop: disable Style/Documentation
2
4
  end
3
5
 
4
6
  require 'pg_objects/parsed_object/base'
5
7
  require 'pg_objects/parsed_object/aggregate'
8
+ require 'pg_objects/parsed_object/base_type'
6
9
  require 'pg_objects/parsed_object/conversion'
10
+ require 'pg_objects/parsed_object/domain'
11
+ require 'pg_objects/parsed_object/enum_type'
7
12
  require 'pg_objects/parsed_object/event_trigger'
13
+ require 'pg_objects/parsed_object/extension'
8
14
  require 'pg_objects/parsed_object/function'
15
+ require 'pg_objects/parsed_object/index'
9
16
  require 'pg_objects/parsed_object/materialized_view'
10
17
  require 'pg_objects/parsed_object/operator'
11
18
  require 'pg_objects/parsed_object/operator_class'
19
+ require 'pg_objects/parsed_object/policy'
20
+ require 'pg_objects/parsed_object/range_type'
21
+ require 'pg_objects/parsed_object/rule'
22
+ require 'pg_objects/parsed_object/sequence'
12
23
  require 'pg_objects/parsed_object/table'
13
24
  require 'pg_objects/parsed_object/text_search_parser'
14
25
  require 'pg_objects/parsed_object/text_search_template'
@@ -1,103 +1,65 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Returns an object of the respective class based on the provided parsed query
3
5
  #
4
6
  class PgObjects::ParsedObjectFactory
5
- class << self
6
- include Dry::Monads[:try, :result]
7
-
8
- SUPPORTED_TYPES = %i[
9
- aggregate
10
- conversion
11
- event_trigger
12
- function
13
- materialized_view
14
- operator
15
- operator_class
16
- table
17
- text_search_parser
18
- text_search_template
19
- trigger
20
- type
21
- view
22
- ].freeze
23
-
24
- def create_object(input_data)
25
- @input_data = input_data
26
- @stmt = input_data.tree.stmts[0].stmt
27
-
28
- determine_class.new(stmt)
29
- end
30
-
31
- private
32
-
33
- attr_reader :stmt, :input_data
34
-
35
- def determine_class
36
- SUPPORTED_TYPES.each do |type|
37
- return class_for(type) if send("#{type}?")
38
- end
39
- end
40
-
41
- def class_for(type)
42
- "PgObjects::ParsedObject::#{type.to_s.classify}".constantize
43
- end
44
-
45
- def aggregate?
46
- try_check_get_result { stmt.define_stmt.kind == :OBJECT_AGGREGATE }
47
- end
48
-
49
- def conversion?
50
- try_check_get_result { stmt.create_conversion_stmt.conversion_name.present? }
51
- end
52
-
53
- def event_trigger?
54
- try_check_get_result { stmt.create_event_trig_stmt.trigname.present? }
55
- end
56
-
57
- def function?
58
- try_check_get_result { stmt.create_function_stmt.funcname.present? }
59
- end
60
-
61
- def materialized_view?
62
- try_check_get_result { stmt.create_table_as_stmt.objtype == :OBJECT_MATVIEW }
63
- end
64
-
65
- def operator_class?
66
- try_check_get_result { stmt.create_op_class_stmt.opclassname.present? }
67
- end
68
-
69
- def operator?
70
- try_check_get_result { stmt.define_stmt.kind == :OBJECT_OPERATOR }
71
- end
72
-
73
- def table?
74
- try_check_get_result { stmt.create_stmt.table_elts.present? }
75
- end
76
-
77
- def text_search_parser?
78
- try_check_get_result { stmt.define_stmt.kind == :OBJECT_TSPARSER }
79
- end
7
+ DISPATCH = {
8
+ composite_type_stmt: PgObjects::ParsedObject::Type,
9
+ create_conversion_stmt: PgObjects::ParsedObject::Conversion,
10
+ create_domain_stmt: PgObjects::ParsedObject::Domain,
11
+ create_enum_stmt: PgObjects::ParsedObject::EnumType,
12
+ create_event_trig_stmt: PgObjects::ParsedObject::EventTrigger,
13
+ create_extension_stmt: PgObjects::ParsedObject::Extension,
14
+ create_function_stmt: PgObjects::ParsedObject::Function,
15
+ create_op_class_stmt: PgObjects::ParsedObject::OperatorClass,
16
+ create_policy_stmt: PgObjects::ParsedObject::Policy,
17
+ create_range_stmt: PgObjects::ParsedObject::RangeType,
18
+ create_seq_stmt: PgObjects::ParsedObject::Sequence,
19
+ create_stmt: PgObjects::ParsedObject::Table,
20
+ create_trig_stmt: PgObjects::ParsedObject::Trigger,
21
+ index_stmt: PgObjects::ParsedObject::Index,
22
+ rule_stmt: PgObjects::ParsedObject::Rule,
23
+ view_stmt: PgObjects::ParsedObject::View
24
+ }.freeze
25
+
26
+ DEFINE_STMT_DISPATCH = {
27
+ OBJECT_AGGREGATE: PgObjects::ParsedObject::Aggregate,
28
+ OBJECT_OPERATOR: PgObjects::ParsedObject::Operator,
29
+ OBJECT_TSPARSER: PgObjects::ParsedObject::TextSearchParser,
30
+ OBJECT_TSTEMPLATE: PgObjects::ParsedObject::TextSearchTemplate,
31
+ OBJECT_TYPE: PgObjects::ParsedObject::BaseType
32
+ }.freeze
33
+
34
+ def self.create_object(input_data)
35
+ new(input_data).create_object
36
+ end
80
37
 
81
- def text_search_template?
82
- try_check_get_result { stmt.define_stmt.kind == :OBJECT_TSTEMPLATE }
83
- end
38
+ def initialize(input_data)
39
+ @input_data = input_data
40
+ @stmt = input_data.tree.stmts[0].stmt
41
+ end
84
42
 
85
- def trigger?
86
- try_check_get_result { stmt.create_trig_stmt.trigname.present? }
87
- end
43
+ def create_object
44
+ determine_class.new(stmt)
45
+ end
88
46
 
89
- def type?
90
- try_check_get_result { stmt.composite_type_stmt.typevar.present? }
91
- end
47
+ private
92
48
 
93
- def view?
94
- try_check_get_result { stmt.view_stmt.view.present? }
95
- end
49
+ attr_reader :stmt, :input_data
96
50
 
97
- def try_check_get_result(&)
98
- result = Try(&).to_result
51
+ def determine_class
52
+ dispatch_class || raise(PgObjects::UnknownObjectTypeError, "unknown object type: #{stmt.node}")
53
+ end
99
54
 
100
- result.success? && result.value!
55
+ def dispatch_class
56
+ case stmt.node
57
+ when :define_stmt
58
+ DEFINE_STMT_DISPATCH[stmt.define_stmt.kind]
59
+ when :create_table_as_stmt
60
+ PgObjects::ParsedObject::MaterializedView if stmt.create_table_as_stmt.objtype == :OBJECT_MATVIEW
61
+ else
62
+ DISPATCH[stmt.node]
101
63
  end
102
64
  end
103
65
  end
@@ -1,20 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pg_query'
2
4
 
3
5
  ##
4
6
  # Reads directives from SQL-comments
5
7
  #
6
- # --!depends_on [name_of_dependency]
8
+ # --!depends_on name_a
9
+ # --!depends_on name_a, name_b, name_c
10
+ # #!depends_on name_a
11
+ #
12
+ # The directive must start the line (no leading whitespace). Dependencies may
13
+ # be listed on one line separated by commas and/or whitespace, or across
14
+ # several directive lines.
7
15
  #
8
16
  # name_of_dependency: short or full name of object as well as object_name
9
17
  #
10
18
  class PgObjects::Parser
11
19
  include Import['parsed_object_factory']
12
- include Memery
13
20
 
14
- PG_ENTITIES = %i[operator_class trigger define_statement conversion event_trigger type function table].freeze
21
+ DEPENDS_ON_DIRECTIVE = /\A(?:--|#)!depends_on\s+(.+)/
15
22
 
16
23
  def load(source)
17
24
  @source = source
25
+ @parsed = nil
26
+ @parsed_object = nil
18
27
  self
19
28
  end
20
29
 
@@ -25,26 +34,33 @@ class PgObjects::Parser
25
34
  end
26
35
 
27
36
  def fetch_object_name
28
- parse_query
29
37
  parsed_object.name
30
- rescue PgQuery::ParseError, NoMethodError
38
+ rescue PgQuery::ParseError, PgObjects::UnknownObjectTypeError
31
39
  nil
32
40
  end
33
41
 
34
- private
42
+ def fetch_qualified_object_name
43
+ parsed_object.qualified_name
44
+ rescue PgQuery::ParseError, PgObjects::UnknownObjectTypeError
45
+ nil
46
+ end
35
47
 
36
- attr_reader :parsed
48
+ private
37
49
 
38
- def parse_query
39
- @parsed = PgQuery.parse(@source)
50
+ # Parse once per loaded source; both fetch_* methods reuse the result and
51
+ # +load+ clears it, so a new source is always reparsed (no stale cache).
52
+ def parsed
53
+ @parsed ||= PgQuery.parse(@source)
40
54
  end
41
55
 
42
- memoize
43
56
  def parsed_object
44
- parsed_object_factory.create_object(parsed)
57
+ @parsed_object ||= parsed_object_factory.create_object(parsed)
45
58
  end
46
59
 
47
60
  def fetch_dependencies
48
- @source.split("\n").grep(/^(--|#)!/).map { |ln| ln.split[1] if ln =~ /!depends_on/ }.compact
61
+ @source.each_line.flat_map do |line|
62
+ match = DEPENDS_ON_DIRECTIVE.match(line)
63
+ match ? match[1].split(/[\s,]+/).reject(&:empty?) : []
64
+ end
49
65
  end
50
66
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ##
2
4
  # Brings rake tasks to rails app
3
5
  class PgObjects::Railtie < Rails::Railtie
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PgObjects
2
- VERSION = '1.4.8'.freeze
4
+ VERSION = '1.5.0'
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # A module that provides a method to load configuration settings from a YAML file.
2
4
  module YamlConfigurable
3
5
  # Loads configuration settings from a YAML file.
@@ -6,17 +8,33 @@ module YamlConfigurable
6
8
  def load_from_yaml(file_path)
7
9
  return unless File.exist?(file_path)
8
10
 
9
- config_hash = YAML.load_file(file_path)
10
-
11
- set_if_present(config, :before_path, config_hash.dig('directories', 'before'))
12
- set_if_present(config, :after_path, config_hash.dig('directories', 'after'))
13
- set_if_present(config, :extensions, config_hash['extensions'])
14
- set_if_present(config, :silent, config_hash['silent'])
11
+ settings_from(YAML.load_file(file_path)).each do |key, value|
12
+ set_if_present(config, key, value)
13
+ end
14
+ rescue Psych::SyntaxError => e
15
+ warn "[pg_objects] Ignoring malformed YAML config #{file_path}: #{e.message}"
15
16
  end
16
17
 
17
18
  private
18
19
 
20
+ # Maps configuration keys to their values in the parsed YAML hash.
21
+ def settings_from(config_hash)
22
+ {
23
+ before_path: config_hash.dig('directories', 'before'),
24
+ after_path: config_hash.dig('directories', 'after'),
25
+ extensions: config_hash['extensions'],
26
+ silent: config_hash['silent'],
27
+ transactional: config_hash['transactional'],
28
+ auto_hook_migrations: config_hash['auto_hook_migrations']
29
+ }
30
+ end
31
+
32
+ # Applies the value unless it is nil or an empty string/array. Booleans
33
+ # (including +false+) have no +empty?+ and are always applied.
19
34
  def set_if_present(config, key, value)
20
- config.public_send("#{key}=", value) if value.present?
35
+ return if value.nil?
36
+ return if value.respond_to?(:empty?) && value.empty?
37
+
38
+ config.public_send("#{key}=", value)
21
39
  end
22
40
  end
data/lib/pg_objects.rb CHANGED
@@ -1,10 +1,61 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'pg_objects/version'
2
4
 
3
5
  module PgObjects
4
- class AmbiguousDependencyError < StandardError; end
5
- class CyclicDependencyError < StandardError; end
6
- class DependencyNotExistError < StandardError; end
6
+ # Raised when a dependency name resolves to more than one object. Carries
7
+ # the matching objects' file paths via +candidates+ and the file that
8
+ # declared the dependency via +referrer+; the message lists them all. Also
9
+ # accepts a bare name for compatibility.
10
+ class AmbiguousDependencyError < StandardError
11
+ attr_reader :candidates, :referrer
12
+
13
+ def initialize(dep_name = nil, candidates: [], referrer: nil)
14
+ @candidates = candidates
15
+ @referrer = referrer
16
+ message = build_message(dep_name)
17
+ message.empty? ? super() : super(message)
18
+ end
19
+
20
+ private
21
+
22
+ def build_message(dep_name)
23
+ message = dep_name.to_s
24
+ message += " (referenced by #{referrer})" if referrer
25
+ message += " matches: #{candidates.join(', ')}" unless candidates.empty?
26
+ message
27
+ end
28
+ end
29
+
30
+ # Raised when object dependencies form a cycle. Carries the resolution chain
31
+ # that closed the cycle (e.g. ["a", "b", "a"]) via +cycle_path+; the message
32
+ # renders it as "a -> b -> a". Also accepts a single name for compatibility.
33
+ class CyclicDependencyError < StandardError
34
+ attr_reader :cycle_path
35
+
36
+ def initialize(cycle_path = nil)
37
+ @cycle_path = Array(cycle_path)
38
+ @cycle_path.empty? ? super() : super(@cycle_path.join(' -> '))
39
+ end
40
+ end
41
+
42
+ # Raised when a declared dependency matches no loaded object. Carries the
43
+ # file that declared the dependency via +referrer+ and includes it in the
44
+ # message. Also accepts a bare name for compatibility.
45
+ class DependencyNotExistError < StandardError
46
+ attr_reader :referrer
47
+
48
+ def initialize(dep_name = nil, referrer: nil)
49
+ @referrer = referrer
50
+ message = dep_name.to_s
51
+ message += " (referenced by #{referrer})" unless referrer.to_s.empty?
52
+ message.empty? ? super() : super(message)
53
+ end
54
+ end
55
+
7
56
  class UnsupportedAdapterError < StandardError; end
57
+ class UnknownObjectTypeError < StandardError; end
58
+ class MalformedStatementError < StandardError; end
8
59
  end
9
60
 
10
61
  require 'pg_objects/railtie' if defined?(Rails)
@@ -12,7 +63,6 @@ require 'pg_objects/railtie' if defined?(Rails)
12
63
  require 'dry-configurable'
13
64
  require 'dry-container'
14
65
  require 'dry-auto_inject'
15
- require 'dry/monads'
16
66
  require 'memery'
17
67
 
18
68
  require 'pg_objects/container'
@@ -1,23 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Resolves the connection to run against. Set PG_OBJECTS_CONNECTION_CLASS to
4
+ # the name of an Active Record class (e.g. "AnimalsRecord") to target that
5
+ # class's database in a multi-DB setup; unset, the global connection is used.
6
+ pg_objects_connection = lambda do
7
+ class_name = ENV.fetch('PG_OBJECTS_CONNECTION_CLASS', nil)
8
+ next nil if class_name.nil? || class_name.strip.empty?
9
+
10
+ begin
11
+ Object.const_get(class_name).connection
12
+ rescue NameError
13
+ raise ArgumentError, "PG_OBJECTS_CONNECTION_CLASS is set to unknown class #{class_name.inspect}"
14
+ end
15
+ end
16
+
1
17
  namespace :db do
2
18
  namespace :create_objects do
3
19
  desc 'Create all the database objects from "before" folder'
4
20
  task before: :environment do
5
- PgObjects::Manager.new.load_files(:before).create_objects
21
+ PgObjects::Manager.new(connection: pg_objects_connection.call).load_files(:before).create_objects
6
22
  end
7
23
 
8
24
  desc 'Create all the database objects from "after" folder'
9
25
  task after: :environment do
10
- PgObjects::Manager.new.load_files(:after).create_objects
26
+ PgObjects::Manager.new(connection: pg_objects_connection.call).load_files(:after).create_objects
11
27
  end
12
28
  end
13
29
  end
14
30
 
15
31
  require 'rake/hooks'
16
32
 
17
- before 'db:migrate' do
18
- Rake::Task['db:create_objects:before'].invoke
19
- end
20
-
21
- after 'db:migrate' do
22
- Rake::Task['db:create_objects:after'].invoke
33
+ # Attach object-creation hooks to the tasks configured in
34
+ # PgObjects::Config.config.hook_tasks (override in an initializer to opt out).
35
+ # Each stage (:before/:after) maps to both the rake-hooks DSL method and the
36
+ # matching db:create_objects:<stage> task. Set config.auto_hook_migrations to
37
+ # false to install no hooks at all.
38
+ if PgObjects::Config.config.auto_hook_migrations
39
+ PgObjects::Config.config.hook_tasks.each do |task_name, stages|
40
+ stages.each do |stage|
41
+ send(stage, task_name) do
42
+ task = Rake::Task["db:create_objects:#{stage}"]
43
+ task.reenable # allow the hook to run again within composed/repeated task runs
44
+ task.invoke
45
+ end
46
+ end
47
+ end
23
48
  end
data/pg_objects.gemspec CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  lib = File.expand_path('lib', __dir__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
 
@@ -12,11 +14,12 @@ Gem::Specification.new do |spec|
12
14
  spec.summary = %q(Simple manager for PostgreSQL objects like triggers and functions)
13
15
  spec.homepage = 'https://github.com/marinazzio/pg_objects'
14
16
 
15
- spec.required_ruby_version = '>= 3.2.0'
17
+ spec.required_ruby_version = '>= 3.3.0'
16
18
 
17
19
  spec.metadata = {
18
20
  'allowed_push_host' => 'https://rubygems.org',
19
21
  'bug_tracker_uri' => 'https://github.com/marinazzio/pg_objects/issues',
22
+ 'changelog_uri' => 'https://github.com/marinazzio/pg_objects/blob/master/CHANGELOG.md',
20
23
  'documentation_uri' => 'https://github.com/marinazzio/pg_objects/blob/master/README.md',
21
24
  'homepage_uri' => 'https://github.com/marinazzio/pg_objects',
22
25
  'rubygems_mfa_required' => 'true',
@@ -46,8 +49,7 @@ Gem::Specification.new do |spec|
46
49
  spec.add_dependency 'activerecord', '>= 6.1.7.0', '< 9'
47
50
  spec.add_dependency 'dry-auto_inject', '~> 1'
48
51
  spec.add_dependency 'dry-configurable', '~> 1'
49
- spec.add_dependency 'dry-container', '0.11.0'
50
- spec.add_dependency 'dry-monads', '~> 1.6'
52
+ spec.add_dependency 'dry-container', '~> 0.11'
51
53
  spec.add_dependency 'memery', '>= 1.5', '< 1.9'
52
54
  spec.add_dependency 'pg_query', '>= 5', '< 7'
53
55
  spec.add_dependency 'railties', '>= 4', '< 9'