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/lib/fx/function.rb CHANGED
@@ -1,23 +1,37 @@
1
1
  module Fx
2
2
  # @api private
3
3
  class Function
4
+ include Comparable
5
+
4
6
  attr_reader :name, :definition
5
- delegate :<=>, to: :name
6
7
 
7
- def initialize(function_row)
8
- @name = function_row.fetch("name")
9
- @definition = function_row.fetch("definition")
8
+ def initialize(row)
9
+ @name = row.fetch("name")
10
+ @definition = row.fetch("definition")
11
+ @arguments = row.fetch("arguments", nil)
12
+ end
13
+
14
+ def <=>(other)
15
+ signature <=> other.signature
10
16
  end
11
17
 
12
18
  def ==(other)
13
- 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
14
28
  end
15
29
 
16
30
  def to_schema
17
- <<-SCHEMA.indent(2)
18
- create_function :#{name}, sql_definition: <<-\SQL
19
- #{definition.indent(4).rstrip}
20
- SQL
31
+ <<~SCHEMA.indent(2)
32
+ create_function :#{name}, sql_definition: <<-'SQL'
33
+ #{definition.indent(4).rstrip}
34
+ SQL
21
35
  SCHEMA
22
36
  end
23
37
  end
data/lib/fx/railtie.rb ADDED
@@ -0,0 +1,15 @@
1
+ require "rails/railtie"
2
+
3
+ module Fx
4
+ # Automatically initializes Fx in the context of a Rails application when
5
+ # ActiveRecord is loaded.
6
+ #
7
+ # @see Fx.load
8
+ class Railtie < Rails::Railtie
9
+ initializer "fx.load" do
10
+ ActiveSupport.on_load :active_record do
11
+ Fx.load
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,15 +1,38 @@
1
- require "fx/schema_dumper/function"
2
- require "fx/schema_dumper/trigger"
3
-
4
1
  module Fx
5
2
  # @api private
6
3
  module SchemaDumper
7
- include Function
8
- include Trigger
4
+ def tables(stream)
5
+ if Fx.configuration.dump_functions_at_beginning_of_schema
6
+ functions(stream)
7
+ end
8
+
9
+ super
10
+
11
+ unless Fx.configuration.dump_functions_at_beginning_of_schema
12
+ functions(stream)
13
+ end
14
+
15
+ triggers(stream)
16
+ end
17
+
18
+ private
19
+
20
+ def functions(stream)
21
+ dumpable_functions_in_database = Fx.database.functions
22
+
23
+ dumpable_functions_in_database.each do |function|
24
+ stream.puts
25
+ stream.puts(function.to_schema)
26
+ end
27
+ end
28
+
29
+ def triggers(stream)
30
+ dumpable_triggers_in_database = Fx.database.triggers
31
+
32
+ dumpable_triggers_in_database.each do |trigger|
33
+ stream.puts
34
+ stream.puts(trigger.to_schema)
35
+ end
36
+ end
9
37
  end
10
38
  end
11
-
12
- ActiveRecord::SchemaDumper.send(
13
- :prepend,
14
- Fx::SchemaDumper,
15
- )
data/lib/fx/statements.rb CHANGED
@@ -1,16 +1,218 @@
1
- require "rails"
2
- require "fx/statements/function"
3
- require "fx/statements/trigger"
4
-
5
1
  module Fx
6
2
  # @api private
7
3
  module Statements
