torque-postgresql 3.4.1 → 4.0.0.rc1

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/lib/torque/postgresql/adapter/database_statements.rb +63 -84
  3. data/lib/torque/postgresql/adapter/oid/array.rb +17 -0
  4. data/lib/torque/postgresql/adapter/oid/line.rb +2 -6
  5. data/lib/torque/postgresql/adapter/oid/range.rb +4 -4
  6. data/lib/torque/postgresql/adapter/oid.rb +1 -23
  7. data/lib/torque/postgresql/adapter/quoting.rb +13 -7
  8. data/lib/torque/postgresql/adapter/schema_creation.rb +7 -28
  9. data/lib/torque/postgresql/adapter/schema_definitions.rb +36 -0
  10. data/lib/torque/postgresql/adapter/schema_dumper.rb +90 -34
  11. data/lib/torque/postgresql/adapter/schema_overrides.rb +45 -0
  12. data/lib/torque/postgresql/adapter/schema_statements.rb +64 -49
  13. data/lib/torque/postgresql/arel/infix_operation.rb +15 -28
  14. data/lib/torque/postgresql/arel/nodes.rb +2 -2
  15. data/lib/torque/postgresql/arel/operations.rb +7 -1
  16. data/lib/torque/postgresql/arel/visitors.rb +3 -9
  17. data/lib/torque/postgresql/associations/association_scope.rb +23 -31
  18. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +25 -0
  19. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +16 -0
  20. data/lib/torque/postgresql/attributes/builder/enum.rb +12 -9
  21. data/lib/torque/postgresql/attributes/builder/full_text_search.rb +121 -0
  22. data/lib/torque/postgresql/attributes/builder/period.rb +21 -21
  23. data/lib/torque/postgresql/attributes/builder.rb +49 -11
  24. data/lib/torque/postgresql/attributes/enum.rb +7 -7
  25. data/lib/torque/postgresql/attributes/enum_set.rb +7 -7
  26. data/lib/torque/postgresql/attributes/full_text_search.rb +19 -0
  27. data/lib/torque/postgresql/attributes/period.rb +2 -2
  28. data/lib/torque/postgresql/attributes.rb +0 -4
  29. data/lib/torque/postgresql/auxiliary_statement/recursive.rb +3 -3
  30. data/lib/torque/postgresql/base.rb +3 -10
  31. data/lib/torque/postgresql/collector.rb +1 -1
  32. data/lib/torque/postgresql/config.rb +95 -5
  33. data/lib/torque/postgresql/function.rb +61 -0
  34. data/lib/torque/postgresql/inheritance.rb +52 -36
  35. data/lib/torque/postgresql/predicate_builder/arel_attribute_handler.rb +33 -0
  36. data/lib/torque/postgresql/predicate_builder/array_handler.rb +47 -0
  37. data/lib/torque/postgresql/predicate_builder/enumerator_lazy_handler.rb +37 -0
  38. data/lib/torque/postgresql/predicate_builder/regexp_handler.rb +21 -0
  39. data/lib/torque/postgresql/predicate_builder.rb +35 -0
  40. data/lib/torque/postgresql/railtie.rb +112 -30
  41. data/lib/torque/postgresql/reflection/abstract_reflection.rb +12 -44
  42. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +4 -0
  43. data/lib/torque/postgresql/reflection/has_many_reflection.rb +4 -0
  44. data/lib/torque/postgresql/reflection/runtime_reflection.rb +1 -1
  45. data/lib/torque/postgresql/relation/inheritance.rb +4 -7
  46. data/lib/torque/postgresql/relation.rb +6 -10
  47. data/lib/torque/postgresql/schema_cache.rb +6 -12
  48. data/lib/torque/postgresql/version.rb +1 -1
  49. data/lib/torque/postgresql.rb +2 -1
  50. data/spec/initialize.rb +58 -0
  51. data/spec/mocks/cache_query.rb +21 -21
  52. data/spec/mocks/create_table.rb +6 -26
  53. data/spec/schema.rb +19 -12
  54. data/spec/spec_helper.rb +5 -1
  55. data/spec/tests/arel_spec.rb +32 -7
  56. data/spec/tests/auxiliary_statement_spec.rb +3 -3
  57. data/spec/tests/belongs_to_many_spec.rb +72 -5
  58. data/spec/tests/enum_set_spec.rb +12 -11
  59. data/spec/tests/enum_spec.rb +4 -2
  60. data/spec/tests/full_text_seach_test.rb +252 -0
  61. data/spec/tests/function_spec.rb +42 -0
  62. data/spec/tests/has_many_spec.rb +21 -8
  63. data/spec/tests/interval_spec.rb +1 -7
  64. data/spec/tests/period_spec.rb +61 -61
  65. data/spec/tests/predicate_builder_spec.rb +132 -0
  66. data/spec/tests/schema_spec.rb +2 -8
  67. data/spec/tests/table_inheritance_spec.rb +25 -26
  68. metadata +34 -39
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'relation/distinct_on'
4
- require_relative 'relation/auxiliary_statement'
5
4
  require_relative 'relation/inheritance'
