fx 0.1.0 → 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.
Files changed (106) hide show
  1. checksums.yaml +5 -5
  2. data/.github/scripts/check-eol +95 -0
  3. data/.github/workflows/ci.yml +61 -0
  4. data/.github/workflows/eol-check.yml +30 -0
  5. data/.rspec +1 -1
  6. data/.standard.yml +3 -0
  7. data/CHANGELOG.md +186 -0
  8. data/CONTRIBUTING.md +13 -4
  9. data/Gemfile +21 -2
  10. data/LICENSE +18 -0
  11. data/README.md +99 -14
  12. data/Rakefile +11 -1
  13. data/bin/rake +2 -3
  14. data/bin/rspec +13 -3
  15. data/bin/setup +1 -4
  16. data/bin/standardrb +27 -0
  17. data/bin/yard +13 -3
  18. data/fx.gemspec +20 -27
  19. data/lib/fx/adapters/postgres/connection.rb +6 -0
  20. data/lib/fx/adapters/postgres/functions.rb +16 -28
  21. data/lib/fx/adapters/postgres/query_executor.rb +34 -0
  22. data/lib/fx/adapters/postgres/triggers.rb +14 -28
  23. data/lib/fx/adapters/postgres.rb +30 -17
  24. data/lib/fx/command_recorder.rb +104 -13
  25. data/lib/fx/configuration.rb +10 -25
  26. data/lib/fx/definition.rb +32 -10
  27. data/lib/fx/function.rb +23 -9
  28. data/lib/fx/railtie.rb +15 -0
  29. data/lib/fx/schema_dumper.rb +33 -10
  30. data/lib/fx/statements.rb +213 -11
  31. data/lib/fx/trigger.rb +5 -3
  32. data/lib/fx/version.rb +1 -1
  33. data/lib/fx.rb +40 -0
  34. data/lib/generators/fx/function/USAGE +2 -0
  35. data/lib/generators/fx/function/function_generator.rb +54 -39
  36. data/lib/generators/fx/function/templates/db/migrate/create_function.erb +1 -1
  37. data/lib/generators/fx/function/templates/db/migrate/update_function.erb +1 -1
  38. data/lib/generators/fx/migration_helper.rb +45 -0
  39. data/lib/generators/fx/name_helper.rb +33 -0
  40. data/lib/generators/fx/trigger/USAGE +2 -0
  41. data/lib/generators/fx/trigger/templates/db/migrate/create_trigger.erb +1 -1
  42. data/lib/generators/fx/trigger/templates/db/migrate/update_trigger.erb +1 -1
  43. data/lib/generators/fx/trigger/trigger_generator.rb +46 -51
  44. data/lib/generators/fx/version_helper.rb +55 -0
  45. data/spec/acceptance/user_manages_functions_spec.rb +26 -6
  46. data/spec/acceptance/user_manages_triggers_spec.rb +11 -11
  47. data/spec/acceptance_helper.rb +17 -11
  48. data/spec/dummy/Rakefile +8 -1
  49. data/spec/dummy/bin/bundle +2 -2
  50. data/spec/dummy/bin/rails +3 -3
  51. data/spec/dummy/bin/rake +2 -2
  52. data/spec/dummy/config/application.rb +2 -0
  53. data/spec/dummy/config/database.yml +2 -0
  54. data/spec/dummy/config.ru +1 -1
  55. data/spec/features/functions/migrations_spec.rb +9 -9
  56. data/spec/features/functions/revert_spec.rb +13 -13
  57. data/spec/features/triggers/migrations_spec.rb +11 -11
  58. data/spec/features/triggers/revert_spec.rb +21 -21
  59. data/spec/fx/adapters/postgres/functions_spec.rb +40 -0
  60. data/spec/fx/adapters/postgres/query_executor_spec.rb +75 -0
  61. data/spec/fx/adapters/postgres/triggers_spec.rb +48 -0
  62. data/spec/fx/adapters/postgres_spec.rb +115 -111
  63. data/spec/fx/command_recorder_spec.rb +39 -53
  64. data/spec/fx/configuration_spec.rb +20 -9
  65. data/spec/fx/definition_spec.rb +53 -38
  66. data/spec/fx/function_spec.rb +143 -0
  67. data/spec/fx/schema_dumper_spec.rb +241 -0
  68. data/spec/fx/statements_spec.rb +219 -0
  69. data/spec/fx/trigger_spec.rb +52 -0
  70. data/spec/fx_spec.rb +28 -0
  71. data/spec/generators/fx/function/function_generator_spec.rb +22 -10
  72. data/spec/generators/fx/migration_helper_spec.rb +108 -0
  73. data/spec/generators/fx/name_helper_spec.rb +114 -0
  74. data/spec/generators/fx/trigger/trigger_generator_spec.rb +55 -20
  75. data/spec/generators/fx/version_helper_spec.rb +157 -0
  76. data/spec/spec_helper.rb +16 -4
  77. data/spec/support/database_reset.rb +24 -0
  78. data/spec/support/definition_helpers.rb +5 -9
  79. data/spec/support/generator_setup.rb +46 -5
  80. data/spec/support/migration_helpers.rb +9 -1
  81. data/spec/support/warning_helper.rb +5 -0
  82. metadata +43 -215
  83. data/.ruby-version +0 -1
  84. data/.travis.yml +0 -27
  85. data/Appraisals +0 -21
  86. data/bin/appraisal +0 -17
  87. data/gemfiles/rails40.gemfile +0 -8
  88. data/gemfiles/rails40.gemfile.lock +0 -111
  89. data/gemfiles/rails41.gemfile +0 -8
  90. data/gemfiles/rails41.gemfile.lock +0 -113
  91. data/gemfiles/rails42.gemfile +0 -8
  92. data/gemfiles/rails42.gemfile.lock +0 -130
  93. data/gemfiles/rails50.gemfile +0 -8
  94. data/gemfiles/rails50.gemfile.lock +0 -126
  95. data/lib/fx/command_recorder/arguments.rb +0 -43
  96. data/lib/fx/command_recorder/function.rb +0 -30
  97. data/lib/fx/command_recorder/trigger.rb +0 -30
  98. data/lib/fx/schema_dumper/function.rb +0 -29
  99. data/lib/fx/schema_dumper/trigger.rb +0 -29
  100. data/lib/fx/statements/function.rb +0 -105
  101. data/lib/fx/statements/trigger.rb +0 -133
  102. data/spec/fx/command_recorder/arguments_spec.rb +0 -41
  103. data/spec/fx/schema_dumper/function_spec.rb +0 -22
  104. data/spec/fx/schema_dumper/trigger_spec.rb +0 -40
  105. data/spec/fx/statements/function_spec.rb +0 -103
  106. data/spec/fx/statements/trigger_spec.rb +0 -132