8
- include Function
9
- include Trigger
4
+ # Create a new database function.
5
+ #
6
+ # @param name [String, Symbol] The name of the database function.
7
+ # @param version [Integer] The version number of the function, used to
8
+ # find the definition file in `db/functions`. This defaults to `1` if
9
+ # not provided.
10
+ # @param sql_definition [String] The SQL query for the function schema.
11
+ # If both `sql_definition` and `version` are provided,
12
+ # `sql_definition` takes precedence.
13
+ # @return [void] The database response from executing the create statement.
14
+ #
15
+ # @example Create from `db/functions/uppercase_users_name_v02.sql`
16
+ # create_function(:uppercase_users_name, version: 2)
17
+ #
18
+ # @example Create from provided SQL string
19
+ # create_function(:uppercase_users_name, sql_definition: <<~SQL)
20
+ # CREATE OR REPLACE FUNCTION uppercase_users_name()
21
+ # RETURNS trigger AS $$
22
+ # BEGIN
23
+ # NEW.upper_name = UPPER(NEW.name);
24
+ # RETURN NEW;
25
+ # END;
26
+ # $$ LANGUAGE plpgsql;
27
+ # SQL
28
+ #
29
+ def create_function(name, version: 1, sql_definition: nil, revert_to_version: nil)
30
+ validate_version_or_sql_definition_present!(version, sql_definition)
31
+ sql_definition = resolve_sql_definition(sql_definition, name, version, :function)
32
+
33
+ Fx.database.create_function(sql_definition)
34
+ end
35
+
36
+ # Drop a database function by name.
37
+ #
38
+ # @param name [String, Symbol] The name of the database function.
39
+ # @param revert_to_version [Integer] Used to reverse the `drop_function`
40
+ # command on `rake db:rollback`. The provided version will be passed as
41
+ # the `version` argument to {#create_function}.
42
+ # @return [void] The database response from executing the drop statement.
43
+ #
44
+ # @example Drop a function, rolling back to version 2 on rollback
45
+ # drop_function(:uppercase_users_name, revert_to_version: 2)
46
+ #
47
+ def drop_function(name, revert_to_version: nil)
48
+ Fx.database.drop_function(name)
49
+ end
50
+
51
+ # Update a database function.
52
+ #
53
+ # @param name [String, Symbol] The name of the database function.
54
+ # @param version [Integer] The version number of the function, used to
55
+ # find the definition file in `db/functions`. This defaults to `1` if
56
+ # not provided.
57
+ # @param sql_definition [String] The SQL query for the function schema.
58
+ # If both `sql_definition` and `version` are provided,
59
+ # `sql_definition` takes precedence.
60
+ # @return [void] The database response from executing the create statement.
61
+ #
62
+ # @example Update function to a given version
63
+ # update_function(
64
+ # :uppercase_users_name,
65
+ # version: 3,
66
+ # revert_to_version: 2,
67
+ # )
68
+ #
69
+ # @example Update function from provided SQL string
70
+ # update_function(:uppercase_users_name, sql_definition: <<~SQL)
71
+ # CREATE OR REPLACE FUNCTION uppercase_users_name()
72
+ # RETURNS trigger AS $$
73
+ # BEGIN
74
+ # NEW.upper_name = UPPER(NEW.name);
75
+ # RETURN NEW;
76
+ # END;
77
+ # $$ LANGUAGE plpgsql;
78
+ # SQL
79
+ #
80
+ def update_function(name, version: nil, sql_definition: nil, revert_to_version: nil)
81
+ validate_version_or_sql_definition_present!(version, sql_definition)
82
+
83
+ sql_definition = resolve_sql_definition(sql_definition, name, version, :function)
84
+
85
+ Fx.database.update_function(name, sql_definition)
86
+ end
87
+
88
+ # Create a new database trigger.
89
+ #
90
+ # @param name [String, Symbol] The name of the database trigger.
91
+ # @param version [Integer] The version number of the trigger, used to
92
+ # find the definition file in `db/triggers`. This defaults to `1` if
93
+ # not provided.
94
+ # @param sql_definition [String] The SQL query for the function. An error
95
+ # will be raised if `sql_definition` and `version` are both set,
96
+ # as they are mutually exclusive.
97
+ # @return [void] The database response from executing the create statement.
98
+ #
99
+ # @example Create trigger from `db/triggers/uppercase_users_name_v01.sql`
100
+ # create_trigger(:uppercase_users_name, version: 1)
101
+ #
102
+ # @example Create trigger from provided SQL string
103
+ # create_trigger(:uppercase_users_name, sql_definition: <<~SQL)
104
+ # CREATE TRIGGER uppercase_users_name
105
+ # BEFORE INSERT ON users
106
+ # FOR EACH ROW
107
+ # EXECUTE FUNCTION uppercase_users_name();
108
+ # SQL
109
+ #
110
+ def create_trigger(name, version: nil, sql_definition: nil, on: nil, revert_to_version: nil)
111
+ validate_version_and_sql_definition_exclusive!(version, sql_definition)
112
+
113
+ version ||= 1
114
+
115
+ sql_definition = resolve_sql_definition(sql_definition, name, version, :trigger)
116
+
117
+ Fx.database.create_trigger(sql_definition)
118
+ end
119
+
120
+ # Drop a database trigger by name.
121
+ #
122
+ # @param name [String, Symbol] The name of the database trigger.
123
+ # @param on [String, Symbol] The name of the table the database trigger
124
+ # is associated with.
125
+ # @param revert_to_version [Integer] Used to reverse the `drop_trigger`
126
+ # command on `rake db:rollback`. The provided version will be passed as
127
+ # the `version` argument to {#create_trigger}.
128
+ # @return [void] The database response from executing the drop statement.
129
+ #
130
+ # @example Drop a trigger, rolling back to version 3 on rollback
131
+ # drop_trigger(:log_inserts, on: :users, revert_to_version: 3)
132
+ #
133
+ def drop_trigger(name, on:, revert_to_version: nil)
134
+ Fx.database.drop_trigger(name, on: on)
135
+ end
136
+
137
+ # Update a database trigger to a new version.
138
+ #
139
+ # The existing trigger is dropped and recreated using the supplied `on`
140
+ # and `version` parameter.
141
+ #
142
+ # @param name [String, Symbol] The name of the database trigger.
143
+ # @param version [Integer] The version number of the trigger.
144
+ # @param on [String, Symbol] The name of the table the database trigger
145
+ # is associated with.
146
+ # @param sql_definition [String] The SQL query for the function. An error
147
+ # will be raised if `sql_definition` and `version` are both set,
148
+ # as they are mutually exclusive.
149
+ # @param revert_to_version [Integer] The version number to rollback to on
150
+ # `rake db rollback`
151
+ # @return [void] The database response from executing the create statement.
152
+ #
153
+ # @example Update trigger to a given version
154
+ # update_trigger(
155
+ # :log_inserts,
156
+ # on: :users,
157
+ # version: 3,
158
+ # revert_to_version: 2,
159
+ # )
160
+ #
161
+ # @example Update trigger from provided SQL string
162
+ # update_trigger(:uppercase_users_name, sql_definition: <<~SQL)
163
+ # CREATE TRIGGER uppercase_users_name
164
+ # BEFORE INSERT ON users
165
+ # FOR EACH ROW
166
+ # EXECUTE FUNCTION uppercase_users_name();
167
+ # SQL
168
+ #
169
+ def update_trigger(name, on:, version: nil, sql_definition: nil, revert_to_version: nil)
170
+ validate_version_or_sql_definition_present!(version, sql_definition)
171
+ validate_version_and_sql_definition_exclusive!(version, sql_definition)
172
+
173
+ sql_definition = resolve_sql_definition(sql_definition, name, version, :trigger)
174
+
175
+ Fx.database.update_trigger(
176
+ name,
177
+ on: on,
178
+ sql_definition: sql_definition
179
+ )
180
+ end
181
+
182
+ private
183
+
184
+ VERSION_OR_SQL_DEFINITION_REQUIRED = "version or sql_definition must be specified".freeze
185
+ private_constant :VERSION_OR_SQL_DEFINITION_REQUIRED
186
+
187
+ VERSION_AND_SQL_DEFINITION_EXCLUSIVE = "sql_definition and version cannot both be set".freeze
188
+ private_constant :VERSION_AND_SQL_DEFINITION_EXCLUSIVE
189
+
190
+ def validate_version_or_sql_definition_present!(version, sql_definition)
191
+ if version.nil? && sql_definition.nil?
192
+ raise ArgumentError, VERSION_OR_SQL_DEFINITION_REQUIRED, caller
193
+ end
194
+ end
195
+
196
+ def validate_version_and_sql_definition_exclusive!(version, sql_definition)
197
+ if version.present? && sql_definition.present?
198
+ raise ArgumentError, VERSION_AND_SQL_DEFINITION_EXCLUSIVE, caller
199
+ end
200
+ end
201
+
202
+ def resolve_sql_definition(sql_definition, name, version, type)
203
+ return sql_definition.strip_heredoc if sql_definition
204
+
205
+ definition =
206
+ case type
207
+ when :function
208
+ Fx::Definition.function(name: name, version: version)
209
+ when :trigger
210
+ Fx::Definition.trigger(name: name, version: version)
211
+ else
212
+ raise ArgumentError, "Unknown type: #{type}. Must be :function or :trigger", caller
213
+ end
214
+
215
+ definition.to_sql
216
+ end
10
217
  end