6
5
 
7
6
  require_relative 'relation/merger'
@@ -12,7 +11,6 @@ module Torque
12
11
  extend ActiveSupport::Concern
13
12
 
14
13
  include DistinctOn
15
- include AuxiliaryStatement
16
14
  include Inheritance
17
15
 
18
16
  SINGLE_VALUE_METHODS = [:itself_only]
@@ -107,7 +105,7 @@ module Torque
107
105
  end
108
106
  end
109
107
 
110
- module ClassMethods
108
+ class_methods do
111
109
  # Easy and storable way to access the name used to get the record table
112
110
  # name when using inheritance tables
113
111
  def _record_class_attribute
@@ -115,7 +113,7 @@ module Torque
115
113
  .inheritance.record_class_column_name.to_sym
116
114
  end
117
115
 
118
- # Easy and storable way to access the name used to get the indicater of
116
+ # Easy and storable way to access the name used to get the indicate of
119
117
  # auto casting inherited records
120
118
  def _auto_cast_attribute
121
119
  @@auto_cast ||= Torque::PostgreSQL.config
@@ -135,16 +133,14 @@ module Torque
135
133
  end
136
134
 
137
135
  # Allow extra keyword arguments to be sent to +InsertAll+
138
- if Torque::PostgreSQL::AR720
139
- def upsert_all(attributes, **xargs)
140
- xargs = xargs.reverse_merge(on_duplicate: :update)
141
- ::ActiveRecord::InsertAll.execute(self, attributes, **xargs)
142
- end
136
+ def upsert_all(attributes, **xargs)
137
+ xargs = xargs.reverse_merge(on_duplicate: :update)
138
+ ::ActiveRecord::InsertAll.execute(self, attributes, **xargs)
143
139
  end
144
140
  end
145
141
  end
146
142
 
147
- # Include the methos here provided and then change the constants to ensure
143
+ # Include the methods here provided and then change the constants to ensure
148
144
  # the operation of ActiveRecord Relation
149
145
  ActiveRecord::Relation.include Relation
150
146
  ActiveRecord::Relation.prepend Relation::Initializer
@@ -1,11 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'torque/postgresql/schema_cache/inheritance'
4
-
5
- if Torque::PostgreSQL::AR710
6
- require 'torque/postgresql/schema_cache/schema_reflection'
7
- require 'torque/postgresql/schema_cache/bound_schema_reflection'
8
- end
4
+ require 'torque/postgresql/schema_cache/schema_reflection'
5
+ require 'torque/postgresql/schema_cache/bound_schema_reflection'
9
6
 
10
7
  module Torque
11
8
  module PostgreSQL
@@ -73,7 +70,7 @@ module Torque
73
70
  end
74
71
 
75
72
  def clear_data_source_cache!(connection_or_name, name = connection_or_name) # :nodoc:
76
- Torque::PostgreSQL::AR710 ? super : super(name)
73
+ super
77
74
  @data_sources_model_names.delete name
78
75
  @inheritance_dependencies.delete name
79
76
  @inheritance_associations.delete name
@@ -130,8 +127,7 @@ module Torque
130
127
  def reload_inheritance_data!(source)
131
128
  return if @inheritance_loaded
132
129
 
133
- method_name = Torque::PostgreSQL::AR720 ? :with_connection : :then
134
- source.public_send(method_name) do |connection|
130
+ source.with_connection do |connection|
135
131
  @inheritance_dependencies = connection.inherited_tables
136
132
  @inheritance_associations = generate_associations
137
133
  @inheritance_loaded = true
@@ -145,14 +141,12 @@ module Torque
145
141
  end
146
142
 
147
143
  # Use this method to also load any irregular model name
148
- method_name = Torque::PostgreSQL::AR720 ? :add_all : :prepare_data_sources
149
- define_method(method_name) do |source = nil|
150
- Torque::PostgreSQL::AR710 ? super(source) : super()
144
+ def add_all(source = nil)
145
+ super
151
146
 
