fx 0.2.0 → 0.6.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 (61) 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 -6
  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 +5 -2
  19. data/lib/fx/adapters/postgres/triggers.rb +2 -2
  20. data/lib/fx/command_recorder.rb +0 -5
  21. data/lib/fx/configuration.rb +10 -0
  22. data/lib/fx/definition.rb +13 -3
  23. data/lib/fx/function.rb +2 -0
  24. data/lib/fx/railtie.rb +15 -0
  25. data/lib/fx/schema_dumper.rb +0 -5
  26. data/lib/fx/schema_dumper/function.rb +14 -5
  27. data/lib/fx/statements.rb +0 -5
  28. data/lib/fx/statements/function.rb +4 -2
  29. data/lib/fx/statements/trigger.rb +2 -0
  30. data/lib/fx/trigger.rb +2 -0
  31. data/lib/fx/version.rb +1 -1
  32. data/lib/generators/fx/function/USAGE +2 -0
  33. data/lib/generators/fx/function/function_generator.rb +14 -1
  34. data/lib/generators/fx/trigger/USAGE +2 -0
  35. data/lib/generators/fx/trigger/trigger_generator.rb +14 -1
  36. data/spec/acceptance/user_manages_functions_spec.rb +22 -2
  37. data/spec/acceptance/user_manages_triggers_spec.rb +6 -6
  38. data/spec/acceptance_helper.rb +2 -1
  39. data/spec/dummy/Rakefile +7 -0
  40. data/spec/features/functions/migrations_spec.rb +2 -2
  41. data/spec/features/functions/revert_spec.rb +2 -2
  42. data/spec/features/triggers/migrations_spec.rb +3 -3
  43. data/spec/features/triggers/revert_spec.rb +4 -4
  44. data/spec/fx/adapters/postgres/functions_spec.rb +37 -0
  45. data/spec/fx/adapters/postgres/triggers_spec.rb +45 -0
  46. data/spec/fx/adapters/postgres_spec.rb +46 -49
  47. data/spec/fx/definition_spec.rb +25 -2
  48. data/spec/fx/function_spec.rb +55 -0
  49. data/spec/fx/schema_dumper/function_spec.rb +58 -2
  50. data/spec/fx/schema_dumper/trigger_spec.rb +3 -3
  51. data/spec/fx/trigger_spec.rb +55 -0
  52. data/spec/generators/fx/function/function_generator_spec.rb +12 -0
  53. data/spec/generators/fx/trigger/trigger_generator_spec.rb +12 -0
  54. metadata +19 -11
  55. data/.ruby-version +0 -1
  56. data/gemfiles/rails40.gemfile +0 -8
  57. data/gemfiles/rails40.gemfile.lock +0 -111
  58. data/gemfiles/rails41.gemfile +0 -8
  59. data/gemfiles/rails41.gemfile.lock +0 -113
  60. data/gemfiles/rails42.gemfile.lock +0 -130
  61. data/gemfiles/rails50.gemfile.lock +0 -126
@@ -1,5 +1,9 @@
1
1
  addons:
2
- postgresql: "9.4"
2
+ postgresql: "10"
3
+ apt:
4
+ packages:
5
+ - postgresql-10
6
+ - postgresql-client-10
3
7
  before_install:
4
8
  - "echo '--colour' > ~/.rspec"
5
9
  - "echo 'gem: --no-document' > ~/.gemrc"
@@ -15,13 +19,21 @@ language:
15
19
  notifications:
16
20
  email: false
17
21
  rvm:
18
- - 2.3.1
22
+ - 2.6
23
+ - 2.5
24
+ - 2.4
19
25
  gemfile:
20
- - gemfiles/rails40.gemfile
21
- - gemfiles/rails41.gemfile
22
26
  - gemfiles/rails42.gemfile
23
27
  - gemfiles/rails50.gemfile
28
+ - gemfiles/rails51.gemfile
29
+ - gemfiles/rails52.gemfile
30
+ - gemfiles/rails60.gemfile
31
+ - gemfiles/rails_edge.gemfile
24
32
  matrix:
33
+ allow_failures:
34
+ - gemfile: gemfiles/rails_edge.gemfile
25
35
  exclude:
26
- - rvm: 2.1.8
27
- gemfile: gemfiles/rails50.gemfile
36
+ - rvm: 2.4
37
+ gemfile: gemfiles/rails_edge.gemfile
38
+ - rvm: 2.4
39
+ gemfile: gemfiles/rails60.gemfile
data/Appraisals CHANGED
@@ -1,16 +1,7 @@
1
- appraise "rails40" do
2
- gem "activerecord", "~> 4.0.0"
3
- gem "railties", "~> 4.0.0"
4
- end
5
-
6
- appraise "rails41" do
7
- gem "activerecord", "~> 4.1.0"
8
- gem "railties", "~> 4.1.0"
9
- end
10
-
11
1
  appraise "rails42" do