11
218
  end
12
-
13
- ActiveRecord::ConnectionAdapters::AbstractAdapter.send(
14
- :include,
15
- Fx::Statements,
16
- )
data/lib/fx/trigger.rb CHANGED
@@ -1,12 +1,14 @@
1
1
  module Fx
2
2
  # @api private
3
3
  class Trigger
4
+ include Comparable
5
+
4
6
  attr_reader :name, :definition
5
7
  delegate :<=>, to: :name
6
8
 
7
- def initialize(function_row)
8
- @name = function_row.fetch("name")
9
- @definition = function_row.fetch("definition")
9
+ def initialize(row)
10
+ @name = row.fetch("name")
11
+ @definition = row.fetch("definition")
10
12
  end
11
13
 
12
14
  def ==(other)
data/lib/fx/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Fx
2
2
  # @api private
3
- VERSION = "0.1.0"
3
+ VERSION = "0.11.0"
4
4
  end
data/lib/fx.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require "rails"
2
+
1
3
  require "fx/version"
2
4
  require "fx/adapters/postgres"
3
5
  require "fx/command_recorder"
@@ -7,10 +9,48 @@ require "fx/function"
7
9
  require "fx/statements"
8
10
  require "fx/schema_dumper"
9
11
  require "fx/trigger"
