torque-postgresql 2.4.4 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +0 -17
  3. data/lib/torque/postgresql/adapter/database_statements.rb +32 -74
  4. data/lib/torque/postgresql/adapter/oid/enum_set.rb +1 -1
  5. data/lib/torque/postgresql/adapter/oid.rb +0 -3
  6. data/lib/torque/postgresql/adapter/quoting.rb +12 -20
  7. data/lib/torque/postgresql/adapter/schema_creation.rb +1 -2
  8. data/lib/torque/postgresql/adapter/schema_definitions.rb +0 -37
  9. data/lib/torque/postgresql/adapter/schema_dumper.rb +2 -60
  10. data/lib/torque/postgresql/adapter/schema_statements.rb +2 -74
  11. data/lib/torque/postgresql/adapter.rb +2 -11
  12. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +7 -6
  13. data/lib/torque/postgresql/associations/{association.rb → foreign_association.rb} +1 -4
  14. data/lib/torque/postgresql/associations/preloader/association.rb +53 -26
  15. data/lib/torque/postgresql/associations/preloader/loader_query.rb +36 -0
  16. data/lib/torque/postgresql/associations/preloader.rb +1 -0
  17. data/lib/torque/postgresql/associations.rb +6 -1
  18. data/lib/torque/postgresql/attributes/builder/period.rb +6 -2
  19. data/lib/torque/postgresql/auxiliary_statement/settings.rb +22 -75
  20. data/lib/torque/postgresql/auxiliary_statement.rb +40 -39
  21. data/lib/torque/postgresql/base.rb +13 -33
  22. data/lib/torque/postgresql/config.rb +3 -30
  23. data/lib/torque/postgresql/inheritance.rb +1 -3
  24. data/lib/torque/postgresql/migration/command_recorder.rb +2 -12
  25. data/lib/torque/postgresql/railtie.rb +1 -5
  26. data/lib/torque/postgresql/reflection/abstract_reflection.rb +44 -20
  27. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +2 -2
  28. data/lib/torque/postgresql/relation/auxiliary_statement.rb +15 -28
  29. data/lib/torque/postgresql/relation.rb +10 -12
  30. data/lib/torque/postgresql/schema_cache.rb +2 -7
  31. data/lib/torque/postgresql/version.rb +1 -1
  32. data/lib/torque/postgresql.rb +1 -2
  33. data/lib/torque-postgresql.rb +0 -1
  34. data/spec/schema.rb +14 -30
  35. data/spec/spec_helper.rb +1 -2
  36. data/spec/tests/arel_spec.rb +2 -4
  37. data/spec/tests/auxiliary_statement_spec.rb +35 -374
  38. data/spec/tests/belongs_to_many_spec.rb +2 -99
  39. data/spec/tests/distinct_on_spec.rb +1 -1
  40. data/spec/tests/enum_set_spec.rb +10 -10
  41. data/spec/tests/enum_spec.rb +0 -90
  42. data/spec/tests/has_many_spec.rb +0 -46
  43. data/spec/tests/relation_spec.rb +1 -1
  44. data/spec/tests/table_inheritance_spec.rb +15 -11
  45. metadata +11 -37
  46. data/lib/torque/postgresql/auxiliary_statement/recursive.rb +0 -149
  47. data/lib/torque/postgresql/table_name.rb +0 -41
  48. data/lib/torque/range.rb +0 -22
  49. data/spec/models/category.rb +0 -2
  50. data/spec/models/internal/user.rb +0 -5
  51. data/spec/tests/range_spec.rb +0 -36
  52. data/spec/tests/schema_spec.rb +0 -134
@@ -5,26 +5,16 @@ module Torque
5
5
  module Migration
6
6
  module CommandRecorder
7
7
 
8
- # Records the rename operation for types
8
+ # Records the rename operation for types.
9
9
  def rename_type(*args, &block)
10
10
  record(:rename_type, args, &block)
11
11
  end
12
12
 
13
- # Inverts the type rename operation
13
+ # Inverts the type name.
14
14
  def invert_rename_type(args)
15
15
  [:rename_type, args.reverse]
16
16
  end
17
17
 
