fx 0.1.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 (89) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +27 -0
  6. data/.yardopts +4 -0
  7. data/Appraisals +21 -0
  8. data/CONTRIBUTING.md +15 -0
  9. data/Gemfile +4 -0
  10. data/README.md +81 -0
  11. data/Rakefile +23 -0
  12. data/bin/appraisal +17 -0
  13. data/bin/console +14 -0
  14. data/bin/rake +17 -0
  15. data/bin/rspec +17 -0
  16. data/bin/setup +12 -0
  17. data/bin/yard +17 -0
  18. data/fx.gemspec +38 -0
  19. data/gemfiles/rails40.gemfile +8 -0
  20. data/gemfiles/rails40.gemfile.lock +111 -0
  21. data/gemfiles/rails41.gemfile +8 -0
  22. data/gemfiles/rails41.gemfile.lock +113 -0
  23. data/gemfiles/rails42.gemfile +8 -0
  24. data/gemfiles/rails42.gemfile.lock +130 -0
  25. data/gemfiles/rails50.gemfile +8 -0
  26. data/gemfiles/rails50.gemfile.lock +126 -0
  27. data/lib/fx.rb +21 -0
  28. data/lib/fx/adapters/postgres.rb +142 -0
  29. data/lib/fx/adapters/postgres/connection.rb +16 -0
  30. data/lib/fx/adapters/postgres/functions.rb +55 -0
  31. data/lib/fx/adapters/postgres/triggers.rb +56 -0
  32. data/lib/fx/command_recorder.rb +29 -0
  33. data/lib/fx/command_recorder/arguments.rb +43 -0
  34. data/lib/fx/command_recorder/function.rb +30 -0
  35. data/lib/fx/command_recorder/trigger.rb +30 -0
  36. data/lib/fx/configuration.rb +38 -0
  37. data/lib/fx/definition.rb +36 -0
  38. data/lib/fx/function.rb +24 -0
  39. data/lib/fx/schema_dumper.rb +15 -0
  40. data/lib/fx/schema_dumper/function.rb +29 -0
  41. data/lib/fx/schema_dumper/trigger.rb +29 -0
  42. data/lib/fx/statements.rb +16 -0
  43. data/lib/fx/statements/function.rb +105 -0
  44. data/lib/fx/statements/trigger.rb +133 -0
  45. data/lib/fx/trigger.rb +24 -0
  46. data/lib/fx/version.rb +4 -0
  47. data/lib/generators.rb +11 -0
  48. data/lib/generators/fx/function/USAGE +9 -0
  49. data/lib/generators/fx/function/function_generator.rb +98 -0
  50. data/lib/generators/fx/function/templates/db/migrate/create_function.erb +5 -0
  51. data/lib/generators/fx/function/templates/db/migrate/update_function.erb +5 -0
  52. data/lib/generators/fx/trigger/USAGE +18 -0
  53. data/lib/generators/fx/trigger/templates/db/migrate/create_trigger.erb +5 -0
  54. data/lib/generators/fx/trigger/templates/db/migrate/update_trigger.erb +5 -0
  55. data/lib/generators/fx/trigger/trigger_generator.rb +108 -0
  56. data/spec/acceptance/user_manages_functions_spec.rb +37 -0
  57. data/spec/acceptance/user_manages_triggers_spec.rb +51 -0
  58. data/spec/acceptance_helper.rb +61 -0
  59. data/spec/dummy/.gitignore +16 -0
  60. data/spec/dummy/Rakefile +6 -0
  61. data/spec/dummy/bin/bundle +3 -0
  62. data/spec/dummy/bin/rails +4 -0
  63. data/spec/dummy/bin/rake +4 -0
  64. data/spec/dummy/config.ru +4 -0
  65. data/spec/dummy/config/application.rb +15 -0
  66. data/spec/dummy/config/boot.rb +5 -0
  67. data/spec/dummy/config/database.yml +9 -0
  68. data/spec/dummy/config/environment.rb +5 -0
  69. data/spec/dummy/db/migrate/.keep +0 -0
  70. data/spec/features/functions/migrations_spec.rb +65 -0
  71. data/spec/features/functions/revert_spec.rb +75 -0
  72. data/spec/features/triggers/migrations_spec.rb +56 -0
  73. data/spec/features/triggers/revert_spec.rb +95 -0
  74. data/spec/fx/adapters/postgres_spec.rb +149 -0
  75. data/spec/fx/command_recorder/arguments_spec.rb +41 -0
  76. data/spec/fx/command_recorder_spec.rb +171 -0
  77. data/spec/fx/configuration_spec.rb +21 -0
  78. data/spec/fx/definition_spec.rb +111 -0
  79. data/spec/fx/schema_dumper/function_spec.rb +22 -0
  80. data/spec/fx/schema_dumper/trigger_spec.rb +40 -0
  81. data/spec/fx/statements/function_spec.rb +103 -0
  82. data/spec/fx/statements/trigger_spec.rb +132 -0
  83. data/spec/generators/fx/function/function_generator_spec.rb +34 -0
  84. data/spec/generators/fx/trigger/trigger_generator_spec.rb +47 -0
  85. data/spec/spec_helper.rb +21 -0
  86. data/spec/support/definition_helpers.rb +37 -0
  87. data/spec/support/generator_setup.rb +11 -0
  88. data/spec/support/migration_helpers.rb +17 -0
  89. metadata +334 -0