152
147
  data_sources = source.present? ? tables_to_cache(source) : @data_sources.keys
153
148
  @data_sources_model_names = prepare_irregular_models(data_sources)
154
149
  end
155
-
156
150
  end
157
151
 
158
152
  ActiveRecord::ConnectionAdapters::SchemaCache.prepend SchemaCache
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Torque
4
4
  module PostgreSQL
5
- VERSION = '3.4.1'
5
+ VERSION = '4.0.0.rc1'
6
6
  end
7
7
  end
@@ -12,6 +12,7 @@ require 'torque/postgresql/config'
12
12
  require 'torque/postgresql/version'
13
13
  require 'torque/postgresql/collector'
14
14
  require 'torque/postgresql/geometry_builder'
15
+ require 'torque/postgresql/predicate_builder'
15
16
 
16
17
  require 'torque/postgresql/i18n'
17
18
  require 'torque/postgresql/arel'
@@ -19,7 +20,6 @@ require 'torque/postgresql/adapter'
19
20
  require 'torque/postgresql/associations'
20
21
  require 'torque/postgresql/attributes'
21
22
  require 'torque/postgresql/autosave_association'
22
- require 'torque/postgresql/auxiliary_statement'
23
23
  require 'torque/postgresql/inheritance'
24
24
  require 'torque/postgresql/base' # Needs to be after inheritance
25
25
  require 'torque/postgresql/insert_all'
@@ -28,5 +28,6 @@ require 'torque/postgresql/relation'
28
28
  require 'torque/postgresql/reflection'
29
29
  require 'torque/postgresql/schema_cache'
30
30
  require 'torque/postgresql/table_name'
31
+ require 'torque/postgresql/function'
31
32
 
32
33
  require 'torque/postgresql/railtie' if defined?(Rails)
@@ -0,0 +1,58 @@
1
+ require_relative '../lib/torque/postgresql/auxiliary_statement'
2
+
3
+ require_relative '../lib/torque/postgresql/adapter/schema_overrides'
4
+
5
+ require_relative '../lib/torque/postgresql/adapter/oid/box'
6
+ require_relative '../lib/torque/postgresql/adapter/oid/circle'
7
+ require_relative '../lib/torque/postgresql/adapter/oid/enum'
8
+ require_relative '../lib/torque/postgresql/adapter/oid/enum_set'
9
+ require_relative '../lib/torque/postgresql/adapter/oid/interval'
10
+ require_relative '../lib/torque/postgresql/adapter/oid/line'
11
+ require_relative '../lib/torque/postgresql/adapter/oid/segment'
12
+
13
+ require_relative '../lib/torque/postgresql/attributes/enum'
14
+ require_relative '../lib/torque/postgresql/attributes/enum_set'
15
+ require_relative '../lib/torque/postgresql/attributes/period'
16
+ require_relative '../lib/torque/postgresql/attributes/full_text_search'
17
+
18
+ require_relative '../lib/torque/postgresql/relation/auxiliary_statement'
19
+
20
+ module Torque
21
+ module PostgreSQL
22
+ ActiveRecord::Base.belongs_to_many_required_by_default = false
23
+
24
+ Attributes::Enum.include_on(ActiveRecord::Base)
25
+ Attributes::EnumSet.include_on(ActiveRecord::Base)
26
+ Attributes::Period.include_on(ActiveRecord::Base)
27
+ Attributes::FullTextSearch.include_on(ActiveRecord::Base)
28
+
29
+ Relation.include(Relation::AuxiliaryStatement)
30
+
31
+ ::Object.const_set('TorqueCTE', AuxiliaryStatement)
32
+ ::Object.const_set('TorqueRecursiveCTE', AuxiliaryStatement::Recursive)
33
+
34
+ config.enum.namespace = ::Object.const_set('Enum', Module.new)
35
+ config.enum.namespace.define_singleton_method(:const_missing) do |name|
36
+ Attributes::Enum.lookup(name)
37
+ end
38
+
39
+ config.enum.namespace.define_singleton_method(:sample) do |name|
40
+ Attributes::Enum.lookup(name).sample
41
+ end
42
+
43
+ ar_type = ActiveRecord::Type
44
+ ar_type.register(:enum, Adapter::OID::Enum, adapter: :postgresql)
45
+ ar_type.register(:enum_set, Adapter::OID::EnumSet, adapter: :postgresql)
46
+
47
+ ar_type.register(:box, Adapter::OID::Box, adapter: :postgresql)
48
+ ar_type.register(:circle, Adapter::OID::Circle, adapter: :postgresql)
49
+ ar_type.register(:line, Adapter::OID::Line, adapter: :postgresql)
50
+ ar_type.register(:segment, Adapter::OID::Segment, adapter: :postgresql)
51
+
52
+ ar_type.register(:interval, Adapter::OID::Interval, adapter: :postgresql)
53
+
54
+ Arel.build_operations(config.arel.infix_operators)
55
+
56
+ ActiveRecord::Base.connection.torque_load_additional_types
57
+ end
58
+ end
@@ -1,33 +1,33 @@
1
1
  module Mocks