18
- # Records the creation of a schema
19
- def create_schema(*args, &block)
20
- record(:create_schema, args, &block)
21
- end
22
-
23
- # Inverts the creation of a schema
24
- def invert_create_schema(args)
25
- [:drop_schema, [args.first]]
26
- end
27
-
28
18
  # Records the creation of the enum to be reverted.
29
19
  def create_enum(*args, &block)
30
20
  record(:create_enum, args, &block)
@@ -30,15 +30,11 @@ module Torque
30
30
  Torque::PostgreSQL::Attributes::Enum.lookup(name).sample
31
31
  end
32
32
 
33
- # Define the exposed constant for both types of auxiliary statements
33
+ # Define the exposed constant for auxiliary statements
34
34
  if torque_config.auxiliary_statement.exposed_class.present?
35
35
  *ns, name = torque_config.auxiliary_statement.exposed_class.split('::')
36
36
  base = ns.present? ? Object.const_get(ns.join('::')) : Object
37
37
  base.const_set(name, Torque::PostgreSQL::AuxiliaryStatement)
38
-
39
- *ns, name = torque_config.auxiliary_statement.exposed_recursive_class.split('::')
40
- base = ns.present? ? Object.const_get(ns.join('::')) : Object
41
- base.const_set(name, Torque::PostgreSQL::AuxiliaryStatement::Recursive)
42
38
  end
43
39
  end
44
40
  end
@@ -6,6 +6,9 @@ module Torque
6
6
  module AbstractReflection
7
7
  AREL_ATTR = ::Arel::Attributes::Attribute
8
8
 
9
+ ARR_NO_CAST = 'bigint'
10
+ ARR_CAST = 'bigint[]'
11
+
9
12
  # Check if the foreign key actually exists
10
13
  def connected_through_array?
11
14
  false
@@ -37,36 +40,39 @@ module Torque
37
40
  result
38
41
  end
39
42
 
40
- # Build the id constraint checking if both types are perfect matching.
41
- # The klass attribute (left side) will always be a column attribute
43
+ # Build the id constraint checking if both types are perfect matching
42
44
  def build_id_constraint(klass_attr, source_attr)
43
45
  return klass_attr.eq(source_attr) unless connected_through_array?
44
46
 
45
47
  # Klass and key are associated with the reflection Class
46
- klass_type = klass.columns_hash[join_keys.key.to_s]
47
-
48
- # Apply an ANY operation which checks if the single value on the left
49
- # side exists in the array on the right side
50
- if source_attr.is_a?(AREL_ATTR)
51
- any_value = [klass_attr, source_attr]
52
- any_value.reverse! if klass_type.try(:array?)
53
- return any_value.shift.eq(::Arel::Nodes::NamedFunction.new('ANY', any_value))
54
- end
48
+ klass_type = klass.columns_hash[join_primary_key.to_s]
49
+ # active_record and foreign_key are associated with the source Class
50
+ source_type = active_record.columns_hash[join_foreign_key.to_s]
51
+
52
+ # If both are attributes but the left side is not an array, and the
53
+ # right side is, use the ANY operation
54
+ any_operation = arel_array_to_any(klass_attr, source_attr, klass_type, source_type)
55
+ return klass_attr.eq(any_operation) if any_operation
55
56
 
56
57
  # If the left side is not an array, just use the IN condition
57
58
  return klass_attr.in(source_attr) unless klass_type.try(:array)
58
59
 
59
- # Build the overlap condition (array && array) ensuring that the right
60
- # side has the same type as the left side
61
- source_attr = ::Arel::Nodes.build_quoted(Array.wrap(source_attr))
62
- klass_attr.overlaps(source_attr.cast(klass_type.sql_type_metadata.sql_type))
60
+ # Decide if should apply a cast to ensure same type comparision
61
+ should_cast = klass_type.type.eql?(:integer) && source_type.type.eql?(:integer)
62
+ should_cast &= !klass_type.sql_type.eql?(source_type.sql_type)
63
+ should_cast |= !(klass_attr.is_a?(AREL_ATTR) && source_attr.is_a?(AREL_ATTR))
64
+
65
+ # Apply necessary transformations to values
66
+ klass_attr = cast_constraint_to_array(klass_type, klass_attr, should_cast)
67
+ source_attr = cast_constraint_to_array(source_type, source_attr, should_cast)
68
+
69
+ # Return the overlap condition
70
+ klass_attr.overlaps(source_attr)
63
71
  end
