fx 0.8.0 → 0.10.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -7
  3. data/CHANGELOG.md +150 -0
  4. data/CONTRIBUTING.md +3 -3
  5. data/Gemfile +11 -1
  6. data/README.md +2 -0
  7. data/bin/rake +2 -3
  8. data/bin/rspec +13 -3
  9. data/bin/standardrb +27 -0
  10. data/bin/yard +13 -3
  11. data/fx.gemspec +10 -15
  12. data/lib/fx/adapters/postgres/connection.rb +20 -0
  13. data/lib/fx/adapters/postgres/functions.rb +11 -28
  14. data/lib/fx/adapters/postgres/query_executor.rb +34 -0
  15. data/lib/fx/adapters/postgres/triggers.rb +14 -29
  16. data/lib/fx/adapters/postgres.rb +16 -24
  17. data/lib/fx/command_recorder.rb +87 -6
  18. data/lib/fx/configuration.rb +2 -27
  19. data/lib/fx/definition.rb +16 -6
  20. data/lib/fx/function.rb +3 -3
  21. data/lib/fx/schema_dumper.rb +37 -5
  22. data/lib/fx/statements.rb +231 -6
  23. data/lib/fx/trigger.rb +3 -3
  24. data/lib/fx/version.rb +1 -1
  25. data/lib/fx.rb +30 -12
  26. data/lib/generators/fx/function/function_generator.rb +50 -53
  27. data/lib/generators/fx/function/templates/db/migrate/create_function.erb +1 -1
  28. data/lib/generators/fx/function/templates/db/migrate/update_function.erb +1 -1
  29. data/lib/generators/fx/migration_helper.rb +53 -0
  30. data/lib/generators/fx/name_helper.rb +33 -0
  31. data/lib/generators/fx/trigger/templates/db/migrate/create_trigger.erb +1 -1
  32. data/lib/generators/fx/trigger/templates/db/migrate/update_trigger.erb +1 -1
  33. data/lib/generators/fx/trigger/trigger_generator.rb +44 -67
  34. data/lib/generators/fx/version_helper.rb +55 -0
  35. data/spec/acceptance/user_manages_functions_spec.rb +7 -7
  36. data/spec/acceptance/user_manages_triggers_spec.rb +11 -11
  37. data/spec/acceptance_helper.rb +4 -4
  38. data/spec/dummy/config/application.rb +5 -1
  39. data/spec/features/functions/migrations_spec.rb +5 -5
  40. data/spec/features/functions/revert_spec.rb +5 -5
  41. data/spec/features/triggers/migrations_spec.rb +7 -7
  42. data/spec/features/triggers/revert_spec.rb +9 -9
  43. data/spec/fx/adapters/postgres/functions_spec.rb +33 -30
  44. data/spec/fx/adapters/postgres/query_executor_spec.rb +75 -0
  45. data/spec/fx/adapters/postgres/triggers_spec.rb +41 -38
  46. data/spec/fx/adapters/postgres_spec.rb +155 -115
  47. data/spec/fx/command_recorder_spec.rb +27 -25
  48. data/spec/fx/configuration_spec.rb +20 -9
  49. data/spec/fx/definition_spec.rb +31 -39
  50. data/spec/fx/function_spec.rb +45 -48
  51. data/spec/fx/schema_dumper_spec.rb +169 -0
  52. data/spec/fx/statements_spec.rb +217 -0
  53. data/spec/fx/trigger_spec.rb +37 -40
  54. data/spec/fx_spec.rb +28 -0
  55. data/spec/generators/fx/function/function_generator_spec.rb +11 -11
  56. data/spec/generators/fx/migration_helper_spec.rb +133 -0
  57. data/spec/generators/fx/name_helper_spec.rb +114 -0
  58. data/spec/generators/fx/trigger/trigger_generator_spec.rb +45 -22
  59. data/spec/generators/fx/version_helper_spec.rb +157 -0
  60. data/spec/spec_helper.rb +7 -0
  61. data/spec/support/definition_helpers.rb +2 -6
  62. data/spec/support/generator_setup.rb +46 -5
  63. data/spec/support/warning_helper.rb +5 -0
  64. metadata +40 -165
  65. data/lib/fx/command_recorder/arguments.rb +0 -43
  66. data/lib/fx/command_recorder/function.rb +0 -30
  67. data/lib/fx/command_recorder/trigger.rb +0 -30
  68. data/lib/fx/schema_dumper/function.rb +0 -38
  69. data/lib/fx/schema_dumper/trigger.rb +0 -29
  70. data/lib/fx/statements/function.rb +0 -113
  71. data/lib/fx/statements/trigger.rb +0 -144
  72. data/spec/fx/command_recorder/arguments_spec.rb +0 -41
  73. data/spec/fx/schema_dumper/function_spec.rb +0 -78
  74. data/spec/fx/schema_dumper/trigger_spec.rb +0 -40
  75. data/spec/fx/statements/function_spec.rb +0 -103
  76. data/spec/fx/statements/trigger_spec.rb +0 -132