2
2
  module CacheQuery
3
- if Torque::PostgreSQL::AR720
4
- def get_last_executed_query(&block)
5
- cache = ActiveRecord::Base.connection.query_cache
6
- cache.instance_variable_set(:@enabled, true)
3
+ def get_last_executed_query(&block)
4
+ cache = ActiveRecord::Base.connection.query_cache
5
+ cache.instance_variable_set(:@enabled, true)
7
6
 
8
- map = cache.instance_variable_get(:@map)
7
+ map = cache.instance_variable_get(:@map)
9
8
 
10
- block.call
11
- result = map.keys.first
9
+ block.call
10
+ result = map.keys.first
12
11
 
13
- cache.instance_variable_set(:@enabled, false)
14
- map.delete(result)
12
+ cache.instance_variable_set(:@enabled, false)
13
+ map.delete(result)
15
14
 
16
- result
17
- end
18
- else
19
- def get_last_executed_query(&block)
20
- conn = ActiveRecord::Base.connection
21
- conn.instance_variable_set(:@query_cache_enabled, true)
22
-
23
- block.call
24
- result = conn.query_cache.keys.first
15
+ result
16
+ end
25
17
 
26
- conn.instance_variable_set(:@query_cache_enabled, false)
27
- conn.instance_variable_get(:@query_cache).delete(result)
18
+ def get_query_with_binds(&block)
19
+ result = nil
28
20
 
29
- result
21
+ original_method = ActiveRecord::Base.connection.method(:raw_execute)
22
+ original_method.receiver.define_singleton_method(:raw_execute) do |*args, **xargs, &block|
23
+ result ||= [args.first, args.third]
24
+ super(*args, **xargs, &block)
30
25
  end
26
+
27
+ block.call
28
+ original_method.receiver.define_singleton_method(:raw_execute, &original_method.to_proc)
29
+
30
+ result
31
31
  end
32
32
  end
33
33
  end
@@ -1,34 +1,14 @@
1
1
  module Mocks
2
2
  module CreateTable
3
3
  def mock_create_table
4
- path = ActiveRecord::Base.connection.method(:create_table).super_method.source_location[0]
5
-
6
- before :all do
7
- ActiveRecord::ConnectionAdapters::SchemaStatements.send(:define_method, :create_table) do |table_name, **options, &block|
8
- td = create_table_definition(table_name, **options)
9
-
10
- # Does things as the same as schema statements
11
- if options[:id] != false && !options[:as]
12
- pk = options.fetch(:primary_key) do
13
- ActiveRecord::Base.get_primary_key table_name.to_s.singularize
14
- end
15
-
16
- if pk.is_a?(Array)
17
- td.primary_keys pk
18
- else
19
- td.primary_key pk, options.fetch(:id, :primary_key), **options
20
- end
21
- end
22
-
23
- block.call(td) if block.present?
24
-
25
- # Now generate the SQL and return it
26
- schema_creation.accept td
4
+ around do |example|
5
+ original_method = ActiveRecord::Base.connection.method(:log)
6
+ original_method.receiver.define_singleton_method(:log) do |sql, *, **, &block|
7
+ sql
27
8
  end
28
- end
29
9
 
30
- after :all do
31
- load path
10
+ example.run
11
+ original_method.receiver.define_singleton_method(:log, &original_method.to_proc)
32
12
  end
33
13
  end
34
14
  end
data/spec/schema.rb CHANGED
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- version = 4
13
+ version = 5
14
14
 
15
15
  return if ActiveRecord::Migrator.current_version == version
16
16
  ActiveRecord::Schema.define(version: version) do
@@ -60,6 +60,7 @@ ActiveRecord::Schema.define(version: version) do
60
60
  t.enum "type", enum_type: :types
61
61
  t.enum "conflicts", enum_type: :conflicts, array: true
62
62
  t.jsonb "metadata"
63
+ # t.column "pieces", :int4multirange
63
64
  t.datetime "created_at", null: false