data/bin/setup CHANGED
@@ -5,8 +5,5 @@ set -e
5
5
  gem install bundler --conservative
6
6
  bundle check || bundle install
7
7
 
8
- if [ -z "$CI" ]; then
9
- bundle exec appraisal install
10
- fi
11
-
8
+ bundle exec rake dummy:db:drop
12
9
  bundle exec rake dummy:db:create
data/bin/standardrb ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'standardrb' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
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
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("standard", "standardrb")
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
@@ -1,38 +1,31 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path("../lib", __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require "fx/version"
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = "fx"
8
- spec.version = Fx::VERSION
9
- spec.authors = ["Teo Ljungberg"]
10
- spec.email = ["teo@teoljungberg.com"]
11
- spec.summary = %q{Support for database functions and triggers in Rails migrations}
12
- spec.description = <<-DESCRIPTION
6
+ spec.name = "fx"
7
+ spec.version = Fx::VERSION
8
+ spec.authors = ["Teo Ljungberg"]
9
+ spec.email = ["teo@teoljungberg.com"]
10
+ spec.summary = "Support for database functions and triggers in Rails migrations"
11
+ spec.description = <<~DESCRIPTION
13
12
  Adds methods to ActiveRecord::Migration to create and manage database functions
14
13
  and triggers in Rails
15
14
  DESCRIPTION
16
- spec.homepage = "https://github.com/teoljungberg/fx"
17
- spec.license = 'MIT'
15
+ spec.homepage = "https://github.com/teoljungberg/fx"
16
+ spec.license = "MIT"
17
+ spec.metadata = {
18
+ "bug_tracker_uri" => "#{spec.homepage}/issues",
19
+ "changelog_uri" => "#{spec.homepage}/blob/v#{spec.version}/CHANGELOG.md",
20
+ "homepage_uri" => spec.homepage,
21
+ "source_code_uri" => spec.homepage
22
+ }
18
23
 
19
- spec.files = `git ls-files -z`.split("\x0")
20
- spec.test_files = spec.files.grep(%r{^spec/})
21
- spec.require_paths = ['lib']
24
+ spec.files = `git ls-files -z`.split("\x0")
25
+ spec.require_paths = ["lib"]
22
26
 
23
- spec.add_development_dependency "appraisal"
24
- spec.add_development_dependency "bundler", '>= 1.5'
25
- spec.add_development_dependency "database_cleaner"
26
- spec.add_development_dependency "rake"
27
- spec.add_development_dependency "rspec", '>= 3.3'
28
- spec.add_development_dependency "pg"
29
- spec.add_development_dependency "pry"
30
- spec.add_development_dependency "ammeter", '>= 1.1.3'
31
- spec.add_development_dependency "yard"
32
- spec.add_development_dependency "redcarpet"
27
+ spec.add_dependency "activerecord", ">= 7.2"
28
+ spec.add_dependency "railties", ">= 7.2"
33
29
 
34
- spec.add_dependency "activerecord", '>= 4.0.0'
35
- spec.add_dependency "railties", '>= 4.0.0'
36
-
37
- spec.required_ruby_version = "~> 2.1"
30
+ spec.required_ruby_version = ">= 3.3"
38
31
  end
@@ -10,6 +10,12 @@ module Fx
10
10
  #
11
11
  # @api private
12
12
  class Connection < SimpleDelegator
13
+ # All supported PostgreSQL versions (14+) support DROP FUNCTION
14
+ # without argument lists.
15
+ # https://www.postgresql.org/docs/10/sql-dropfunction.html
16
+ def support_drop_function_without_args
17
+ true
18
+ end
13
19
  end
14
20
  end
15
21
  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,46 +9,33 @@ 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 = <<~SQL
12
+ FUNCTIONS_WITH_DEFINITIONS_QUERY = <<~SQL.freeze
12
13
  SELECT
13
14
  pp.proname AS name,
14
- 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
15
17
  FROM pg_proc pp
16
18
  JOIN pg_namespace pn
17
19
  ON pn.oid = pp.pronamespace
18
20
  LEFT JOIN pg_depend pd
19
21
  ON pd.objid = pp.oid AND pd.deptype = 'e'
20
- WHERE pn.nspname = 'public' AND pd.objid IS NULL;
22
+ LEFT JOIN pg_aggregate pa
23
+ ON pa.aggfnoid = pp.oid
24
+ WHERE pn.nspname = ANY (current_schemas(false))
25
+ AND pd.objid IS NULL
26
+ AND pa.aggfnoid IS NULL
27
+ ORDER BY pp.oid;
21
28
  SQL
22
29
 
23
30
  # Wraps #all as a static facade.
24
31
  #
25
32
  # @return [Array<Fx::Function>]
26
- def self.all(*args)
27
- new(*args).all
28
- end
29
-
30
- def initialize(connection)
31
- @connection = connection
32
- end
33
-
34
- # All of the functions that this connection has defined.
35
- #
36
- # @return [Array<Fx::Function>]
37
- def all
38
- functions_from_postgres.map { |function| to_fx_function(function) }
39
- end
40
-
41
- private
42
-
43
- attr_reader :connection
44
-
45
- def functions_from_postgres
46
- connection.execute(FUNCTIONS_WITH_DEFINITIONS_QUERY)
47
- end
48
-
49
- def to_fx_function(result)
50
- Fx::Function.new(result)
33
+ def self.all(connection)
34
+ Fx::Adapters::Postgres::QueryExecutor.call(
35
+ connection: connection,
36
+ query: FUNCTIONS_WITH_DEFINITIONS_QUERY,
37
+ model_class: Fx::Function
38
+ )
51
39
  end
52
40
  end
53
41
  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 = <<~SQL
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,38 +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%'
26
+ ORDER BY pc.oid;
22
27
  SQL
23
28
 
24
29
  # Wraps #all as a static facade.
25
30
  #
26
31
  # @return [Array<Fx::Trigger>]
27
- def self.all(*args)
28
- new(*args).all
29
- end
30
-
31
- def initialize(connection)
32
- @connection = connection
33
- end
34
-
35
- # All of the triggers that this connection has defined.
36
- #
37
- # @return [Array<Fx::Trigger>]
38
- def all
39
- triggers_from_postgres.map { |trigger| to_fx_trigger(trigger) }
40
- end
41
-
42
- private
43
-
44
- attr_reader :connection
45
-
46
- def triggers_from_postgres
47
- connection.execute(TRIGGERS_WITH_DEFINITIONS_QUERY)
48
- end
49
-
50
- def to_fx_trigger(result)
51
- Fx::Trigger.new(result)
32
+ def self.all(connection)
33
+ Fx::Adapters::Postgres::QueryExecutor.call(
34
+ connection: connection,
35
+ query: TRIGGERS_WITH_DEFINITIONS_QUERY,
36
+ model_class: Fx::Trigger
37
+ )
52
38
  end
53
39
  end
54
40
  end
@@ -23,6 +23,19 @@ module Fx
23
23
  # config.adapter = Fx::Adapters::Postgres.new
24
24
  # end
25
25
  class Postgres
26
+ # Creates an instance of the F(x) Postgres adapter.
27
+ #
28
+ # This is the default adapter for F(x). Configuring it via
29
+ # {Fx.configure} is not required, but the example below shows how one
30
+ # would explicitly set it.
31
+ #
32
+ # @param [#connection] connectable An object that returns the connection
33
+ # for F(x) to use. Defaults to `ActiveRecord::Base`.
34
+ #
35
+ # @example
36
+ # Fx.configure do |config|
37
+ # config.adapter = Fx::Adapters::Postgres.new
38
+ # end
26
39
  def initialize(connectable = ActiveRecord::Base)
27
40
  @connectable = connectable
28
41
  end
@@ -34,7 +47,7 @@ module Fx
34
47
  #
35
48
  # @return [Array<Fx::Function>]
36
49
  def functions
37
- Functions.all(connection)
50
+ Fx::Adapters::Postgres::Functions.all(connection)
38
51
  end
39
52
 
40
53
  # Returns an array of triggers in the database.
@@ -44,7 +57,7 @@ module Fx
44
57
  #
45
58
  # @return [Array<Fx::Trigger>]
46
59
  def triggers
47
- Triggers.all(connection)
60
+ Fx::Adapters::Postgres::Triggers.all(connection)
48
61
  end
49
62
 
50
63
  # Creates a function in the database.
@@ -52,11 +65,11 @@ module Fx
52
65
  # This is typically called in a migration via
53
66
  # {Fx::Statements::Function#create_function}.
54
67
  #
55
- # @param sql_definition The SQL schema for the function.
68
+ # @param sql_definition [String] The SQL schema for the function.
56
69
  #
57
70
  # @return [void]
58
71
  def create_function(sql_definition)
59
- execute sql_definition
72
+ execute(sql_definition)
60
73
  end
61
74
 
62
75
  # Creates a trigger in the database.
@@ -64,11 +77,11 @@ module Fx
64
77
  # This is typically called in a migration via
65
78
  # {Fx::Statements::Trigger#create_trigger}.
66
79
  #
67
- # @param sql_definition The SQL schema for the trigger.
80
+ # @param sql_definition [String] The SQL schema for the trigger.
68
81
  #
69
82
  # @return [void]
70
83
  def create_trigger(sql_definition)
71
- execute sql_definition
84
+ execute(sql_definition)
72
85
  end
73
86
 
74
87
  # Updates a function in the database.
@@ -76,8 +89,8 @@ module Fx
76
89
  # This is typically called in a migration via
77
90
  # {Fx::Statements::Function#update_function}.
78
91
  #
79
- # @param name The name of the function.
80
- # @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.
81
94
  #
82
95
  # @return [void]
83
96
  def update_function(name, sql_definition)
@@ -93,9 +106,9 @@ module Fx
93
106
  # This is typically called in a migration via
94
107
  # {Fx::Statements::Function#update_trigger}.
95
108
  #
96
- # @param name The name of the trigger.
97
- # @param on The associated table for the trigger to drop
98
- # @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.
99
112
  #
100
113
  # @return [void]
101
114
  def update_trigger(name, on:, sql_definition:)
@@ -108,11 +121,11 @@ module Fx
108
121
  # This is typically called in a migration via
109
122
  # {Fx::Statements::Function#drop_function}.
110
123
  #
111
- # @param name The name of the function to drop
124
+ # @param name [String, Symbol] The name of the function to drop
112
125
  #
113
126
  # @return [void]
114
127
  def drop_function(name)
115
- execute "DROP FUNCTION #{name}();"
128
+ execute("DROP FUNCTION #{name};")
116
129
  end
117
130
 
118
131
  # Drops the trigger from the database
@@ -120,12 +133,12 @@ module Fx
120
133
  # This is typically called in a migration via
121
134
  # {Fx::Statements::Trigger#drop_trigger}.
122
135
  #
123
- # @param name The name of the trigger to drop
124
- # @param on The associated table for the trigger to drop
136
+ # @param name [String, Symbol] The name of the trigger to drop
137
+ # @param on [String, Symbol] The associated table for the trigger to drop
125
138
  #
126
139
  # @return [void]
127
140
  def drop_trigger(name, on:)
128
- execute "DROP TRIGGER #{name} ON #{on};"
141
+ execute("DROP TRIGGER #{name} ON #{on};")
129
142
  end
130
143
 
131
144
  private
@@ -135,7 +148,7 @@ module Fx
135
148
  delegate :execute, to: :connection
136
149
 
137
150
  def connection
138
- Connection.new(connectable.connection)
151
+ Fx::Adapters::Postgres::Connection.new(connectable.connection)
139
152
  end
140
153
  end
141
154
  end
@@ -1,29 +1,120 @@
1
- require "fx/command_recorder/arguments"
2
- require "fx/command_recorder/function"
3
- require "fx/command_recorder/trigger"
4
-
5
1
  module Fx
6
2
  # @api private
7
3
  module CommandRecorder
8
- include Function
9
- include Trigger
4
+ def create_function(*args, &block)
5
+ record(:create_function, args, &block)
6
+ end
7
+ ruby2_keywords :create_function
8
+
9
+ def drop_function(*args, &block)
10
+ record(:drop_function, args, &block)
11
+ end
12
+ ruby2_keywords :drop_function
13
+
14
+ def update_function(*args, &block)
15
+ record(:update_function, args, &block)
16
+ end
17
+ ruby2_keywords :update_function
18
+
19
+ def invert_create_function(args)
20
+ [:drop_function, args]
21
+ end
22
+
23
+ def invert_drop_function(args)
24
+ perform_inversion(:create_function, args)
25
+ end
26
+
27
+ def invert_update_function(args)
28
+ perform_inversion(:update_function, args)
29
+ end
30
+
31
+ def create_trigger(*args, &block)
32
+ record(:create_trigger, args, &block)
33
+ end
34
+ ruby2_keywords :create_trigger
35
+
36
+ def drop_trigger(*args, &block)
37
+ record(:drop_trigger, args, &block)
38
+ end
39
+ ruby2_keywords :drop_trigger
40
+
41
+ def update_trigger(*args, &block)
42
+ record(:update_trigger, args, &block)
43
+ end
44
+ ruby2_keywords :update_trigger
45
+
46
+ def invert_create_trigger(args)
47
+ [:drop_trigger, args]
48
+ end
49
+
50
+ def invert_drop_trigger(args)
51
+ perform_inversion(:create_trigger, args)
52
+ end
53
+
54
+ def invert_update_trigger(args)
55
+ perform_inversion(:update_trigger, args)
56
+ end
10
57
 
11
58
  private
12
59
 
60
+ MESSAGE_IRREVERSIBLE = "`%s` is reversible only if given a `revert_to_version`".freeze
61
+
13
62
  def perform_inversion(method, args)
14
63
  arguments = Arguments.new(args)
15
64
 
16
65
  if arguments.revert_to_version.nil?
17
- message = "`#{method}` is reversible only if given a `revert_to_version`"
18
- raise ActiveRecord::IrreversibleMigration, message
66
+ raise ActiveRecord::IrreversibleMigration, format(MESSAGE_IRREVERSIBLE, method)
19
67
  end
20
68
 
21
69
  [method, arguments.invert_version.to_a]
22
70
  end
71
+
72
+ class Arguments
73
+ def initialize(args)
74
+ @args = args.freeze
75
+ end
76
+
77
+ def function
78
+ args.fetch(0)
79
+ end
80
+
81
+ def version
82
+ options.fetch(:version)
83
+ end
84
+
85
+ def revert_to_version
86
+ options.fetch(:revert_to_version, nil)
87
+ end
88
+
89
+ def invert_version
90
+ self.class.new([function, options_for_revert])
91
+ end
92
+
93
+ def to_a
94
+ args.to_a
95
+ end
96
+
97
+ private
98
+
99
+ attr_reader :args
100
+
101
+ def options
102
+ @options ||= args.fetch(1, {}).dup
103
+ end
104
+
105
+ def options_for_revert
106
+ opts = options.clone.tap do |revert_options|
107
+ revert_options[:version] = revert_to_version
108
+ revert_options.delete(:revert_to_version)
109
+ end
110
+
111
+ keyword_hash(opts)
112
+ end
113
+
114
+ def keyword_hash(hash)
115
+ Hash.ruby2_keywords_hash(hash)
116
+ end
117
+ end
118
+ private_constant :Arguments
23
119
  end
24
120
  end
25
-
26
- ActiveRecord::Migration::CommandRecorder.send(
27
- :include,
28
- Fx::CommandRecorder,
29
- )
@@ -1,38 +1,23 @@
1
1
  module Fx
2
- # @return [Fx::Configuration] F(x)'s current configuration
3
- def self.configuration
4
- @_configuration ||= Configuration.new
5
- end
6
-
7
- # Set F(x)'s configuration
8
- #
9
- # @param config [Fx::Configuration]
10
- def self.configuration=(config)
11
- @_configuration = config
12
- end
13
-
14
- # Modify F(x)'s current configuration
15
- #
16
- # @yieldparam [Fx::Configuration] config current F(x) config
17
- # ```
18
- # Fx.configure do |config|
19
- # config.database = Fx::Adapters::Postgres
20
- # end
21
- # ```
22
- def self.configure
23
- yield configuration
24
- end
25
-
26
2
  # F(x)'s configuration object.
27
3
  class Configuration
28
4
  # The F(x) database adapter instance to use when executing SQL.
29
5
  #
30
6
  # Defaults to an instance of {Fx::Adapters::Postgres}
31
- # @return Fx adapter
7
+ # @return [Fx::Adapters::Postgres] Fx adapter
32
8
  attr_accessor :database
33
9
 
10
+ # Prioritizes the order in the schema.rb of functions before other
11
+ # statements in order to make directly schema load work when using functions
12
+ # in statements below, i.e.: default column values.
13
+ #
14
+ # Defaults to false
15
+ # @return [Boolean] Boolean
16
+ attr_accessor :dump_functions_at_beginning_of_schema
17
+
34
18
  def initialize
35
19
  @database = Fx::Adapters::Postgres.new
20
+ @dump_functions_at_beginning_of_schema = false
36
21
  end
37
22
  end
38
23
  end
data/lib/fx/definition.rb CHANGED
@@ -1,18 +1,28 @@
1
1
  module Fx
2
2
  # @api private
3
3
  class Definition
4
- def initialize(name:, version:, type: "function")
4
+ FUNCTION = "function".freeze
5
+ TRIGGER = "trigger".freeze
6
+
7
+ def self.function(name:, version:)
8
+ new(name: name, version: version, type: FUNCTION)
9
+ end
10
+
11
+ def self.trigger(name:, version:)
12
+ new(name: name, version: version, type: TRIGGER)
13
+ end
14
+
15
+ def initialize(name:, version:, type:)
5
16
  @name = name
6
- @version = version.to_i
17
+ @version_number = version.to_i
7
18
  @type = type
8
19
  end
9
20
 
10
21
  def to_sql
11
- File.read(full_path).tap do |content|
12
- if content.empty?
13
- raise "Define #{@type} in #{path} before migrating."
14
- end
15
- end
22
+ content = File.read(find_file || full_path)
23
+ raise "Define #{type} in #{path} before migrating." if content.empty?
24
+
25
+ content
16
26
  end
17
27
 
18
28
  def full_path
@@ -20,17 +30,29 @@ module Fx
20
30
  end
21
31
 
22
32
  def path
23
- File.join("db", @type.pluralize, filename)
33
+ @_path ||= File.join("db", type.pluralize, filename)
24
34
  end
25
35
 
26
36
  def version
27
- @version.to_s.rjust(2, "0")
37
+ version_number.to_s.rjust(2, "0")
28
38
  end
29
39
 
30
40
  private
31
41
 
42
+ attr_reader :name, :version_number, :type
43
+
32
44
  def filename
33
- "#{@name}_v#{version}.sql"
45
+ @_filename ||= "#{name}_v#{version}.sql"
46
+ end
47
+
48
+ def find_file
49
+ migration_paths.lazy
50
+ .map { |migration_path| File.expand_path(File.join("..", "..", path), migration_path) }
51
+ .find { |definition_path| File.exist?(definition_path) }
52
+ end
53
+
54
+ def migration_paths
55
+ Rails.application.config.paths["db/migrate"].expanded
34
56
  end
35
57
  end
36
58
  end