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,21 @@
1
+ require "spec_helper"
2
+
3
+ describe Fx::Configuration do
4
+ it "defaults the database adapter to postgres" do
5
+ expect(Fx.configuration.database).to be_a Fx::Adapters::Postgres
6
+ expect(Fx.database).to be_a Fx::Adapters::Postgres
7
+ end
8
+
9
+ it "allows the database adapter to be set" do
10
+ adapter = double("Fx Adapter")
11
+
12
+ Fx.configure do |config|
13
+ config.database = adapter
14
+ end
15
+
16
+ expect(Fx.configuration.database).to eq adapter
17
+ expect(Fx.database).to eq adapter
18
+
19
+ Fx.configuration = Fx::Configuration.new
20
+ end
21
+ end
@@ -0,0 +1,111 @@
1
+ require "spec_helper"
2
+
3
+ describe Fx::Definition do
4
+ describe "#to_sql" do
5
+ context "representing a function definition" do
6
+ it "returns the content of a function definition" do
7
+ sql_definition = <<~EOS
8
+ CREATE OR REPLACE FUNCTION test()
9
+ RETURNS text AS $$
10
+ BEGIN
11
+ RETURN 'test';
12
+ END;
13
+ $$ LANGUAGE plpgsql;
14
+ EOS
15
+ allow(File).to receive(:read).and_return(sql_definition)
16
+
17
+ definition = Fx::Definition.new(name: "test", version: 1)
18
+
19
+ expect(definition.to_sql).to eq sql_definition
20
+ end
21
+
22
+ it "raises an error if the file is empty" do
23
+ allow(File).to receive(:read).and_return("")
24
+ definition = Fx::Definition.new(name: "test", version: 1)
25
+
26
+ expect { definition.to_sql }.to raise_error(
27
+ RuntimeError,
28
+ %r(Define function in db/functions/test_v01.sql before migrating),
29
+ )
30
+ end
31
+ end
32
+
33
+ context "representing a trigger definition" do
34
+ it "returns the content of a trigger definition" do
35
+ sql_definition = <<~EOS
36
+ CREATE TRIGGER check_update
37
+ BEFORE UPDATE ON accounts
38
+ FOR EACH ROW
39
+ EXECUTE PROCEDURE check_account_update();
40
+ EOS
41
+ allow(File).to receive(:read).and_return(sql_definition)
42
+
43
+ definition = Fx::Definition.new(
44
+ name: "test",
45
+ version: 1,
46
+ type: "trigger",
47
+ )
48
+
49
+ expect(definition.to_sql).to eq sql_definition
50
+ end
51
+
52
+ it "raises an error if the file is empty" do
53
+ allow(File).to receive(:read).and_return("")
54
+ definition = Fx::Definition.new(
55
+ name: "test",
56
+ version: 1,
57
+ type: "trigger",
58
+ )
59
+
60
+ expect { definition.to_sql }.to raise_error(
61
+ RuntimeError,
62
+ %r(Define trigger in db/triggers/test_v01.sql before migrating),
63
+ )
64
+ end
65
+ end
66
+ end
67
+
68
+ describe "#path" do
69
+ context "representing a function definition" do
70
+ it "returns a sql file with padded version and function name" do
71
+ definition = Fx::Definition.new(name: "test", version: 1)
72
+
73
+ expect(definition.path).to eq "db/functions/test_v01.sql"
74
+ end
75
+ end
76
+
77
+ context "representing a trigger definition" do
78
+ it "returns a sql file with padded version and trigger name" do
79
+ definition = Fx::Definition.new(
80
+ name: "test",
81
+ version: 1,
82
+ type: "trigger",
83
+ )
84
+
85
+ expect(definition.path).to eq "db/triggers/test_v01.sql"
86
+ end
87
+ end
88
+ end
89
+
90
+ describe "#full_path" do
91
+ it "joins the path with Rails.root" do
92
+ definition = Fx::Definition.new(name: "test", version: 15)
93
+
94
+ expect(definition.full_path).to eq Rails.root.join(definition.path)
95
+ end
96
+ end
97
+
98
+ describe "#version" do
99
+ it "pads the version number with 0" do
100
+ definition = Fx::Definition.new(name: :_, version: 1)
101
+
102
+ expect(definition.version).to eq "01"
103
+ end
104
+
105
+ it "does not pad more than 2 characters" do
106
+ definition = Fx::Definition.new(name: :_, version: 15)
107
+
108
+ expect(definition.version).to eq "15"
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,22 @@
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 test()
7
+ RETURNS text AS $$
8
+ BEGIN
9
+ RETURN 'test';
10
+ END;
11
+ $$ LANGUAGE plpgsql;
12
+ EOS
13
+ connection.create_function :test, sql_definition: sql_definition
14
+ stream = StringIO.new
15
+
16
+ ActiveRecord::SchemaDumper.dump(connection, stream)
17
+
18
+ output = stream.string
19
+ expect(output).to include "create_function :test, sql_definition: <<-SQL"
20
+ expect(output).to include "RETURN 'test';"
21
+ end
22
+ end
@@ -0,0 +1,40 @@
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 PROCEDURE 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 PROCEDURE uppercase_users_name()"
39
+ end
40
+ end
@@ -0,0 +1,103 @@
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
@@ -0,0 +1,132 @@
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
@@ -0,0 +1,34 @@
1
+ require "spec_helper"
2
+ require "generators/fx/function/function_generator"
3
+
4
+ describe Fx::Generators::FunctionGenerator, :generator do
5
+ it "creates a function definition file, and a migration" do
6
+ migration = file("db/migrate/create_function_test.rb")
7
+ function_definition = file("db/functions/test_v01.sql")
8
+
9
+ run_generator ["test"]
10
+
11
+ expect(function_definition).to exist
12
+ expect(migration).to be_a_migration
13
+ expect(migration_file(migration)).to contain "CreateFunctionTest"
14
+ end
15
+
16
+ it "updates an existing function" do
17
+ with_function_definition(
18
+ name: "test",
19
+ version: 1,
20
+ sql_definition: "hello",
21
+ ) do
22
+ allow(Dir).to receive(:entries).and_return(["test_v01.sql"])
23
+ migration = file("db/migrate/update_function_test_to_version_2.rb")
24
+ function_definition = file("db/functions/test_v02.sql")
25
+
26
+ run_generator ["test"]
27
+
28
+ expect(function_definition).to exist
29
+ expect(migration).to be_a_migration
30
+ expect(migration_file(migration)).
31
+ to contain("UpdateFunctionTestToVersion2")
32
+ end
33
+ end
34
+ end