64
65
  t.datetime "updated_at", null: false
65
66
  end
@@ -92,12 +93,14 @@ ActiveRecord::Schema.define(version: version) do
92
93
  end
93
94
 
94
95
  create_table "courses", force: :cascade do |t|
95
- t.integer "category_id"
96
- t.string "title", null: false
97
- t.interval "duration"
98
- t.enum "types", enum_type: :types, array: true
99
- t.datetime "created_at", null: false
100
- t.datetime "updated_at", null: false
96
+ t.integer "category_id"
97
+ t.string "title", null: false
98
+ t.interval "duration"
99
+ t.enum "types", enum_type: :types, array: true
100
+ t.search_language "lang", null: false, default: 'english'
101
+ t.search_vector "search_vector", columns: :title, language: :lang
102
+ t.datetime "created_at", null: false
103
+ t.datetime "updated_at", null: false
101
104
  end
102
105
 
103
106
  create_table "images", force: :cascade, id: false do |t|
@@ -105,11 +108,12 @@ ActiveRecord::Schema.define(version: version) do
105
108
  end
106
109
 
107
110
  create_table "posts", force: :cascade do |t|
108
- t.integer "author_id"
109
- t.integer "activity_id"
110
- t.string "title"
111
- t.text "content"
112
- t.enum "status", enum_type: :content_status
111
+ t.integer "author_id"
112
+ t.integer "activity_id"
113
+ t.string "title"
114
+ t.text "content"
115
+ t.enum "status", enum_type: :content_status
116
+ t.search_vector "search_vector", columns: %i[title content]
113
117
  t.index ["author_id"], name: "index_posts_on_author_id", using: :btree
114
118
  end
115
119
 
@@ -172,6 +176,9 @@ ActiveRecord::Schema.define(version: version) do
172
176
  # create_table "activity_images", force: :cascade, inherits: [:activities, :images]
173
177
 
174
178
  add_foreign_key "posts", "authors"
179
+ rescue Exception => e
180
+ byebug
181
+ raise
175
182
  end
176
183
 
177
184
  ActiveRecord::Base.connection.schema_cache.clear!
data/spec/spec_helper.rb CHANGED
@@ -21,7 +21,11 @@ cleaner = ->() do
21
21
  cache.instance_variable_set(:@inheritance_associations, {})
22
22
  end
23
23
 
24
- load File.join('schema.rb')
24
+ # Load all the files that are optional and managed by Railtie
25
+ require_relative 'initialize'
26
+
27
+ # This needs to come after loading all optional features
28
+ require_relative 'schema'
25
29
  Dir.glob(File.join('spec', '{models,factories,mocks}', '**', '*.rb')) do |file|
26
30
  require file[5..-4]
27
31
  end
@@ -2,7 +2,6 @@ require 'spec_helper'
2
2
 
3
3
  RSpec.describe 'Arel' do
4
4
  context 'on inflix operation' do
5
- let(:list) { Torque::PostgreSQL::Arel::INFLIX_OPERATION }
6
5
  let(:collector) { ::Arel::Collectors::SQLString }
7
6
  let(:attribute) { ::Arel::Table.new('a')['sample'] }
8
7
  let(:conn) { ActiveRecord::Base.connection }
@@ -21,12 +20,13 @@ RSpec.describe 'Arel' do
21
20
  [:doesnt_right_extend, ::Arel.sql('numrange(5, 6)'), 'numrange(5, 6)'],
22
21
  [:doesnt_left_extend, ::Arel.sql('numrange(7, 8)'), 'numrange(7, 8)'],
23
22
  [:adjacent_to, ::Arel.sql('numrange(9, 0)'), 'numrange(9, 0)'],
24
- ].each do |(operator, value, quoted_value)|
25
- klass_name = operator.to_s.camelize
23
+ ].each do |(operation, value, quoted_value)|
24
+ klass_name = operation.to_s.camelize
26
25
 
27
- context "##{operator}" do
26
+ context "##{operation}" do
27
+ let(:operator) { instance.operator }
28
28
  let(:instance) do
29
- attribute.public_send(operator, value.is_a?(Array) ? ::Arel.array(value) : value)
29
+ attribute.public_send(operation, value.is_a?(Array) ? ::Arel.array(value) : value)
30
30
  end
31
31
 
32
32
  context 'for attribute' do
@@ -41,7 +41,7 @@ RSpec.describe 'Arel' do
41
41
  let(:result) { visitor.accept(instance, collector.new).value }
42
42
 
43
43
  it 'returns a formatted operation' do