12
+ require "fx/railtie"
10
13
 
11
14
  # F(x) adds methods `ActiveRecord::Migration` to create and manage database
12
15
  # triggers and functions in Rails applications.
13
16
  module Fx
17
+ # Hooks Fx into Rails.
18
+ #
19
+ # Enables fx migration methods, migration reversability, and `schema.rb`
20
+ # dumping.
21
+ def self.load
22
+ ActiveRecord::Migration::CommandRecorder.include(Fx::CommandRecorder)
23
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.include(Fx::Statements)
24
+ ActiveRecord::SchemaDumper.prepend(Fx::SchemaDumper)
25
+
26
+ true
27
+ end
28
+
29
+ # @return [Fx::Configuration] F(x)'s current configuration
30
+ def self.configuration
31
+ @_configuration ||= Fx::Configuration.new
32
+ end
33
+
34
+ # Set F(x)'s configuration
35
+ #
36
+ # @param config [Fx::Configuration]
37
+ def self.configuration=(config)
38
+ @_configuration = config
39
+ end
40
+
41
+ # Modify F(x)'s current configuration
42
+ #
43
+ # @yieldparam [Fx::Configuration] config current F(x) config
44
+ # ```
45
+ # Fx.configure do |config|
46
+ # config.database = Fx::Adapters::Postgres
47
+ # config.dump_functions_at_beginning_of_schema = true
48
+ # end
49
+ # ```
50
+ def self.configure
51
+ yield configuration
52
+ end
53
+
14
54
  # The current database adapter used by F(x).
15
55
  #
16
56
  # This defaults to {Fx::Adapters::Postgres} but can be overridden
