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,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