metka 2.3.3 → 3.0.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 +4 -4
- data/.github/workflows/lint_code.yml +9 -6
- data/.github/workflows/lint_docs.yml +15 -16
- data/.github/workflows/release.yml +23 -0
- data/.github/workflows/tests.yml +97 -0
- data/.gitignore +16 -7
- data/.rubocop.yml +8 -16
- data/.ruby-version +1 -1
- data/Gemfile +1 -1
- data/README.md +273 -236
- data/Rakefile +9 -4
- data/assets/metka-icon.svg +23 -0
- data/assets/metka-logo.svg +28 -0
- data/benchmark/Gemfile +17 -0
- data/benchmark/README.md +170 -0
- data/benchmark/benchmark.rb +718 -0
- data/benchmark/results.sqlite.txt +131 -0
- data/benchmark/results.txt +150 -0
- data/bin/setup +7 -0
- data/docs/superpowers/plans/2026-08-18-cloud-table-naming.md +316 -0
- data/docs/superpowers/specs/2026-08-18-cloud-table-naming-design.md +105 -0
- data/forspell.dict +3 -1
- data/gemfiles/rails71.gemfile +6 -0
- data/gemfiles/rails72.gemfile +6 -0
- data/gemfiles/rails80.gemfile +6 -0
- data/gemfiles/rails81.gemfile +6 -0
- data/gemfiles/rubocop.gemfile +2 -2
- data/lib/generators/metka/strategies/index/index_generator.rb +77 -0
- data/lib/generators/metka/strategies/index/templates/migration.rb.erb +89 -0
- data/lib/generators/metka/strategies/table/table_generator.rb +83 -0
- data/lib/generators/metka/strategies/table/templates/migration.rb.erb +128 -0
- data/lib/generators/metka/strategies/table/templates/migration.sqlite.rb.erb +93 -0
- data/lib/metka/generic_parser.rb +28 -14
- data/lib/metka/model.rb +121 -51
- data/lib/metka/query_builder.rb +45 -49
- data/lib/metka/tag_list.rb +16 -8
- data/lib/metka/tags_query.rb +93 -0
- data/lib/metka/version.rb +1 -1
- data/lib/metka.rb +24 -10
- data/metka.gemspec +21 -16
- metadata +45 -92
- data/.github/workflows/specs.yml +0 -86
- data/.rspec +0 -2
- data/Gemfile.lock +0 -201
- data/gemfiles/rails52.gemfile +0 -6
- data/gemfiles/rails6.gemfile +0 -6
- data/gemfiles/rails61.gemfile +0 -6
- data/lib/generators/metka/strategies/materialized_view/materialized_view_generator.rb +0 -73
- data/lib/generators/metka/strategies/materialized_view/templates/migration.rb.erb +0 -54
- data/lib/generators/metka/strategies/view/templates/migration.rb.erb +0 -26
- data/lib/generators/metka/strategies/view/view_generator.rb +0 -70
- data/lib/metka/query_builder/all_tags_query.rb +0 -11
- data/lib/metka/query_builder/any_tags_query.rb +0 -11
- data/lib/metka/query_builder/base_query.rb +0 -48
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class <%= @migration_class_name %> < ActiveRecord::Migration<%= ActiveRecord::VERSION::MAJOR < 5 ? '' : '[5.0]' %>
|
|
4
|
-
def up
|
|
5
|
-
execute <<-SQL
|
|
6
|
-
CREATE OR REPLACE FUNCTION metka_refresh_<%= view_name %>_materialized_view() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
7
|
-
BEGIN
|
|
8
|
-
IF TG_OP = 'INSERT' AND
|
|
9
|
-
(<%= source_columns.map { |column| "NEW.#{column} IS NOT NULL" }.join(' OR ') %>) THEN
|
|
10
|
-
REFRESH MATERIALIZED VIEW CONCURRENTLY <%= view_name %>;
|
|
11
|
-
END IF;
|
|
12
|
-
|
|
13
|
-
IF TG_OP = 'UPDATE' AND
|
|
14
|
-
(<%= source_columns.map { |column| "OLD.#{column} IS DISTINCT FROM NEW.#{column}" }.join(' OR ') %>) THEN
|
|
15
|
-
REFRESH MATERIALIZED VIEW CONCURRENTLY <%= view_name %>;
|
|
16
|
-
END IF;
|
|
17
|
-
|
|
18
|
-
IF TG_OP = 'DELETE' AND
|
|
19
|
-
(<%= source_columns.map { |column| "OLD.#{column} IS NOT NULL" }.join(' OR ') %>) THEN
|
|
20
|
-
REFRESH MATERIALIZED VIEW CONCURRENTLY <%= view_name %>;
|
|
21
|
-
END IF;
|
|
22
|
-
RETURN NEW;
|
|
23
|
-
END $$;
|
|
24
|
-
|
|
25
|
-
DROP MATERIALIZED VIEW IF EXISTS <%= view_name %>;
|
|
26
|
-
CREATE MATERIALIZED VIEW <%= view_name %> AS
|
|
27
|
-
SELECT
|
|
28
|
-
tag_name,
|
|
29
|
-
COUNT(*) AS taggings_count
|
|
30
|
-
FROM (
|
|
31
|
-
SELECT UNNEST
|
|
32
|
-
(<%= source_columns.join(' || ') %>) AS tag_name
|
|
33
|
-
FROM
|
|
34
|
-
<%= source_table_name %>
|
|
35
|
-
) subquery
|
|
36
|
-
GROUP BY
|
|
37
|
-
tag_name;
|
|
38
|
-
|
|
39
|
-
CREATE UNIQUE INDEX idx_<%= source_table_name %>_<%= source_columns_names %> ON <%= view_name %>(tag_name);
|
|
40
|
-
|
|
41
|
-
CREATE TRIGGER metka_on_<%= source_table_name %>_<%= source_columns_names %>
|
|
42
|
-
AFTER UPDATE OR INSERT OR DELETE ON <%= source_table_name %> FOR EACH ROW
|
|
43
|
-
EXECUTE PROCEDURE metka_refresh_<%= view_name %>_materialized_view();
|
|
44
|
-
SQL
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def down
|
|
48
|
-
execute <<-SQL
|
|
49
|
-
DROP TRIGGER IF EXISTS metka_on_<%= source_table_name %>_<%= source_columns_names %> ON <%= source_table_name %>;
|
|
50
|
-
DROP FUNCTION IF EXISTS metka_refresh_<%= view_name %>_materialized_view;
|
|
51
|
-
DROP MATERIALIZED VIEW IF EXISTS <%= view_name %>;
|
|
52
|
-
SQL
|
|
53
|
-
end
|
|
54
|
-
end
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class <%= @migration_class_name %> < ActiveRecord::Migration<%= ActiveRecord::VERSION::MAJOR < 5 ? '' : '[5.0]' %>
|
|
4
|
-
def up
|
|
5
|
-
execute <<-SQL
|
|
6
|
-
CREATE OR REPLACE VIEW <%= view_name %> AS
|
|
7
|
-
SELECT
|
|
8
|
-
tag_name,
|
|
9
|
-
COUNT(*) AS taggings_count
|
|
10
|
-
FROM (
|
|
11
|
-
SELECT UNNEST
|
|
12
|
-
(<%= source_columns.join(' || ') %>) AS tag_name
|
|
13
|
-
FROM
|
|
14
|
-
<%= source_table_name %>
|
|
15
|
-
) subquery
|
|
16
|
-
GROUP BY
|
|
17
|
-
tag_name;
|
|
18
|
-
SQL
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def down
|
|
22
|
-
execute <<-SQL
|
|
23
|
-
DROP VIEW <%= view_name %>;
|
|
24
|
-
SQL
|
|
25
|
-
end
|
|
26
|
-
end
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'rails/generators'
|
|
4
|
-
require 'rails/generators/active_record'
|
|
5
|
-
|
|
6
|
-
module Metka
|
|
7
|
-
module Generators
|
|
8
|
-
module Strategies
|
|
9
|
-
class ViewGenerator < ::Rails::Generators::Base # :nodoc:
|
|
10
|
-
include Rails::Generators::Migration
|
|
11
|
-
|
|
12
|
-
DEFAULT_SOURCE_COLUMNS = ['tags'].freeze
|
|
13
|
-
|
|
14
|
-
desc <<~LONGDESC
|
|
15
|
-
Generates migration to implement view strategy for Metka
|
|
16
|
-
|
|
17
|
-
> $ rails g metka:strategies:view --source-table-name=NAME_OF_TABLE_WITH_TAGS
|
|
18
|
-
LONGDESC
|
|
19
|
-
|
|
20
|
-
source_root File.expand_path('templates', __dir__)
|
|
21
|
-
|
|
22
|
-
class_option :source_table_name, type: :string, required: true,
|
|
23
|
-
desc: 'Name of the table that has a column with tags'
|
|
24
|
-
|
|
25
|
-
class_option :source_columns, type: :array, default: DEFAULT_SOURCE_COLUMNS,
|
|
26
|
-
desc: 'List of the tagged columns names'
|
|
27
|
-
|
|
28
|
-
class_option :view_name, type: :string,
|
|
29
|
-
desc: 'Custom name for the resulting view'
|
|
30
|
-
|
|
31
|
-
def generate_migration
|
|
32
|
-
migration_template 'migration.rb.erb', "db/migrate/#{migration_name}.rb"
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
no_tasks do
|
|
36
|
-
def source_table_name
|
|
37
|
-
options[:source_table_name]
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def source_columns
|
|
41
|
-
options[:source_columns]
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def source_columns_names
|
|
45
|
-
source_columns.join('_and_')
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def view_name
|
|
49
|
-
return options[:view_name] if options[:view_name]
|
|
50
|
-
|
|
51
|
-
columns_sequence = source_columns == DEFAULT_SOURCE_COLUMNS ? nil : "_with_#{source_columns_names}"
|
|
52
|
-
"tagged#{columns_sequence}_#{source_table_name}"
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def migration_name
|
|
56
|
-
"create_#{view_name}_view"
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def migration_class_name
|
|
60
|
-
migration_name.classify
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def self.next_migration_number(dir)
|
|
65
|
-
::ActiveRecord::Generators::Base.next_migration_number(dir)
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'singleton'
|
|
4
|
-
|
|
5
|
-
module Metka
|
|
6
|
-
class BaseQuery
|
|
7
|
-
include Singleton
|
|
8
|
-
|
|
9
|
-
def call(model, column_name, tag_list)
|
|
10
|
-
tags = tag_list.to_a
|
|
11
|
-
|
|
12
|
-
if tags.one?
|
|
13
|
-
value = Arel::Nodes::SqlLiteral.new(
|
|
14
|
-
ActiveRecord::Base.sanitize_sql_for_conditions(['?', tags.first])
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
column_cast = Arel::Nodes::NamedFunction.new(
|
|
18
|
-
'ANY',
|
|
19
|
-
[model.arel_table[column_name]]
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
Arel::Nodes::Equality.new(value, column_cast)
|
|
23
|
-
else
|
|
24
|
-
value = Arel::Nodes::SqlLiteral.new(
|
|
25
|
-
ActiveRecord::Base.sanitize_sql_for_conditions(['ARRAY[?]::varchar[]', tags])
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
Arel::Nodes::InfixOperation.new(infix_operator, model.arel_table[column_name], value)
|
|
29
|
-
end
|
|
30
|
-
# column_cast = Arel::Nodes::NamedFunction.new(
|
|
31
|
-
# 'CAST',
|
|
32
|
-
# [model.arel_table[column_name].as('text[]')]
|
|
33
|
-
# )
|
|
34
|
-
|
|
35
|
-
# value = Arel::Nodes::SqlLiteral.new(
|
|
36
|
-
# ActiveRecord::Base.sanitize_sql_for_conditions(['ARRAY[?]::varchar[]', tag_list.to_a])
|
|
37
|
-
# )
|
|
38
|
-
|
|
39
|
-
# value_cast = Arel::Nodes::NamedFunction.new(
|
|
40
|
-
# 'CAST',
|
|
41
|
-
# [value.as('text[]')]
|
|
42
|
-
# )
|
|
43
|
-
|
|
44
|
-
# # Arel::Nodes::InfixOperation.new(infix_operator, column_cast, value_cast)
|
|
45
|
-
# Arel::Nodes::InfixOperation.new(infix_operator, model.arel_table[column_name], value)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|