@@ -2,6 +2,8 @@ Description:
2
2
  Create a new database function for your application. This will create a new
3
3
  function definition file and the accompanying migration.
4
4
 
5
+ When --no-migration is passed, skips generating a migration.
6
+
5
7
  Examples:
6
8
  rails generate fx:function test
7
9
 
@@ -1,39 +1,49 @@
1
1
  require "rails/generators"
2
2
  require "rails/generators/active_record"
3
+ require "generators/fx/version_helper"
4
+ require "generators/fx/migration_helper"
5
+ require "generators/fx/name_helper"
3
6
 
4
7
  module Fx
5
8
  module Generators
6
9
  # @api private
7
10
  class FunctionGenerator < Rails::Generators::NamedBase
8
11
  include Rails::Generators::Migration
12
+
9
13
  source_root File.expand_path("../templates", __FILE__)
10
14
 
15
+ DEFINITION_PATH = %w[db functions].freeze
16
+
17
+ class_option :migration, type: :boolean
18
+
11
19
  def create_functions_directory
12
- unless function_definition_path.exist?
13
- empty_directory(function_definition_path)
14
- end
20
+ return if function_definition_path.exist?
21
+
22
+ empty_directory(function_definition_path)
15
23
  end
16
24
 
17
25
  def create_function_definition
18
- if creating_new_function?
19
- create_file definition.path
26
+ if version_helper.creating_new?
27
+ create_file(definition.path)
20
28
  else
21
- copy_file previous_definition.full_path, definition.full_path
29
+ copy_file(previous_definition.full_path, definition.full_path)
22
30
  end
23
31
  end
24
32
 
25
33
  def create_migration_file
26
- if updating_existing_function?
27
- migration_template(
28
- "db/migrate/update_function.erb",
29
- "db/migrate/update_function_#{file_name}_to_version_#{version}.rb",
30
- )
31
- else
32
- migration_template(
33
- "db/migrate/create_function.erb",
34
- "db/migrate/create_function_#{file_name}.rb",
35
- )
36
- end
34
+ return if migration_helper.skip_creation?
35
+
36
+ template_info = migration_helper.migration_template_info(
37
+ object_type: :function,
38
+ file_name: file_name,
39
+ updating_existing: version_helper.updating_existing?,
40
+ version: version_helper.current_version
41
+ )
42
+
43
+ migration_template(
44
+ template_info.fetch(:template),
45
+ template_info.fetch(:filename)
46
+ )
37
47
  end
38
48
 
39
49
  def self.next_migration_number(dir)
@@ -42,56 +52,61 @@ module Fx
42
52
 
43
53
  no_tasks do
44
54
  def previous_version
45
- @_previous_version ||= Dir.entries(function_definition_path).
46
- map { |name| version_regex.match(name).try(:[], "version").to_i }.
47
- max
55
+ version_helper.previous_version
48
56
  end
49
57
 
50
58
  def version
51
- @_version ||= previous_version.next
59
+ version_helper.current_version
52
60
  end
53
61
 
54
62
  def migration_class_name
55
- if updating_existing_function?
56
- "UpdateFunction#{class_name}ToVersion#{version}"
63
+ if version_helper.updating_existing?
64
+ migration_helper.update_migration_class_name(
65
+ object_type: :function,
66
+ class_name: class_name,
67
+ version: version
68
+ )
57
69
  else
58
70
  super
59
71
  end
60
72
  end
61
73
 
62
74
  def formatted_name
63
- if singular_name.include?(".")
64
- "\"#{singular_name}\""
65
- else
66
- ":#{singular_name}"
67
- end
75
+ Fx::Generators::NameHelper.format_for_migration(singular_name)
68
76
  end
69
77
  end
70
78
 
71
79
  private
72
80
 
73
81
  def function_definition_path
74
- @_function_definition_path ||= Rails.root.join(*%w(db functions))
82
+ @_function_definition_path ||= Rails.root.join(*DEFINITION_PATH)
75
83
  end
76
84
 