64
72
 
65
- if PostgreSQL::AR610
66
- # TODO: Deprecate this method
67
- def join_keys
68
- OpenStruct.new(key: join_primary_key, foreign_key: join_foreign_key)
69
- end
73
+ # TODO: Deprecate this method
74
+ def join_keys
75
+ OpenStruct.new(key: join_primary_key, foreign_key: join_foreign_key)
70
76
  end
71
77
 
72
78
  private
@@ -77,6 +83,24 @@ module Torque
77
83
 
78
84
  build_id_constraint(klass_attr, source_attr)
79
85
  end
86
+
87
+ # Prepare a value for an array constraint overlap condition
88
+ def cast_constraint_to_array(type, value, should_cast)
89
+ base_ready = type.try(:array) && value.is_a?(AREL_ATTR)
90
+ return value if base_ready && (type.sql_type.eql?(ARR_NO_CAST) || !should_cast)
91
+
92
+ value = ::Arel::Nodes.build_quoted(Array.wrap(value)) unless base_ready
93
+ value = value.cast(ARR_CAST) if should_cast
94
+ value
95
+ end
96
+
97
+ # Check if it's possible to turn both attributes into an ANY condition
98
+ def arel_array_to_any(klass_attr, source_attr, klass_type, source_type)
99
+ return unless !klass_type.try(:array) && source_type.try(:array) &&
100
+ klass_attr.is_a?(AREL_ATTR) && source_attr.is_a?(AREL_ATTR)
101
+
102
+ ::Arel::Nodes::NamedFunction.new('ANY', [source_attr])
103
+ end
80
104
  end
81
105
 
82
106
  ::ActiveRecord::Reflection::AbstractReflection.prepend(AbstractReflection)
@@ -25,7 +25,7 @@ module Torque
25
25
  end
26
26
 
27
27
  def foreign_key
28
- @foreign_key ||= options[:foreign_key]&.to_s || derive_foreign_key.freeze
28
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze
29
29
  end
30
30
 
31
31
  def association_foreign_key
@@ -33,7 +33,7 @@ module Torque
33
33
  end
34
34
 
35
35
  def active_record_primary_key
36
- @active_record_primary_key ||= options[:primary_key]&.to_s || derive_primary_key
36
+ @active_record_primary_key ||= options[:primary_key] || derive_primary_key
37
37
  end
38
38
 
39
39
  def join_primary_key(*)
@@ -10,14 +10,22 @@ module Torque
10
10
  # :nodoc:
11
11
  def auxiliary_statements_values=(value); set_value(:auxiliary_statements, value); end
12
12
 
13
- # Set use of an auxiliary statement
14
- def with(*args, **settings)
15
- spawn.with!(*args, **settings)
13
+ # Set use of an auxiliary statement already configurated on the model
14
+ def with(*args)
15
+ spawn.with!(*args)
16
16
  end
17
17
 
18
18
  # Like #with, but modifies relation in place.
19
- def with!(*args, **settings)
20
- instantiate_auxiliary_statements(*args, **settings)
19
+ def with!(*args)
20
+ options = args.extract_options!
21
+ args.each do |table|
22
+ instance = table.is_a?(Class) && table < PostgreSQL::AuxiliaryStatement \
23
+ ? table.new(options) \
24
+ : PostgreSQL::AuxiliaryStatement.instantiate(table, self, options)
25
+
26
+ self.auxiliary_statements_values += [instance]
27
+ end
28
+
21
29
  self
22
30
  end
23
31
 
@@ -39,23 +47,8 @@ module Torque
39
47
  # Hook arel build to add the distinct on clause
40
48
  def build_arel(*)
41
49
  arel = super
