fx 0.9.0 → 0.10.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/CHANGELOG.md +27 -1
  4. data/Gemfile +0 -1
  5. data/bin/rake +2 -3
  6. data/bin/rspec +13 -3
  7. data/bin/yard +13 -3
  8. data/fx.gemspec +3 -3
  9. data/lib/fx/adapters/postgres/connection.rb +12 -4
  10. data/lib/fx/adapters/postgres/functions.rb +11 -28
  11. data/lib/fx/adapters/postgres/query_executor.rb +34 -0
  12. data/lib/fx/adapters/postgres/triggers.rb +14 -29
  13. data/lib/fx/adapters/postgres.rb +10 -10
  14. data/lib/fx/configuration.rb +2 -2
  15. data/lib/fx/schema_dumper.rb +6 -14
  16. data/lib/fx/statements.rb +61 -58
  17. data/lib/fx/version.rb +1 -1
  18. data/lib/generators/fx/function/function_generator.rb +50 -53
  19. data/lib/generators/fx/function/templates/db/migrate/create_function.erb +1 -1
  20. data/lib/generators/fx/function/templates/db/migrate/update_function.erb +1 -1
  21. data/lib/generators/fx/migration_helper.rb +53 -0
  22. data/lib/generators/fx/name_helper.rb +33 -0
  23. data/lib/generators/fx/trigger/templates/db/migrate/create_trigger.erb +1 -1
  24. data/lib/generators/fx/trigger/templates/db/migrate/update_trigger.erb +1 -1
  25. data/lib/generators/fx/trigger/trigger_generator.rb +45 -64
  26. data/lib/generators/fx/version_helper.rb +55 -0
  27. data/spec/acceptance/user_manages_functions_spec.rb +6 -6
  28. data/spec/acceptance/user_manages_triggers_spec.rb +10 -10
  29. data/spec/acceptance_helper.rb +2 -2
  30. data/spec/features/functions/migrations_spec.rb +4 -4
  31. data/spec/features/functions/revert_spec.rb +4 -4
  32. data/spec/features/triggers/migrations_spec.rb +6 -6
  33. data/spec/features/triggers/revert_spec.rb +8 -8
  34. data/spec/fx/adapters/postgres/functions_spec.rb +12 -5
  35. data/spec/fx/adapters/postgres/query_executor_spec.rb +75 -0
  36. data/spec/fx/adapters/postgres/triggers_spec.rb +14 -7
  37. data/spec/fx/adapters/postgres_spec.rb +62 -20
  38. data/spec/fx/definition_spec.rb +6 -6
  39. data/spec/fx/schema_dumper_spec.rb +60 -14
  40. data/spec/fx_spec.rb +1 -1
  41. data/spec/generators/fx/function/function_generator_spec.rb +10 -10
  42. data/spec/generators/fx/migration_helper_spec.rb +133 -0
  43. data/spec/generators/fx/name_helper_spec.rb +114 -0
  44. data/spec/generators/fx/trigger/trigger_generator_spec.rb +42 -19
  45. data/spec/generators/fx/version_helper_spec.rb +157 -0
  46. data/spec/spec_helper.rb +2 -0
  47. data/spec/support/generator_setup.rb +46 -5
  48. metadata +28 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a1eaca24d4bb2262d766c845df89e92051c4824b6cf621714d4786a6b1f2eaaf
4
- data.tar.gz: 32b2aaf58fbb8844adf514d5b3e15bf924bcc47d69f048135579cb002c15581b
3
+ metadata.gz: 817dff811b3dc0f946828957f9274117436ac97b273416cfdffdcf7bccf493fd
4
+ data.tar.gz: 88d246804584933f266198d0edf384bfdd09eb0f6e7f57be5d4cbebcb8f7843e
5
5
  SHA512:
6
- metadata.gz: 0fffacf063521bd38e8f4343c4879b8872b9a750a50d278b90fc08434ca6fe6bfdaad7d79f96349b042d1fa0145f5254294d44b38cc0ca72d5516c8a08c023e7
7
- data.tar.gz: 41c966c110817c920c4280f7a83ebfac59687cb98f0603bdf11fecd368332172441f6c1e428f364b1dfc7b7552fa896b82489979a1e2f5744b6b20c9c8e406ee
6
+ metadata.gz: f414bb7dc859d720dc4d9e4e5f48a37cb0745e63a03d109824c5d17230d1384f53eb013c122a675c17c78e8e9d8f4c71dbf944ec351b8ee8c3df0e406556e241
7
+ data.tar.gz: d5caa370b2a0f132fd7aff53de631a2a836ba71ea8ac4c2eec79b73ea151d5df9615183855e3a2e0a4a608e45d8d43f2ea9c28ef66f53320ca6cc95ad2bdd215
@@ -14,8 +14,8 @@ jobs:
14
14
  strategy:
15
15
  fail-fast: false
16
16
  matrix:
17
- ruby: ["3.0", "3.1", "3.2", "3.3", "3.4.0-preview2"]
18
- rails: ["7.0", "7.1", "7.2", "8.0.0"]
17
+ ruby: ["3.2", "3.3", "3.4"]
18
+ rails: ["7.2", "8.0", "8.1"]
19
19
  continue-on-error: [false]
20
20
 
21
21
  services:
data/CHANGELOG.md CHANGED
@@ -7,7 +7,33 @@ changelog, see the [commits] for each version via the version links.
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- [Unreleased]: https://github.com/teoljungberg/fx/compare/v0.8.0..HEAD
10
+ [Unreleased]: https://github.com/teoljungberg/fx/compare/v0.10.0..HEAD
11
+
12
+ ## [0.10.0]
13
+
14
+ [0.10.0]: https://github.com/teoljungberg/fx/compare/v0.9.0...v0.10.0
15
+
16
+ - Scope functions and triggers to schema search path (#168)
17
+ - Ensure multi-schema dumping in Rails 8.1.0 does not dump the same objects for
18
+ all schemas (#177)
19
+ - Require Ruby >= 3.2 (#166)
20
+ - Require Ruby >= 3.1 (#162)
21
+ - New version support
22
+ - Add Rails 8.1 to the test matrix (#175)
23
+ - Add Ruby 3.4 to the test matrix (#161)
24
+ - Removed version support
25
+ - Drop Rails 7.1 due to EOL (#176)
26
+ - Drop EOL Ruby versions (3.0) (#162)
27
+ - Drop EOL Rails versions (7.0) (#162)
28
+ - Internal refactorings / improvements
29
+ - Extract validation methods in `Statements` module (#184)
30
+ - Refactor generators with helper classes and improvements (#183)
31
+ - Extract `QueryExecutor` to remove duplication (#182)
32
+ - Extract PostgreSQL version constants (#181)
33
+ - YARD documentation improvements (#179)
34
+ - Ensure fx has been loaded (#173)
35
+ - Replace ammeter (#172)
36
+ - Remove teardown phase (#171)
11
37
 
12
38
  ## [0.9.0]
13
39
 
data/Gemfile CHANGED
@@ -2,7 +2,6 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem "ammeter", ">= 1.1.3"
6
5
  gem "bundler", ">= 1.5"
7
6
  gem "database_cleaner"
8
7
  gem "pg"
data/bin/rake CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
+
3
4
  #
4
5
  # This file was generated by Bundler.
5
6
  #
@@ -7,9 +8,7 @@
7
8
  # this file is here to facilitate running it.
8
9
  #
9
10
 
10
- require "pathname"
11
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
- Pathname.new(__FILE__).realpath)
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
13
12
 
14
13
  require "rubygems"
15
14
  require "bundler/setup"
data/bin/rspec CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
+
3
4
  #
4
5
  # This file was generated by Bundler.
5
6
  #
@@ -7,9 +8,18 @@
7
8
  # this file is here to facilitate running it.
8
9
  #
9
10
 
10
- require "pathname"
11
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
- Pathname.new(__FILE__).realpath)
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
13
23
 
14
24
  require "rubygems"
15
25
  require "bundler/setup"
data/bin/yard CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
+
3
4
  #
4
5
  # This file was generated by Bundler.
5
6
  #
@@ -7,9 +8,18 @@
7
8
  # this file is here to facilitate running it.
8
9
  #
9
10
 
10
- require "pathname"
11
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
- Pathname.new(__FILE__).realpath)
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
13
23
 
14
24
  require "rubygems"
15
25
  require "bundler/setup"
data/fx.gemspec CHANGED
@@ -24,8 +24,8 @@ Gem::Specification.new do |spec|
24
24
  spec.files = `git ls-files -z`.split("\x0")
25
25
  spec.require_paths = ["lib"]
26
26
 
27
- spec.add_dependency "activerecord", ">= 7.0"
28
- spec.add_dependency "railties", ">= 7.0"
27
+ spec.add_dependency "activerecord", ">= 7.2", "< 8.2"
28
+ spec.add_dependency "railties", ">= 7.2", "< 8.2"
29
29
 
30
- spec.required_ruby_version = ">= 3.0"
30
+ spec.required_ruby_version = ">= 3.2"
31
31
  end
@@ -10,15 +10,23 @@ module Fx
10
10
  #
11
11
  # @api private
12
12
  class Connection < SimpleDelegator
13
- # https://www.postgresql.org/docs/9.6/sql-dropfunction.html
14
- # https://www.postgresql.org/docs/10/sql-dropfunction.html
13
+ # PostgreSQL version constants for feature support
14
+ POSTGRES_VERSIONS = {
15
+ # PostgreSQL 10.0 - introduced DROP FUNCTION without args
16
+ # https://www.postgresql.org/docs/10/sql-dropfunction.html
17
+ v10: 10_00_00
18
+ }.freeze
19
+
15
20
  def support_drop_function_without_args
16
- pg_connection = undecorated_connection.raw_connection
17
- pg_connection.server_version >= 10_00_00
21
+ server_version >= POSTGRES_VERSIONS[:v10]
18
22
  end
19
23
 
20
24
  private
21
25
 
26
+ def server_version
27
+ undecorated_connection.raw_connection.server_version
28
+ end
29
+
22
30
  def undecorated_connection
23
31
  __getobj__
24
32
  end
@@ -1,4 +1,5 @@
1
1
  require "fx/function"
2
+ require "fx/adapters/postgres/query_executor"
2
3
 
3
4
  module Fx
4
5
  module Adapters
@@ -8,7 +9,7 @@ module Fx
8
9
  class Functions
9
10
  # The SQL query used by F(x) to retrieve the functions considered
10
11
  # dumpable into `db/schema.rb`.
11
- FUNCTIONS_WITH_DEFINITIONS_QUERY = <<~EOS.freeze
12
+ FUNCTIONS_WITH_DEFINITIONS_QUERY = <<~SQL.freeze
12
13
  SELECT
13
14
  pp.proname AS name,
14
15
  pg_get_functiondef(pp.oid) AS definition
@@ -19,39 +20,21 @@ module Fx
19
20
  ON pd.objid = pp.oid AND pd.deptype = 'e'
20
21
  LEFT JOIN pg_aggregate pa
21
22
  ON pa.aggfnoid = pp.oid
22
- WHERE pn.nspname = 'public' AND pd.objid IS NULL
23
+ WHERE pn.nspname = ANY (current_schemas(false))
24
+ AND pd.objid IS NULL
23
25
  AND pa.aggfnoid IS NULL
24
26
  ORDER BY pp.oid;
25
- EOS
27
+ SQL
26
28
 
27
29
  # Wraps #all as a static facade.
28
30
  #
29
31
  # @return [Array<Fx::Function>]
30
- def self.all(...)
31
- new(...).all
32
- end
33
-
34
- def initialize(connection)
35
- @connection = connection
36
- end
37
-
38
- # All of the functions that this connection has defined.
39
- #
40
- # @return [Array<Fx::Function>]
41
- def all
42
- functions_from_postgres.map { |function| to_fx_function(function) }
43
- end
44
-
45
- private
46
-
47
- attr_reader :connection
48
-
49
- def functions_from_postgres
50
- connection.execute(FUNCTIONS_WITH_DEFINITIONS_QUERY)
51
- end
52
-
53
- def to_fx_function(result)
54
- Fx::Function.new(result)
32
+ def self.all(connection)
33
+ QueryExecutor.call(
34
+ connection: connection,
35
+ query: FUNCTIONS_WITH_DEFINITIONS_QUERY,
36
+ model_class: Fx::Function
37
+ )
55
38
  end
56
39
  end
57
40
  end
@@ -0,0 +1,34 @@
1
+ module Fx
2
+ module Adapters
3
+ class Postgres
4
+ # Executes database queries and maps results to domain objects.
5
+ # @api private
6
+ class QueryExecutor
7
+ def self.call(...)
8
+ new(...).call
9
+ end
10
+
11
+ def initialize(connection:, query:, model_class:)
12
+ @connection = connection
13
+ @query = query
14
+ @model_class = model_class
15
+ end
16
+
17
+ # Executes the query and maps results to domain objects.
18
+ #
19
+ # @return [Array] Array of domain objects (Functions or Triggers)
20
+ def call
21
+ results_from_postgres.map { |result| model_class.new(result) }
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :connection, :query, :model_class
27
+
28
+ def results_from_postgres
29
+ connection.execute(query)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,4 +1,5 @@
1
1
  require "fx/trigger"
2
+ require "fx/adapters/postgres/query_executor"
2
3
 
3
4
  module Fx
4
5
  module Adapters
@@ -8,7 +9,7 @@ module Fx
8
9
  class Triggers
9
10
  # The SQL query used by F(x) to retrieve the triggers considered
10
11
  # dumpable into `db/schema.rb`.
11
- TRIGGERS_WITH_DEFINITIONS_QUERY = <<~EOS.freeze
12
+ TRIGGERS_WITH_DEFINITIONS_QUERY = <<~SQL.freeze
12
13
  SELECT
13
14
  pt.tgname AS name,
14
15
  pg_get_triggerdef(pt.oid) AS definition
@@ -17,39 +18,23 @@ module Fx
17
18
  ON (pc.oid = pt.tgrelid)
18
19
  JOIN pg_proc pp
19
20
  ON (pp.oid = pt.tgfoid)
20
- WHERE pt.tgname
21
- NOT ILIKE '%constraint%' AND pt.tgname NOT ILIKE 'pg%'
21
+ JOIN pg_namespace pn
22
+ ON pn.oid = pc.relnamespace
23
+ WHERE pn.nspname = ANY (current_schemas(false))
24
+ AND pt.tgname NOT ILIKE '%constraint%'
25
+ AND pt.tgname NOT ILIKE 'pg%'
22
26
  ORDER BY pc.oid;
23
- EOS
27
+ SQL
24
28
 
25
29
  # Wraps #all as a static facade.
26
30
  #
27
31
  # @return [Array<Fx::Trigger>]
28
- def self.all(...)
29
- new(...).all
30
- end
31
-
32
- def initialize(connection)
33
- @connection = connection
34
- end
35
-
36
- # All of the triggers that this connection has defined.
37
- #
38
- # @return [Array<Fx::Trigger>]
39
- def all
40
- triggers_from_postgres.map { |trigger| to_fx_trigger(trigger) }
41
- end
42
-
43
- private
44
-
45
- attr_reader :connection
46
-
47
- def triggers_from_postgres
48
- connection.execute(TRIGGERS_WITH_DEFINITIONS_QUERY)
49
- end
50
-
51
- def to_fx_trigger(result)
52
- Fx::Trigger.new(result)
32
+ def self.all(connection)
33
+ QueryExecutor.call(
34
+ connection: connection,
35
+ query: TRIGGERS_WITH_DEFINITIONS_QUERY,
36
+ model_class: Fx::Trigger
37
+ )
53
38
  end
54
39
  end
55
40
  end
@@ -65,7 +65,7 @@ module Fx
65
65
  # This is typically called in a migration via
66
66
  # {Fx::Statements::Function#create_function}.
67
67
  #
68
- # @param sql_definition The SQL schema for the function.
68
+ # @param sql_definition [String] The SQL schema for the function.
69
69
  #
70
70
  # @return [void]
71
71
  def create_function(sql_definition)
@@ -77,7 +77,7 @@ module Fx
77
77
  # This is typically called in a migration via
78
78
  # {Fx::Statements::Trigger#create_trigger}.
79
79
  #
80
- # @param sql_definition The SQL schema for the trigger.
80
+ # @param sql_definition [String] The SQL schema for the trigger.
81
81
  #
82
82
  # @return [void]
83
83
  def create_trigger(sql_definition)
@@ -89,8 +89,8 @@ module Fx
89
89
  # This is typically called in a migration via
90
90
  # {Fx::Statements::Function#update_function}.
91
91
  #
92
- # @param name The name of the function.
93
- # @param sql_definition The SQL schema for the function.
92
+ # @param name [String, Symbol] The name of the function.
93
+ # @param sql_definition [String] The SQL schema for the function.
94
94
  #
95
95
  # @return [void]
96
96
  def update_function(name, sql_definition)
@@ -106,9 +106,9 @@ module Fx
106
106
  # This is typically called in a migration via
107
107
  # {Fx::Statements::Function#update_trigger}.
108
108
  #
109
- # @param name The name of the trigger.
110
- # @param on The associated table for the trigger to drop
111
- # @param sql_definition The SQL schema for the function.
109
+ # @param name [String, Symbol] The name of the trigger.
110
+ # @param on [String, Symbol] The associated table for the trigger to update
111
+ # @param sql_definition [String] The SQL schema for the trigger.
112
112
  #
113
113
  # @return [void]
114
114
  def update_trigger(name, on:, sql_definition:)
@@ -121,7 +121,7 @@ module Fx
121
121
  # This is typically called in a migration via
122
122
  # {Fx::Statements::Function#drop_function}.
123
123
  #
124
- # @param name The name of the function to drop
124
+ # @param name [String, Symbol] The name of the function to drop
125
125
  #
126
126
  # @return [void]
127
127
  def drop_function(name)
@@ -137,8 +137,8 @@ module Fx
137
137
  # This is typically called in a migration via
138
138
  # {Fx::Statements::Trigger#drop_trigger}.
139
139
  #
140
- # @param name The name of the trigger to drop
141
- # @param on The associated table for the trigger to drop
140
+ # @param name [String, Symbol] The name of the trigger to drop
141
+ # @param on [String, Symbol] The associated table for the trigger to drop
142
142
  #
143
143
  # @return [void]
144
144
  def drop_trigger(name, on:)
@@ -4,7 +4,7 @@ module Fx
4
4
  # The F(x) database adapter instance to use when executing SQL.
5
5
  #
6
6
  # Defaults to an instance of {Fx::Adapters::Postgres}
7
- # @return Fx adapter
7
+ # @return [Fx::Adapters::Postgres] Fx adapter
8
8
  attr_accessor :database
9
9
 
10
10
  # Prioritizes the order in the schema.rb of functions before other
@@ -12,7 +12,7 @@ module Fx
12
12
  # in statements below, i.e.: default column values.
13
13
  #
14
14
  # Defaults to false
15
- # @return Boolean
15
+ # @return [Boolean] Boolean
16
16
  attr_accessor :dump_functions_at_beginning_of_schema
17
17
 
18
18
  def initialize
@@ -4,14 +4,12 @@ module Fx
4
4
  def tables(stream)
5
5
  if Fx.configuration.dump_functions_at_beginning_of_schema
6
6
  functions(stream)
7
- empty_line(stream)
8
7
  end
9
8
 
10
9
  super
11
10
 
12
11
  unless Fx.configuration.dump_functions_at_beginning_of_schema
13
12
  functions(stream)
14
- empty_line(stream)
15
13
  end
16
14
 
17
15
  triggers(stream)
@@ -19,17 +17,19 @@ module Fx
19
17
 
20
18
  private
21
19
 
22
- def empty_line(stream)
23
- stream.puts if dumpable_functions_in_database.any?
24
- end
25
-
26
20
  def functions(stream)
21
+ dumpable_functions_in_database = Fx.database.functions
22
+
27
23
  dumpable_functions_in_database.each do |function|
28
24
  stream.puts(function.to_schema)
29
25
  end
26
+
27
+ stream.puts if dumpable_functions_in_database.any?
30
28
  end
31
29
 
32
30
  def triggers(stream)
31
+ dumpable_triggers_in_database = Fx.database.triggers
32
+
33
33
  if dumpable_triggers_in_database.any?
34
34
  stream.puts
35
35
  end
@@ -38,13 +38,5 @@ module Fx
38
38
  stream.puts(trigger.to_schema)
39
39
  end
40
40
  end
41
-
42
- def dumpable_functions_in_database
43
- @_dumpable_functions_in_database ||= Fx.database.functions
44
- end
45
-
46
- def dumpable_triggers_in_database
47
- @_dumpable_triggers_in_database ||= Fx.database.triggers
48
- end
49
41
  end
50
42
  end