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.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.hound.yml +2 -0
- data/.rubocop.yml +648 -0
- data/.travis.yml +18 -6
- data/Appraisals +21 -10
- data/LICENSE +18 -0
- data/README.md +45 -13
- data/bin/setup +1 -0
- data/gemfiles/rails42.gemfile +2 -1
- data/gemfiles/rails50.gemfile +1 -1
- data/gemfiles/rails51.gemfile +8 -0
- data/gemfiles/rails52.gemfile +8 -0
- data/gemfiles/rails60.gemfile +8 -0
- data/gemfiles/rails_edge.gemfile +8 -0
- data/lib/fx.rb +22 -0
- data/lib/fx/adapters/postgres.rb +26 -1
- data/lib/fx/adapters/postgres/functions.rb +5 -2
- data/lib/fx/adapters/postgres/triggers.rb +2 -2
- data/lib/fx/command_recorder.rb +0 -5
- data/lib/fx/configuration.rb +10 -0
- data/lib/fx/definition.rb +13 -3
- data/lib/fx/function.rb +2 -0
- data/lib/fx/railtie.rb +15 -0
- data/lib/fx/schema_dumper.rb +0 -5
- data/lib/fx/schema_dumper/function.rb +14 -5
- data/lib/fx/statements.rb +0 -5
- data/lib/fx/statements/function.rb +4 -2
- data/lib/fx/statements/trigger.rb +2 -0
- data/lib/fx/trigger.rb +2 -0
- data/lib/fx/version.rb +1 -1
- data/lib/generators/fx/function/USAGE +2 -0
- data/lib/generators/fx/function/function_generator.rb +14 -1
- data/lib/generators/fx/trigger/USAGE +2 -0
- data/lib/generators/fx/trigger/trigger_generator.rb +14 -1
- data/spec/acceptance/user_manages_functions_spec.rb +22 -2
- data/spec/acceptance/user_manages_triggers_spec.rb +6 -6
- data/spec/acceptance_helper.rb +2 -1
- data/spec/dummy/Rakefile +7 -0
- data/spec/features/functions/migrations_spec.rb +2 -2
- data/spec/features/functions/revert_spec.rb +2 -2
- data/spec/features/triggers/migrations_spec.rb +3 -3
- data/spec/features/triggers/revert_spec.rb +4 -4
- data/spec/fx/adapters/postgres/functions_spec.rb +37 -0
- data/spec/fx/adapters/postgres/triggers_spec.rb +45 -0
- data/spec/fx/adapters/postgres_spec.rb +46 -49
- data/spec/fx/definition_spec.rb +25 -2
- data/spec/fx/function_spec.rb +55 -0
- data/spec/fx/schema_dumper/function_spec.rb +58 -2
- data/spec/fx/schema_dumper/trigger_spec.rb +3 -3
- data/spec/fx/trigger_spec.rb +55 -0
- data/spec/generators/fx/function/function_generator_spec.rb +12 -0
- data/spec/generators/fx/trigger/trigger_generator_spec.rb +12 -0
- metadata +19 -11
- data/.ruby-version +0 -1
- data/gemfiles/rails40.gemfile +0 -8
- data/gemfiles/rails40.gemfile.lock +0 -111
- data/gemfiles/rails41.gemfile +0 -8
- data/gemfiles/rails41.gemfile.lock +0 -113
- data/gemfiles/rails42.gemfile.lock +0 -130
- data/gemfiles/rails50.gemfile.lock +0 -126
data/.travis.yml
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
addons:
|
2
|
-
postgresql: "
|
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.
|
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.
|
27
|
-
gemfile: gemfiles/
|
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
|
+
[](https://travis-ci.com/teoljungberg/fx)
|
4
|
+
[](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
|
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:
|
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
|
-
|
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
data/gemfiles/rails42.gemfile
CHANGED
data/gemfiles/rails50.gemfile
CHANGED
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
|
data/lib/fx/adapters/postgres.rb
CHANGED
@@ -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
|
-
|
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 =
|
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
|
-
|
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 =
|
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
|
-
|
23
|
+
EOS
|
24
24
|
|
25
25
|
# Wraps #all as a static facade.
|
26
26
|
#
|
data/lib/fx/command_recorder.rb
CHANGED
data/lib/fx/configuration.rb
CHANGED
@@ -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
|
data/lib/fx/definition.rb
CHANGED
@@ -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
|