fx 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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