fx 0.3.0 → 0.6.1

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 (54) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.hound.yml +2 -0
  4. data/.rubocop.yml +648 -0
  5. data/.travis.yml +18 -8
  6. data/Appraisals +21 -10
  7. data/LICENSE +18 -0
  8. data/README.md +45 -13
  9. data/bin/setup +1 -0
  10. data/gemfiles/rails42.gemfile +2 -1
  11. data/gemfiles/rails50.gemfile +1 -1
  12. data/gemfiles/rails51.gemfile +8 -0
  13. data/gemfiles/rails52.gemfile +8 -0
  14. data/gemfiles/rails60.gemfile +8 -0
  15. data/gemfiles/rails_edge.gemfile +8 -0
  16. data/lib/fx.rb +22 -0
  17. data/lib/fx/adapters/postgres.rb +26 -1
  18. data/lib/fx/adapters/postgres/functions.rb +3 -0
  19. data/lib/fx/command_recorder.rb +0 -5
  20. data/lib/fx/configuration.rb +10 -0
  21. data/lib/fx/definition.rb +13 -3
  22. data/lib/fx/function.rb +2 -0
  23. data/lib/fx/railtie.rb +15 -0
  24. data/lib/fx/schema_dumper.rb +0 -5
  25. data/lib/fx/schema_dumper/function.rb +14 -5
  26. data/lib/fx/statements.rb +0 -5
  27. data/lib/fx/statements/function.rb +4 -2
  28. data/lib/fx/statements/trigger.rb +2 -0
  29. data/lib/fx/trigger.rb +2 -0
  30. data/lib/fx/version.rb +1 -1
  31. data/lib/generators/fx/function/USAGE +2 -0
  32. data/lib/generators/fx/function/function_generator.rb +15 -1
  33. data/lib/generators/fx/trigger/USAGE +2 -0
  34. data/lib/generators/fx/trigger/trigger_generator.rb +15 -1
  35. data/spec/acceptance/user_manages_functions_spec.rb +20 -0
  36. data/spec/acceptance_helper.rb +2 -1
  37. data/spec/dummy/Rakefile +7 -0
  38. data/spec/fx/adapters/postgres/functions_spec.rb +37 -0
  39. data/spec/fx/adapters/postgres/triggers_spec.rb +45 -0
  40. data/spec/fx/adapters/postgres_spec.rb +36 -14
  41. data/spec/fx/definition_spec.rb +23 -0
  42. data/spec/fx/function_spec.rb +55 -0
  43. data/spec/fx/schema_dumper/function_spec.rb +57 -1
  44. data/spec/fx/trigger_spec.rb +55 -0
  45. data/spec/generators/fx/function/function_generator_spec.rb +12 -0
  46. data/spec/generators/fx/trigger/trigger_generator_spec.rb +12 -0
  47. metadata +19 -11
  48. data/.ruby-version +0 -1
  49. data/gemfiles/rails40.gemfile +0 -8
  50. data/gemfiles/rails40.gemfile.lock +0 -111
  51. data/gemfiles/rails41.gemfile +0 -8
  52. data/gemfiles/rails41.gemfile.lock +0 -113
  53. data/gemfiles/rails42.gemfile.lock +0 -130
  54. data/gemfiles/rails50.gemfile.lock +0 -126
@@ -5,15 +5,24 @@ module Fx
5
5
  # @api private
6
6
  module Function
7
7
  def tables(stream)
8
+ if Fx.configuration.dump_functions_at_beginning_of_schema
9
+ functions(stream)
10
+ empty_line(stream)
11
+ end
12
+
8
13
  super
9
- functions(stream)
10
- end
11
14
 
12
- def functions(stream)
13
- if dumpable_functions_in_database.any?
14
- stream.puts
15
+ unless Fx.configuration.dump_functions_at_beginning_of_schema
16
+ functions(stream)
17
+ empty_line(stream)
15
18
  end