12
2
  gem "activerecord", "~> 4.2.0"
13
3
  gem "railties", "~> 4.2.0"
4
+ gem "pg", "~> 0.15"
14
5
  end
15
6
 
16
7
  if RUBY_VERSION > "2.2.0"
@@ -18,4 +9,24 @@ if RUBY_VERSION > "2.2.0"
18
9
  gem "activerecord", "~> 5.0"
19
10
  gem "railties", "~> 5.0"
20
11
  end
12
+
13
+ appraise "rails51" do
14
+ gem "activerecord", "~> 5.1"
15
+ gem "railties", "~> 5.1"
16
+ end
17
+
18
+ appraise "rails52" do
19
+ gem "activerecord", "~> 5.2"
20
+ gem "railties", "~> 5.2"
21
+ end
22
+
23
+ appraise "rails60" do
24
+ gem "activerecord", "~> 6.0"
25
+ gem "railties", "~> 6.0"
26
+ end
27
+
28
+ appraise "rails-edge" do
29
+ gem "rails", github: "rails/rails"
30
+ gem "arel", :github => "rails/arel"
31
+ end
21
32
  end
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright 2016 Teo Ljungberg
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # F(x)
2
2
 
3
+ [![Build Status](https://travis-ci.com/teoljungberg/fx.svg?token=AgJn4nPeY6ue2Pvy23JQ&branch=master)](https://travis-ci.com/teoljungberg/fx)
4
+ [![Documentation Quality](http://inch-ci.org/github/teoljungberg/fx.svg?branch=master)](http://inch-ci.org/github/teoljungberg/fx)
5
+
3
6
  F(x) adds methods to `ActiveRecord::Migration` to create and manage database
4
7
  functions and triggers in Rails.
5
8
 
@@ -18,18 +21,29 @@ F(x) ships with support for PostgreSQL. The adapter is configurable (see
18
21
 
19
22
  ## Great, how do I create a trigger and a function?
20
23
 
21
- You've got this great idea for a trigger you'd like to call
24
+ You've got this great idea for a function you'd like to call
22
25
  `uppercase_users_name`. You can create the migration and the corresponding
23
26
  definition file with the following command:
24
27
 
25
28
  ```sh
26
- % rails generate fx:trigger uppercase_users_name
29
+ % rails generate fx:function uppercase_users_name
30
+ create db/functions/uppercase_users_name_v01.sql
31
+ create db/migrate/[TIMESTAMP]_create_function_uppercase_users_name.rb
32
+ ```
33
+
34
+ Edit the `db/functions/uppercase_users_name_v01.sql` file with the SQL statement
35
+ that defines your function.
36
+
37
+ Next, let's add a trigger called `uppercase_users_name` to call our new
38
+ function each time we `INSERT` on the `users` table.
39
+
40
+ ```sh
41
+ % rails generate fx:trigger uppercase_users_name table_name:users
27
42
  create db/triggers/uppercase_users_name_v01.sql
28
43
  create db/migrate/[TIMESTAMP]_create_trigger_uppercase_users_name.rb
29
44
  ```
30
45
 
31
- Edit the `db/triggers/uppercase_users_name_v01.sql` file with the SQL statement
32
- that defines your trigger. In our example, this might look something like this:
46
+ In our example, this might look something like this:
33
47
 
34
48
  ```sql
35
49
  CREATE TRIGGER uppercase_users_name
@@ -38,15 +52,6 @@ CREATE TRIGGER uppercase_users_name
38
52
  EXECUTE PROCEDURE uppercase_users_name();
39
53
  ```
40
54
 
41
- As you see, we execute a function called `uppercase_users_name` before each
42
- `INSERT` on the `users` table, which is a function we don't have yet.
43
-
44
- ```sh
45
- % rails generate fx:function uppercase_users_name
46
- create db/functions/uppercase_users_name_v01.sql
47
- create db/migrate/[TIMESTAMP]_create_function_uppercase_users_name.rb
48
- ```
49
-
50
55
  The generated migrations contains `create_function` and `create_trigger`
51
56
  statements. The migration is reversible and the schema will be dumped into your
52
57
  `schema.rb` file.
@@ -79,3 +84,30 @@ def change
79
84
  drop_function :uppercase_users_name, revert_to_version: 2
80
85
  end
81
86
  ```
87
+
88
+ ## What if I need to use a function as the default value of a column?
89
+
90
+ You need to set F(x) to dump the functions in the beginning of db/schema.rb in a
91
+ initializer:
92
+
93
+ ```ruby
94
+ # config/initializers/fx.rb
95
+ Fx.configure do |config|
96
+ config.dump_functions_at_beginning_of_schema = true
97
+ end
98
+ ```
99
+
100
+ And then you can use a lambda in your migration file:
101
+
102
+ ```ruby
103
+ create_table :my_table do |t|
104
+ t.string :my_column, default: -> { "my_function()" }
105
+ end
106
+ ```
107
+
108
+ That's how you tell Rails to use the default as a literal SQL for the default
109
+ column value instead of a plain string.
110
+
111
+ ## Contributing
112
+
113
+ See [contributing](CONTRIBUTING.md) for more details.
data/bin/setup CHANGED
@@ -9,4 +9,5 @@ if [ -z "$CI" ]; then
9
9
  bundle exec appraisal install
10
10
  fi
11
11
 
12
+ bundle exec rake dummy:db:drop
12
13
  bundle exec rake dummy:db:create
@@ -4,5 +4,6 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "activerecord", "~> 4.2.0"
6
6
  gem "railties", "~> 4.2.0"
7
+ gem "pg", "~> 0.15"
7
8
 
8
- gemspec :path => "../"
9
+ gemspec path: "../"
@@ -5,4 +5,4 @@ source "https://rubygems.org"
5
5
  gem "activerecord", "~> 5.0"
6
6
  gem "railties", "~> 5.0"
7
7
 
8
- gemspec :path => "../"
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 5.1"
6
+ gem "railties", "~> 5.1"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 5.2"
6
+ gem "railties", "~> 5.2"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 6.0"
6
+ gem "railties", "~> 6.0"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", github: "rails/rails"
6
+ gem "arel", github: "rails/arel"
7
+
8
+ gemspec path: "../"
data/lib/fx.rb CHANGED
@@ -7,10 +7,32 @@ require "fx/function"
7
7
  require "fx/statements"
8
8
  require "fx/schema_dumper"
9
9
  require "fx/trigger"
10
+ require "fx/railtie"
10
11
 
11
12
  # F(x) adds methods `ActiveRecord::Migration` to create and manage database
12
13
  # triggers and functions in Rails applications.
13
14
  module Fx
15
+ # Hooks Fx into Rails.
16
+ #
17
+ # Enables fx migration methods, migration reversability, and `schema.rb`
18
+ # dumping.
19
+ def self.load
20
+ ActiveRecord::Migration::CommandRecorder.send(
21
+ :include,
22
+ Fx::CommandRecorder,
23
+ )
24
+
25
+ ActiveRecord::SchemaDumper.send(
26
+ :prepend,
27
+ Fx::SchemaDumper,
28
+ )
29
+
30
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.send(
31
+ :include,
32
+ Fx::Statements,
33
+ )
34
+ end
35
+
14
36
  # The current database adapter used by F(x).
15
37
  #
16
38
  # This defaults to {Fx::Adapters::Postgres} but can be overridden
@@ -23,6 +23,19 @@ module Fx
23
23
  # config.adapter = Fx::Adapters::Postgres.new
24
24
  # end
25
25
  class Postgres
26
+ # Creates an instance of the F(x) Postgres adapter.
27
+ #
28
+ # This is the default adapter for F(x). Configuring it via
29
+ # {Fx.configure} is not required, but the example below shows how one
30
+ # would explicitly set it.
31
+ #
32
+ # @param [#connection] connectable An object that returns the connection
33
+ # for F(x) to use. Defaults to `ActiveRecord::Base`.
34
+ #
35
+ # @example
36
+ # Fx.configure do |config|
37
+ # config.adapter = Fx::Adapters::Postgres.new
38
+ # end
26
39
  def initialize(connectable = ActiveRecord::Base)
27
40
  @connectable = connectable
28
41
  end
@@ -112,7 +125,11 @@ module Fx
112
125
  #
113
126
  # @return [void]
114
127
  def drop_function(name)
115
- execute "DROP FUNCTION #{name}();"
128
+ if support_drop_function_without_args
129
+ execute "DROP FUNCTION #{name};"
130
+ else
131
+ execute "DROP FUNCTION #{name}();"
132
+ end
116
133
  end
117
134
 
118
135
  # Drops the trigger from the database
@@ -137,6 +154,14 @@ module Fx
137
154
  def connection
138
155
  Connection.new(connectable.connection)
139
156
  end
157
+
158
+ def support_drop_function_without_args
159
+ # https://www.postgresql.org/docs/9.6/sql-dropfunction.html
160
+ # https://www.postgresql.org/docs/10/sql-dropfunction.html
161
+
162
+ pg_connection = connectable.connection.raw_connection
163
+ pg_connection.server_version >= 10_00_00
164
+ end
140
165
  end
141
166
  end
142
167
  end
@@ -8,7 +8,7 @@ module Fx
8
8
  class Functions
9
9
  # The SQL query used by F(x) to retrieve the functions considered
10
10
  # dumpable into `db/schema.rb`.
11
- FUNCTIONS_WITH_DEFINITIONS_QUERY = <<~SQL
11
+ FUNCTIONS_WITH_DEFINITIONS_QUERY = <<-EOS.freeze
12
12
  SELECT
13
13
  pp.proname AS name,
14
14
  pg_get_functiondef(pp.oid) AS definition
@@ -17,9 +17,12 @@ module Fx
17
17
  ON pn.oid = pp.pronamespace
18
18
  LEFT JOIN pg_depend pd
19
19
  ON pd.objid = pp.oid AND pd.deptype = 'e'
20
+ LEFT JOIN pg_aggregate pa
21
+ ON pa.aggfnoid = pp.oid
20
22
  WHERE pn.nspname = 'public' AND pd.objid IS NULL
23
+ AND pa.aggfnoid IS NULL
21
24
  ORDER BY pp.oid;
22
- SQL
25
+ EOS
23
26
 
24
27
  # Wraps #all as a static facade.
25
28
  #
@@ -8,7 +8,7 @@ module Fx
8
8
  class Triggers
9
9
  # The SQL query used by F(x) to retrieve the triggers considered
10
10
  # dumpable into `db/schema.rb`.
11
- TRIGGERS_WITH_DEFINITIONS_QUERY = <<~SQL
11
+ TRIGGERS_WITH_DEFINITIONS_QUERY = <<-EOS.freeze
12
12
  SELECT
13
13
  pt.tgname AS name,
14
14
  pg_get_triggerdef(pt.oid) AS definition
@@ -20,7 +20,7 @@ module Fx
20
20
  WHERE pt.tgname
21
21
  NOT ILIKE '%constraint%' AND pt.tgname NOT ILIKE 'pg%'
22
22
  ORDER BY pc.oid;
23
- SQL
23
+ EOS
24
24
 
25
25
  # Wraps #all as a static facade.
26
26
  #
@@ -22,8 +22,3 @@ module Fx
22
22
  end
23
23
  end
24
24
  end
25
-
26
- ActiveRecord::Migration::CommandRecorder.send(
27
- :include,
28
- Fx::CommandRecorder,
29
- )
@@ -17,6 +17,7 @@ module Fx
17
17
  # ```
18
18
  # Fx.configure do |config|
19
19
  # config.database = Fx::Adapters::Postgres
20
+ # config.dump_functions_at_beginning_of_schema = true
20
21
  # end
21
22
  # ```
22
23
  def self.configure
@@ -31,8 +32,17 @@ module Fx
31
32
  # @return Fx adapter
32
33
  attr_accessor :database
33
34
 
35
+ # Prioritizes the order in the schema.rb of functions before other
36
+ # statements in order to make directly schema load work when using functions
37
+ # in statements below, i.e.: default column values.
38
+ #
39
+ # Defaults to false
40
+ # @return Boolean
41
+ attr_accessor :dump_functions_at_beginning_of_schema
42
+
34
43
  def initialize
35
44
  @database = Fx::Adapters::Postgres.new
45
+ @dump_functions_at_beginning_of_schema = false
36
46
  end
37
47
  end
38
48
  end
@@ -8,7 +8,7 @@ module Fx
8
8
  end
9
9
 
10
10
  def to_sql
11
- File.read(full_path).tap do |content|
11
+ File.read(find_file || full_path).tap do |content|
12
12
  if content.empty?
13
13
  raise "Define #{@type} in #{path} before migrating."
14
14
  end
@@ -20,7 +20,7 @@ module Fx
20
20
  end
21
21
 
22
22
  def path
23
- File.join("db", @type.pluralize, filename)
23
+ @_path ||= File.join("db", @type.pluralize, filename)
24
24
  end
25
25
 
26
26
  def version
@@ -30,7 +30,17 @@ module Fx
30
30
  private
31
31
 
32
32
  def filename
33
- "#{@name}_v#{version}.sql"
33
+ @_filename ||= "#{@name}_v#{version}.sql"
34
+ end
35
+
36
+ def find_file
37
+ migration_paths.lazy
38
+ .map { |migration_path| File.expand_path(File.join("..", "..", path), migration_path) }
39
+ .find { |definition_path| File.exist?(definition_path) }
40
+ end
41
+
42
+ def migration_paths
43
+ Rails.application.config.paths["db/migrate"].expanded
34
44
  end
35
45
  end
36
46
  end
@@ -1,6 +1,8 @@
1
1
  module Fx
2
2
  # @api private
3
3
  class Function
4
+ include Comparable
5
+
4
6
  attr_reader :name, :definition
5
7
  delegate :<=>, to: :name
6
8