44
- expect(result).to be_eql("\"a\".\"sample\" #{list[klass_name]} #{quoted_value}")
44
+ expect(result).to be_eql("\"a\".\"sample\" #{operator} #{quoted_value}")
45
45
  end
46
46
  end
47
47
  end
@@ -123,9 +123,34 @@ RSpec.describe 'Arel' do
123
123
  quoted = ::Arel::Nodes::build_quoted([1])
124
124
  casted = ::Arel::Nodes::build_quoted(1, attribute)
125
125
 
126
+ expect(attribute.pg_cast('text').to_sql).to be_eql('"a"."sample"::text')
127
+ expect(quoted.pg_cast('bigint', true).to_sql).to be_eql('ARRAY[1]::bigint[]')
128
+ expect(casted.pg_cast('string').to_sql).to be_eql("1::string")
129
+ end
130
+
131
+ it 'provides proper support to cast methods' do
132
+ attribute = ::Arel::Table.new('a')['sample']
133
+ quoted = ::Arel::Nodes::build_quoted([1])
134
+ casted = ::Arel::Nodes::build_quoted(1)
135
+
126
136
  expect(attribute.cast('text').to_sql).to be_eql('"a"."sample"::text')
127
137
  expect(quoted.cast('bigint', true).to_sql).to be_eql('ARRAY[1]::bigint[]')
128
- expect(casted.cast('string').to_sql).to be_eql("1::string")
138
+
139
+ changed_result = ActiveRecord.gem_version >= Gem::Version.new('8.0.2')
140
+ changed_result = changed_result ? 'CAST(1 AS string)' : '1::string'
141
+ expect(casted.pg_cast('string').to_sql).to be_eql("1::string")
142
+ end
143
+
144
+ it 'properly works combined on a query' do
145
+ condition = Video.arel_table[:tag_ids].contains([1,2]).cast(:bigint, :array)
146
+ query = Video.all.where(condition).to_sql
147
+
148
+ expect(query).to include('WHERE "videos"."tag_ids" @> ARRAY[1, 2]::bigint[]')
149
+
150
+ condition = QuestionSelect.arel_table[:options].overlaps(%w[a b]).cast(:string, :array)
151
+ query = QuestionSelect.all.where(condition).to_sql
152
+
153
+ expect(query).to include('"options" && ARRAY[\'a\', \'b\']::string[]')
129
154
  end
130
155
  end
131
156
  end
@@ -587,7 +587,7 @@ RSpec.describe 'AuxiliaryStatement' do
587
587
  result << ' FROM "categories"'
588
588
  result << ' WHERE "categories"."parent_id" IS NULL'
589
589
  result << ' UNION'
590
- result << ' SELECT "categories"."id", "categories"."parent_id", array_append("all_categories"."path", "categories"."id"::varchar) AS path'
590
+ result << ' SELECT "categories"."id", "categories"."parent_id", ARRAY_APPEND("all_categories"."path", "categories"."id"::varchar) AS path'
591
591
  result << ' FROM "categories", "all_categories"'
592
592
  result << ' WHERE "categories"."parent_id" = "all_categories"."id"'
593
593
  result << ' ) SELECT "courses".* FROM "courses" INNER JOIN "all_categories"'
@@ -607,7 +607,7 @@ RSpec.describe 'AuxiliaryStatement' do
607
607
  result << ' FROM "categories"'
608
608
  result << ' WHERE "categories"."parent_id" IS NULL'
609
609
  result << ' UNION'
610
- result << ' SELECT "categories"."id", "categories"."parent_id", array_append("all_categories"."p", "categories"."name"::varchar) AS p'
610
+ result << ' SELECT "categories"."id", "categories"."parent_id", ARRAY_APPEND("all_categories"."p", "categories"."name"::varchar) AS p'
611
611
  result << ' FROM "categories", "all_categories"'
612
612
  result << ' WHERE "categories"."parent_id" = "all_categories"."id"'
613
613
  result << ' ) SELECT "courses".*, "all_categories"."p" AS category_path FROM "courses" INNER JOIN "all_categories"'
@@ -889,7 +889,7 @@ RSpec.describe 'AuxiliaryStatement' do
889
889
  result << ' FROM "categories"'
890
890
  result << ' WHERE "categories"."parent_id" IS NULL'
891
891
  result << ' UNION'
892
- result << ' SELECT "categories"."id", "categories"."parent_id", array_append("category"."a", "categories"."b"::varchar) AS a'
892
+ result << ' SELECT "categories"."id", "categories"."parent_id", ARRAY_APPEND("category"."a", "categories"."b"::varchar) AS a'
893
893
  result << ' FROM "categories", "category"'
