fx 0.10.1 → 0.11.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: c58b9ff49b98b110220c15bb8e017038a237e2583b86a771a4fdad267fed316d
4
- data.tar.gz: 4309eea32302e5828871134973269edeb532da5aa817639e4b8b9b466a7d8f1f
3
+ metadata.gz: a1b25361cfa43280f81e493a4eb518864862dad321878048282ce8f9daad6e1a
4
+ data.tar.gz: ce20ce6b1bb9031b420e78b83ecf23416d50ca44668cf15e0951142e32a0c75e
5
5
  SHA512:
6
- metadata.gz: '09a4bc41360e92347b92bc9a3cadf3acd04cef9a87baaf549504c6667bf621053c0a156f00aea267b61505e1d14abee4993a1ce9b4acddea0a22c7bd926672fa'
7
- data.tar.gz: 72883cf8649ac59df101613905842ac034ae8ecf5f7533474185ee15bda83e3e27c3373f790dbabd9209b06ec969ba7119aa2ea94590858a11cb9269d0c97b93
6
+ metadata.gz: 2e8d5db85fb283586a9d713cdffb6fc50604e85498eeb2c16ac8f63708fabe01a29e03b4157b32f84fe061d4bedb996f4e44db90f5d53d2b655c730352c7c101
7
+ data.tar.gz: 70e6670c67e267f383fa7ed3001350fa3c6548d4bf6183625ee10bf1a5c53cfa5c87cbd9bf231b196524f206d96bf638ba16f6b9020f20803f4ae85685fbf44a
@@ -0,0 +1,95 @@
1
+ #!/bin/sh
2
+ #
3
+ # Check if minimum supported dependency versions are at or approaching EOL.
4
+ # Uses the endoflife.date API. Outputs GitHub Actions annotations and opens
5
+ # an issue when action is needed.
6
+ #
7
+ # Environment:
8
+ # WARN_DAYS - days before EOL to start warning (default: 90)
9
+ # GH_TOKEN - GitHub token for creating issues (optional)
10
+
11
+ set -e
12
+
13
+ WARN_DAYS="${WARN_DAYS:-90}"
14
+
15
+ ruby_min=$(ruby -e "
16
+ spec = Gem::Specification.load('fx.gemspec')
17
+ puts spec.required_ruby_version.requirements
18
+ .select { |op, _| op == '>=' }
19
+ .map { |_, v| v.segments[0..1].join('.') }
20
+ .first
21
+ ")
22
+
23
+ rails_min=$(ruby -e "
24
+ spec = Gem::Specification.load('fx.gemspec')
25
+ dep = spec.dependencies.find { |d| d.name == 'activerecord' }
26
+ puts dep.requirement.requirements
27
+ .select { |op, _| op == '>=' }
28
+ .map { |_, v| v.segments[0..1].join('.') }
29
+ .first
30
+ ")
31
+
32
+ postgres_min=$(ruby -ryaml -e "
33
+ ci = YAML.load_file('.github/workflows/ci.yml')
34
+ versions = ci.dig('jobs', 'tests', 'strategy', 'matrix', 'postgres')
35
+ puts versions.map(&:to_i).min
36
+ ")
37
+
38
+ echo "Ruby minimum: $ruby_min"
39
+ echo "Rails minimum: $rails_min"
40
+ echo "PostgreSQL minimum: $postgres_min"
41
+
42
+ alerts=""
43
+
44
+ check_eol() {
45
+ product="$1"
46
+ version="$2"
47
+
48
+ eol_date=$(curl -sf "https://endoflife.date/api/${product}/${version}.json" \
49
+ | ruby -rjson -e '
50
+ data = JSON.parse($stdin.read)
51
+ eol = data["eol"]
52
+ if eol == true
53
+ puts "1970-01-01"
54
+ elsif eol == false
55
+ exit
56
+ else
57
+ puts eol
58
+ end
59
+ ') || return 0
60
+
61
+ if [ -z "$eol_date" ]; then
62
+ echo "$product $version: no EOL date set (still supported)"
63
+ return
64
+ fi
65
+
66
+ today=$(date -u +%Y-%m-%d)
67
+ warn_date=$(date -u -d "$today + ${WARN_DAYS} days" +%Y-%m-%d 2>/dev/null \
68
+ || date -u -v+"${WARN_DAYS}"d +%Y-%m-%d)
69
+
70
+ if [ "$eol_date" \< "$today" ] || [ "$eol_date" = "$today" ]; then
71
+ echo "::error::$product $version reached EOL on $eol_date"
72
+ alerts="$alerts- **$product $version** reached EOL on $eol_date\n"
73
+ elif [ "$eol_date" \< "$warn_date" ]; then
74
+ echo "::warning::$product $version reaches EOL on $eol_date (within ${WARN_DAYS} days)"
75
+ alerts="$alerts- **$product $version** reaches EOL on $eol_date (within ${WARN_DAYS} days)\n"
76
+ else
77
+ echo "$product $version: EOL on $eol_date (no action needed)"
78
+ fi
79
+ }
80
+
81
+ check_eol "ruby" "$ruby_min"
82
+ check_eol "rails" "$rails_min"
83
+ check_eol "postgresql" "$postgres_min"
84
+
85
+ if [ -n "$alerts" ]; then
86
+ title="Dependency EOL alert"
87
+ body=$(printf "The following minimum supported versions are at or approaching end-of-life:\n\n%b\n\nConsider bumping the minimum version in \`fx.gemspec\` and updating the CI matrix." "$alerts")
88
+
89
+ existing=$(gh issue list --state open --search "$title" --json number --jq '.[0].number' 2>/dev/null || true)
90
+ if [ -n "$existing" ]; then
91
+ echo "Issue #$existing already open, skipping creation"
92
+ else
93
+ gh issue create --title "$title" --body "$body"
94
+ fi
95
+ fi
@@ -8,18 +8,26 @@ on:
8
8
 
9
9
  jobs:
10
10
  tests:
11
- name: Ruby ${{ matrix.ruby }}, Rails ${{ matrix.rails }}
11
+ name: "ruby ${{ matrix.ruby }} / rails ${{ matrix.rails }} / postgres ${{ matrix.postgres }}"
12
12
  runs-on: ubuntu-latest
13
13
 
14
14
  strategy:
15
15
  fail-fast: false
16
16
  matrix:
17
- ruby: ["3.2", "4.0"]
17
+ ruby: ["3.3", "4.0"]
18
18
  rails: ["7.2", "8.1"]
19
+ postgres: ["14", "18"]
20
+ include:
21
+ - ruby: "4.0"
22
+ rails: "main"
23
+ postgres: "18"
24
+ allow-failure: true
25
+
26
+ continue-on-error: ${{ matrix.allow-failure || false }}
19
27
 
20
28
  services:
21
29
  postgres:
22
- image: postgres:14
30
+ image: postgres:${{ matrix.postgres }}
23
31
  env:
24
32
  POSTGRES_USER: postgres
25
33
  POSTGRES_HOST_AUTH_METHOD: trust
@@ -0,0 +1,30 @@
1
+ name: EOL Check
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "0 9 1 * *" # 1st of each month at 09:00 UTC
6
+ workflow_dispatch:
7
+ inputs:
8
+ warn_days:
9
+ description: "Days before EOL to start warning"
10
+ required: false
11
+ default: "90"
12
+
13
+ permissions:
14
+ issues: write
15
+
16
+ env:
17
+ WARN_DAYS: ${{ inputs.warn_days || '90' }}
18
+
19
+ jobs:
20
+ check:
21
+ name: Check dependency EOL status
22
+ runs-on: ubuntu-latest
23
+
24
+ steps:
25
+ - uses: actions/checkout@v6
26
+
27
+ - name: Check EOL status
28
+ env:
29
+ GH_TOKEN: ${{ github.token }}
30
+ run: .github/scripts/check-eol
data/CHANGELOG.md CHANGED
@@ -5,9 +5,29 @@ changelog, see the [commits] for each version via the version links.
5
5
 
6
6
  [commits]: https://github.com/teoljungberg/fx/commits/master
7
7
 
8
- ## [Unreleased]
8
+ ## [0.11.0]
9
9
 
10
- [Unreleased]: https://github.com/teoljungberg/fx/compare/v0.10.1..HEAD
10
+ [0.11.0]: https://github.com/teoljungberg/fx/compare/v0.10.2...v0.11.0
11
+
12
+ - Drop EOL Ruby 3.2 (#212)
13
+ - Add `Function#signature` for PostgreSQL function identity (#207)
14
+ - Add PostgreSQL versioning policy, officially supporting PostgreSQL 14-18 (#194)
15
+ - Refactor Statements module to use explicit keyword arguments instead of `**options` hash (#186)
16
+ - Internal refactorings / improvements
17
+ - Add scheduled EOL check for Ruby, Rails, and PostgreSQL (#205)
18
+ - Add GitHub release creation to release task (#209)
19
+ - Add PostgreSQL 18 to CI test matrix (#194)
20
+ - Default `support_drop_function_without_args` to `true` (#194)
21
+ - Document adapter subclass pattern for custom schema dump ordering (#211)
22
+ - Remove duplicate command recorder specs (#202)
23
+
24
+ ## [0.10.2]
25
+
26
+ [0.10.2]: https://github.com/teoljungberg/fx/compare/v0.10.1...v0.10.2
27
+
28
+ - Remove upper bound on Rails dependency (#198)
29
+ - Internal refactorings / improvements
30
+ - Add Rails main to CI matrix
11
31
 
12
32
  ## [0.10.1]
13
33
 
data/Gemfile CHANGED
@@ -4,8 +4,13 @@ rails_version = ENV.fetch("RAILS_VERSION", "8.1")
4
4
 
5
5
  gemspec
6
6
 
7
- gem "activerecord", "~> #{rails_version}.0"
8
- gem "railties", "~> #{rails_version}.0"
7
+ if rails_version == "main"
8
+ gem "activerecord", github: "rails/rails", branch: "main"
9
+ gem "railties", github: "rails/rails", branch: "main"
10
+ else
11
+ gem "activerecord", "~> #{rails_version}.0"
12
+ gem "railties", "~> #{rails_version}.0"
13
+ end
9
14
 
10
15
  gem "bundler", ">= 1.5"
11
16
  gem "pg"
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # F(x)
2
2
 
3
3
  [![Build Status](https://github.com/teoljungberg/fx/actions/workflows/ci.yml/badge.svg)](https://github.com/teoljungberg/fx/actions/workflows/ci.yml)
4
- [![Documentation Quality](http://inch-ci.org/github/teoljungberg/fx.svg?branch=master)](http://inch-ci.org/github/teoljungberg/fx)
5
4
 
6
5
  F(x) adds methods to `ActiveRecord::Migration` to create and manage database
7
6
  functions and triggers in Rails.
@@ -108,6 +107,35 @@ end
108
107
  That's how you tell Rails to use the default as a literal SQL for the default
109
108
  column value instead of a plain string.
110
109
 
110
+ ## Customizing Schema Dump Order
111
+
112
+ By default, functions and triggers are dumped to `schema.rb` in the order
113
+ returned by the database. If you need a specific ordering (e.g., alphabetical
114
+ for deterministic diffs), subclass the adapter and override `#functions` or
115
+ `#triggers`. These methods are part of the adapter's public API and will remain
116
+ stable across releases:
117
+
118
+ ```ruby
119
+ # config/initializers/fx.rb
120
+ class SortedPostgresAdapter < Fx::Adapters::Postgres
121
+ def functions
122
+ super.sort_by(&:name)
123
+ end
124
+
125
+ def triggers
126
+ super.sort_by(&:name)
127
+ end
128
+ end
129
+
130
+ Fx.configure do |config|
131
+ config.database = SortedPostgresAdapter.new
132
+ end
133
+ ```
134
+
135
+ The same approach works for more advanced ordering. For example, if your
136
+ functions depend on each other and need to be dumped in dependency order, you
137
+ could use Ruby's built-in `TSort` to topologically sort them.
138
+
111
139
  ## Plugins/Adapters
112
140
 
113
141
  - [MySQL](https://github.com/f-mer/fx-adapters-mysql/)
@@ -116,19 +144,22 @@ column value instead of a plain string.
116
144
 
117
145
  ## Version Support
118
146
 
119
- F(x) follows the maintenance policies of Ruby and Rails, supporting versions
120
- within their official maintenance windows.
147
+ F(x) follows the maintenance policies of Ruby, Rails, and PostgreSQL, supporting
148
+ versions within their official maintenance windows.
121
149
 
122
- **Ruby:** 3.2+ ([maintenance branches])
150
+ **Ruby:** 3.3+ ([maintenance branches])
123
151
 
124
152
  **Rails:** 7.2, 8.0, 8.1 ([maintenance policy])
125
153
 
126
- When a Ruby or Rails version reaches end-of-life, support will be dropped in the
127
- next minor release of F(x). Older versions may continue to work but are not
128
- tested or guaranteed.
154
+ **PostgreSQL:** 14, 15, 16, 17, 18 ([versioning policy])
155
+
156
+ When a version reaches end-of-life, support will be dropped in the next minor
157
+ release of F(x). Older versions may continue to work but are not tested or
158
+ guaranteed.
129
159
 
130
160
  [maintenance branches]: https://www.ruby-lang.org/en/downloads/branches/
131
161
  [maintenance policy]: https://rubyonrails.org/maintenance
162
+ [versioning policy]: https://www.postgresql.org/support/versioning/
132
163
 
133
164
  ## Contributing
134
165
 
data/Rakefile CHANGED
@@ -2,6 +2,15 @@ require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
  require "standard/rake"
4
4
 
5
+ Rake::Task["release"].enhance do
6
+ unless system("which gh > /dev/null 2>&1")
7
+ abort "gh CLI is not installed. Install it to create GitHub releases."
8
+ end
9
+
10
+ tag = "v#{Fx::VERSION}"
11
+ sh "gh release create #{tag} --verify-tag -t #{tag} --generate-notes"
12
+ end
13
+
5
14
  namespace :dummy do
6
15
  require_relative "spec/dummy/config/application"
7
16
  Dummy::Application.load_tasks
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.2", "< 8.2"
28
- spec.add_dependency "railties", ">= 7.2", "< 8.2"
27
+ spec.add_dependency "activerecord", ">= 7.2"
28
+ spec.add_dependency "railties", ">= 7.2"
29
29
 
30
- spec.required_ruby_version = ">= 3.2"
30
+ spec.required_ruby_version = ">= 3.3"
31
31
  end
@@ -10,25 +10,11 @@ module Fx
10
10
  #
11
11
  # @api private
12
12
  class Connection < SimpleDelegator
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
-
13
+ # All supported PostgreSQL versions (14+) support DROP FUNCTION
14
+ # without argument lists.
15
+ # https://www.postgresql.org/docs/10/sql-dropfunction.html
20
16
  def support_drop_function_without_args
21
- server_version >= POSTGRES_VERSIONS[:v10]
22
- end
23
-
24
- private
25
-
26
- def server_version
27
- undecorated_connection.raw_connection.server_version
28
- end
29
-
30
- def undecorated_connection
31
- __getobj__
17
+ true
32
18
  end
33
19
  end
34
20
  end
@@ -12,7 +12,8 @@ module Fx
12
12
  FUNCTIONS_WITH_DEFINITIONS_QUERY = <<~SQL.freeze
13
13
  SELECT
14
14
  pp.proname AS name,
15
- pg_get_functiondef(pp.oid) AS definition
15
+ pg_get_functiondef(pp.oid) AS definition,
16
+ pg_get_function_identity_arguments(pp.oid) AS arguments
16
17
  FROM pg_proc pp
17
18
  JOIN pg_namespace pn
18
19
  ON pn.oid = pp.pronamespace
@@ -125,11 +125,7 @@ module Fx
125
125
  #
126
126
  # @return [void]
127
127
  def drop_function(name)
128
- if connection.support_drop_function_without_args
129
- execute("DROP FUNCTION #{name};")
130
- else
131
- execute("DROP FUNCTION #{name}();")
132
- end
128
+ execute("DROP FUNCTION #{name};")
133
129
  end
134
130
 
135
131
  # Drops the trigger from the database
@@ -1,17 +1,20 @@
1
1
  module Fx
2
2
  # @api private
3
3
  module CommandRecorder
4
- def create_function(*args)
5
- record(:create_function, args)
4
+ def create_function(*args, &block)
5
+ record(:create_function, args, &block)
6
6
  end
7
+ ruby2_keywords :create_function
7
8
 
8
- def drop_function(*args)
9
- record(:drop_function, args)
9
+ def drop_function(*args, &block)
10
+ record(:drop_function, args, &block)
10
11
  end
12
+ ruby2_keywords :drop_function
11
13
 
12
- def update_function(*args)
13
- record(:update_function, args)
14
+ def update_function(*args, &block)
15
+ record(:update_function, args, &block)
14
16
  end
17
+ ruby2_keywords :update_function
15
18
 
16
19
  def invert_create_function(args)
17
20
  [:drop_function, args]
@@ -25,17 +28,20 @@ module Fx
25
28
  perform_inversion(:update_function, args)
26
29
  end
27
30
 
28
- def create_trigger(*args)
29
- record(:create_trigger, args)
31
+ def create_trigger(*args, &block)
32
+ record(:create_trigger, args, &block)
30
33
  end
34
+ ruby2_keywords :create_trigger
31
35
 
32
- def drop_trigger(*args)
33
- record(:drop_trigger, args)
36
+ def drop_trigger(*args, &block)
37
+ record(:drop_trigger, args, &block)
34
38
  end
39
+ ruby2_keywords :drop_trigger
35
40
 
36
- def update_trigger(*args)
37
- record(:update_trigger, args)
41
+ def update_trigger(*args, &block)
42
+ record(:update_trigger, args, &block)
38
43
  end
44
+ ruby2_keywords :update_trigger
39
45
 
40
46
  def invert_create_trigger(args)
41
47
  [:drop_trigger, args]
@@ -51,12 +57,13 @@ module Fx
51
57
 
52
58
  private
53
59
 
60
+ MESSAGE_IRREVERSIBLE = "`%s` is reversible only if given a `revert_to_version`".freeze
61
+
54
62
  def perform_inversion(method, args)
55
63
  arguments = Arguments.new(args)
56
64
 
57
65
  if arguments.revert_to_version.nil?
58
- message = "`#{method}` is reversible only if given a `revert_to_version`"
59
- raise ActiveRecord::IrreversibleMigration, message
66
+ raise ActiveRecord::IrreversibleMigration, format(MESSAGE_IRREVERSIBLE, method)
60
67
  end
61
68
 
62
69
  [method, arguments.invert_version.to_a]
@@ -68,19 +75,19 @@ module Fx
68
75
  end
69
76
 
70
77
  def function
71
- args[0]
78
+ args.fetch(0)
72
79
  end
73
80
 
74
81
  def version
75
- options[:version]
82
+ options.fetch(:version)
76
83
  end
77
84
 
78
85
  def revert_to_version
79
- options[:revert_to_version]
86
+ options.fetch(:revert_to_version, nil)
80
87
  end
81
88
 
82
89
  def invert_version
83
- Arguments.new([function, options_for_revert])
90
+ self.class.new([function, options_for_revert])
84
91
  end
85
92
 
86
93
  def to_a
@@ -92,14 +99,20 @@ module Fx
92
99
  attr_reader :args
93
100
 
94
101
  def options
95
- @options ||= args[1] || {}
102
+ @options ||= args.fetch(1, {}).dup
96
103
  end
97
104
 
98
105
  def options_for_revert
99
- options.clone.tap do |revert_options|
106
+ opts = options.clone.tap do |revert_options|
100
107
  revert_options[:version] = revert_to_version
101
108
  revert_options.delete(:revert_to_version)
102
109
  end
110
+
111
+ keyword_hash(opts)
112
+ end
113
+
114
+ def keyword_hash(hash)
115
+ Hash.ruby2_keywords_hash(hash)
103
116
  end
104
117
  end
105
118
  private_constant :Arguments
data/lib/fx/function.rb CHANGED
@@ -4,15 +4,27 @@ module Fx
4
4
  include Comparable
5
5
 
6
6
  attr_reader :name, :definition
7
- delegate :<=>, to: :name
8
7
 
9
8
  def initialize(row)
10
9
  @name = row.fetch("name")
11
10
  @definition = row.fetch("definition")
11
+ @arguments = row.fetch("arguments", nil)
12
+ end
13
+
14
+ def <=>(other)
15
+ signature <=> other.signature
12
16
  end
13
17
 
14
18
  def ==(other)
15
- name == other.name && definition == other.definition
19
+ signature == other.signature && definition == other.definition
20
+ end
21
+
22
+ def signature
23
+ if @arguments.nil?
24
+ name
25
+ else
26
+ "#{name}(#{@arguments})"
27
+ end
16
28
  end
17
29
 
18
30
  def to_schema
data/lib/fx/statements.rb CHANGED
@@ -26,10 +26,7 @@ module Fx
26
26
  # $$ LANGUAGE plpgsql;
27
27
  # SQL
28
28
  #
29
- def create_function(name, options = {})
30
- version = options.fetch(:version, 1)
31
- sql_definition = options[:sql_definition]
32
-
29
+ def create_function(name, version: 1, sql_definition: nil, revert_to_version: nil)
33
30
  validate_version_or_sql_definition_present!(version, sql_definition)
34
31
  sql_definition = resolve_sql_definition(sql_definition, name, version, :function)
35
32
 
@@ -47,7 +44,7 @@ module Fx
47
44
  # @example Drop a function, rolling back to version 2 on rollback
48
45
  # drop_function(:uppercase_users_name, revert_to_version: 2)
49
46
  #
50
- def drop_function(name, options = {})
47
+ def drop_function(name, revert_to_version: nil)
51
48
  Fx.database.drop_function(name)
52
49
  end
53
50
 
@@ -80,10 +77,7 @@ module Fx
80
77
  # $$ LANGUAGE plpgsql;
81
78
  # SQL
82
79
  #
83
- def update_function(name, options = {})
84
- version = options[:version]
85
- sql_definition = options[:sql_definition]
86
-
80
+ def update_function(name, version: nil, sql_definition: nil, revert_to_version: nil)
87
81
  validate_version_or_sql_definition_present!(version, sql_definition)
88
82
 
89
83
  sql_definition = resolve_sql_definition(sql_definition, name, version, :function)
@@ -113,10 +107,7 @@ module Fx
113
107
  # EXECUTE FUNCTION uppercase_users_name();
114
108
  # SQL
115
109
  #
116
- def create_trigger(name, options = {})
117
- version = options[:version]
118
- sql_definition = options[:sql_definition]
119
-
110
+ def create_trigger(name, version: nil, sql_definition: nil, on: nil, revert_to_version: nil)
120
111
  validate_version_and_sql_definition_exclusive!(version, sql_definition)
121
112
 
122
113
  version ||= 1
@@ -139,8 +130,7 @@ module Fx
139
130
  # @example Drop a trigger, rolling back to version 3 on rollback
140
131
  # drop_trigger(:log_inserts, on: :users, revert_to_version: 3)
141
132
  #
142
- def drop_trigger(name, options = {})
143
- on = options.fetch(:on)
133
+ def drop_trigger(name, on:, revert_to_version: nil)
144
134
  Fx.database.drop_trigger(name, on: on)
145
135
  end
146
136
 
@@ -176,18 +166,10 @@ module Fx
176
166
  # EXECUTE FUNCTION uppercase_users_name();
177
167
  # SQL
178
168
  #
179
- def update_trigger(name, options = {})
180
- version = options[:version]
181
- on = options[:on]
182
- sql_definition = options[:sql_definition]
183
-
169
+ def update_trigger(name, on:, version: nil, sql_definition: nil, revert_to_version: nil)
184
170
  validate_version_or_sql_definition_present!(version, sql_definition)
185
171
  validate_version_and_sql_definition_exclusive!(version, sql_definition)
186
172
 
187
- if on.nil?
188
- raise ArgumentError, "on is required"
189
- end
190
-
191
173
  sql_definition = resolve_sql_definition(sql_definition, name, version, :trigger)
192
174
 
193
175
  Fx.database.update_trigger(
data/lib/fx/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Fx
2
2
  # @api private
3
- VERSION = "0.10.1"
3
+ VERSION = "0.11.0"
4
4
  end
@@ -143,44 +143,11 @@ RSpec.describe Fx::Adapters::Postgres, :db do
143
143
  end
144
144
 
145
145
  describe "#support_drop_function_without_args" do
146
- it "returns true for PostgreSQL version 10.0" do
146
+ it "returns true for all supported PostgreSQL versions" do
147
147
  adapter = Fx::Adapters::Postgres.new
148
148
  connection = adapter.send(:connection)
149
- allow(connection).to receive(:server_version).and_return(10_00_00)
150
149
 
151
- result = connection.support_drop_function_without_args
152
-
153
- expect(result).to be(true)
154
- end
155
-
156
- it "returns true for PostgreSQL version 11.0" do
157
- adapter = Fx::Adapters::Postgres.new
158
- connection = adapter.send(:connection)
159
- allow(connection).to receive(:server_version).and_return(11_00_00)
160
-
161
- result = connection.support_drop_function_without_args
162
-
163
- expect(result).to be(true)
164
- end
165
-
166
- it "returns false for PostgreSQL version 9.6" do
167
- adapter = Fx::Adapters::Postgres.new
168
- connection = adapter.send(:connection)
169
- allow(connection).to receive(:server_version).and_return(9_06_00)
170
-
171
- result = connection.support_drop_function_without_args
172
-
173
- expect(result).to be(false)
174
- end
175
-
176
- it "returns false for PostgreSQL version 9.5" do
177
- adapter = Fx::Adapters::Postgres.new
178
- connection = adapter.send(:connection)
179
- allow(connection).to receive(:server_version).and_return(9_05_00)
180
-
181
- result = connection.support_drop_function_without_args
182
-
183
- expect(result).to be(false)
150
+ expect(connection.support_drop_function_without_args).to be(true)
184
151
  end
185
152
  end
186
153
  end
@@ -10,14 +10,6 @@ RSpec.describe Fx::CommandRecorder, :db do
10
10
  expect(recorder.commands).to eq([[:create_function, [:test], nil]])
11
11
  end
12
12
 
13
- it "reverts to drop_function" do
14
- recorder = ActiveRecord::Migration::CommandRecorder.new
15
-
16
- recorder.create_function :test
17
-
18
- expect(recorder.commands).to eq([[:create_function, [:test], nil]])
19
- end
20
-
21
13
  it "reverts to drop_function" do
22
14
  recorder = ActiveRecord::Migration::CommandRecorder.new
23
15
 
@@ -95,14 +87,6 @@ RSpec.describe Fx::CommandRecorder, :db do
95
87
  expect(recorder.commands).to eq([[:create_trigger, [:greetings], nil]])
96
88
  end
97
89
 
98
- it "reverts to drop_trigger" do
99
- recorder = ActiveRecord::Migration::CommandRecorder.new
100
-
101
- recorder.create_trigger :greetings
102
-
103
- expect(recorder.commands).to eq([[:create_trigger, [:greetings], nil]])
104
- end
105
-
106
90
  it "reverts to drop_trigger" do
107
91
  recorder = ActiveRecord::Migration::CommandRecorder.new
108
92
 
@@ -2,44 +2,121 @@ require "spec_helper"
2
2
 
3
3
  RSpec.describe Fx::Function do
4
4
  describe "#<=>" do
5
- it "delegates to `name`" do
5
+ it "delegates to `signature`" do
6
6
  function_a = Fx::Function.new(
7
7
  "name" => "name_a",
8
- "definition" => "some definition"
8
+ "definition" => "some definition",
9
+ "arguments" => ""
9
10
  )
10
11
  function_b = Fx::Function.new(
11
12
  "name" => "name_b",
12
- "definition" => "some definition"
13
+ "definition" => "some definition",
14
+ "arguments" => ""
13
15
  )
14
16
  function_c = Fx::Function.new(
15
17
  "name" => "name_c",
16
- "definition" => "some definition"
18
+ "definition" => "some definition",
19
+ "arguments" => ""
17
20
  )
18
21
 
19
22
  expect(function_b).to be_between(function_a, function_c)
20
23
  end
24
+
25
+ it "orders overloads by signature" do
26
+ add_int = Fx::Function.new(
27
+ "name" => "add",
28
+ "definition" => "some definition",
29
+ "arguments" => "integer, integer"
30
+ )
31
+ add_text = Fx::Function.new(
32
+ "name" => "add",
33
+ "definition" => "some definition",
34
+ "arguments" => "text, text"
35
+ )
36
+
37
+ expect(add_int <=> add_text).to be < 0
38
+ end
21
39
  end
22
40
 
23
41
  describe "#==" do
24
- it "compares `name` and `definition`" do
42
+ it "compares `signature` and `definition`" do
25
43
  function_a = Fx::Function.new(
26
44
  "name" => "name_a",
27
- "definition" => "some definition"
45
+ "definition" => "some definition",
46
+ "arguments" => ""
28
47
  )
29
48
  function_b = Fx::Function.new(
30
49
  "name" => "name_b",
31
- "definition" => "some other definition"
50
+ "definition" => "some other definition",
51
+ "arguments" => ""
32
52
  )
33
53
 
34
54
  expect(function_a).not_to eq(function_b)
35
55
  end
56
+
57
+ it "distinguishes overloads with the same name" do
58
+ add_int = Fx::Function.new(
59
+ "name" => "add",
60
+ "definition" => "CREATE FUNCTION add(integer)",
61
+ "arguments" => "integer"
62
+ )
63
+ add_text = Fx::Function.new(
64
+ "name" => "add",
65
+ "definition" => "CREATE FUNCTION add(text)",
66
+ "arguments" => "text"
67
+ )
68
+
69
+ expect(add_int).not_to eq(add_text)
70
+ end
71
+ end
72
+
73
+ describe "#signature" do
74
+ it "returns name with arguments when arguments are present" do
75
+ function = Fx::Function.new(
76
+ "name" => "inc",
77
+ "definition" => "some definition",
78
+ "arguments" => "integer"
79
+ )
80
+
81
+ expect(function.signature).to eq("inc(integer)")
82
+ end
83
+
84
+ it "returns name with multiple arguments" do
85
+ function = Fx::Function.new(
86
+ "name" => "add",
87
+ "definition" => "some definition",
88
+ "arguments" => "integer, integer"
89
+ )
90
+
91
+ expect(function.signature).to eq("add(integer, integer)")
92
+ end
93
+
94
+ it "returns name with empty parens for no-arg functions" do
95
+ function = Fx::Function.new(
96
+ "name" => "now_utc",
97
+ "definition" => "some definition",
98
+ "arguments" => ""
99
+ )
100
+
101
+ expect(function.signature).to eq("now_utc()")
102
+ end
103
+
104
+ it "returns just the name when arguments key is missing" do
105
+ function = Fx::Function.new(
106
+ "name" => "now_utc",
107
+ "definition" => "some definition"
108
+ )
109
+
110
+ expect(function.signature).to eq("now_utc")
111
+ end
36
112
  end
37
113
 
38
114
  describe "#to_schema" do
39
115
  it "returns a schema compatible version of the function" do
40
116
  function = Fx::Function.new(
41
117
  "name" => "uppercase_users_name",
42
- "definition" => "CREATE OR REPLACE TRIGGER uppercase_users_name ..."
118
+ "definition" => "CREATE OR REPLACE TRIGGER uppercase_users_name ...",
119
+ "arguments" => ""
43
120
  )
44
121
 
45
122
  expect(function.to_schema).to eq(<<-EOS)
@@ -52,7 +129,8 @@ RSpec.describe Fx::Function do
52
129
  it "maintains backslashes" do
53
130
  function = Fx::Function.new(
54
131
  "name" => "regex",
55
- "definition" => "CREATE OR REPLACE FUNCTION regex \\1"
132
+ "definition" => "CREATE OR REPLACE FUNCTION regex \\1",
133
+ "arguments" => ""
56
134
  )
57
135
 
58
136
  expect(function.to_schema).to eq(<<-'EOS')
@@ -176,6 +176,7 @@ RSpec.describe Fx::Statements, :db do
176
176
  expect do
177
177
  connection.update_trigger(
178
178
  :whatever,
179
+ on: :users,
179
180
  version: nil,
180
181
  sql_definition: nil
181
182
  )
@@ -191,6 +192,7 @@ RSpec.describe Fx::Statements, :db do
191
192
  expect do
192
193
  connection.update_trigger(
193
194
  :whatever,
195
+ on: :users,
194
196
  version: 1,
195
197
  sql_definition: "a definition"
196
198
  )
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.1
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Teo Ljungberg
@@ -16,9 +16,6 @@ dependencies:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
18
  version: '7.2'
19
- - - "<"
20
- - !ruby/object:Gem::Version
21
- version: '8.2'
22
19
  type: :runtime
23
20
  prerelease: false
24
21
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,9 +23,6 @@ dependencies:
26
23
  - - ">="
27
24
  - !ruby/object:Gem::Version
28
25
  version: '7.2'
29
- - - "<"
30
- - !ruby/object:Gem::Version
31
- version: '8.2'
32
26
  - !ruby/object:Gem::Dependency
33
27
  name: railties
34
28
  requirement: !ruby/object:Gem::Requirement
@@ -36,9 +30,6 @@ dependencies:
36
30
  - - ">="
37
31
  - !ruby/object:Gem::Version
38
32
  version: '7.2'
39
- - - "<"
40
- - !ruby/object:Gem::Version
41
- version: '8.2'
42
33
  type: :runtime
43
34
  prerelease: false
44
35
  version_requirements: !ruby/object:Gem::Requirement
@@ -46,9 +37,6 @@ dependencies:
46
37
  - - ">="
47
38
  - !ruby/object:Gem::Version
48
39
  version: '7.2'
49
- - - "<"
50
- - !ruby/object:Gem::Version
51
- version: '8.2'
52
40
  description: |
53
41
  Adds methods to ActiveRecord::Migration to create and manage database functions
54
42
  and triggers in Rails
@@ -58,7 +46,9 @@ executables: []
58
46
  extensions: []
59
47
  extra_rdoc_files: []
60
48
  files:
49
+ - ".github/scripts/check-eol"
61
50
  - ".github/workflows/ci.yml"
51
+ - ".github/workflows/eol-check.yml"
62
52
  - ".gitignore"
63
53
  - ".rspec"
64
54
  - ".standard.yml"
@@ -149,7 +139,7 @@ licenses:
149
139
  - MIT
150
140
  metadata:
151
141
  bug_tracker_uri: https://github.com/teoljungberg/fx/issues
152
- changelog_uri: https://github.com/teoljungberg/fx/blob/v0.10.1/CHANGELOG.md
142
+ changelog_uri: https://github.com/teoljungberg/fx/blob/v0.11.0/CHANGELOG.md
153
143
  homepage_uri: https://github.com/teoljungberg/fx
154
144
  source_code_uri: https://github.com/teoljungberg/fx
155
145
  rdoc_options: []
@@ -159,14 +149,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
159
149
  requirements:
160
150
  - - ">="
161
151
  - !ruby/object:Gem::Version
162
- version: '3.2'
152
+ version: '3.3'
163
153
  required_rubygems_version: !ruby/object:Gem::Requirement
164
154
  requirements:
165
155
  - - ">="
166
156
  - !ruby/object:Gem::Version
167
157
  version: '0'
168
158
  requirements: []
169
- rubygems_version: 4.0.6
159
+ rubygems_version: 4.0.3
170
160
  specification_version: 4
171
161
  summary: Support for database functions and triggers in Rails migrations
172
162
  test_files: []