19
+ end
20
+
21
+ def empty_line(stream)
22
+ stream.puts if dumpable_functions_in_database.any?
23
+ end
16
24
 
25
+ def functions(stream)
17
26
  dumpable_functions_in_database.each do |function|
18
27
  stream.puts(function.to_schema)
19
28
  end
@@ -9,8 +9,3 @@ module Fx
9
9
  include Trigger
10
10
  end
11
11
  end
12
-
13
- ActiveRecord::ConnectionAdapters::AbstractAdapter.send(
14
- :include,
15
- Fx::Statements,
16
- )
@@ -36,6 +36,7 @@ module Fx
36
36
  "version or sql_definition must be specified",
37
37
  )
38
38
  end
39
+ sql_definition = sql_definition.strip_heredoc if sql_definition
39
40
  sql_definition ||= Fx::Definition.new(name: name, version: version).to_sql
40
41
 
41
42
  Fx.database.create_function(sql_definition)
@@ -49,8 +50,8 @@ module Fx
49
50
  # the `version` argument to {#create_function}.
50
51
  # @return The database response from executing the drop statement.
51
52
  #
52
- # @example Drop a function, rolling back to version 3 on rollback
53
- # drop_function(:uppercase_users_name, on: :users, revert_to_version: 3)
53
+ # @example Drop a function, rolling back to version 2 on rollback
54
+ # drop_function(:uppercase_users_name, revert_to_version: 2)
54
55
  #
55
56
  def drop_function(name, revert_to_version: nil)
56
57
  Fx.database.drop_function(name)
@@ -93,6 +94,7 @@ module Fx
93
94
  )
94
95
  end
95
96
 