894
894
  result << ' WHERE "categories"."parent_id" = "category"."id"'
895
895
  result << ' ) SELECT "courses".*, "category"."a" AS c FROM "courses" INNER JOIN "category"'
@@ -3,9 +3,9 @@ require 'spec_helper'
3
3
  RSpec.describe 'BelongsToMany' do
4
4
  context 'on model' do
5
5
  let(:model) { Video }
6
+ let(:key) { :tests }
6
7
  let(:builder) { Torque::PostgreSQL::Associations::Builder::BelongsToMany }
7
8
  let(:reflection) { Torque::PostgreSQL::Reflection::BelongsToManyReflection }
8
- let(:key) { Torque::PostgreSQL::AR720 ? :tests : 'tests' }
9
9
 
10
10
  after { model._reflections = {} }
11
11
 
@@ -34,8 +34,8 @@ RSpec.describe 'BelongsToMany' do
34
34
 
35
35
  context 'on association' do
36
36
  let(:other) { Tag }
37
+ let(:key) { :tags }
37
38
  let(:initial) { FactoryBot.create(:tag) }
38
- let(:key) { Torque::PostgreSQL::AR720 ? :tags : 'tags' }
39
39
 
40
40
  before { Video.belongs_to_many(:tags) }
41
41
  subject { Video.create(title: 'A') }
@@ -58,7 +58,7 @@ RSpec.describe 'BelongsToMany' do
58
58
  it 'loads associated records' do
59
59
  subject.update(tag_ids: [initial.id])
60
60
  expect(subject.tags.to_sql).to be_eql(<<-SQL.squish)
