pg_tags_on 0.1.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 +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +9 -0
  5. data/CHANGELOG.md +3 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +91 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +195 -0
  11. data/Rakefile +16 -0
  12. data/bin/console +16 -0
  13. data/bin/setup +8 -0
  14. data/lib/pg_tags_on/active_record/arel.rb +66 -0
  15. data/lib/pg_tags_on/active_record/base.rb +41 -0
  16. data/lib/pg_tags_on/benchmark/benchmark.rb +52 -0
  17. data/lib/pg_tags_on/predicate_handler/array_integer_handler.rb +9 -0
  18. data/lib/pg_tags_on/predicate_handler/array_jsonb_handler.rb +31 -0
  19. data/lib/pg_tags_on/predicate_handler/array_jsonb_with_attrs_handler.rb +41 -0
  20. data/lib/pg_tags_on/predicate_handler/array_string_handler.rb +9 -0
  21. data/lib/pg_tags_on/predicate_handler/array_text_handler.rb +9 -0
  22. data/lib/pg_tags_on/predicate_handler/base_handler.rb +89 -0
  23. data/lib/pg_tags_on/predicate_handler.rb +64 -0
  24. data/lib/pg_tags_on/repositories/array_jsonb_repository.rb +88 -0
  25. data/lib/pg_tags_on/repositories/array_repository.rb +103 -0
  26. data/lib/pg_tags_on/repositories/base_repository.rb +44 -0
  27. data/lib/pg_tags_on/repository.rb +59 -0
  28. data/lib/pg_tags_on/tag.rb +31 -0
  29. data/lib/pg_tags_on/tags_query.rb +27 -0
  30. data/lib/pg_tags_on/validations/validator.rb +43 -0
  31. data/lib/pg_tags_on/version.rb +5 -0
  32. data/lib/pg_tags_on.rb +56 -0
  33. data/pg_tags_on.gemspec +38 -0
  34. data/spec/array_integers/records_spec.rb +47 -0
  35. data/spec/array_integers/tag_ops_spec.rb +65 -0
  36. data/spec/array_integers/taggings_spec.rb +27 -0
  37. data/spec/array_integers/tags_spec.rb +53 -0
  38. data/spec/array_jsonb/records_spec.rb +89 -0
  39. data/spec/array_jsonb/tag_ops_spec.rb +115 -0
  40. data/spec/array_jsonb/taggings_spec.rb +27 -0
  41. data/spec/array_jsonb/tags_spec.rb +41 -0
  42. data/spec/array_strings/records_spec.rb +61 -0
  43. data/spec/array_strings/tag_ops_spec.rb +65 -0
  44. data/spec/array_strings/taggings_spec.rb +27 -0
  45. data/spec/array_strings/tags_spec.rb +54 -0
  46. data/spec/config/database.yml +6 -0
  47. data/spec/configuration_spec.rb +48 -0
  48. data/spec/helpers/database_helpers.rb +46 -0
  49. data/spec/spec_helper.rb +39 -0
  50. data/spec/support/factory.rb +47 -0
  51. data/spec/tags_query_spec.rb +31 -0
  52. data/spec/validator_spec.rb +40 -0
  53. data/tasks/benchmark.rake +58 -0
  54. metadata +260 -0
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgTagsOn
4
+ class PredicateHandler
5
+ # Predicate handler for jsonb[] column type
6
+ class ArrayJsonbWithAttrsHandler < ArrayJsonbHandler
7
+ OPERATORS = {
8
+ eq: :eq,
9
+ all: '?&',
10
+ any: '?|',
11
+ in: '<@',
12
+ one: '?&'
13
+ }.freeze
14
+
15
+ def left
16
+ node = arel_function('array_to_json', attribute)
17
+ node = arel_cast(node, 'jsonb')
18
+ node = arel_function('jsonb_path_query_array', node, arel_build_quoted("$[*].#{key.join('.')}"))
19
+
20
+ node
21
+ end
22
+
23
+ def right
24
+ if predicate == :in
25
+ arel_cast(arel_sql("'#{value.to_json}'"), 'jsonb')
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def value
32
+ @value ||= Array.wrap(query.value)
33
+ end
34
+
35
+ def cast_type
36
+ subtype = ActiveModel::Type::String.new
37
+ ::ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(subtype)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgTagsOn
4
+ class PredicateHandler
5
+ # Predicate handler for character varying[] column type
6
+ class ArrayStringHandler < BaseHandler
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgTagsOn
4
+ class PredicateHandler
5
+ # Predicate handler for character varying[] column type
6
+ class ArrayTextHandler < BaseHandler
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgTagsOn
4
+ class PredicateHandler
5
+ # Base predicate handler
6
+ class BaseHandler
7
+ include PgTagsOn::ActiveRecord::Arel
8
+
9
+ OPERATORS = {
10
+ eq: :eq,
11
+ all: '@>',
12
+ any: '&&',
13
+ in: '<@',
14
+ one: '@>'
15
+ }.freeze
16
+
17
+ def initialize(attribute, query, predicate_builder)
18
+ @attribute = attribute
19
+ @query = query
20
+ @predicate_builder = predicate_builder
21
+ end
22
+
23
+ def call
24
+ raise 'Invalid predicate' unless OPERATORS.keys.include?(predicate)
25
+
26
+ if operator.is_a?(Symbol)
27
+ send("#{operator}_node")
28
+ else
29
+ ::Arel::Nodes::InfixOperation.new(operator, left, right)
30
+ end
31
+ end
32
+
33
+ def predicate
34
+ @predicate ||= query.predicate.to_sym
35
+ end
36
+
37
+ def operator
38
+ @operator ||= self.class.const_get('OPERATORS').fetch(predicate)
39
+ end
40
+
41
+ def eq_node
42
+ node = ::Arel::Nodes::InfixOperation.new(self.class::OPERATORS[:all], left, right)
43
+ node.and(arel_function('array_length', attribute, 1).eq(value.size))
44
+ end
45
+
46
+ def left
47
+ attribute
48
+ end
49
+
50
+ def right
51
+ bind_node
52
+ end
53
+
54
+ def bind_node
55
+ query_attr = ::ActiveRecord::Relation::QueryAttribute.new(attribute_name, value, cast_type)
56
+ Arel::Nodes::BindParam.new(query_attr)
57
+ end
58
+
59
+ def value
60
+ @value ||= Array.wrap(query.value)
61
+ end
62
+
63
+ def klass
64
+ @klass ||= predicate_builder.send(:table).send(:klass)
65
+ end
66
+
67
+ def table_name
68
+ @table_name ||= attribute.relation.name
69
+ end
70
+
71
+ def attribute_name
72
+ attribute.name.to_s
73
+ end
74
+
75
+ # Returns Type object
76
+ def cast_type
77
+ @cast_type ||= klass.type_for_attribute(attribute_name)
78
+ end
79
+
80
+ def settings
81
+ @settings ||= (klass.pg_tags_on_options_for(attribute_name) || {}).symbolize_keys
82
+ end
83
+
84
+ private
85
+
86
+ attr_reader :attribute, :query, :predicate_builder
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgTagsOn
4
+ # Models' predicate handlers register this class
5
+ class PredicateHandler < ::ActiveRecord::PredicateBuilder::BaseHandler
6
+ def call(attribute, query)
7
+ handler = Builder.new(attribute, query, predicate_builder).call
8
+
9
+ handler.call
10
+ end
11
+
12
+ # Handler builder class
13
+ class Builder
14
+ def initialize(attribute, query, predicate_builder)
15
+ @attribute = attribute
16
+ @query = query
17
+ @predicate_builder = predicate_builder
18
+ end
19
+
20
+ def call
21
+ if column.array?
22
+ array_handler
23
+ else
24
+ BaseHandler.new(attribute, query, predicate_builder)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :attribute, :query, :predicate_builder
31
+
32
+ def klass
33
+ @klass ||= predicate_builder.send(:table).send(:klass)
34
+ end
35
+
36
+ def column
37
+ @column ||= klass.columns_hash[attribute.name]
38
+ end
39
+
40
+ def column_type
41
+ @column_type ||= column.type.to_s
42
+ end
43
+
44
+ def settings
45
+ @settings ||= (klass.pg_tags_on_options_for(attribute.name) || {}).symbolize_keys
46
+ end
47
+
48
+ def array_handler
49
+ handler_klass =
50
+ if column_type == 'jsonb'
51
+ if settings.key?(:has_attributes)
52
+ ArrayJsonbWithAttrsHandler
53
+ else
54
+ ArrayJsonbHandler
55
+ end
56
+ else
57
+ PredicateHandler.const_get("Array#{column_type.classify}Handler")
58
+ end
59
+
60
+ handler_klass.new(attribute, query, predicate_builder)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgTagsOn
4
+ module Repositories
5
+ # Operatons for 'jsonb[]' column type
6
+ class ArrayJsonbRepository < ArrayRepository
7
+ def create(tag)
8
+ with_normalized_tags(tag) do |n_tag|
9
+ super(n_tag)
10
+ end
11
+ end
12
+
13
+ def update(tag, new_tag)
14
+ with_normalized_tags(tag, new_tag) do |n_tag, n_new_tag|
15
+ sql_set = <<-SQL.strip
16
+ #{column_name}[index] = #{column_name}[index] || $2
17
+ SQL
18
+
19
+ update_tag(n_tag, sql_set, [query_attribute(n_new_tag.to_json)])
20
+ end
21
+ end
22
+
23
+ def delete(tag)
24
+ with_normalized_tags(tag) do |n_tag|
25
+ sql_set = <<-SQL.strip
26
+ #{column_name} = #{column_name}[1:index-1] || #{column_name}[index+1:2147483647]
27
+ SQL
28
+
29
+ update_tag(n_tag, sql_set)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def with_normalized_tags(*tags, &block)
36
+ normalized_tags = Array.wrap(tags).flatten.map do |tag|
37
+ key? && Array.wrap(key).reverse.inject(tag) { |a, n| { n => a } } || tag
38
+ end
39
+
40
+ block.call(*normalized_tags)
41
+ end
42
+
43
+ def array_to_recordset
44
+ return unnest unless key?
45
+
46
+ arel_jsonb_extract_path(unnest, *key_sql)
47
+ end
48
+
49
+ def key
50
+ @key ||= options[:key]
51
+ end
52
+
53
+ def key_sql
54
+ @key_sql ||= Array.wrap(key).map { |k| Arel.sql("'#{k}'") }
55
+ end
56
+
57
+ def key?
58
+ key.present?
59
+ end
60
+
61
+ def taggings_with_ordinality_query(tag)
62
+ column = Arel::Table.new('t')['name']
63
+ value = bind_for(tag.to_json, nil)
64
+
65
+ arel_table
66
+ .project('id, name, index')
67
+ .from("#{table_name}, #{unnest_with_ordinality}")
68
+ .where(arel_infix_operation('@>', column, value))
69
+ end
70
+
71
+ def update_tag(tag, set_sql, bindings = [])
72
+ subquery = taggings_with_ordinality_query(tag)
73
+ .where(arel_table[:id].in(arel_sql(klass.reselect('id').to_sql)))
74
+
75
+ sql = <<-SQL.strip
76
+ WITH records as ( #{subquery.to_sql} )
77
+ UPDATE #{table_name}
78
+ SET #{set_sql}
79
+ FROM records
80
+ WHERE #{table_name}.id = records.id
81
+ SQL
82
+
83
+ bindings = [query_attribute(tag.to_json)] + Array.wrap(bindings)
84
+ klass.connection.exec_query(sql, 'SQL', bindings)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgTagsOn
4
+ module Repositories
5
+ # This repository works with "character varying[]" and "integer[]" column types
6
+ class ArrayRepository < BaseRepository
7
+ def all
8
+ subquery = klass
9
+ .select(arel_distinct(array_to_recordset).as('name'))
10
+ .arel
11
+ .as('tags')
12
+
13
+ PgTagsOn::Tag
14
+ .select(Arel.star)
15
+ .from(subquery)
16
+ .order('tags.name') # override rails' default order by id
17
+ end
18
+
19
+ def all_with_counts
20
+ taggings
21
+ .except(:select)
22
+ .select('name, count(name) as count')
23
+ .group('name')
24
+ end
25
+
26
+ def find(tag)
27
+ all.where(name: tag).first
28
+ end
29
+
30
+ def exists?(tag)
31
+ all.exists?(tag)
32
+ end
33
+
34
+ def taggings
35
+ PgTagsOn::Tag
36
+ .select(Arel.star)
37
+ .from(taggings_query)
38
+ .order('taggings.name')
39
+ end
40
+
41
+ def count
42
+ all.count
43
+ end
44
+
45
+ def create(tag)
46
+ return true if tag.blank?
47
+
48
+ klass.update_all(column_name => arel_array_cat(arel_column, bind_for(Array.wrap(tag))))
49
+ end
50
+
51
+ def update(tag, new_tag)
52
+ return true if tag.blank? || new_tag.blank? || tag == new_tag
53
+
54
+ klass
55
+ .where(column_name => Tags.one(tag))
56
+ .update_all(column_name => arel_array_replace(arel_column, bind_for(tag), bind_for(new_tag)))
57
+ end
58
+
59
+ def delete(tag)
60
+ klass
61
+ .where(column_name => Tags.one(tag))
62
+ .update_all(column_name => arel_array_remove(arel_column, bind_for(tag)))
63
+ end
64
+
65
+ private
66
+
67
+ def array_to_recordset
68
+ unnest
69
+ end
70
+
71
+ def taggings_query
72
+ klass
73
+ .select(
74
+ array_to_recordset.as('name'),
75
+ arel_table['id'].as('entity_id')
76
+ )
77
+ .arel
78
+ .as('taggings')
79
+ end
80
+
81
+ def ref
82
+ "#{table_name}.#{column_name}"
83
+ end
84
+
85
+ def unnest
86
+ arel_unnest(arel_column)
87
+ end
88
+
89
+ def unnest_with_ordinality(alias_table: 't')
90
+ "#{unnest.to_sql} WITH ORDINALITY #{alias_table}(name, index)"
91
+ end
92
+
93
+ def query_attribute(value)
94
+ arel_query_attribute(arel_column, value, cast_type)
95
+ end
96
+
97
+ def bind_for(value, attr = arel_column)
98
+ query_attr = arel_query_attribute(attr, value, cast_type)
99
+ arel_bind(query_attr)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgTagsOn
4
+ module Repositories
5
+ # Base class for repositories.
6
+ class BaseRepository
7
+ include ::PgTagsOn::ActiveRecord::Arel
8
+
9
+ def self.api_methods
10
+ %i[all all_with_counts find exists? taggings count create update delete to_s]
11
+ end
12
+
13
+ api_methods.each do |m|
14
+ define_method(m) do
15
+ raise 'Not implemented'
16
+ end
17
+ end
18
+
19
+ attr_reader :klass, :column_name, :options
20
+
21
+ def initialize(klass, column_name, options = {})
22
+ @klass = klass
23
+ @column_name = column_name
24
+ @options = options.deep_symbolize_keys
25
+ end
26
+
27
+ def table_name
28
+ @table_name ||= klass.table_name
29
+ end
30
+
31
+ def cast_type
32
+ @cast_type ||= klass.type_for_attribute(column_name)
33
+ end
34
+
35
+ def arel_table
36
+ klass.arel_table
37
+ end
38
+
39
+ def arel_column
40
+ arel_table[column_name]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgTagsOn
4
+ # Repository class for tags.
5
+ # Examples:
6
+ #
7
+ # repo = PgTagsOn::Repository.new(Entity, :tags)
8
+ # repo.all
9
+ # repo.update('foo', 'boo')
10
+ # ...
11
+ #
12
+ class Repository
13
+ extend Forwardable
14
+ def_delegators :gateway, :all, :all_with_counts, :taggings, :count, :create, :update, :delete
15
+
16
+ attr_reader :klass, :column_name
17
+
18
+ def initialize(klass, column_name)
19
+ @klass = klass
20
+ @column_name = column_name.to_s
21
+ end
22
+
23
+ private
24
+
25
+ def gateway
26
+ raise 'Invalid column type' unless column.array?
27
+
28
+ @gateway ||= send("#{column.type}_gateway")
29
+ end
30
+
31
+ def column
32
+ @column ||= klass.columns_hash[column_name]
33
+ end
34
+
35
+ def default_gateway
36
+ PgTagsOn::Repositories::ArrayRepository.new(klass, column_name, settings)
37
+ end
38
+
39
+ def string_gateway
40
+ default_gateway
41
+ end
42
+
43
+ def text_gateway
44
+ default_gateway
45
+ end
46
+
47
+ def integer_gateway
48
+ default_gateway
49
+ end
50
+
51
+ def jsonb_gateway
52
+ PgTagsOn::Repositories::ArrayJsonbRepository.new(klass, column_name, settings)
53
+ end
54
+
55
+ def settings
56
+ @settings ||= (klass.pg_tags_on_options_for(column_name) || {}).symbolize_keys
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgTagsOn
4
+ # Model for tags.
5
+ # Schema is defined dynamically and has only +name+ column as string.
6
+ class Tag < ::ActiveRecord::Base
7
+ self.table_name = 'tags'
8
+
9
+ class << self
10
+ def load_schema!
11
+ @load_schema ||= begin
12
+ name_column = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Column.new('name', '', pg_string_type)
13
+ @columns_hash = { 'name' => name_column }
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def pg_string_type
20
+ string = ::ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(sql_type: 'character varying', type: 'string')
21
+ ::ActiveRecord::ConnectionAdapters::PostgreSQL::TypeMetadata.new(string)
22
+ end
23
+ end
24
+
25
+ def inspect
26
+ info = attributes.map { |name, value| %(#{name}: #{format_for_inspect(value)}) }.join(', ')
27
+
28
+ "#<#{self.class.name} #{info}>"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgTagsOn
4
+ # Helper class to construct queries.
5
+ # This class is registered in models' predicate builders.
6
+ # See configuration in order to create an alias for it.
7
+ class TagsQuery
8
+ %w[one all any in eq].each do |predicate|
9
+ instance_eval <<-RUBY, __FILE__, __LINE__ + 1
10
+ def #{predicate}(*args)
11
+ params = args.size == 1 ? args.first : args
12
+ new(params, "#{predicate}")
13
+ end
14
+ RUBY
15
+ end
16
+
17
+ attr_reader :value
18
+ attr_reader :predicate
19
+ attr_reader :options
20
+
21
+ def initialize(value, predicate, options = {})
22
+ @value = value
23
+ @predicate = predicate
24
+ @options = options
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgTagsOn
4
+ # Validator for max. number of tags and max. tag length.
5
+ #
6
+ # class Entity
7
+ # pg_tags_on :tags, limit: 20, tag_length: 64
8
+ # end
9
+ #
10
+ class TagsValidator < ActiveModel::EachValidator
11
+ def initialize(options = {})
12
+ super
13
+ @klass = options[:class]
14
+ end
15
+
16
+ def validate_each(record, attribute, value)
17
+ validate_limit(record, attribute, value)
18
+ validate_tag_length(record, attribute, value)
19
+
20
+ record.errors.present?
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :klass
26
+
27
+ def validate_limit(record, attr, value)
28
+ limit = klass.pg_tags_on_options_for(attr)[:limit]
29
+ return true unless limit && value
30
+
31
+ record.errors.add(attr, "size exceeded #{limit} tags") if value.size > limit.to_i
32
+ end
33
+
34
+ def validate_tag_length(record, attr, value)
35
+ limit, key = klass.pg_tags_on_options_for(attr).values_at(:tag_length, :key)
36
+ return true unless limit && value
37
+
38
+ value.map! { |tag| tag.with_indifferent_access.dig(*key) } if key
39
+
40
+ record.errors.add(attr, "length exceeded #{limit} characters") if value.any? { |val| val.size > limit.to_i }
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgTagsOn
4
+ VERSION = '0.1.1'
5
+ end
data/lib/pg_tags_on.rb ADDED
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ require 'pg_tags_on/version'
6
+ require 'pg_tags_on/active_record/base'
7
+ require 'pg_tags_on/active_record/arel'
8
+ require 'pg_tags_on/predicate_handler'
9
+ require 'pg_tags_on/predicate_handler/base_handler'
10
+ require 'pg_tags_on/predicate_handler/array_string_handler'
11
+ require 'pg_tags_on/predicate_handler/array_text_handler'
12
+ require 'pg_tags_on/predicate_handler/array_integer_handler'
13
+ require 'pg_tags_on/predicate_handler/array_jsonb_handler'
14
+ require 'pg_tags_on/predicate_handler/array_jsonb_with_attrs_handler'
15
+ require 'pg_tags_on/tag'
16
+ require 'pg_tags_on/tags_query'
17
+ require 'pg_tags_on/validations/validator'
18
+ require 'pg_tags_on/repository'
19
+ require 'pg_tags_on/repositories/base_repository'
20
+ require 'pg_tags_on/repositories/array_repository'
21
+ require 'pg_tags_on/repositories/array_jsonb_repository'
22
+ require 'pg_tags_on/benchmark/benchmark'
23
+
24
+ # PgTagsOn configuration methods
25
+ module PgTagsOn
26
+ class Error < StandardError; end
27
+ class ColumnNotFoundError < Error; end
28
+
29
+ def configure
30
+ @config ||= OpenStruct.new(query_class: 'Tags')
31
+ yield @config if block_given?
32
+ @config
33
+ end
34
+
35
+ def configuration
36
+ @config || configure
37
+ end
38
+
39
+ def register_query_class
40
+ return true if query_class?
41
+
42
+ Kernel.const_set(PgTagsOn.configuration.query_class.to_sym, PgTagsOn::TagsQuery)
43
+ end
44
+
45
+ def query_class?
46
+ Kernel.const_defined?(PgTagsOn.configuration.query_class)
47
+ end
48
+
49
+ def query_class
50
+ Kernel.const_get(PgTagsOn.configuration.query_class)
51
+ end
52
+
53
+ module_function :configure, :configuration, :register_query_class, :query_class, :query_class?
54
+ end
55
+
56
+ ActiveRecord::Base.include PgTagsOn::ActiveRecord::Base