fx-jets 0.6.3s
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 +7 -0
- data/.gitignore +10 -0
- data/.hound.yml +2 -0
- data/.rspec +2 -0
- data/.rubocop.yml +648 -0
- data/.travis.yml +60 -0
- data/.yardopts +4 -0
- data/Appraisals +45 -0
- data/CONTRIBUTING.md +15 -0
- data/Gemfile +4 -0
- data/LICENSE +18 -0
- data/README.md +1 -0
- data/Rakefile +23 -0
- data/bin/appraisal +17 -0
- data/bin/console +14 -0
- data/bin/rake +17 -0
- data/bin/rspec +17 -0
- data/bin/setup +13 -0
- data/bin/yard +17 -0
- data/fx.gemspec +39 -0
- data/gemfiles/rails42.gemfile +10 -0
- data/gemfiles/rails50.gemfile +8 -0
- data/gemfiles/rails51.gemfile +8 -0
- data/gemfiles/rails52.gemfile +8 -0
- data/gemfiles/rails60.gemfile +8 -0
- data/gemfiles/rails61.gemfile +8 -0
- data/gemfiles/rails_edge.gemfile +8 -0
- data/lib/fx/adapters/postgres/connection.rb +16 -0
- data/lib/fx/adapters/postgres/functions.rb +59 -0
- data/lib/fx/adapters/postgres/triggers.rb +57 -0
- data/lib/fx/adapters/postgres.rb +167 -0
- data/lib/fx/command_recorder/arguments.rb +43 -0
- data/lib/fx/command_recorder/function.rb +30 -0
- data/lib/fx/command_recorder/trigger.rb +30 -0
- data/lib/fx/command_recorder.rb +24 -0
- data/lib/fx/configuration.rb +48 -0
- data/lib/fx/definition.rb +46 -0
- data/lib/fx/function.rb +26 -0
- data/lib/fx/railtie.rb +15 -0
- data/lib/fx/schema_dumper/function.rb +38 -0
- data/lib/fx/schema_dumper/trigger.rb +29 -0
- data/lib/fx/schema_dumper.rb +10 -0
- data/lib/fx/statements/function.rb +115 -0
- data/lib/fx/statements/trigger.rb +146 -0
- data/lib/fx/statements.rb +11 -0
- data/lib/fx/trigger.rb +26 -0
- data/lib/fx/version.rb +4 -0
- data/lib/fx.rb +43 -0
- data/lib/generators/fx/function/USAGE +11 -0
- data/lib/generators/fx/function/function_generator.rb +120 -0
- data/lib/generators/fx/function/templates/db/migrate/create_function.erb +5 -0
- data/lib/generators/fx/function/templates/db/migrate/update_function.erb +5 -0
- data/lib/generators/fx/trigger/USAGE +20 -0
- data/lib/generators/fx/trigger/templates/db/migrate/create_trigger.erb +5 -0
- data/lib/generators/fx/trigger/templates/db/migrate/update_trigger.erb +5 -0
- data/lib/generators/fx/trigger/trigger_generator.rb +130 -0
- data/lib/generators.rb +11 -0
- data/spec/acceptance/user_manages_functions_spec.rb +57 -0
- data/spec/acceptance/user_manages_triggers_spec.rb +51 -0
- data/spec/acceptance_helper.rb +62 -0
- data/spec/dummy/.gitignore +16 -0
- data/spec/dummy/Rakefile +13 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +15 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +9 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/.keep +0 -0
- data/spec/features/functions/migrations_spec.rb +65 -0
- data/spec/features/functions/revert_spec.rb +75 -0
- data/spec/features/triggers/migrations_spec.rb +56 -0
- data/spec/features/triggers/revert_spec.rb +95 -0
- data/spec/fx/adapters/postgres/functions_spec.rb +37 -0
- data/spec/fx/adapters/postgres/triggers_spec.rb +45 -0
- data/spec/fx/adapters/postgres_spec.rb +146 -0
- data/spec/fx/command_recorder/arguments_spec.rb +41 -0
- data/spec/fx/command_recorder_spec.rb +171 -0
- data/spec/fx/configuration_spec.rb +21 -0
- data/spec/fx/definition_spec.rb +134 -0
- data/spec/fx/function_spec.rb +68 -0
- data/spec/fx/schema_dumper/function_spec.rb +80 -0
- data/spec/fx/schema_dumper/trigger_spec.rb +40 -0
- data/spec/fx/statements/function_spec.rb +103 -0
- data/spec/fx/statements/trigger_spec.rb +132 -0
- data/spec/fx/trigger_spec.rb +55 -0
- data/spec/generators/fx/function/function_generator_spec.rb +46 -0
- data/spec/generators/fx/trigger/trigger_generator_spec.rb +59 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/definition_helpers.rb +37 -0
- data/spec/support/generator_setup.rb +11 -0
- data/spec/support/migration_helpers.rb +25 -0
- metadata +357 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
module Fx
|
|
2
|
+
module Statements
|
|
3
|
+
# Methods that are made available in migrations for managing Fx triggers.
|
|
4
|
+
module Trigger
|
|
5
|
+
# @api private
|
|
6
|
+
DEFINTION_TYPE = "trigger".freeze
|
|
7
|
+
|
|
8
|
+
# Create a new database trigger.
|
|
9
|
+
#
|
|
10
|
+
# @param name [String, Symbol] The name of the database trigger.
|
|
11
|
+
# @param version [Fixnum] The version number of the trigger, used to
|
|
12
|
+
# find the definition file in `db/triggers`. This defaults to `1` if
|
|
13
|
+
# not provided.
|
|
14
|
+
# @param sql_definition [String] The SQL query for the function. An error
|
|
15
|
+
# will be raised if `sql_definition` and `version` are both set,
|
|
16
|
+
# as they are mutually exclusive.
|
|
17
|
+
# @return The database response from executing the create statement.
|
|
18
|
+
#
|
|
19
|
+
# @example Create trigger from `db/triggers/uppercase_users_name_v01.sql`
|
|
20
|
+
# create_trigger(:uppercase_users_name, version: 1)
|
|
21
|
+
#
|
|
22
|
+
# @example Create trigger from provided SQL string
|
|
23
|
+
# create_trigger(:uppercase_users_name, sql_definition: <<-SQL)
|
|
24
|
+
# CREATE TRIGGER uppercase_users_name
|
|
25
|
+
# BEFORE INSERT ON users
|
|
26
|
+
# FOR EACH ROW
|
|
27
|
+
# EXECUTE PROCEDURE uppercase_users_name();
|
|
28
|
+
# SQL
|
|
29
|
+
#
|
|
30
|
+
def create_trigger(name, options = {})
|
|
31
|
+
version = options[:version]
|
|
32
|
+
_on = options[:on]
|
|
33
|
+
sql_definition = options[:sql_definition]
|
|
34
|
+
|
|
35
|
+
if version.present? && sql_definition.present?
|
|
36
|
+
raise(
|
|
37
|
+
ArgumentError,
|
|
38
|
+
"sql_definition and version cannot both be set",
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if version.nil?
|
|
43
|
+
version = 1
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
sql_definition = sql_definition.strip_heredoc if sql_definition
|
|
47
|
+
sql_definition ||= Fx::Definition.new(
|
|
48
|
+
name: name,
|
|
49
|
+
version: version,
|
|
50
|
+
type: DEFINTION_TYPE,
|
|
51
|
+
).to_sql
|
|
52
|
+
|
|
53
|
+
Fx.database.create_trigger(sql_definition)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Drop a database trigger by name.
|
|
57
|
+
#
|
|
58
|
+
# @param name [String, Symbol] The name of the database trigger.
|
|
59
|
+
# @param on [String, Symbol] The name of the table the database trigger
|
|
60
|
+
# is associated with.
|
|
61
|
+
# @param revert_to_version [Fixnum] Used to reverse the `drop_trigger`
|
|
62
|
+
# command on `rake db:rollback`. The provided version will be passed as
|
|
63
|
+
# the `version` argument to {#create_trigger}.
|
|
64
|
+
# @return The database response from executing the drop statement.
|
|
65
|
+
#
|
|
66
|
+
# @example Drop a trigger, rolling back to version 3 on rollback
|
|
67
|
+
# drop_trigger(:log_inserts, on: :users, revert_to_version: 3)
|
|
68
|
+
#
|
|
69
|
+
def drop_trigger(name, options = {})
|
|
70
|
+
on = options.fetch(:on)
|
|
71
|
+
revert_to_version = options[:revert_to_version]
|
|
72
|
+
Fx.database.drop_trigger(name, on: on)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Update a database trigger to a new version.
|
|
76
|
+
#
|
|
77
|
+
# The existing trigger is dropped and recreated using the supplied `on`
|
|
78
|
+
# and `version` parameter.
|
|
79
|
+
#
|
|
80
|
+
# @param name [String, Symbol] The name of the database trigger.
|
|
81
|
+
# @param version [Fixnum] The version number of the trigger.
|
|
82
|
+
# @param on [String, Symbol] The name of the table the database trigger
|
|
83
|
+
# is associated with.
|
|
84
|
+
# @param sql_definition [String] The SQL query for the function. An error
|
|
85
|
+
# will be raised if `sql_definition` and `version` are both set,
|
|
86
|
+
# as they are mutually exclusive.
|
|
87
|
+
# @param revert_to_version [Fixnum] The version number to rollback to on
|
|
88
|
+
# `rake db rollback`
|
|
89
|
+
# @return The database response from executing the create statement.
|
|
90
|
+
#
|
|
91
|
+
# @example Update trigger to a given version
|
|
92
|
+
# update_trigger(
|
|
93
|
+
# :log_inserts,
|
|
94
|
+
# on: :users,
|
|
95
|
+
# version: 3,
|
|
96
|
+
# revert_to_version: 2,
|
|
97
|
+
# )
|
|
98
|
+
#
|
|
99
|
+
# @example Update trigger from provided SQL string
|
|
100
|
+
# update_trigger(:uppercase_users_name, sql_definition: <<-SQL)
|
|
101
|
+
# CREATE TRIGGER uppercase_users_name
|
|
102
|
+
# BEFORE INSERT ON users
|
|
103
|
+
# FOR EACH ROW
|
|
104
|
+
# EXECUTE PROCEDURE uppercase_users_name();
|
|
105
|
+
# SQL
|
|
106
|
+
#
|
|
107
|
+
def update_trigger(name, options = {})
|
|
108
|
+
version = options[:version]
|
|
109
|
+
on = options[:on]
|
|
110
|
+
sql_definition = options[:sql_definition]
|
|
111
|
+
revert_to_version = options[:revert_to_version]
|
|
112
|
+
|
|
113
|
+
if version.nil? && sql_definition.nil?
|
|
114
|
+
raise(
|
|
115
|
+
ArgumentError,
|
|
116
|
+
"version or sql_definition must be specified",
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
if version.present? && sql_definition.present?
|
|
121
|
+
raise(
|
|
122
|
+
ArgumentError,
|
|
123
|
+
"sql_definition and version cannot both be set",
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
if on.nil?
|
|
128
|
+
raise ArgumentError, "on is required"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
sql_definition = sql_definition.strip_heredoc if sql_definition
|
|
132
|
+
sql_definition ||= Fx::Definition.new(
|
|
133
|
+
name: name,
|
|
134
|
+
version: version,
|
|
135
|
+
type: DEFINTION_TYPE,
|
|
136
|
+
).to_sql
|
|
137
|
+
|
|
138
|
+
Fx.database.update_trigger(
|
|
139
|
+
name,
|
|
140
|
+
on: on,
|
|
141
|
+
sql_definition: sql_definition,
|
|
142
|
+
)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
data/lib/fx/trigger.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Fx
|
|
2
|
+
# @api private
|
|
3
|
+
class Trigger
|
|
4
|
+
include Comparable
|
|
5
|
+
|
|
6
|
+
attr_reader :name, :definition
|
|
7
|
+
delegate :<=>, to: :name
|
|
8
|
+
|
|
9
|
+
def initialize(function_row)
|
|
10
|
+
@name = function_row.fetch("name")
|
|
11
|
+
@definition = function_row.fetch("definition")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def ==(other)
|
|
15
|
+
name == other.name && definition == other.definition
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_schema
|
|
19
|
+
<<-SCHEMA
|
|
20
|
+
create_trigger :#{name}, sql_definition: <<-\SQL
|
|
21
|
+
#{definition}
|
|
22
|
+
SQL
|
|
23
|
+
SCHEMA
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/fx/version.rb
ADDED
data/lib/fx.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require "fx/version"
|
|
2
|
+
require "fx/adapters/postgres"
|
|
3
|
+
require "fx/command_recorder"
|
|
4
|
+
require "fx/configuration"
|
|
5
|
+
require "fx/definition"
|
|
6
|
+
require "fx/function"
|
|
7
|
+
require "fx/statements"
|
|
8
|
+
require "fx/schema_dumper"
|
|
9
|
+
require "fx/trigger"
|
|
10
|
+
require "fx/railtie"
|
|
11
|
+
|
|
12
|
+
# F(x) adds methods `ActiveRecord::Migration` to create and manage database
|
|
13
|
+
# triggers and functions in Rails applications.
|
|
14
|
+
module Fx
|
|
15
|
+
# Hooks Fx into Rails.
|
|
16
|
+
#
|
|
17
|
+
# Enables fx migration methods, migration reversability, and `schema.rb`
|
|
18
|
+
# dumping.
|
|
19
|
+
def self.load
|
|
20
|
+
ActiveRecord::Migration::CommandRecorder.send(
|
|
21
|
+
:include,
|
|
22
|
+
Fx::CommandRecorder,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
ActiveRecord::SchemaDumper.send(
|
|
26
|
+
:prepend,
|
|
27
|
+
Fx::SchemaDumper,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.send(
|
|
31
|
+
:include,
|
|
32
|
+
Fx::Statements,
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The current database adapter used by F(x).
|
|
37
|
+
#
|
|
38
|
+
# This defaults to {Fx::Adapters::Postgres} but can be overridden
|
|
39
|
+
# via {Configuration}.
|
|
40
|
+
def self.database
|
|
41
|
+
configuration.database
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Create a new database function for your application. This will create a new
|
|
3
|
+
function definition file and the accompanying migration.
|
|
4
|
+
|
|
5
|
+
When --no-migration is passed, skips generating a migration.
|
|
6
|
+
|
|
7
|
+
Examples:
|
|
8
|
+
rails generate fx:function test
|
|
9
|
+
|
|
10
|
+
create: db/functions/test_v01.sql
|
|
11
|
+
create: db/migrate/[TIMESTAMP]_create_test.rb
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
require "rails/generators/active_record"
|
|
3
|
+
|
|
4
|
+
module Fx
|
|
5
|
+
module Generators
|
|
6
|
+
# @api private
|
|
7
|
+
class FunctionGenerator < Rails::Generators::NamedBase
|
|
8
|
+
include Rails::Generators::Migration
|
|
9
|
+
source_root File.expand_path("../templates", __FILE__)
|
|
10
|
+
|
|
11
|
+
class_option :migration, type: :boolean
|
|
12
|
+
|
|
13
|
+
def create_functions_directory
|
|
14
|
+
unless function_definition_path.exist?
|
|
15
|
+
empty_directory(function_definition_path)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create_function_definition
|
|
20
|
+
if creating_new_function?
|
|
21
|
+
create_file definition.path
|
|
22
|
+
else
|
|
23
|
+
copy_file previous_definition.full_path, definition.full_path
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def create_migration_file
|
|
28
|
+
return if skip_migration_creation?
|
|
29
|
+
if updating_existing_function?
|
|
30
|
+
migration_template(
|
|
31
|
+
"db/migrate/update_function.erb",
|
|
32
|
+
"db/migrate/update_function_#{file_name}_to_version_#{version}.rb",
|
|
33
|
+
)
|
|
34
|
+
else
|
|
35
|
+
migration_template(
|
|
36
|
+
"db/migrate/create_function.erb",
|
|
37
|
+
"db/migrate/create_function_#{file_name}.rb",
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.next_migration_number(dir)
|
|
43
|
+
::ActiveRecord::Generators::Base.next_migration_number(dir)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
no_tasks do
|
|
47
|
+
def previous_version
|
|
48
|
+
@_previous_version ||= Dir.entries(function_definition_path).
|
|
49
|
+
map { |name| version_regex.match(name).try(:[], "version").to_i }.
|
|
50
|
+
max
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def version
|
|
54
|
+
@_version ||= previous_version.next
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def migration_class_name
|
|
58
|
+
if updating_existing_function?
|
|
59
|
+
"UpdateFunction#{class_name}ToVersion#{version}"
|
|
60
|
+
else
|
|
61
|
+
super
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def activerecord_migration_class
|
|
66
|
+
if ActiveRecord::Migration.respond_to?(:current_version)
|
|
67
|
+
"ActiveRecord::Migration[#{ActiveRecord::Migration.current_version}]"
|
|
68
|
+
else
|
|
69
|
+
"ActiveRecord::Migration"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def formatted_name
|
|
74
|
+
if singular_name.include?(".")
|
|
75
|
+
"\"#{singular_name}\""
|
|
76
|
+
else
|
|
77
|
+
":#{singular_name}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def function_definition_path
|
|
85
|
+
@_function_definition_path ||= Jets.root.join(*%w(db functions))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def version_regex
|
|
89
|
+
/\A#{file_name}_v(?<version>\d+)\.sql\z/
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def updating_existing_function?
|
|
93
|
+
previous_version > 0
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def creating_new_function?
|
|
97
|
+
previous_version == 0
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def definition
|
|
101
|
+
Fx::Definition.new(name: file_name, version: version)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def previous_definition
|
|
105
|
+
Fx::Definition.new(name: file_name, version: previous_version)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Skip creating migration file if:
|
|
109
|
+
# - migrations option is nil or false
|
|
110
|
+
def skip_migration_creation?
|
|
111
|
+
!migration
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# True unless explicitly false
|
|
115
|
+
def migration
|
|
116
|
+
options[:migration] != false
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Create a new database trigger for your application. This will create a new
|
|
3
|
+
trigger definition file and the accompanying migration.
|
|
4
|
+
|
|
5
|
+
If a trigger of the given name already exists, create a new version of the
|
|
6
|
+
trigger and a migration to replace the old version with the new.
|
|
7
|
+
|
|
8
|
+
When --no-migration is passed, skips generating a migration.
|
|
9
|
+
|
|
10
|
+
Examples:
|
|
11
|
+
|
|
12
|
+
rails generate fx:trigger test
|
|
13
|
+
|
|
14
|
+
create: db/triggers/test_v01.sql
|
|
15
|
+
create: db/migrate/[TIMESTAMP]_create_trigger_test.rb
|
|
16
|
+
|
|
17
|
+
rails generate fx:trigger test
|
|
18
|
+
|
|
19
|
+
create: db/triggers/test_v02.sql
|
|
20
|
+
create: db/migrate/[TIMESTAMP]_update_trigger_test_to_version_2.rb
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
require "rails/generators/active_record"
|
|
3
|
+
|
|
4
|
+
module Fx
|
|
5
|
+
module Generators
|
|
6
|
+
# @api private
|
|
7
|
+
class TriggerGenerator < Rails::Generators::NamedBase
|
|
8
|
+
include Rails::Generators::Migration
|
|
9
|
+
source_root File.expand_path("../templates", __FILE__)
|
|
10
|
+
argument :table_name, type: :hash, required: true
|
|
11
|
+
|
|
12
|
+
class_option :migration, type: :boolean
|
|
13
|
+
|
|
14
|
+
def create_triggers_directory
|
|
15
|
+
unless trigger_definition_path.exist?
|
|
16
|
+
empty_directory(trigger_definition_path)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create_trigger_definition
|
|
21
|
+
create_file definition.path
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_migration_file
|
|
25
|
+
return if skip_migration_creation?
|
|
26
|
+
if updating_existing_trigger?
|
|
27
|
+
migration_template(
|
|
28
|
+
"db/migrate/update_trigger.erb",
|
|
29
|
+
"db/migrate/update_trigger_#{file_name}_to_version_#{version}.rb"
|
|
30
|
+
)
|
|
31
|
+
else
|
|
32
|
+
migration_template(
|
|
33
|
+
"db/migrate/create_trigger.erb",
|
|
34
|
+
"db/migrate/create_trigger_#{file_name}.rb"
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.next_migration_number(dir)
|
|
40
|
+
::ActiveRecord::Generators::Base.next_migration_number(dir)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
no_tasks do
|
|
44
|
+
def previous_version
|
|
45
|
+
@_previous_version ||= Dir.entries(trigger_definition_path).
|
|
46
|
+
map { |name| version_regex.match(name).try(:[], "version").to_i }.
|
|
47
|
+
max
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def version
|
|
51
|
+
@_version ||= previous_version.next
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def migration_class_name
|
|
55
|
+
if updating_existing_trigger?
|
|
56
|
+
"UpdateTrigger#{class_name}ToVersion#{version}"
|
|
57
|
+
else
|
|
58
|
+
super
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def activerecord_migration_class
|
|
63
|
+
if ActiveRecord::Migration.respond_to?(:current_version)
|
|
64
|
+
"ActiveRecord::Migration[#{ActiveRecord::Migration.current_version}]"
|
|
65
|
+
else
|
|
66
|
+
"ActiveRecord::Migration"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def formatted_name
|
|
71
|
+
if singular_name.include?(".")
|
|
72
|
+
"\"#{singular_name}\""
|
|
73
|
+
else
|
|
74
|
+
":#{singular_name}"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def formatted_table_name
|
|
79
|
+
name = table_name["table_name"] || table_name["on"]
|
|
80
|
+
|
|
81
|
+
if name.nil?
|
|
82
|
+
raise(
|
|
83
|
+
ArgumentError,
|
|
84
|
+
"Either `table_name:NAME` or `on:NAME` must be specified",
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if name.include?(".")
|
|
89
|
+
"\"#{name}\""
|
|
90
|
+
else
|
|
91
|
+
":#{name}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def version_regex
|
|
99
|
+
/\A#{file_name}_v(?<version>\d+)\.sql\z/
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def updating_existing_trigger?
|
|
103
|
+
previous_version > 0
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def definition
|
|
107
|
+
Fx::Definition.new(
|
|
108
|
+
name: file_name,
|
|
109
|
+
version: version,
|
|
110
|
+
type: "trigger",
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def trigger_definition_path
|
|
115
|
+
@_trigger_definition_path ||= Jets.root.join(*["db", "triggers"])
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Skip creating migration file if:
|
|
119
|
+
# - migrations option is nil or false
|
|
120
|
+
def skip_migration_creation?
|
|
121
|
+
!migration
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# True unless explicitly false
|
|
125
|
+
def migration
|
|
126
|
+
options[:migration] != false
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
data/lib/generators.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Fx
|
|
2
|
+
# Fx provides generators for creating and updating functions and triggers.
|
|
3
|
+
#
|
|
4
|
+
# See:
|
|
5
|
+
#
|
|
6
|
+
# * {file:lib/generators/fx/function/USAGE Function Generator}
|
|
7
|
+
# * {file:lib/generators/fx/trigger/USAGE Trigger Generator}
|
|
8
|
+
# * {file:README.md README}
|
|
9
|
+
module Generators
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require "acceptance_helper"
|
|
2
|
+
|
|
3
|
+
describe "User manages functions" do
|
|
4
|
+
it "handles simple functions" do
|
|
5
|
+
successfully "rails generate fx:function test"
|
|
6
|
+
write_function_definition "test_v01", <<-EOS
|
|
7
|
+
CREATE OR REPLACE FUNCTION test()
|
|
8
|
+
RETURNS text AS $$
|
|
9
|
+
BEGIN
|
|
10
|
+
RETURN 'test';
|
|
11
|
+
END;
|
|
12
|
+
$$ LANGUAGE plpgsql;
|
|
13
|
+
EOS
|
|
14
|
+
successfully "rake db:migrate"
|
|
15
|
+
|
|
16
|
+
result = execute("SELECT * FROM test() AS result")
|
|
17
|
+
expect(result).to eq("result" => "test")
|
|
18
|
+
|
|
19
|
+
successfully "rails generate fx:function test"
|
|
20
|
+
verify_identical_definitions(
|
|
21
|
+
"db/functions/test_v01.sql",
|
|
22
|
+
"db/functions/test_v02.sql",
|
|
23
|
+
)
|
|
24
|
+
write_function_definition "test_v02", <<-EOS
|
|
25
|
+
CREATE OR REPLACE FUNCTION test()
|
|
26
|
+
RETURNS text AS $$
|
|
27
|
+
BEGIN
|
|
28
|
+
RETURN 'testest';
|
|
29
|
+
END;
|
|
30
|
+
$$ LANGUAGE plpgsql;
|
|
31
|
+
EOS
|
|
32
|
+
successfully "rake db:migrate"
|
|
33
|
+
|
|
34
|
+
result = execute("SELECT * FROM test() AS result")
|
|
35
|
+
expect(result).to eq("result" => "testest")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "handles functions with arguments" do
|
|
39
|
+
successfully "rails generate fx:function adder"
|
|
40
|
+
write_function_definition "adder_v01", <<-EOS
|
|
41
|
+
CREATE FUNCTION adder(x int, y int)
|
|
42
|
+
RETURNS int AS $$
|
|
43
|
+
BEGIN
|
|
44
|
+
RETURN $1 + $2;
|
|
45
|
+
END;
|
|
46
|
+
$$ LANGUAGE plpgsql;
|
|
47
|
+
EOS
|
|
48
|
+
successfully "rake db:migrate"
|
|
49
|
+
|
|
50
|
+
result = execute("SELECT * FROM adder(1, 2) AS result")
|
|
51
|
+
result["result"] = result["result"].to_i
|
|
52
|
+
expect(result).to eq("result" => 3)
|
|
53
|
+
|
|
54
|
+
successfully "rails destroy fx:function adder"
|
|
55
|
+
successfully "rake db:migrate"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require "acceptance_helper"
|
|
2
|
+
|
|
3
|
+
describe "User manages triggers" do
|
|
4
|
+
it "handles simple triggers" do
|
|
5
|
+
successfully "rails generate model user name:string upper_name:string"
|
|
6
|
+
successfully "rails generate fx:function uppercase_users_name"
|
|
7
|
+
write_function_definition "uppercase_users_name_v01", <<-EOS
|
|
8
|
+
CREATE OR REPLACE FUNCTION uppercase_users_name()
|
|
9
|
+
RETURNS trigger AS $$
|
|
10
|
+
BEGIN
|
|
11
|
+
NEW.upper_name = UPPER(NEW.name);
|
|
12
|
+
RETURN NEW;
|
|
13
|
+
END;
|
|
14
|
+
$$ LANGUAGE plpgsql;
|
|
15
|
+
EOS
|
|
16
|
+
successfully "rails generate fx:trigger uppercase_users_name table_name:users"
|
|
17
|
+
write_trigger_definition "uppercase_users_name_v01", <<-EOS
|
|
18
|
+
CREATE TRIGGER uppercase_users_name
|
|
19
|
+
BEFORE INSERT ON users
|
|
20
|
+
FOR EACH ROW
|
|
21
|
+
EXECUTE PROCEDURE uppercase_users_name();
|
|
22
|
+
EOS
|
|
23
|
+
successfully "rake db:migrate"
|
|
24
|
+
|
|
25
|
+
execute <<-EOS
|
|
26
|
+
INSERT INTO users
|
|
27
|
+
(name, created_at, updated_at)
|
|
28
|
+
VALUES
|
|
29
|
+
('Bob', NOW(), NOW());
|
|
30
|
+
EOS
|
|
31
|
+
result = execute("SELECT upper_name FROM users WHERE name = 'Bob';")
|
|
32
|
+
expect(result).to eq("upper_name" => "BOB")
|
|
33
|
+
|
|
34
|
+
successfully "rails generate fx:trigger uppercase_users_name table_name:users"
|
|
35
|
+
write_trigger_definition "uppercase_users_name_v02", <<-EOS
|
|
36
|
+
CREATE TRIGGER uppercase_users_name
|
|
37
|
+
BEFORE UPDATE ON users
|
|
38
|
+
FOR EACH ROW
|
|
39
|
+
EXECUTE PROCEDURE uppercase_users_name();
|
|
40
|
+
EOS
|
|
41
|
+
successfully "rake db:migrate"
|
|
42
|
+
execute <<-EOS
|
|
43
|
+
UPDATE users
|
|
44
|
+
SET name = 'Alice'
|
|
45
|
+
WHERE id = 1;
|
|
46
|
+
EOS
|
|
47
|
+
|
|
48
|
+
result = execute("SELECT upper_name FROM users WHERE name = 'Alice';")
|
|
49
|
+
expect(result).to eq("upper_name" => "ALICE")
|
|
50
|
+
end
|
|
51
|
+
end
|