77
- def version_regex
78
- /\A#{file_name}_v(?<version>\d+)\.sql\z/
85
+ def version_helper
86
+ @_version_helper ||= Fx::Generators::VersionHelper.new(
87
+ file_name: file_name,
88
+ definition_path: function_definition_path
89
+ )
79
90
  end
80
91
 
81
- def updating_existing_function?
82
- previous_version > 0
83
- end
84
-
85
- def creating_new_function?
86
- previous_version == 0
92
+ def migration_helper
93
+ @_migration_helper ||= Fx::Generators::MigrationHelper.new(options)
87
94
  end
88
95
 
89
96
  def definition
90
- Fx::Definition.new(name: file_name, version: version)
97
+ version_helper.definition_for_version(version: version, type: :function)
91
98
  end
92
99
 
93
100
  def previous_definition
94
- Fx::Definition.new(name: file_name, version: previous_version)
101
+ version_helper.definition_for_version(version: previous_version, type: :function)
102
+ end
103
+
104
+ def updating_existing_function?
105
+ version_helper.updating_existing?
106
+ end
107
+
108
+ def creating_new_function?
109
+ version_helper.creating_new?
95
110
  end
96
111
  end
97
112
  end
@@ -1,4 +1,4 @@
1
- class <%= migration_class_name %> < ActiveRecord::Migration
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
2
  def change
3
3
  create_function <%= formatted_name %>
4
4
  end
@@ -1,4 +1,4 @@
1
- class <%= migration_class_name %> < ActiveRecord::Migration
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
2
  def change
3
3
  update_function <%= formatted_name %>, version: <%= version %>, revert_to_version: <%= previous_version %>
4
4
  end
@@ -0,0 +1,45 @@
1
+ module Fx
2
+ module Generators
3
+ # @api private
4
+ class MigrationHelper
5
+ def initialize(options)
6
+ @options = options
7
+ end
8
+
9
+ def skip_creation?
10
+ !should_create_migration?
11
+ end
12
+
13
+ def update_migration_class_name(object_type:, class_name:, version:)
14
+ "Update#{object_type.capitalize}#{class_name}ToVersion#{version}"
15
+ end
16
+
17
+ def migration_template_info(
18
+ object_type:,
19
+ file_name:,
20
+ updating_existing:,
21
+ version:
22
+ )
23
+ if updating_existing
24
+ {
25
+ template: "db/migrate/update_#{object_type}.erb",
26
+ filename: "db/migrate/update_#{object_type}_#{file_name}_to_version_#{version}.rb"
27
+ }
28
+ else
29
+ {
30
+ template: "db/migrate/create_#{object_type}.erb",
31
+ filename: "db/migrate/create_#{object_type}_#{file_name}.rb"
32
+ }
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :options
39
+
40
+ def should_create_migration?
41
+ options.fetch(:migration, true)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,33 @@
1
+ module Fx
2
+ module Generators
3
+ # @api private
4
+ class NameHelper
5
+ def self.format_for_migration(name)
6
+ if name.include?(".")
7
+ "\"#{name}\""
8
+ else
9
+ ":#{name}"
10
+ end
11
+ end
12
+
13
+ def self.format_table_name_from_hash(table_hash)
14
+ name = table_hash["table_name"] || table_hash["on"]
15
+
16
+ if name.nil?
17
+ raise(
18
+ ArgumentError,
19
+ "Either `table_name:NAME` or `on:NAME` must be specified"
20
+ )
21
+ end
22
+
23
+ format_for_migration(name)
24
+ end
25
+
26
+ def self.validate_and_format(name)
27
+ raise ArgumentError, "Name cannot be blank" if name.blank?
28
+
29
+ format_for_migration(name)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -5,6 +5,8 @@ Description:
5
5
  If a trigger of the given name already exists, create a new version of the
6
6
  trigger and a migration to replace the old version with the new.
7
7
 
8
+ When --no-migration is passed, skips generating a migration.
9
+
8
10
  Examples:
9
11
 
10
12
  rails generate fx:trigger test