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.
- checksums.yaml +5 -5
- data/.github/scripts/check-eol +95 -0
- data/.github/workflows/ci.yml +61 -0
- data/.github/workflows/eol-check.yml +30 -0
- data/.rspec +1 -1
- data/.standard.yml +3 -0
- data/CHANGELOG.md +186 -0
- data/CONTRIBUTING.md +13 -4
- data/Gemfile +21 -2
- data/LICENSE +18 -0
- data/README.md +99 -14
- data/Rakefile +11 -1
- data/bin/rake +2 -3
- data/bin/rspec +13 -3
- data/bin/setup +1 -4
- data/bin/standardrb +27 -0
- data/bin/yard +13 -3
- data/fx.gemspec +20 -27
- data/lib/fx/adapters/postgres/connection.rb +6 -0
- data/lib/fx/adapters/postgres/functions.rb +16 -28
- data/lib/fx/adapters/postgres/query_executor.rb +34 -0
- data/lib/fx/adapters/postgres/triggers.rb +14 -28
- data/lib/fx/adapters/postgres.rb +30 -17
- data/lib/fx/command_recorder.rb +104 -13
- data/lib/fx/configuration.rb +10 -25
- data/lib/fx/definition.rb +32 -10
- data/lib/fx/function.rb +23 -9
- data/lib/fx/railtie.rb +15 -0
- data/lib/fx/schema_dumper.rb +33 -10
- data/lib/fx/statements.rb +213 -11
- data/lib/fx/trigger.rb +5 -3
- data/lib/fx/version.rb +1 -1
- data/lib/fx.rb +40 -0
- data/lib/generators/fx/function/USAGE +2 -0
- data/lib/generators/fx/function/function_generator.rb +54 -39
- data/lib/generators/fx/function/templates/db/migrate/create_function.erb +1 -1
- data/lib/generators/fx/function/templates/db/migrate/update_function.erb +1 -1
- data/lib/generators/fx/migration_helper.rb +45 -0
- data/lib/generators/fx/name_helper.rb +33 -0
- data/lib/generators/fx/trigger/USAGE +2 -0
- data/lib/generators/fx/trigger/templates/db/migrate/create_trigger.erb +1 -1
- data/lib/generators/fx/trigger/templates/db/migrate/update_trigger.erb +1 -1
- data/lib/generators/fx/trigger/trigger_generator.rb +46 -51
- data/lib/generators/fx/version_helper.rb +55 -0
- data/spec/acceptance/user_manages_functions_spec.rb +26 -6
- data/spec/acceptance/user_manages_triggers_spec.rb +11 -11
- data/spec/acceptance_helper.rb +17 -11
- data/spec/dummy/Rakefile +8 -1
- data/spec/dummy/bin/bundle +2 -2
- data/spec/dummy/bin/rails +3 -3
- data/spec/dummy/bin/rake +2 -2
- data/spec/dummy/config/application.rb +2 -0
- data/spec/dummy/config/database.yml +2 -0
- data/spec/dummy/config.ru +1 -1
- data/spec/features/functions/migrations_spec.rb +9 -9
- data/spec/features/functions/revert_spec.rb +13 -13
- data/spec/features/triggers/migrations_spec.rb +11 -11
- data/spec/features/triggers/revert_spec.rb +21 -21
- data/spec/fx/adapters/postgres/functions_spec.rb +40 -0
- data/spec/fx/adapters/postgres/query_executor_spec.rb +75 -0
- data/spec/fx/adapters/postgres/triggers_spec.rb +48 -0
- data/spec/fx/adapters/postgres_spec.rb +115 -111
- data/spec/fx/command_recorder_spec.rb +39 -53
- data/spec/fx/configuration_spec.rb +20 -9
- data/spec/fx/definition_spec.rb +53 -38
- data/spec/fx/function_spec.rb +143 -0
- data/spec/fx/schema_dumper_spec.rb +241 -0
- data/spec/fx/statements_spec.rb +219 -0
- data/spec/fx/trigger_spec.rb +52 -0
- data/spec/fx_spec.rb +28 -0
- data/spec/generators/fx/function/function_generator_spec.rb +22 -10
- data/spec/generators/fx/migration_helper_spec.rb +108 -0
- data/spec/generators/fx/name_helper_spec.rb +114 -0
- data/spec/generators/fx/trigger/trigger_generator_spec.rb +55 -20
- data/spec/generators/fx/version_helper_spec.rb +157 -0
- data/spec/spec_helper.rb +16 -4
- data/spec/support/database_reset.rb +24 -0
- data/spec/support/definition_helpers.rb +5 -9
- data/spec/support/generator_setup.rb +46 -5
- data/spec/support/migration_helpers.rb +9 -1
- data/spec/support/warning_helper.rb +5 -0
- metadata +43 -215
- data/.ruby-version +0 -1
- data/.travis.yml +0 -27
- data/Appraisals +0 -21
- data/bin/appraisal +0 -17
- data/gemfiles/rails40.gemfile +0 -8
- data/gemfiles/rails40.gemfile.lock +0 -111
- data/gemfiles/rails41.gemfile +0 -8
- data/gemfiles/rails41.gemfile.lock +0 -113
- data/gemfiles/rails42.gemfile +0 -8
- data/gemfiles/rails42.gemfile.lock +0 -130
- data/gemfiles/rails50.gemfile +0 -8
- data/gemfiles/rails50.gemfile.lock +0 -126
- data/lib/fx/command_recorder/arguments.rb +0 -43
- data/lib/fx/command_recorder/function.rb +0 -30
- data/lib/fx/command_recorder/trigger.rb +0 -30
- data/lib/fx/schema_dumper/function.rb +0 -29
- data/lib/fx/schema_dumper/trigger.rb +0 -29
- data/lib/fx/statements/function.rb +0 -105
- data/lib/fx/statements/trigger.rb +0 -133
- data/spec/fx/command_recorder/arguments_spec.rb +0 -41
- data/spec/fx/schema_dumper/function_spec.rb +0 -22
- data/spec/fx/schema_dumper/trigger_spec.rb +0 -40
- data/spec/fx/statements/function_spec.rb +0 -103
- 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(
|
|
8
|
-
@name =
|
|
9
|
-
@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
|
-
|
|
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
|
-
|
|
18
|
-
create_function :#{name}, sql_definition:
|
|
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
|
data/lib/fx/schema_dumper.rb
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
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
|
-
|
|
9
|
-
|
|
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(
|
|
8
|
-
@name =
|
|
9
|
-
@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
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
|
|
@@ -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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
19
|
-
create_file
|
|
26
|
+
if version_helper.creating_new?
|
|
27
|
+
create_file(definition.path)
|
|
20
28
|
else
|
|
21
|
-
copy_file
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
+
version_helper.current_version
|
|
52
60
|
end
|
|
53
61
|
|
|
54
62
|
def migration_class_name
|
|
55
|
-
if
|
|
56
|
-
|
|
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
|
-
|
|
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(
|
|
82
|
+
@_function_definition_path ||= Rails.root.join(*DEFINITION_PATH)
|
|
75
83
|
end
|
|
76
84
|
|
|
77
|
-
def
|
|
78
|
-
|
|
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
|
|
82
|
-
|
|
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
|
-
|
|
97
|
+
version_helper.definition_for_version(version: version, type: :function)
|
|
91
98
|
end
|
|
92
99
|
|
|
93
100
|
def previous_definition
|
|
94
|
-
|
|
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
|
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
|