97
+ sql_definition = sql_definition.strip_heredoc if sql_definition
96
98
  sql_definition ||= Fx::Definition.new(
97
99
  name: name,
98
100
  version: version,
@@ -39,6 +39,7 @@ module Fx
39
39
  version = 1
40
40
  end
41
41
 
42
+ sql_definition = sql_definition.strip_heredoc if sql_definition
42
43
  sql_definition ||= Fx::Definition.new(
43
44
  name: name,
44
45
  version: version,
@@ -116,6 +117,7 @@ module Fx
116
117
  raise ArgumentError, "on is required"
117
118
  end
118
119
 
120
+ sql_definition = sql_definition.strip_heredoc if sql_definition
119
121
  sql_definition ||= Fx::Definition.new(
120
122
  name: name,
121
123
  version: version,
@@ -1,6 +1,8 @@
1
1
  module Fx
2
2
  # @api private
3
3
  class Trigger
4
+ include Comparable
5
+
4
6
  attr_reader :name, :definition
5
7
  delegate :<=>, to: :name
6
8
 
@@ -1,4 +1,4 @@
1
1
  module Fx
2
2
  # @api private
3
- VERSION = "0.3.0"
3
+ VERSION = "0.6.1"
4
4
  end
@@ -2,6 +2,8 @@ Description:
2
2
  Create a new database function for your application. This will create a new
3
3
  function definition file and the accompanying migration.
4
4
 
5
+ When --no-migration is passed, skips generating a migration.
6
+
5
7
  Examples:
6
8
  rails generate fx:function test
7
9
 
@@ -8,6 +8,8 @@ module Fx
8
8
  include Rails::Generators::Migration
9
9
  source_root File.expand_path("../templates", __FILE__)
10
10
 
11
+ class_option :migration, type: :boolean
12
+
11
13
  def create_functions_directory
12
14
  unless function_definition_path.exist?
13
15
  empty_directory(function_definition_path)
@@ -23,6 +25,7 @@ module Fx
23
25
  end
24
26
 
25
27
  def create_migration_file
28
+ return if skip_migration_creation?
26
29
  if updating_existing_function?
27
30
  migration_template(
28
31
  "db/migrate/update_function.erb",
@@ -61,7 +64,7 @@ module Fx
61
64
 
62
65
  def activerecord_migration_class
63
66
  if ActiveRecord::Migration.respond_to?(:current_version)
64
- "ActiveRecord::Migration[5.0]"
67
+ "ActiveRecord::Migration[#{ActiveRecord::Migration.current_version}]"
65
68
  else
66
69
  "ActiveRecord::Migration"
67
70
  end
@@ -101,6 +104,17 @@ module Fx
101
104
  def previous_definition
102
105
  Fx::Definition.new(name: file_name, version: previous_version)
103
106
  end
107
+
108
+ # Skip creating migration file if:
109
+ # - migrations option is nil or false
110
+ def skip_migration_creation?
111
+ !migration
112
+ end
113
+
114
+ # True unless explicitly false
115
+ def migration
116
+ options[:migration] != false
117
+ end
104
118
  end
105
119
  end
106
120
  end
@@ -5,6 +5,8 @@ Description:
5
5
  If a trigger of the given name already exists, create a new version of the
6
6
  trigger and a migration to replace the old version with the new.
7
7
 
8
+ When --no-migration is passed, skips generating a migration.
9
+
8
10
  Examples:
9
11
 
10
12
  rails generate fx:trigger test
@@ -9,6 +9,8 @@ module Fx
9
9
  source_root File.expand_path("../templates", __FILE__)
10
10
  argument :table_name, type: :hash, required: true
11
11
 
12
+ class_option :migration, type: :boolean
13
+
12
14
  def create_triggers_directory
13
15
  unless trigger_definition_path.exist?
14
16
  empty_directory(trigger_definition_path)
@@ -20,6 +22,7 @@ module Fx
20
22
  end
21
23
 
22
24
  def create_migration_file
25
+ return if skip_migration_creation?
23
26
  if updating_existing_trigger?
24
27
  migration_template(
25
28
  "db/migrate/update_trigger.erb",
@@ -58,7 +61,7 @@ module Fx
58
61
 
59
62
  def activerecord_migration_class
60
63
  if ActiveRecord::Migration.respond_to?(:current_version)
61
- "ActiveRecord::Migration[5.0]"
64
+ "ActiveRecord::Migration[#{ActiveRecord::Migration.current_version}]"
62
65
  else
63
66
  "ActiveRecord::Migration"
64
67
  end
@@ -111,6 +114,17 @@ module Fx
111
114
  def trigger_definition_path
112
115
  @_trigger_definition_path ||= Rails.root.join(*["db", "triggers"])
113
116
  end
117
+
118
+ # Skip creating migration file if:
119
+ # - migrations option is nil or false
120
+ def skip_migration_creation?
121
+ !migration
122
+ end
123
+
124
+ # True unless explicitly false
125
+ def migration
126
+ options[:migration] != false
127
+ end
114
128
  end
115
129
  end
116
130
  end
@@ -34,4 +34,24 @@ describe "User manages functions" do
34
34
  result = execute("SELECT * FROM test() AS result")
35
35
  expect(result).to eq("result" => "testest")
36
36
  end
37
+
38
+ it "handles functions with arguments" do
39
+ successfully "rails generate fx:function adder"
40
+ write_function_definition "adder_v01", <<-EOS
41
+ CREATE FUNCTION adder(x int, y int)
42
+ RETURNS int AS $$
43
+ BEGIN
44
+ RETURN $1 + $2;
45
+ END;
46
+ $$ LANGUAGE plpgsql;
47
+ EOS
48
+ successfully "rake db:migrate"
49
+
50
+ result = execute("SELECT * FROM adder(1, 2) AS result")
51
+ result["result"] = result["result"].to_i
52
+ expect(result).to eq("result" => 3)
53
+
54
+ successfully "rails destroy fx:function adder"
55
+ successfully "rake db:migrate"
56
+ end
37
57
  end
@@ -23,7 +23,8 @@ RSpec.configure do |config|
23
23
  Dir.chdir("spec/dummy") do
24
24
  ActiveRecord::Base.connection.disconnect!
25
25
  system <<-CMD
26
- rake db:drop db:create &&
26
+ echo &&
27
+ rake db:environment:set db:drop db:create &&
27
28
  git add -A &&
28
29
  git reset --hard HEAD 1>/dev/null &&
29
30
  rm -rf .git/ 1>/dev/null
@@ -4,3 +4,10 @@
4
4
  require File.expand_path('../config/application', __FILE__)
5
5
 
6
6
  Rails.application.load_tasks
7
+
8
+ unless Rake::Task.task_defined?('db:environment:set')
9
+ desc 'dummy task for rails versions where this task does not exist'
10
+ task 'db:environment:set' do
11
+ # no-op
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ module Fx
4
+ module Adapters
5
+ describe Postgres::Functions, :db do
6
+ describe ".all" do
7
+ it "returns `Function` objects" do
8
+ connection = ActiveRecord::Base.connection
9
+ connection.execute <<-EOS.strip_heredoc
10
+ CREATE OR REPLACE FUNCTION test()
11
+ RETURNS text AS $$
12
+ BEGIN
13
+ RETURN 'test';
14
+ END;
15
+ $$ LANGUAGE plpgsql;
16
+ EOS
17
+
18
+ functions = Postgres::Functions.new(connection).all
19
+
20
+ first = functions.first
21
+ expect(functions.size).to eq 1
22
+ expect(first.name).to eq "test"
23
+ expect(first.definition).to eq <<-EOS.strip_heredoc
24
+ CREATE OR REPLACE FUNCTION public.test()
25
+ RETURNS text
26
+ LANGUAGE plpgsql
27
+ AS $function$
28
+ BEGIN
29
+ RETURN 'test';
30
+ END;
31
+ $function$
32
+ EOS
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,45 @@
1
+ require "spec_helper"
2
+
3
+ module Fx
4
+ module Adapters
5
+ describe Postgres::Triggers, :db do
6
+ describe ".all" do
7
+ it "returns `Trigger` objects" do
8
+ connection = ActiveRecord::Base.connection
9
+ connection.execute <<-EOS.strip_heredoc
10
+ CREATE TABLE users (
11
+ id int PRIMARY KEY,
12
+ name varchar(256),
13
+ upper_name varchar(256)
14
+ );
15
+ EOS
16
+ connection.execute <<-EOS.strip_heredoc
17
+ CREATE OR REPLACE FUNCTION uppercase_users_name()
18
+ RETURNS trigger AS $$
19
+ BEGIN
20
+ NEW.upper_name = UPPER(NEW.name);
21
+ RETURN NEW;
22
+ END;
23
+ $$ LANGUAGE plpgsql;
24
+ EOS
25
+ connection.execute <<-EOS.strip_heredoc
26
+ CREATE TRIGGER uppercase_users_name
27
+ BEFORE INSERT ON users
28
+ FOR EACH ROW
29
+ EXECUTE PROCEDURE uppercase_users_name();
30
+ EOS
31
+
32
+ triggers = Postgres::Triggers.new(connection).all
33
+
34
+ first = triggers.first
35
+ expect(triggers.size).to eq 1
36
+ expect(first.name).to eq "uppercase_users_name"
37
+ expect(first.definition).to include("BEFORE INSERT")
38
+ expect(first.definition).to match(/ON [public\.users|users]/)
39
+ expect(first.definition).to include("FOR EACH ROW")
40
+ expect(first.definition).to include("EXECUTE PROCEDURE uppercase_users_name()")
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -53,22 +53,44 @@ module Fx::Adapters
53
53
  end
54
54
 
55
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
- )
56
+ context "when the function has arguments" do
57
+ it "successfully drops a function with the entire function signature" do
58
+ adapter = Postgres.new
59
+ adapter.create_function(
60
+ <<-EOS
61
+ CREATE FUNCTION adder(x int, y int)
62
+ RETURNS int AS $$
63
+ BEGIN
64
+ RETURN $1 + $2;
65
+ END;
66
+ $$ LANGUAGE plpgsql;
67
+ EOS
68
+ )
69
+
70
+ adapter.drop_function(:adder)
71
+
72
+ expect(adapter.functions.map(&:name)).not_to include("adder")
73
+ end
74
+ end
75
+
76
+ context "when the function does not have arguments" do
77
+ it "successfully drops a function" do
78
+ adapter = Postgres.new
79
+ adapter.create_function(
80
+ <<-EOS
81
+ CREATE OR REPLACE FUNCTION test()
82
+ RETURNS text AS $$
83
+ BEGIN
84
+ RETURN 'test';
85
+ END;
86
+ $$ LANGUAGE plpgsql;
87
+ EOS
88
+ )
68
89
 
69
- adapter.drop_function(:test)
90
+ adapter.drop_function(:test)
70
91
 
71
- expect(adapter.functions.map(&:name)).not_to include("test")
92
+ expect(adapter.functions.map(&:name)).not_to include("test")
93
+ end
72
94
  end
73
95
  end
74
96
 
@@ -28,6 +28,29 @@ describe Fx::Definition do
28
28
  %r(Define function in db/functions/test_v01.sql before migrating),
29
29
  )
30
30
  end
31
+
32
+ context "when definition is at Rails engine" do
33
+ it "returns the content of a function definition" do
34
+ sql_definition = <<-EOS
35
+ CREATE OR REPLACE FUNCTION test()
36
+ RETURNS text AS $$
37
+ BEGIN
38
+ RETURN 'test';
39
+ END;
40
+ $$ LANGUAGE plpgsql;
41
+ EOS
42
+ engine_path = Rails.root.join("tmp", "engine")
43
+ FileUtils.mkdir_p(engine_path.join("db", "functions"))
44
+ File.write(engine_path.join("db", "functions", "custom_test_v01.sql"), sql_definition)
45
+ Rails.application.config.paths["db/migrate"].push(engine_path.join("db", "migrate"))
46
+
47
+ definition = Fx::Definition.new(name: "custom_test", version: 1)
48
+
49
+ expect(definition.to_sql).to eq sql_definition
50
+
51
+ FileUtils.rm_rf(engine_path)
52
+ end
53
+ end
31
54
  end
32
55
 
33
56
  context "representing a trigger definition" do
@@ -0,0 +1,55 @@
1
+ require "spec_helper"
2
+ require "fx/function"
3
+
4
+ module Fx
5
+ describe Function do
6
+ describe "#<=>" do
7
+ it "delegates to `name`" do
8
+ function_a = Function.new(
9
+ "name" => "name_a",
10
+ "definition" => "some defintion",
11
+ )
12
+ function_b = Function.new(
13
+ "name" => "name_b",
14
+ "definition" => "some defintion",
15
+ )
16
+ function_c = Function.new(
17
+ "name" => "name_c",
18
+ "definition" => "some defintion",
19
+ )
20
+
21
+ expect(function_b).to be_between(function_a, function_c)
22
+ end
23
+ end
24
+
25
+ describe "#==" do
26
+ it "compares `name` and `definition`" do
27
+ function_a = Function.new(
28
+ "name" => "name_a",
29
+ "definition" => "some defintion",
30
+ )
31
+ function_b = Function.new(
32
+ "name" => "name_b",
33
+ "definition" => "some other defintion",
34
+ )
35
+
36
+ expect(function_a).not_to eq(function_b)
37
+ end
38
+ end
39
+
40
+ describe "#to_schema" do
41
+ it "returns a schema compatible version of the function" do
42
+ function = Function.new(
43
+ "name" => "uppercase_users_name",
44
+ "definition" => "CREATE OR REPLACE TRIGGER uppercase_users_name ...",
45
+ )
46
+
47
+ expect(function.to_schema).to eq <<-EOS
48
+ create_function :uppercase_users_name, sql_definition: <<-\SQL
49
+ CREATE OR REPLACE TRIGGER uppercase_users_name ...
50
+ SQL
51
+ EOS
52
+ end
53
+ end
54
+ end
55
+ end