arfi 0.4.0 → 0.5.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.
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'rails'
5
+ require File.expand_path('config/environment', Dir.pwd)
6
+
7
+ module Arfi
8
+ module Commands
9
+ # +Arfi::Commands::FIdx+ module contains commands for manipulating functional index in Rails project.
10
+ class FIdx < Thor
11
+ ADAPTERS = %i[postgresql mysql].freeze
12
+
13
+ # steep:ignore:start
14
+ desc 'create FUNCTION_NAME [--template=template_file --adapter=adapter]', 'Initialize the functional index'
15
+ option :template, type: :string, banner: 'template_file',
16
+ desc: 'Path to the template file. See `README.md` for details.'
17
+ option :adapter, type: :string,
18
+ desc: 'Specify database adapter, used for projects with multiple database architecture. ' \
19
+ "Available adapters: #{ADAPTERS.join(', ')}",
20
+ banner: 'adapter'
21
+ # steep:ignore:end
22
+
23
+ # +Arfi::Commands::FIdx#create+ -> void
24
+ #
25
+ # This command is used to create the functional index.
26
+ #
27
+ # @example
28
+ # bundle exec arfi f_idx create some_function
29
+ #
30
+ # ARFI also supports the use of custom templates for SQL functions, but now there are some restrictions and rules
31
+ # according to which it is necessary to describe the function. First, the function must be written in a
32
+ # Ruby-compatible syntax: the file name is not so important, but the name for the function name must be
33
+ # interpolated with the +index_name+ variable name, and the function itself must be placed in the HEREDOC
34
+ # statement. Below is an example file.
35
+ #
36
+ # @example
37
+ # # ./template/my_custom_template
38
+ # <<~SQL
39
+ # CREATE OR REPLACE FUNCTION #{index_name}() RETURNS TEXT[]
40
+ # LANGUAGE SQL
41
+ # IMMUTABLE AS
42
+ # $$
43
+ # -- Function body here
44
+ # $$
45
+ # SQL
46
+ #
47
+ # To use a custom template, add the --template flag.
48
+ #
49
+ # @example
50
+ # bundle exec arfi f_idx create some_function --template ./template/my_custom_template
51
+ #
52
+ # @param index_name [String] Name of the index.
53
+ # @return [void]
54
+ # @raise [Arfi::Errors::InvalidSchemaFormat] if ActiveRecord.schema_format is not :ruby
55
+ # @raise [Arfi::Errors::NoFunctionsDir] if there is no `db/functions` directory
56
+ # @see Arfi::Commands::FIdx#validate_schema_format!
57
+ def create(index_name)
58
+ validate_schema_format!
59
+ content = build_sql_function(index_name)
60
+ create_function_file(index_name, content)
61
+ end
62
+
63
+ # steep:ignore:start
64
+ desc 'destroy INDEX_NAME [--revision=revision --adapter=adapter]', 'Delete the functional index.'
65
+ option :revision, type: :string, banner: 'revision', desc: 'Revision of the function.'
66
+ option :adapter, type: :string,
67
+ desc: 'Specify database adapter, used for projects with multiple database architecture. ' \
68
+ "Available adapters: #{ADAPTERS.join(', ')}",
69
+ banner: 'adapter'
70
+ # steep:ignore:end
71
+
72
+ # +Arfi::Commands::FIdx#destroy+ -> void
73
+ #
74
+ # This command is used to delete the functional index.
75
+ #
76
+ # @example
77
+ # bundle exec arfi f_idx destroy some_function [revision index (just an integer, 1 is by default)]
78
+ # @param index_name [String] Name of the index.
79
+ # @return [void]
80
+ # @raise [Arfi::Errors::InvalidSchemaFormat] if ActiveRecord.schema_format is not :ruby
81
+ def destroy(index_name)
82
+ validate_schema_format!
83
+
84
+ revision = Integer(options[:revision] || '01') # steep:ignore NoMethod
85
+ revision = "0#{revision}"
86
+ FileUtils.rm("#{functions_dir}/#{index_name}_v#{revision}.sql")
87
+ puts "Deleted: #{functions_dir}/#{index_name}_v#{revision}.sql"
88
+ end
89
+
90
+ private
91
+
92
+ # +Arfi::Commands::FIdx#validate_schema_format!+ -> void
93
+ #
94
+ # Helper method to validate the schema format.
95
+ #
96
+ # @!visibility private
97
+ # @private
98
+ # @raise [Arfi::Errors::InvalidSchemaFormat] if ActiveRecord.schema_format is not :ruby.
99
+ # @return [nil] if the schema format is valid.
100
+ def validate_schema_format!
101
+ raise Arfi::Errors::InvalidSchemaFormat unless ActiveRecord.schema_format == :ruby # steep:ignore NoMethod
102
+ end
103
+
104
+ # +Arfi::Commands::FIdx#build_sql_function+ -> String
105
+ #
106
+ # Helper method to build the SQL function.
107
+ #
108
+ # @!visibility private
109
+ # @private
110
+ # @param index_name [String] Name of the index.
111
+ # @return [String] SQL function body.
112
+ def build_sql_function(index_name) # rubocop:disable Metrics/MethodLength
113
+ return build_from_file(index_name) if options[:template] # steep:ignore NoMethod
114
+
115
+ unless options[:adapter] # steep:ignore NoMethod
116
+ return <<~SQL
117
+ CREATE OR REPLACE FUNCTION #{index_name}() RETURNS TEXT[]
118
+ LANGUAGE SQL
119
+ IMMUTABLE AS
120
+ $$
121
+ -- Function body here
122
+ $$
123
+ SQL
124
+ end
125
+
126
+ case options[:adapter] # steep:ignore NoMethod
127
+ when 'postgresql'
128
+ <<~SQL
129
+ CREATE OR REPLACE FUNCTION #{index_name}() RETURNS TEXT[]
130
+ LANGUAGE SQL
131
+ IMMUTABLE AS
132
+ $$
133
+ -- Function body here
134
+ $$
135
+ SQL
136
+ when 'mysql'
137
+ <<~SQL
138
+ CREATE FUNCTION #{index_name} ()
139
+ RETURNS return_type
140
+ BEGIN
141
+ -- Function body here
142
+ END;
143
+ SQL
144
+ else
145
+ # steep:ignore:start
146
+ raise "Unknown adapter: #{options[:adapter]}. Supported adapters: #{ADAPTERS.join(', ')}"
147
+ # steep:ignore:end
148
+ end
149
+ end
150
+
151
+ # +Arfi::Commands::FIdx#build_from_file+ -> String
152
+ #
153
+ # Helper method to build the SQL function. Used with flag `--template`.
154
+ #
155
+ # @!visibility private
156
+ # @private
157
+ # @param index_name [String] Name of the index.
158
+ # @return [String] SQL function body.
159
+ # @see Arfi::Commands::FIdx#create
160
+ # @see Arfi::Commands::FIdx#build_sql_function
161
+ def build_from_file(index_name)
162
+ # steep:ignore:start
163
+ RubyVM::InstructionSequence.compile("index_name = '#{index_name}'; #{File.read(options[:template])}").eval
164
+ # steep:ignore:end
165
+ end
166
+
167
+ # +Arfi::Commands::FIdx#create_function_file+ -> void
168
+ #
169
+ # Helper method to create the index file.
170
+ #
171
+ # @!visibility private
172
+ # @private
173
+ # @param index_name [String] Name of the index.
174
+ # @param content [String] SQL function body.
175
+ # @return [void]
176
+ def create_function_file(index_name, content)
177
+ existing_files = Dir.glob("#{functions_dir}/#{index_name}*.sql")
178
+
179
+ return write_file(index_name, content, 1) if existing_files.empty?
180
+
181
+ latest_version = extract_latest_version(existing_files)
182
+ write_file(index_name, content, latest_version.succ)
183
+ end
184
+
185
+ # +Arfi::Commands::FIdx#extract_latest_version+ -> Integer
186
+ #
187
+ # Helper method to extract the latest version of the index.
188
+ #
189
+ # @!visibility private
190
+ # @private
191
+ # @param files [Array<String>] List of files.
192
+ # @return [String] Latest version of the index.
193
+ def extract_latest_version(files)
194
+ version_numbers = files.map do |file|
195
+ File.basename(file)[/\w+_v(\d+)\.sql/, 1]
196
+ end.compact
197
+
198
+ version_numbers.max
199
+ end
200
+
201
+ # +Arfi::Commands::FIdx#write_file+ -> void
202
+ #
203
+ # Helper method to write the index file.
204
+ #
205
+ # @!visibility private
206
+ # @private
207
+ # @param index_name [String] Name of the index.
208
+ # @param content [String] SQL function body.
209
+ # @param version [String|Integer] Version of the index.
210
+ # @return [void]
211
+ def write_file(index_name, content, version)
212
+ version_str = format('%02d', version)
213
+ path = "#{functions_dir}/#{index_name}_v#{version_str}.sql"
214
+ File.write(path, content.to_s)
215
+ puts "Created: #{path}"
216
+ end
217
+
218
+ # +Arfi::Commands::FIdx#functions_dir+ -> Pathname
219
+ #
220
+ # Helper method to get path to `db/functions` directory.
221
+ #
222
+ # @!visibility private
223
+ # @private
224
+ # @return [Pathname] Path to `db/functions` directory
225
+ def functions_dir
226
+ # steep:ignore:start
227
+ if options[:adapter]
228
+ raise Arfi::Errors::AdapterNotSupported unless ADAPTERS.include?(options[:adapter].to_sym)
229
+
230
+ Rails.root.join("db/functions/#{options[:adapter]}")
231
+ # steep:ignore:end
232
+ else
233
+ Rails.root.join('db/functions')
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'fileutils'
5
+ require 'rails'
6
+
7
+ module Arfi
8
+ module Commands
9
+ # +Arfi::Commands::Project+ class is used to create `db/functions` directory.
10
+ class Project < Thor
11
+ ADAPTERS = %i[postgresql mysql].freeze
12
+
13
+ # steep:ignore:start
14
+ desc 'create', 'Initialize project by creating db/functions directory'
15
+ option :adapter, type: :string,
16
+ desc: 'Specify database adapter, used for projects with multiple database architecture. ' \
17
+ "Available adapters: #{ADAPTERS.join(', ')}",
18
+ banner: 'adapter'
19
+ # steep:ignore:end
20
+
21
+ # +Arfi::Commands::Project#create+ -> void
22
+ #
23
+ # This command is used to create `db/functions` directory.
24
+ #
25
+ # @example
26
+ # bundle exec arfi project create
27
+ # @return [void]
28
+ # @raise [Arfi::Errors::InvalidSchemaFormat] if ActiveRecord.schema_format is not :ruby.
29
+ def create
30
+ raise Arfi::Errors::InvalidSchemaFormat unless ActiveRecord.schema_format == :ruby # steep:ignore NoMethod
31
+ return puts "Directory #{functions_dir} already exists" if Dir.exist?(functions_dir)
32
+
33
+ FileUtils.mkdir_p(functions_dir)
34
+ puts "Created: #{functions_dir}"
35
+ end
36
+
37
+ private
38
+
39
+ # +Arfi::Commands::Project#functions_dir+ -> Pathname
40
+ #
41
+ # Helper method to get path to `db/functions` directory.
42
+ #
43
+ # @!visibility private
44
+ # @private
45
+ # @return [Pathname] Path to `db/functions` directory
46
+ def functions_dir
47
+ # steep:ignore:start
48
+ if options[:adapter]
49
+ raise Arfi::Errors::AdapterNotSupported unless ADAPTERS.include?(options[:adapter].to_sym)
50
+
51
+ Rails.root.join("db/functions/#{options[:adapter]}")
52
+ # steep:ignore:end
53
+ else
54
+ Rails.root.join('db/functions')
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arfi
4
+ module Errors
5
+ # This error is raised when there is no `db/functions` directory
6
+ class NoFunctionsDir < StandardError
7
+ def initialize(message =
8
+ 'There is no such directory: db/functions. Did you run `bundle exec arfi project:create`?')
9
+ @message = message
10
+ super
11
+ end
12
+ end
13
+
14
+ # This error is raised when Rails project schema format is not +schema.rb+
15
+ class InvalidSchemaFormat < StandardError
16
+ def initialize(message = 'Invalid schema format. ARFI supports only ruby format schemas.')
17
+ @message = message
18
+ super
19
+ end
20
+ end
21
+
22
+ # This error is raised when database adapter is not supported (for e.g., SQLite3).
23
+ class AdapterNotSupported < StandardError
24
+ def initialize(message = 'Adapter not supported')
25
+ @message = message
26
+ super
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+
5
+ module ActiveRecord
6
+ class Base # :nodoc:
7
+ # +ActiveRecord::Base.function_exists?+ -> bool
8
+ #
9
+ # This method checks if a custom SQL function exists in the database.
10
+ #
11
+ # @example
12
+ # ActiveRecord::Base.function_exists?('my_function') #=> true
13
+ # ActiveRecord::Base.function_exists?('my_function123') #=> false
14
+ # @param [String] function_name The name of the function to check.
15
+ # @return [Boolean] Returns true if the function exists, false otherwise.
16
+ def self.function_exists?(function_name) # rubocop:disable Metrics/MethodLength
17
+ case connection
18
+ when ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
19
+ connection.execute("SELECT * FROM pg_proc WHERE proname = '#{function_name}'").any?
20
+ when ActiveRecord::ConnectionAdapters::Mysql2Adapter
21
+ sql = <<~SQL
22
+ SELECT 1
23
+ FROM information_schema.ROUTINES
24
+ WHERE ROUTINE_TYPE = 'FUNCTION'
25
+ AND ROUTINE_SCHEMA = '#{connection.current_database}'
26
+ AND ROUTINE_NAME = '#{function_name}'
27
+ LIMIT 1;
28
+ SQL
29
+
30
+ !!connection.execute(sql).first
31
+ else
32
+ raise ActiveRecord::AdapterNotFound, "adapter #{connection.class.name} is not supported"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record/connection_adapters/postgresql/database_statements'
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ module PostgreSQL
8
+ module DatabaseStatements
9
+ # This patch is used for db:prepare task.
10
+ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
11
+ log(sql, name, async: async) do |notification_payload|
12
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
13
+ result = conn.async_exec(sql)
14
+ verified!
15
+ handle_warnings(result)
16
+ notification_payload[:row_count] = result.count
17
+ result
18
+ rescue => e
19
+ if e.message.match?(/ERROR:\s{2}function (\S+\(\w+\)) does not exist/)
20
+ function_name = e.message.match(/ERROR:\s{2}function (\S+\(\w+\)) does not exist/)[1][/^[^(]*/]
21
+ if (function_file = Dir.glob(Rails.root.join('db', 'functions', "#{function_name}_v*.sql").to_s).first)
22
+ conn.async_exec(File.read(function_file).chop)
23
+ retry
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+ require 'active_record/schema'
5
+ require 'arfi/sql_function_loader'
6
+ require_relative 'active_record/base'
7
+ require_relative 'active_record/connection_adapters/postgresql/database_statements'
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'arfi'
4
+ require 'rails'
5
+
6
+ module Arfi
7
+ class Railtie < ::Rails::Railtie # :nodoc:
8
+ railtie_name :arfi
9
+
10
+ rake_tasks do
11
+ path = File.expand_path(__dir__ || '.')
12
+ Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arfi
4
+ # +Arfi::SqlFunctionLoader+ is a class which loads user defined SQL functions into database.
5
+ class SqlFunctionLoader
6
+ class << self
7
+ # +Arfi::SqlFunctionLoader.load!+ -> (nil | void)
8
+ #
9
+ # Loads user defined SQL functions into database.
10
+ #
11
+ # @param task_name [String|nil] Name of the task.
12
+ # @return [nil] if there is no `db/functions` directory.
13
+ # @return [void] if there is no errors.
14
+ def load!(task_name: nil)
15
+ self.task_name = task_name[/([^:]+$)/] if task_name
16
+ return puts 'No SQL files found. Skipping db population with ARFI' unless sql_files.any?
17
+
18
+ raise_unless_supported_adapter
19
+ handle_db_population
20
+ conn.close
21
+ end
22
+
23
+ private
24
+
25
+ attr_accessor :task_name
26
+
27
+ # +Arfi::SqlFunctionLoader#raise_unless_supported_adapter+ -> void
28
+ #
29
+ # Checks if the database adapter is supported.
30
+ #
31
+ # @!visibility private
32
+ # @private
33
+ # @return [void]
34
+ # @raise [Arfi::Errors::AdapterNotSupported]
35
+ def raise_unless_supported_adapter
36
+ allowed = %w[ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
37
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter].freeze
38
+ return if allowed.include?(conn.class.to_s) # steep:ignore ArgumentTypeMismatch
39
+
40
+ raise Arfi::Errors::AdapterNotSupported
41
+ end
42
+
43
+ # +Arfi::SqlFunctionLoader#handle_db_population+ -> void
44
+ #
45
+ # Loads user defined SQL functions into database. This conditional branch was written this way because if we
46
+ # call db:migrate:db_name, then task_name will not be nil, but it will be zero if we call db:migrate. Then we
47
+ # check that the application has been configured to work with multiple databases in order to populate all
48
+ # databases, and only after this check can we populate the database in case the db:migrate (or any other) task
49
+ # has been called for configuration with a single database. Go to `lib/arfi/tasks/db.rake` for additional info.
50
+ #
51
+ # @!visibility private
52
+ # @private
53
+ # @return [void]
54
+ def handle_db_population
55
+ if task_name || (task_name && multi_db?) || task_name.nil?
56
+ populate_db
57
+ elsif multi_db?
58
+ populate_multiple_db
59
+ end
60
+ end
61
+
62
+ # +Arfi::SqlFunctionLoader#multi_db?+ -> Boolean
63
+ #
64
+ # Checks if the application has been configured to work with multiple databases.
65
+ #
66
+ # @return [Boolean]
67
+ def multi_db?
68
+ ActiveRecord::Base.configurations.configurations.count { _1.env_name == Rails.env } > 1 # steep:ignore NoMethod
69
+ end
70
+
71
+ # +Arfi::SqlFunctionLoader#populate_multiple_db+ -> void
72
+ #
73
+ # Loads user defined SQL functions into all databases.
74
+ #
75
+ # @!visibility private
76
+ # @private
77
+ # @return [void]
78
+ # @see Arfi::SqlFunctionLoader#multi_db?
79
+ # @see Arfi::SqlFunctionLoader#populate_db
80
+ def populate_multiple_db
81
+ # steep:ignore:start
82
+ ActiveRecord::Base.configurations.configurations.select { _1.env_name == Rails.env }.each do |config|
83
+ ActiveRecord::Base.establish_connection(config)
84
+ populate_db
85
+ end
86
+ # steep:ignore:end
87
+ end
88
+
89
+ # +Arfi::SqlFunctionLoader#populate_db+ -> void
90
+ #
91
+ # Loads user defined SQL functions into database.
92
+ #
93
+ # @!visibility private
94
+ # @private
95
+ # @return [void]
96
+ def populate_db
97
+ sql_files.each do |file|
98
+ sql = File.read(file).strip
99
+ conn.execute(sql)
100
+ puts "[ARFI] Loaded: #{File.basename(file)} into #{conn.pool.db_config.env_name} #{conn.pool.db_config.name}"
101
+ end
102
+ end
103
+
104
+ # +Arfi::SqlFunctionLoader#sql_files+ -> Array<String>
105
+ #
106
+ # Helper method to get list of SQL files. Here we check if we need to populate all databases or just one.
107
+ #
108
+ # @!visibility private
109
+ # @private
110
+ # @return [Array<String>] List of SQL files.
111
+ # @see Arfi::SqlFunctionLoader#load!
112
+ # @see Arfi::SqlFunctionLoader#multi_db?
113
+ # @see Arfi::SqlFunctionLoader#sql_functions_by_adapter
114
+ def sql_files
115
+ if task_name || multi_db?
116
+ sql_functions_by_adapter
117
+ else
118
+ Dir.glob(Rails.root.join('db', 'functions').join('*.sql'))
119
+ end
120
+ end
121
+
122
+ # +Arfi::SqlFunctionLoader#sql_functions_by_adapter+ -> Array<String>
123
+ #
124
+ # Helper method to get list of SQL files for specific database adapter.
125
+ #
126
+ # @!visibility private
127
+ # @private
128
+ # @return [Array<String>] List of SQL files.
129
+ # @raise [Arfi::Errors::AdapterNotSupported] if database adapter is not supported.
130
+ def sql_functions_by_adapter
131
+ case conn
132
+ when ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
133
+ Dir.glob(Rails.root.join('db', 'functions', 'postgresql').join('*.sql'))
134
+ when ActiveRecord::ConnectionAdapters::Mysql2Adapter
135
+ Dir.glob(Rails.root.join('db', 'functions', 'mysql').join('*.sql'))
136
+ else
137
+ raise Arfi::Errors::AdapterNotSupported
138
+ end
139
+ end
140
+
141
+ # +Arfi::SqlFunctionLoader#conn+ -> ActiveRecord::ConnectionAdapters::AbstractAdapter
142
+ #
143
+ # Helper method to get database connection.
144
+ #
145
+ # @!visibility private
146
+ # @private
147
+ # @return [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection.
148
+ def conn
149
+ ActiveRecord::Base.lease_connection
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake'
4
+ require 'arfi/sql_function_loader'
5
+
6
+ namespace :_db do
7
+ task :arfi_enhance do
8
+ Arfi::SqlFunctionLoader.load!
9
+ end
10
+ end
11
+
12
+ Rake::Task.define_task(:environment) unless Rake::Task.task_defined?(:environment)
13
+
14
+ # Enhancing single db tasks
15
+ %w[db:migrate db:schema:load db:setup].each do |task|
16
+ Rake::Task[task].enhance(['_db:arfi_enhance']) if Rake::Task.task_defined?(task)
17
+ end
18
+
19
+ # We remove user defined keys in database.yml and use only the ones about RDBMS connections.
20
+ # Here we try to find tasks like db:migrate:your_db_name and enhance them as well as tasks for single db connections.
21
+ rdbms_configs = Rails.configuration.database_configuration[Rails.env].select { |_k, v| v.is_a?(Hash) }.keys
22
+ possible_tasks = Rake::Task.tasks.select do |task|
23
+ task.name.match?(/^(db:migrate:|db:schema:load:|db:setup:|db:prepare:|db:test:prepare:)(\w+)$/)
24
+ end
25
+ possible_tasks = possible_tasks.select do |task|
26
+ rdbms_configs.any? { |n| task.name.include?(n) }
27
+ end
28
+
29
+ # In general, this is a small trick due to the fact that Rake does not allow you to view parent tasks from a task that
30
+ # was called for enhancing. Moreover, the utility does not provide for passing parameters to the called task.
31
+ # To get around this limitation, we dynamically create a task with the name we need, and then pass this name as
32
+ # an argument to the method.
33
+ possible_tasks.each do |task|
34
+ Rake::Task.define_task("_db:arfi_enhance:#{task.name}") do
35
+ Arfi::SqlFunctionLoader.load!(task_name: task.name)
36
+ end
37
+ task.enhance(["_db:arfi_enhance:#{task.name}"])
38
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arfi
4
+ VERSION = '0.5.0'
5
+ end
data/lib/arfi.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'arfi/version'
4
+ require_relative 'arfi/errors'
5
+ require 'arfi/extensions/extensions'
6
+ require 'rails' if defined?(Rails)
7
+
8
+ # Top level module
9
+ module Arfi
10
+ require_relative 'arfi/railtie' if defined?(Rails)
11
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yard'
4
+
5
+ # run as `rm -rf arfi_docs && bundle exec rake docs:generate && bundle exec rake docs:push`
6
+
7
+ namespace :docs do
8
+ desc 'Generate new docs and push them repo'
9
+ task :generate do
10
+ puts 'Generating docs...'
11
+ args = %w[--no-cache --private --protected --readme README.md --no-progress --output-dir doc]
12
+ YARD::CLI::Yardoc.run(*args)
13
+ puts 'Docs generated'
14
+ end
15
+
16
+ desc 'Push docs to repo'
17
+ task :push do
18
+ puts 'Copying docs...'
19
+ `git clone git@github.com:unurgunite/arfi_docs.git`
20
+ Dir.chdir('arfi_docs') do
21
+ cp_r('../doc/.', '.', remove_destination: true)
22
+ `git add .`
23
+ `git commit -m "Update docs #{Time.now.utc}"`
24
+ `git push`
25
+ end
26
+ rm_rf('arfi_docs')
27
+ puts 'Docs pushed'
28
+ end
29
+ end