42
- type = auxiliary_statement_type
43
- sub_queries = build_auxiliary_statements(arel)
44
- sub_queries.nil? ? arel : arel.with(*type, *sub_queries)
45
- end
46
-
47
- # Instantiate one or more auxiliary statements for the given +klass+
48
- def instantiate_auxiliary_statements(*args, **options)
49
- klass = PostgreSQL::AuxiliaryStatement
50
- klass = klass::Recursive if options.delete(:recursive).present?
51
-
52
- self.auxiliary_statements_values += args.map do |table|
53
- if table.is_a?(Class) && table < klass
54
- table.new(**options)
55
- else
56
- klass.instantiate(table, self, **options)
57
- end
58
- end
50
+ subqueries = build_auxiliary_statements(arel)
51
+ subqueries.nil? ? arel : arel.with(subqueries)
59
52
  end
60
53
 
61
54
  # Build all necessary data for auxiliary statements
@@ -66,12 +59,6 @@ module Torque
66
59
  end
67
60
  end
68
61
 
69
- # Return recursive if any auxiliary statement is recursive
70
- def auxiliary_statement_type
71
- klass = PostgreSQL::AuxiliaryStatement::Recursive
72
- :recursive if auxiliary_statements_values.any?(klass)
73
- end
74
-
75
62
  # Throw an error showing that an auxiliary statement of the given
76
63
  # table name isn't defined
77
64
  def auxiliary_statement_error(name)
@@ -19,6 +19,8 @@ module Torque
19
19
  MULTI_VALUE_METHODS = [:distinct_on, :auxiliary_statements, :cast_records, :select_extra]
20
20
  VALUE_METHODS = SINGLE_VALUE_METHODS + MULTI_VALUE_METHODS
21
21
 
22
+ ARColumn = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Column
23
+
22
24
  # :nodoc:
23
25
  def select_extra_values; get_value(:select_extra); end
24
26
  # :nodoc:
@@ -74,6 +76,14 @@ module Torque
74
76
  end
75
77
  end
76
78
 
79
+ # Serialize the given value so it can be used in a condition tha involves
80
+ # the given column
81
+ def cast_for_condition(column, value)
82
+ column = columns_hash[column.to_s] unless column.is_a?(ARColumn)
83
+ caster = connection.lookup_cast_type_from_column(column)
84
+ connection.type_cast(caster.serialize(value))
85
+ end
86
+
77
87
  private
78
88
 
79
89
  def build_arel(*)
@@ -140,18 +150,6 @@ module Torque
140
150
  ActiveRecord::QueryMethods::VALID_UNSCOPING_VALUES += %i[cast_records itself_only
141
151
  distinct_on auxiliary_statements]
142
152
 
143
- unless AR610
144
- Relation::SINGLE_VALUE_METHODS.each do |value|
145
- ActiveRecord::QueryMethods::DEFAULT_VALUES[value] = nil \
146
- if ActiveRecord::QueryMethods::DEFAULT_VALUES[value].nil?
147
- end
148
-
149
- Relation::MULTI_VALUE_METHODS.each do |value|
150
- ActiveRecord::QueryMethods::DEFAULT_VALUES[value] ||= \
151
- ActiveRecord::QueryMethods::FROZEN_EMPTY_ARRAY
152
- end
153
- end
154
-
155
153
  $VERBOSE = warn_level
156
154
  end
157
155
  end
@@ -109,6 +109,7 @@ module Torque
109
109
 
110
110
  # Try to find a model based on a given table
111
111
  def lookup_model(table_name, scoped_class = '')
112
+ # byebug if table_name == 'activities'
112
113
  scoped_class = scoped_class.name if scoped_class.is_a?(Class)
113
114
  return @data_sources_model_names[table_name] \
114
115
  if @data_sources_model_names.key?(table_name)
@@ -117,19 +118,13 @@ module Torque
117
118
  scopes = scoped_class.scan(/(?:::)?[A-Z][a-z]+/)
118
119
  scopes.unshift('Object::')
119
120
 
120
- # Check if the table name comes with a schema
121
- if table_name.include?('.')
122
- schema, table_name = table_name.split('.')
123
- scopes.insert(1, schema.camelize) if schema != 'public'
124
- end
125
-
126
121
  # Consider the maximum namespaced possible model name
127
122
  max_name = table_name.tr('_', '/').camelize.split(/(::)/)
128
123
  max_name[-1] = max_name[-1].singularize