@@ -1,144 +0,0 @@
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
- DEFINITION_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 FUNCTION 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: DEFINITION_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
- Fx.database.drop_trigger(name, on: on)
72
- end
73
-
74
- # Update a database trigger to a new version.
75
- #
76
- # The existing trigger is dropped and recreated using the supplied `on`
77
- # and `version` parameter.
78
- #
79
- # @param name [String, Symbol] The name of the database trigger.
80
- # @param version [Fixnum] The version number of the trigger.
81
- # @param on [String, Symbol] The name of the table the database trigger
82
- # is associated with.
83
- # @param sql_definition [String] The SQL query for the function. An error
84
- # will be raised if `sql_definition` and `version` are both set,
85
- # as they are mutually exclusive.
86
- # @param revert_to_version [Fixnum] The version number to rollback to on
87
- # `rake db rollback`
88
- # @return The database response from executing the create statement.
89
- #
90
- # @example Update trigger to a given version
91
- # update_trigger(
92
- # :log_inserts,
93
- # on: :users,
94
- # version: 3,
95
- # revert_to_version: 2,
96
- # )
97
- #
98
- # @example Update trigger from provided SQL string
99
- # update_trigger(:uppercase_users_name, sql_definition: <<-SQL)
100
- # CREATE TRIGGER uppercase_users_name
101
- # BEFORE INSERT ON users
102
- # FOR EACH ROW
103
- # EXECUTE FUNCTION uppercase_users_name();
104
- # SQL
105
- #
106
- def update_trigger(name, options = {})
107
- version = options[:version]
108
- on = options[:on]
109
- sql_definition = options[:sql_definition]
110
-
111
- if version.nil? && sql_definition.nil?
112
- raise(
113
- ArgumentError,
114
- "version or sql_definition must be specified"
115
- )
116
- end
117
-
118
- if version.present? && sql_definition.present?
119
- raise(
120
- ArgumentError,
121
- "sql_definition and version cannot both be set"
122
- )
123
- end
124
-
125
- if on.nil?
126
- raise ArgumentError, "on is required"
127
- end
128
-
129
- sql_definition = sql_definition.strip_heredoc if sql_definition
130
- sql_definition ||= Fx::Definition.new(
131
- name: name,
132
- version: version,
133
- type: DEFINITION_TYPE
134
- ).to_sql
135
-
136
- Fx.database.update_trigger(
137
- name,
138
- on: on,
139
- sql_definition: sql_definition
140
- )
141
- end
142
- end
143
- end
144
- end
@@ -1,41 +0,0 @@
1
- require "spec_helper"
2
-
3
- module Fx::CommandRecorder
4
- describe Arguments do
5
- describe "#function" do
6
- it "returns the function name" do
7
- raw_args = [:spaceships, {foo: :bar}]
8
- args = Arguments.new(raw_args)
9
-
10
- expect(args.function).to eq :spaceships
11
- end
12
- end
13
-
14
- describe "#revert_to_version" do
15
- it "is the revert_to_version from the keyword arguments" do
16
- raw_args = [:spaceships, {revert_to_version: 42}]
17
- args = Arguments.new(raw_args)
18
-
19
- expect(args.revert_to_version).to eq 42
20
- end
21
-
22
- it "is nil if the revert_to_version was not supplied" do
23
- raw_args = [:spaceships, {foo: :bar}]
24
- args = Arguments.new(raw_args)
25
-
26
- expect(args.revert_to_version).to be nil
27
- end
28
- end
29
-
30
- describe "#invert_version" do
31
- it "returns object with version set to revert_to_version" do
32
- raw_args = [:meatballs, {version: 42, revert_to_version: 15}]
33
-
34
- inverted_args = Arguments.new(raw_args).invert_version
35
-
36
- expect(inverted_args.version).to eq 15
37
- expect(inverted_args.revert_to_version).to be nil
38
- end
39
- end
40
- end
41
- end
@@ -1,78 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Fx::SchemaDumper::Function, :db do
4
- it "dumps a create_function for a function in the database" do
5
- sql_definition = <<-EOS
6
- CREATE OR REPLACE FUNCTION my_function()
7
- RETURNS text AS $$
8
- BEGIN
9
- RETURN 'test';
10
- END;
11
- $$ LANGUAGE plpgsql;
12
- EOS
13
- connection.create_function :my_function, sql_definition: sql_definition
14
- connection.create_table :my_table
15
- stream = StringIO.new
16
- output = stream.string
17
-
18
- ActiveRecord::SchemaDumper.dump(connection, stream)
19
-
20
- expect(output).to(
21
- match(/table "my_table".*function :my_function.*RETURN 'test';/m)
22
- )
23
- end
24
-
25
- it "dumps a create_function for a function in the database" do
26
- Fx.configuration.dump_functions_at_beginning_of_schema = true
27
- sql_definition = <<-EOS
28
- CREATE OR REPLACE FUNCTION my_function()
29
- RETURNS text AS $$
30
- BEGIN
31
- RETURN 'test';
32
- END;
33
- $$ LANGUAGE plpgsql;
34
- EOS
35
- connection.create_function :my_function, sql_definition: sql_definition
36
- connection.create_table :my_table
37
- stream = StringIO.new
38
- output = stream.string
39
-
40
- ActiveRecord::SchemaDumper.dump(connection, stream)
41
-
42
- expect(output).to(
43
- match(/function :my_function.*RETURN 'test';.*table "my_table"/m)
44
- )
45
- ensure
46
- Fx.configuration.dump_functions_at_beginning_of_schema = false
47
- end
48
-
49
- it "does not dump a create_function for aggregates in the database" do
50
- sql_definition = <<-EOS
51
- CREATE OR REPLACE FUNCTION test(text, text)
52
- RETURNS text AS $$
53
- BEGIN
54
- RETURN 'test';
55
- END;
56
- $$ LANGUAGE plpgsql;
57
- EOS
58
-
59
- aggregate_sql_definition = <<-EOS
60
- CREATE AGGREGATE aggregate_test(text)
61
- (
62
- sfunc = test,
63
- stype = text
64
- );
65
- EOS
66
-
67
- connection.create_function :test, sql_definition: sql_definition
68
- connection.execute aggregate_sql_definition
69
- stream = StringIO.new
70
-
71
- ActiveRecord::SchemaDumper.dump(connection, stream)
72
-
73
- output = stream.string
74
- expect(output).to include "create_function :test, sql_definition: <<-'SQL'"
75
- expect(output).to include "RETURN 'test';"
76
- expect(output).not_to include "aggregate_test"
77
- end
78
- end
@@ -1,40 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Fx::SchemaDumper::Trigger, :db do
4
- it "dumps a create_trigger for a trigger in the database" do
5
- connection.execute <<-EOS
6
- CREATE TABLE users (
7
- id int PRIMARY KEY,
8
- name varchar(256),
9
- upper_name varchar(256)
10
- );
11
- EOS
12
- Fx.database.create_function <<-EOS
13
- CREATE OR REPLACE FUNCTION uppercase_users_name()
14
- RETURNS trigger AS $$
15
- BEGIN
16
- NEW.upper_name = UPPER(NEW.name);
17
- RETURN NEW;
18
- END;
19
- $$ LANGUAGE plpgsql;
20
- EOS
21
- sql_definition = <<-EOS
22
- CREATE TRIGGER uppercase_users_name
23
- BEFORE INSERT ON users
24
- FOR EACH ROW
25
- EXECUTE FUNCTION uppercase_users_name();
26
- EOS
27
- connection.create_trigger(
28
- :uppercase_users_name,
29
- sql_definition: sql_definition
30
- )
31
- stream = StringIO.new
32
-
33
- ActiveRecord::SchemaDumper.dump(connection, stream)
34
-
35
- output = stream.string
36
- expect(output).to include "create_trigger :uppercase_users_name"
37
- expect(output).to include "sql_definition: <<-SQL"
38
- expect(output).to include "EXECUTE FUNCTION uppercase_users_name()"
39
- end
40
- end
@@ -1,103 +0,0 @@
1
- require "spec_helper"
2
- require "fx/statements/function"
3
-
4
- describe Fx::Statements::Function, :db do
5
- describe "#create_function" do
6
- it "creates a function from a file" do
7
- database = stubbed_database
8
- definition = stubbed_definition
9
-
10
- connection.create_function(:test)
11
-
12
- expect(database).to have_received(:create_function)
13
- .with(definition.to_sql)
14
- expect(Fx::Definition).to have_received(:new)
15
- .with(name: :test, version: 1)
16
- end
17
-
18
- it "allows creating a function with a specific version" do
19
- database = stubbed_database
20
- definition = stubbed_definition
21
-
22
- connection.create_function(:test, version: 2)
23
-
24
- expect(database).to have_received(:create_function)
25
- .with(definition.to_sql)
26
- expect(Fx::Definition).to have_received(:new)
27
- .with(name: :test, version: 2)
28
- end
29
-
30
- it "raises an error if both arguments are nil" do
31
- expect {
32
- connection.create_function(
33
- :whatever,
34
- version: nil,
35
- sql_definition: nil
36
- )
37
- }.to raise_error(
38
- ArgumentError,
39
- /version or sql_definition must be specified/
40
- )
41
- end
42
- end
43
-
44
- describe "#drop_function" do
45
- it "drops the function" do
46
- database = stubbed_database
47
-
48
- connection.drop_function(:test)
49
-
50
- expect(database).to have_received(:drop_function).with(:test)
51
- end
52
- end
53
-
54
- describe "#update_function" do
55
- it "updates the function" do
56
- database = stubbed_database
57
- definition = stubbed_definition
58
-
59
- connection.update_function(:test, version: 3)
60
-
61
- expect(database).to have_received(:update_function)
62
- .with(:test, definition.to_sql)
63
- expect(Fx::Definition).to have_received(:new)
64
- .with(name: :test, version: 3)
65
- end
66
-
67
- it "updates a function from a text definition" do
68
- database = stubbed_database
69
-
70
- connection.update_function(:test, sql_definition: "a definition")
71
-
72
- expect(database).to have_received(:update_function).with(
73
- :test,
74
- "a definition"
75
- )
76
- end
77
-
78
- it "raises an error if not supplied a version" do
79
- expect {
80
- connection.update_function(
81
- :whatever,
82
- version: nil,
83
- sql_definition: nil
84
- )
85
- }.to raise_error(
86
- ArgumentError,
87
- /version or sql_definition must be specified/
88
- )
89
- end
90
- end
91
-
92
- def stubbed_database
93
- instance_spy("StubbedDatabase").tap do |stubbed_database|
94
- allow(Fx).to receive(:database).and_return(stubbed_database)
95
- end
96
- end
97
-
98
- def stubbed_definition
99
- instance_double("Fx::Definition", to_sql: nil).tap do |stubbed_definition|
100
- allow(Fx::Definition).to receive(:new).and_return(stubbed_definition)
101
- end
102
- end
103
- end
@@ -1,132 +0,0 @@
1
- require "spec_helper"
2
- require "fx/statements/trigger"
3
-
4
- describe Fx::Statements::Trigger, :db do
5
- describe "#create_trigger" do
6
- it "creates a trigger from a file" do
7
- database = stubbed_database
8
- definition = stubbed_definition
9
-
10
- connection.create_trigger(:test)
11
-
12
- expect(database).to have_received(:create_trigger)
13
- .with(definition.to_sql)
14
- expect(Fx::Definition).to have_received(:new)
15
- .with(name: :test, version: 1, type: "trigger")
16
- end
17
-
18
- it "allows creating a trigger with a specific version" do
19
- database = stubbed_database
20
- definition = stubbed_definition
21
-
22
- connection.create_trigger(:test, version: 2)
23
-
24
- expect(database).to have_received(:create_trigger)
25
- .with(definition.to_sql)
26
- expect(Fx::Definition).to have_received(:new)
27
- .with(name: :test, version: 2, type: "trigger")
28
- end
29
-
30
- it "raises an error if both arguments are set" do
31
- stubbed_database
32
-
33
- expect {
34
- connection.create_trigger(
35
- :whatever,
36
- version: 1,
37
- sql_definition: "a definition"
38
- )
39
- }.to raise_error(
40
- ArgumentError,
41
- /cannot both be set/
42
- )
43
- end
44
- end
45
-
46
- describe "#drop_trigger" do
47
- it "drops the trigger" do
48
- database = stubbed_database
49
-
50
- connection.drop_trigger(:test, on: :users)
51
-
52
- expect(database).to have_received(:drop_trigger)
53
- .with(:test, on: :users)
54
- end
55
- end
56
-
57
- describe "#update_trigger" do
58
- it "updates the trigger" do
59
- database = stubbed_database
60
- definition = stubbed_definition
61
-
62
- connection.update_trigger(:test, on: :users, version: 3)
63
-
64
- expect(database).to have_received(:update_trigger).with(
65
- :test,
66
- on: :users,
67
- sql_definition: definition.to_sql
68
- )
69
- expect(Fx::Definition).to have_received(:new).with(
70
- name: :test,
71
- version: 3,
72
- type: "trigger"
73
- )
74
- end
75
-
76
- it "updates a trigger from a text definition" do
77
- database = stubbed_database
78
-
79
- connection.update_trigger(
80
- :test,
81
- on: :users,
82
- sql_definition: "a definition"
83
- )
84
-
85
- expect(database).to have_received(:update_trigger).with(
86
- :test,
87
- on: :users,
88
- sql_definition: "a definition"
89
- )
90
- end
91
-
92
- it "raises an error if not supplied a version" do
93
- expect {
94
- connection.update_trigger(
95
- :whatever,
96
- version: nil,
97
- sql_definition: nil
98
- )
99
- }.to raise_error(
100
- ArgumentError,
101
- /version or sql_definition must be specified/
102
- )
103
- end
104
-
105
- it "raises an error if both arguments are set" do
106
- stubbed_database
107
-
108
- expect {
109
- connection.update_trigger(
110
- :whatever,
111
- version: 1,
112
- sql_definition: "a definition"
113
- )
114
- }.to raise_error(
115
- ArgumentError,
116
- /cannot both be set/
117
- )
118
- end
119
- end
120
-
121
- def stubbed_database
122
- instance_spy("StubbedDatabase").tap do |stubbed_database|
123
- allow(Fx).to receive(:database).and_return(stubbed_database)
124
- end
125
- end
126
-
127
- def stubbed_definition
128
- instance_double("Fx::Definition", to_sql: nil).tap do |stubbed_definition|
129
- allow(Fx::Definition).to receive(:new).and_return(stubbed_definition)
130
- end
131
- end
132
- end