@@ -0,0 +1,56 @@
1
+ require "spec_helper"
2
+
3
+ describe "Trigger migrations", :db do
4
+ around do |example|
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 PROCEDURE uppercase_users_name();
26
+ EOS
27
+ with_trigger_definition(
28
+ name: :uppercase_users_name,
29
+ sql_definition: sql_definition,
30
+ ) do
31
+ example.run
32
+ end
33
+ end
34
+
35
+ it "can run migrations that create triggers" do
36
+ migration = Class.new(ActiveRecord::Migration) do
37
+ def up
38
+ create_trigger :uppercase_users_name
39
+ end
40
+ end
41
+
42
+ expect { run_migration(migration, :up) }.not_to raise_error
43
+ end
44
+
45
+ it "can run migrations that drop triggers" do
46
+ connection.create_trigger(:uppercase_users_name)
47
+
48
+ migration = Class.new(ActiveRecord::Migration) do
49
+ def up
50
+ drop_trigger :uppercase_users_name, on: :users
51
+ end
52
+ end
53
+
54
+ expect { run_migration(migration, :up) }.not_to raise_error
55
+ end
56
+ end
@@ -0,0 +1,95 @@
1
+ require "spec_helper"
2
+
3
+ describe "Reverting migrations", :db do
4
+ around do |example|
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 PROCEDURE uppercase_users_name();
26
+ EOS
27
+ with_trigger_definition(
28
+ name: :uppercase_users_name,
29
+ sql_definition: sql_definition,
30
+ ) do
31
+ example.run
32
+ end
33
+ end
34
+
35
+ it "can run reversible migrations for creating triggers" do
36
+ migration = Class.new(ActiveRecord::Migration) do
37
+ def change
38
+ create_trigger :uppercase_users_name, on: :users
39
+ end
40
+ end
41
+
42
+ expect { run_migration(migration, [:up, :down]) }.not_to raise_error
43
+ end
44
+
45
+ it "can run reversible migrations for dropping triggers" do
46
+ connection.create_trigger(:uppercase_users_name, on: :users)
47
+
48
+ good_migration = Class.new(ActiveRecord::Migration) do
49
+ def change
50
+ drop_trigger :uppercase_users_name, on: :users, revert_to_version: 1
51
+ end
52
+ end
53
+ bad_migration = Class.new(ActiveRecord::Migration) do
54
+ def change
55
+ drop_trigger :uppercase_users_name, on: :users
56
+ end
57
+ end
58
+
59
+ expect { run_migration(good_migration, [:up, :down]) }.not_to raise_error
60
+ expect { run_migration(bad_migration, [:up, :down]) }.
61
+ to raise_error(
62
+ ActiveRecord::IrreversibleMigration,
63
+ /`create_trigger` is reversible only if given a `revert_to_version`/,
64
+ )
65
+ end
66
+
67
+ it "can run reversible migrations for updating triggers" do
68
+ connection.create_trigger(:uppercase_users_name)
69
+
70
+ sql_definition = <<~EOS
71
+ CREATE TRIGGER uppercase_users_name
72
+ BEFORE UPDATE ON users
73
+ FOR EACH ROW
74
+ EXECUTE PROCEDURE uppercase_users_name();
75
+ EOS
76
+ with_trigger_definition(
77
+ name: :uppercase_users_name,
78
+ sql_definition: sql_definition,
79
+ version: 2,
80
+ ) do
81
+ migration = Class.new(ActiveRecord::Migration) do
82
+ def change
83
+ update_trigger(
84
+ :uppercase_users_name,
85
+ on: :users,
86
+ version: 2,
87
+ revert_to_version: 1,
88
+ )
89
+ end
90
+ end
91
+
92
+ expect { run_migration(migration, [:up, :down]) }.not_to raise_error
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,149 @@
1
+ require "spec_helper"
2
+
3
+ module Fx::Adapters
4
+ describe Postgres, :db do
5
+ describe "#create_function" do
6
+ it "successfully creates a function" do
7
+ adapter = Postgres.new
8
+ adapter.create_function(
9
+ <<~EOS
10
+ CREATE OR REPLACE FUNCTION test()
11
+ RETURNS text AS $$
12
+ BEGIN
13
+ RETURN 'test';
14
+ END;
15
+ $$ LANGUAGE plpgsql;
16
+ EOS
17
+ )
18
+
19
+ expect(adapter.functions.map(&:name)).to include("test")
20
+ end
21
+ end
22
+
23
+ describe "#create_trigger" do
24
+ it "successfully creates a trigger" do
25
+ connection.execute <<~EOS
26
+ CREATE TABLE users (
27
+ id int PRIMARY KEY,
28
+ name varchar(256),
29
+ upper_name varchar(256)
30
+ );
31
+ EOS
32
+ adapter = Postgres.new
33
+ adapter.create_function <<~EOS
34
+ CREATE OR REPLACE FUNCTION uppercase_users_name()
35
+ RETURNS trigger AS $$
36
+ BEGIN
37
+ NEW.upper_name = UPPER(NEW.name);
38
+ RETURN NEW;
39
+ END;
40
+ $$ LANGUAGE plpgsql;
41
+ EOS
42
+ adapter.create_trigger(
43
+ <<~EOS
44
+ CREATE TRIGGER uppercase_users_name
45
+ BEFORE INSERT ON users
46
+ FOR EACH ROW
47
+ EXECUTE PROCEDURE uppercase_users_name();
48
+ EOS
49
+ )
50
+
51
+ expect(adapter.triggers.map(&:name)).to include("uppercase_users_name")
52
+ end
53
+ end
54
+
55
+ describe "#drop_function" do
56
+ it "successfully drops a function" do
57
+ adapter = Postgres.new
58
+ adapter.create_function(
59
+ <<~EOS
60
+ CREATE OR REPLACE FUNCTION test()
61
+ RETURNS text AS $$
62
+ BEGIN
63
+ RETURN 'test';
64
+ END;
65
+ $$ LANGUAGE plpgsql;
66
+ EOS
67
+ )
68
+
69
+ adapter.drop_function(:test)
70
+
71
+ expect(adapter.functions.map(&:name)).not_to include("test")
72
+ end
73
+ end
74
+
75
+ describe "#functions" do
76
+ it "finds functions and builds Fx::Function objects" do
77
+ adapter = Postgres.new
78
+ adapter.create_function(
79
+ <<~EOS
80
+ CREATE OR REPLACE FUNCTION test()
81
+ RETURNS text AS $$
82
+ BEGIN
83
+ RETURN 'test';
84
+ END;
85
+ $$ LANGUAGE plpgsql;
86
+ EOS
87
+ )
88
+
89
+ expect(adapter.functions).to eq([
90
+ Fx::Function.new(
91
+ "name" => "test",
92
+ "definition" => <<~EOS
93
+ CREATE OR REPLACE FUNCTION public.test()
94
+ RETURNS text
95
+ LANGUAGE plpgsql
96
+ AS $function$
97
+ BEGIN
98
+ RETURN 'test';
99
+ END;
100
+ $function$
101
+ EOS
102
+ ),
103
+ ])
104
+ end
105
+ end
106
+
107
+ describe "#triggers" do
108
+ it "finds triggers and builds Fx::Trigger objects" do
109
+ connection.execute <<~EOS
110
+ CREATE TABLE users (
111
+ id int PRIMARY KEY,
112
+ name varchar(256),
113
+ upper_name varchar(256)
114
+ );
115
+ EOS
116
+ adapter = Postgres.new
117
+ adapter.create_function <<~EOS
118
+ CREATE OR REPLACE FUNCTION uppercase_users_name()
119
+ RETURNS trigger AS $$
120
+ BEGIN
121
+ NEW.upper_name = UPPER(NEW.name);
122
+ RETURN NEW;
123
+ END;
124
+ $$ LANGUAGE plpgsql;
125
+ EOS
126
+ sql_definition = <<~EOS
127
+ CREATE TRIGGER uppercase_users_name
128
+ BEFORE INSERT ON users
129
+ FOR EACH ROW
130
+ EXECUTE PROCEDURE uppercase_users_name()
131
+ EOS
132
+ adapter.create_trigger(sql_definition)
133
+
134
+ expect(adapter.triggers).to eq(
135
+ [
136
+ Fx::Trigger.new(
137
+ "name" => "uppercase_users_name",
138
+ "definition" => format_trigger(sql_definition),
139
+ ),
140
+ ],
141
+ )
142
+ end
143
+ end
144
+
145
+ def format_trigger(trigger)
146
+ trigger.gsub("\n", "").gsub(" ", " ")
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,41 @@
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
@@ -0,0 +1,171 @@
1
+ require "spec_helper"
2
+
3
+ describe Fx::CommandRecorder, :db do
4
+ describe "#create_function" do
5
+ it "records the created function" do
6
+ recorder = ActiveRecord::Migration::CommandRecorder.new
7
+
8
+ recorder.create_function :test
9
+
10
+ expect(recorder.commands).to eq [[:create_function, [:test], nil]]
11
+ end
12
+
13
+ it "reverts to drop_function" do
14
+ recorder = ActiveRecord::Migration::CommandRecorder.new
15
+
16
+ recorder.create_function :test
17
+
18
+ expect(recorder.commands).to eq [[:create_function, [:test], nil]]
19
+ end
20
+
21
+ it "reverts to drop_function" do
22
+ recorder = ActiveRecord::Migration::CommandRecorder.new
23
+
24
+ recorder.revert { recorder.create_function :test }
25
+
26
+ expect(recorder.commands).to eq [[:drop_function, [:test]]]
27
+ end
28
+ end
29
+
30
+ describe "#drop_function" do
31
+ it "records the dropped function" do
32
+ recorder = ActiveRecord::Migration::CommandRecorder.new
33
+
34
+ recorder.drop_function :test
35
+
36
+ expect(recorder.commands).to eq [[:drop_function, [:test], nil]]
37
+ end
38
+
39
+ it "reverts to create_function with specified revert_to_version" do
40
+ recorder = ActiveRecord::Migration::CommandRecorder.new
41
+ args = [:test, { revert_to_version: 3 }]
42
+ revert_args = [:test, { version: 3 }]
43
+
44
+ recorder.revert { recorder.drop_function(*args) }
45
+
46
+ expect(recorder.commands).to eq [[:create_function, revert_args]]
47
+ end
48
+
49
+ it "raises when reverting without revert_to_version set" do
50
+ recorder = ActiveRecord::Migration::CommandRecorder.new
51
+ args = [:test, { another_argument: 1 }]
52
+
53
+ expect { recorder.revert { recorder.drop_function(*args) } }.
54
+ to raise_error(ActiveRecord::IrreversibleMigration)
55
+ end
56
+ end
57
+
58
+ describe "#update_function" do
59
+ it "records the updated function" do
60
+ recorder = ActiveRecord::Migration::CommandRecorder.new
61
+ args = [:test, { version: 2 }]
62
+
63
+ recorder.update_function(*args)
64
+
65
+ expect(recorder.commands).to eq [[:update_function, args, nil]]
66
+ end
67
+
68
+ it "reverts to update_function with the specified revert_to_version" do
69
+ recorder = ActiveRecord::Migration::CommandRecorder.new
70
+ args = [:test, { version: 2, revert_to_version: 1 }]
71
+ revert_args = [:test, { version: 1 }]
72
+
73
+ recorder.revert { recorder.update_function(*args) }
74
+
75
+ expect(recorder.commands).to eq [[:update_function, revert_args]]
76
+ end
77
+
78
+ it "raises when reverting without revert_to_version set" do
79
+ recorder = ActiveRecord::Migration::CommandRecorder.new
80
+ args = [:test, { version: 42, another_argument: 1 }]
81
+
82
+ expect { recorder.revert { recorder.update_function(*args) } }.
83
+ to raise_error(ActiveRecord::IrreversibleMigration)
84
+ end
85
+ end
86
+
87
+ describe "#create_trigger" do
88
+ it "records the created trigger" do
89
+ recorder = ActiveRecord::Migration::CommandRecorder.new
90
+
91
+ recorder.create_trigger :greetings
92
+
93
+ expect(recorder.commands).to eq [[:create_trigger, [:greetings], nil]]
94
+ end
95
+
96
+ it "reverts to drop_trigger" do
97
+ recorder = ActiveRecord::Migration::CommandRecorder.new
98
+
99
+ recorder.create_trigger :greetings
100
+
101
+ expect(recorder.commands).to eq [
102
+ [:create_trigger, [:greetings], nil],
103
+ ]
104
+ end
105
+
106
+ it "reverts to drop_trigger" do
107
+ recorder = ActiveRecord::Migration::CommandRecorder.new
108
+
109
+ recorder.revert { recorder.create_trigger :greetings }
110
+
111
+ expect(recorder.commands).to eq [[:drop_trigger, [:greetings]]]
112
+ end
113
+ end
114
+
115
+ describe "#drop_trigger" do
116
+ it "records the dropped trigger" do
117
+ recorder = ActiveRecord::Migration::CommandRecorder.new
118
+
119
+ recorder.drop_trigger :users
120
+
121
+ expect(recorder.commands).to eq [[:drop_trigger, [:users], nil]]
122
+ end
123
+
124
+ it "reverts to create_trigger with specified revert_to_version" do
125
+ recorder = ActiveRecord::Migration::CommandRecorder.new
126
+ args = [:users, { revert_to_version: 3 }]
127
+ revert_args = [:users, { version: 3 }]
128
+
129
+ recorder.revert { recorder.drop_trigger(*args) }
130
+
131
+ expect(recorder.commands).to eq [[:create_trigger, revert_args]]
132
+ end
133
+
134
+ it "raises when reverting without revert_to_version set" do
135
+ recorder = ActiveRecord::Migration::CommandRecorder.new
136
+ args = [:users, { another_argument: 1 }]
137
+
138
+ expect { recorder.revert { recorder.drop_trigger(*args) } }.
139
+ to raise_error(ActiveRecord::IrreversibleMigration)
140
+ end
141
+ end
142
+
143
+ describe "#update_trigger" do
144
+ it "records the updated trigger" do
145
+ recorder = ActiveRecord::Migration::CommandRecorder.new
146
+ args = [:users, { version: 2 }]
147
+
148
+ recorder.update_trigger(*args)
149
+
150
+ expect(recorder.commands).to eq [[:update_trigger, args, nil]]
151
+ end
152
+
153
+ it "reverts to update_trigger with the specified revert_to_version" do
154
+ recorder = ActiveRecord::Migration::CommandRecorder.new
155
+ args = [:users, { version: 2, revert_to_version: 1 }]
156
+ revert_args = [:users, { version: 1 }]
157
+
158
+ recorder.revert { recorder.update_trigger(*args) }
159
+
160
+ expect(recorder.commands).to eq [[:update_trigger, revert_args]]
161
+ end
162
+
163
+ it "raises when reverting without revert_to_version set" do
164
+ recorder = ActiveRecord::Migration::CommandRecorder.new
165
+ args = [:users, { version: 42, another_argument: 1 }]
166
+
167
+ expect { recorder.revert { recorder.update_trigger(*args) } }.
168
+ to raise_error(ActiveRecord::IrreversibleMigration)
169
+ end
170
+ end
171
+ end