129
124
 
130
125
  # Test all the possible names against all the possible scopes
131
126
  until scopes.size == 0
132
- scope = scopes.join.safe_constantize
127
+ scope = scopes.join.chomp('::').safe_constantize
133
128
  model = find_model(max_name, table_name, scope) unless scope.nil?
134
129
  return @data_sources_model_names[table_name] = model unless model.nil?
135
130
  scopes.pop
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Torque
4
4
  module PostgreSQL
5
- VERSION = '2.4.4'
5
+ VERSION = '3.0.0'
6
6
  end
7
7
  end
@@ -20,13 +20,12 @@ require 'torque/postgresql/associations'
20
20
  require 'torque/postgresql/attributes'
21
21
  require 'torque/postgresql/autosave_association'
22
22
  require 'torque/postgresql/auxiliary_statement'
23
+ require 'torque/postgresql/base'
23
24
  require 'torque/postgresql/inheritance'
24
- require 'torque/postgresql/base'# Needs to be after inheritance
25
25
  require 'torque/postgresql/insert_all'
26
26
  require 'torque/postgresql/migration'
27
27
  require 'torque/postgresql/relation'
28
28
  require 'torque/postgresql/reflection'
29
29
  require 'torque/postgresql/schema_cache'
30
- require 'torque/postgresql/table_name'
31
30
 
32
31
  require 'torque/postgresql/railtie' if defined?(Rails)
@@ -1,2 +1 @@
1
1
  require 'torque/postgresql'
2
- require 'torque/range'
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 = 3
13
+ version = 2
14
14
 
15
15
  return if ActiveRecord::Migrator.current_version == version
16
16
  ActiveRecord::Schema.define(version: version) do
@@ -20,16 +20,13 @@ ActiveRecord::Schema.define(version: version) do
20
20
  enable_extension "pgcrypto"
21
21
  enable_extension "plpgsql"
22
22
 
23
- # Custom schemas used in this database.
24
- create_schema "internal", force: :cascade
25
-
26
23
  # Custom types defined in this database.
27
24
  # Note that some types may not work with other database engines. Be careful if changing database.
28
- create_enum "content_status", ["created", "draft", "published", "archived"], force: :cascade
29
- create_enum "specialties", ["books", "movies", "plays"], force: :cascade
30
- create_enum "roles", ["visitor", "assistant", "manager", "admin"], force: :cascade
31
- create_enum "conflicts", ["valid", "invalid", "untrusted"], force: :cascade
32
- create_enum "types", ["A", "B", "C", "D"], force: :cascade
25
+ create_enum "content_status", ["created", "draft", "published", "archived"]
26
+ create_enum "specialties", ["books", "movies", "plays"]
27
+ create_enum "roles", ["visitor", "assistant", "manager", "admin"]
28
+ create_enum "conflicts", ["valid", "invalid", "untrusted"]
29
+ create_enum "types", ["A", "B", "C", "D"]
33
30
 
34
31
  create_table "geometries", force: :cascade do |t|
35
32
  t.point "point"
@@ -69,30 +66,24 @@ ActiveRecord::Schema.define(version: version) do
69
66
  t.enum "specialty", enum_type: :specialties
70
67
  end
71
68
 
72
- create_table "categories", force: :cascade do |t|
73
- t.integer "parent_id"
74
- t.string "title"
75
- end
76
-
77
69
  create_table "texts", force: :cascade do |t|
78
70
  t.integer "user_id"
79
71
  t.string "content"
80
- t.enum "conflict", enum_type: :conflicts
72
+ t.enum "conflict", enum_type: :conflicts
81
73
  end
82
74
 
83
75
  create_table "comments", force: :cascade do |t|
84
- t.integer "user_id", null: false
76
+ t.integer "user_id", null: false
85
77
  t.integer "comment_id"
86
78
  t.integer "video_id"
87
- t.text "content", null: false
79
+ t.text "content", null: false
88
80
  t.string "kind"
89
81
  t.index ["user_id"], name: "index_comments_on_user_id", using: :btree
90
82
  t.index ["comment_id"], name: "index_comments_on_comment_id", using: :btree
91
83
  end
92
84
 