61
- SELECT "tags".* FROM "tags" WHERE "tags"."id" IN (#{initial.id})
61
+ SELECT "tags".* FROM "tags" WHERE "tags"."id" = #{initial.id}
62
62
  SQL
63
63
 
64
64
  expect(subject.tags.load).to be_a(ActiveRecord::Associations::CollectionProxy)
@@ -370,6 +370,18 @@ RSpec.describe 'BelongsToMany' do
370
370
  expect { query.load }.not_to raise_error
371
371
  end
372
372
 
373
+ context 'when handling binds' do
374
+ let(:tag_ids) { FactoryBot.create_list(:tag, 5).map(&:id) }
375
+ let!(:record) { Video.new(tag_ids: tag_ids) }
376
+
377
+ it 'uses rails default with in and several binds' do
378
+ sql, binds = get_query_with_binds { record.tags.load }
379
+
380
+ expect(sql).to include(' WHERE "tags"."id" IN ($1, $2, $3, $4, $5)')
381
+ expect(binds.size).to be_eql(5)
382
+ end
383
+ end
384
+
373
385
  context 'when the attribute has a default value' do
374
386
  subject { FactoryBot.create(:item) }
375
387
 
@@ -424,16 +436,28 @@ RSpec.describe 'BelongsToMany' do
424
436
 
425
437
  subject { game.create }
426
438
 
427
- it 'loads associated records' do
439
+ it 'loads one associated records' do
428
440
  subject.update(player_ids: [other.id])
429
441
  expect(subject.players.to_sql).to be_eql(<<-SQL.squish)
430
- SELECT "players".* FROM "players" WHERE "players"."id" IN ('#{other.id}')
442
+ SELECT "players".* FROM "players" WHERE "players"."id" = '#{other.id}'
431
443
  SQL
432
444
 
433
445
  expect(subject.players.load).to be_a(ActiveRecord::Associations::CollectionProxy)
434
446
  expect(subject.players.to_a).to be_eql([other])
435
447
  end
436
448
 
449
+ it 'loads several associated records' do
450
+ entries = [other, player.create]
451
+ subject.update(player_ids: entries.map(&:id))
452
+ expect(subject.players.to_sql).to be_eql(<<-SQL.squish)
453
+ SELECT "players".* FROM "players"
454
+ WHERE "players"."id" IN ('#{entries[0].id}', '#{entries[1].id}')
455
+ SQL
456
+
457
+ expect(subject.players.load).to be_a(ActiveRecord::Associations::CollectionProxy)
458
+ expect(subject.players.to_a).to be_eql(entries)
459
+ end
460
+
437
461
  it 'can preload records' do
438
462
  records = 5.times.map { player.create }
439
463
  subject.players.concat(records)
@@ -452,6 +476,49 @@ RSpec.describe 'BelongsToMany' do
452
476
  end
453
477
  end
454
478
 
479
+ context 'using callbacks' do
480
+ let(:tags) { FactoryBot.create_list(:tag, 3) }
481
+ let(:collectors) { Hash.new { |h, k| h[k] = [] } }
482
+
483
+ subject { Video.create(title: 'A') }
484
+
485
+ after do
486
+ Video.reset_callbacks(:save)
487
+ Video._reflections = {}
488
+ end
489
+
490
+ before do
491
+ subject.update_attribute(:tag_ids, tags.first(2).pluck(:id))
492
+ Video.belongs_to_many(:tags,
493
+ before_add: ->(_, tag) { collectors[:before_add] << tag },
494
+ after_add: ->(_, tag) { collectors[:after_add] << tag },
495
+ before_remove: ->(_, tag) { collectors[:before_remove] << tag },
496
+ after_remove: ->(_, tag) { collectors[:after_remove] << tag },
497
+ )
498
+ end
499
+
500
+ it 'works with id changes' do
501
+ subject.tag_ids = tags.drop(1).pluck(:id)
502
+ subject.save!
503
+
504
+ expect(collectors[:before_add]).to be_eql([tags.last])
505
+ expect(collectors[:after_add]).to be_eql([tags.last])
506
+
507
+ expect(collectors[:before_remove]).to be_eql([tags.first])
508
+ expect(collectors[:after_remove]).to be_eql([tags.first])
509
+ end
510
+
511
+ it 'works with record changes' do
512
+ subject.tags = tags.drop(1)
513
+
514
+ expect(collectors[:before_add]).to be_eql([tags.last])
515
+ expect(collectors[:after_add]).to be_eql([tags.last])
516
+
517
+ expect(collectors[:before_remove]).to be_eql([tags.first])
518
+ expect(collectors[:after_remove]).to be_eql([tags.first])
519
+ end
520
+ end
521
+
455
522
  context 'using custom keys' do
456
523
  let(:connection) { ActiveRecord::Base.connection }
457
524
  let(:post) { Post }
@@ -43,13 +43,7 @@ RSpec.describe 'Enum' do
43
43
  end
44
44
 
45
45
  context 'on schema' do
46
- let(:source) do
47
- if Torque::PostgreSQL::AR720
48
- ActiveRecord::Base.connection_pool
49
- else
50
- ActiveRecord::Base.connection
51
- end
52
- end
46
+ let(:source) { ActiveRecord::Base.connection_pool }
53
47
 
54
48
  let(:dump_result) do
55
49
  ActiveRecord::SchemaDumper.dump(source, (dump_result = StringIO.new))
@@ -275,10 +269,11 @@ RSpec.describe 'Enum' do
275
269
  end
276
270
 
277
271
  context 'on model' do
272
+ let(:instance) { Course.new }
273
+
278
274
  before(:each) { decorate(Course, :types) }
279
275
 
280
276
  subject { Course }
281
- let(:instance) { Course.new }
282
277
 
283
278
  it 'has all enum set methods' do
284
279
  expect(subject).to respond_to(:types)
@@ -301,17 +296,23 @@ RSpec.describe 'Enum' do
301
296
 
302
297
  it 'scope the model correctly' do
303
298
  query = subject.a.to_sql
304
- expect(query).to match(/"courses"."types" @> ARRAY\['A'\]::types\[\]/)
299
+ expect(query).to include(%{WHERE "courses"."types" @> '{A}'::types[]})
305
300
  end
306
301
 
307
302
  it 'has a match all scope' do
308
303
  query = subject.has_types('B', 'A').to_sql
309
- expect(query).to match(/"courses"."types" @> ARRAY\['B', 'A'\]::types\[\]/)
304
+ expect(query).to include(%{WHERE "courses"."types" @> '{B,A}'::types[]})
310
305
  end
311
306
 
312
307
  it 'has a match any scope' do
313
308
  query = subject.has_any_types('B', 'A').to_sql
314
- expect(query).to match(/"courses"."types" && ARRAY\['B', 'A'\]::types\[\]/)
309
+ expect(query).to include(%{WHERE "courses"."types" && '{B,A}'::types[]})
310
+ end
311
+
312
+ it 'uses bind param instead of raw value' do
313
+ sql, binds = get_query_with_binds { subject.has_any_types('B', 'A').load }
314
+ expect(sql).to include('WHERE "courses"."types" && $1::types[]')
315
+ expect(binds.first.value).to eq(%w[B A])
315
316
  end
316
317
  end
317
318
  end