pg_tags_on 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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