93
85
  create_table "courses", force: :cascade do |t|
94
- t.integer "category_id"
95
- t.string "title", null: false
86
+ t.string "title", null: false
96
87
  t.interval "duration"
97
88
  t.enum "types", enum_type: :types, array: true
98
89
  t.datetime "created_at", null: false
@@ -108,7 +99,7 @@ ActiveRecord::Schema.define(version: version) do
108
99
  t.integer "activity_id"
109
100
  t.string "title"
110
101
  t.text "content"
111
- t.enum "status", enum_type: :content_status
102
+ t.enum "status", enum_type: :content_status
112
103
  t.index ["author_id"], name: "index_posts_on_author_id", using: :btree
113
104
  end
114
105
 
@@ -120,24 +111,17 @@ ActiveRecord::Schema.define(version: version) do
120
111
  end
121
112
 
122
113
  create_table "users", force: :cascade do |t|
123
- t.string "name", null: false
124
- t.enum "role", enum_type: :roles, default: :visitor
125
- t.datetime "created_at", null: false
126
- t.datetime "updated_at", null: false
127
- end
128
-
129
- create_table "users", schema: "internal", force: :cascade do |t|
130
- t.string "email"
114
+ t.string "name", null: false
115
+ t.enum "role", enum_type: :roles, default: :visitor
131
116
  t.datetime "created_at", null: false
132
117
  t.datetime "updated_at", null: false
133
- t.index ["email"], name: "index_internal_users_on_email", unique: true
134
118
  end
135
119
 
136
120
  create_table "activities", force: :cascade do |t|
137
121
  t.integer "author_id"
138
122
  t.string "title"
139
123
  t.boolean "active"
140
- t.enum "kind", enum_type: :types
124
+ t.enum "kind", enum_type: :types
141
125
  t.datetime "created_at", null: false
142
126
  t.datetime "updated_at", null: false
143
127
  end
data/spec/spec_helper.rb CHANGED
@@ -22,7 +22,7 @@ cleaner = ->() do
22
22
  end
23
23
 
24
24
  load File.join('schema.rb')
25
- Dir.glob(File.join('spec', '{models,factories,mocks}', '**', '*.rb')) do |file|
25
+ Dir.glob(File.join('spec', '{models,factories,mocks}', '*.rb')) do |file|
26
26
  require file[5..-4]
27
27
  end
28
28
 
@@ -39,7 +39,6 @@ RSpec.configure do |config|
39
39
 
40
40
  # Handles acton before rspec initialize
41
41
  config.before(:suite) do
42
- ActiveSupport::Deprecation.silenced = true
43
42
  DatabaseCleaner.clean_with(:truncation)
44
43
  end
45
44
 
@@ -51,8 +51,6 @@ RSpec.describe 'Arel' do
51
51
  context 'on default value' do
52
52
  let(:connection) { ActiveRecord::Base.connection }
53
53
 
54
- before(:context) { Torque::PostgreSQL.config.use_extended_defaults = true }
55
- after(:context) { Torque::PostgreSQL.config.use_extended_defaults = false }
56
54
  after { Author.reset_column_information }
57
55
 
58
56
  it 'does not break the change column default value method' do
@@ -76,7 +74,7 @@ RSpec.describe 'Arel' do
76
74
 
77
75
  expect do
78
76
  connection.create_enum(:samples, %i[a b c d])
79
- connection.add_column(:authors, :samples, :samples, array: true, default: value)
77
+ connection.add_column(:authors, :samples, :enum, enum_type: :samples, array: true, default: value)
80
78
  end.not_to raise_error
81
79
 
82
80
  expect(Author.new.samples).to eq(value)
@@ -84,7 +82,7 @@ RSpec.describe 'Arel' do
84
82
 
85
83
  it 'works with an array with enum values for an existing enum' do
86
84
  value = ['visitor', 'assistant']
87
- expect { connection.add_column(:authors, :roles, :roles, array: true, default: value) }.not_to raise_error
85
+ expect { connection.add_column(:authors, :roles, :enum, enum_type: :roles, array: true, default: value) }.not_to raise_error
88
86
  expect(Author.new.roles).to eq(value)
89
87
  end
90
88