torque-postgresql 3.2.2 → 3.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1948838632756aeab31c22f59995a8a41ce1900671e3cc9e1f8d802d898a4fe6
4
- data.tar.gz: ca24eb91ead1a52927d1b0069e8408419600ba7884744a31de3909f45b59bb4d
3
+ metadata.gz: 2ea2d3fe5e64c12ea8cf8b77ee39a08092b23c5f15802442d5bc92464e248111
4
+ data.tar.gz: 17541c4f9cbd22c940b5082be168c0fd62e9736abe77efc79340c73058342db4
5
5
  SHA512:
6
- metadata.gz: 41071ecb22d7730bdb0debe558cf4b14db7ca648637d8f59cc43de31d0ca847b0ce7df2447b2e75be507b7ccd03f668c0dc7fa7232a38217889f5172f923a243
7
- data.tar.gz: 74be2a0828976d3f521e3c9ff0c49708cc767dd019c8a22b6e98080bbda036da4297675c8852f124d01de3588f04b390ae7f5138549a3e85a6f6ce0ad1e50ccb
6
+ metadata.gz: 39f36019706145c9b6ff5c4e407ee95b7965746d194338a52d1ee3d67876008b62f673996fb41bc0eb257184454ba74cbe3ffc2cb60805bcaf2d366a3a8ad54f
7
+ data.tar.gz: 8c77b20d700a66a002eecf369d3d7e2f458d88b280e182e93c4e25bcd8ddbe10f03f77d578d50f13a7234683721ed3cc99d49eac9db7f3de1f0ade9c058f5e53
@@ -176,11 +176,11 @@ module Torque
176
176
  # Build the query for allowed schemas
177
177
  def user_defined_schemas_sql
178
178
  conditions = []
179
- conditions << <<-SQL if schemas_blacklist.any?
180
- nspname NOT LIKE ANY (ARRAY['#{schemas_blacklist.join("', '")}'])
179
+ conditions << <<-SQL.squish if schemas_blacklist.any?
180
+ nspname NOT LIKE ALL (ARRAY['#{schemas_blacklist.join("', '")}'])
181
181
  SQL
182
182
 
183
- conditions << <<-SQL if schemas_whitelist.any?
183
+ conditions << <<-SQL.squish if schemas_whitelist.any?
184
184
  nspname LIKE ANY (ARRAY['#{schemas_whitelist.join("', '")}'])
185
185
  SQL
186
186
 
@@ -16,7 +16,7 @@ module Torque
16
16
  statements.concat(o.indexes.map { |c, o| index_in_create(o.name, c, o) })
17
17
  end
18
18
 
19
- if supports_foreign_keys?
19
+ if @conn.supports_foreign_keys?
20
20
  statements.concat(o.foreign_keys.map { |fk| accept fk })
21
21
  end
22
22
 
@@ -122,6 +122,12 @@ module Torque
122
122
  super.sub('SELECT c.relname FROM', "SELECT n.nspname || '.' || c.relname FROM")
123
123
  end
124
124
 
125
+ # Add schema and inherits as one of the valid options for table
126
+ # definition
127
+ def valid_table_definition_options
128
+ super + [:schema, :inherits]
129
+ end
130
+
125
131
  private
126
132
 
127
133
  # Remove the schema from the sequence name
@@ -4,6 +4,9 @@ module Torque
4
4
  module PostgreSQL
5
5
  include ActiveSupport::Configurable
6
6
 
7
+ # Stores a version check for compatibility purposes
8
+ AR710 = (ActiveRecord.gem_version >= Gem::Version.new('7.1.0'))
9
+
7
10
  # Use the same logger as the Active Record one
8
11
  def self.logger
9
12
  ActiveRecord::Base.logger
@@ -17,8 +20,8 @@ module Torque
17
20
  send("#{name}=", klass)
18
21
  end
19
22
 
20
- # Set if any information that requires querying and searching or collectiong
21
- # information shuld be eager loaded. This automatically changes when rails
23
+ # Set if any information that requires querying and searching or collecting
24
+ # information should be eager loaded. This automatically changes when rails
22
25
  # same configuration is set to true
23
26
  config.eager_load = false
24
27
 
@@ -17,7 +17,7 @@ module Torque
17
17
  private
18
18
 
19
19
  # Check if the foreign key should be pluralized
20
- def derive_foreign_key
20
+ def derive_foreign_key(*, **)
21
21
  result = super
22
22
  result = ActiveSupport::Inflector.pluralize(result) \
23
23
  if collection? && connected_through_array?
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ module BoundSchemaReflection
6
+ def add_model_name(table_name, model)
7
+ @schema_reflection.add_model_name(@connection, table_name, model)
8
+ end
9
+
10
+ def dependencies(table_name)
11
+ @schema_reflection.dependencies(@connection, table_name)
12
+ end
13
+
14
+ def associations(table_name)
15
+ @schema_reflection.associations(@connection, table_name)
16
+ end
17
+
18
+ def lookup_model(table_name, scoped_class = '')
19
+ @schema_reflection.lookup_model(@connection, table_name, scoped_class)
20
+ end
21
+ end
22
+
23
+ ActiveRecord::ConnectionAdapters::BoundSchemaReflection.prepend BoundSchemaReflection
24
+ end
25
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ module SchemaCache
6
+ module Inheritance
7
+
8
+ # Try to find a model based on a given table
9
+ def lookup_model(table_name, scoped_class = '', source_to_model:)
10
+ scoped_class = scoped_class.name if scoped_class.is_a?(Class)
11
+ return source_to_model[table_name] if source_to_model.key?(table_name)
12
+
13
+ # Get all the possible scopes
14
+ scopes = scoped_class.scan(/(?:::)?[A-Z][a-z]+/)
15
+ scopes.unshift('Object::')
16
+
17
+ # Check if the table name comes with a schema
18
+ if table_name.include?('.')
19
+ schema, table_name = table_name.split('.')
20
+ scopes.insert(1, schema.camelize) if schema != 'public'
21
+ end
22
+
23
+ # Consider the maximum namespaced possible model name
24
+ max_name = table_name.tr('_', '/').camelize.split(/(::)/)
25
+ max_name[-1] = max_name[-1].singularize
26
+
27
+ # Test all the possible names against all the possible scopes
28
+ until scopes.size == 0
29
+ scope = scopes.join.chomp('::').safe_constantize
30
+ model = find_model(max_name, table_name, scope) unless scope.nil?
31
+ return source_to_model[table_name] = model unless model.nil?
32
+ scopes.pop
33
+ end
34
+
35
+ # If this part is reach, no model name was found
36
+ raise LookupError.new(<<~MSG.squish)
37
+ Unable to find a valid model that is associated with the
38
+ '#{table_name}' table. Please, check if they correctly inherit from
39
+ ActiveRecord::Base
40
+ MSG
41
+ end
42
+
43
+ protected
44
+
45
+ # Find a model by a given max namespaced class name that matches the
46
+ # given table name
47
+ def find_model(max_name, table_name, scope = Object)
48
+ pieces = max_name.is_a?(::Array) ? max_name : max_name.split(/(::)/)
49
+ ns_places = (1..(max_name.size - 1)).step(2).to_a
50
+
51
+ # Generate all possible combinations
52
+ conditions = []
53
+ range = Torque::PostgreSQL.config.inheritance.inverse_lookup \
54
+ ? 0.upto(ns_places.size) \
55
+ : ns_places.size.downto(0)
56
+ range.each do |size|
57
+ conditions.concat(ns_places.combination(size).to_a)
58
+ end
59
+
60
+ # Now iterate over
61
+ while (condition = conditions.shift)
62
+ ns_places.each do |i|
63
+ pieces[i] = condition.include?(i) ? '::' : ''
64
+ end
65
+
66
+ candidate = pieces.join
67
+ candidate.prepend("#{scope.name}::") unless scope === Object
68
+
69
+ klass = candidate.safe_constantize
70
+ next if klass.nil?
71
+
72
+ # Check if the class match the table name
73
+ return klass if klass < ::ActiveRecord::Base &&
74
+ klass.table_name == table_name
75
+ end
76
+ end
77
+
78
+ # Calculates the inverted dependency (association), where even indirect
79
+ # inheritance comes up in the list
80
+ def generate_associations(inheritance_dependencies)
81
+ return {} if inheritance_dependencies.empty?
82
+
83
+ result = Hash.new{ |h, k| h[k] = [] }
84
+ masters = inheritance_dependencies.values.flatten.uniq
85
+
86
+ # Add direct associations
87
+ masters.map do |master|
88
+ inheritance_dependencies.each do |(dependent, associations)|
89
+ result[master] << dependent if associations.include?(master)
90
+ end
91
+ end
92
+
93
+ # Add indirect associations
94
+ result.each do |master, children|
95
+ children.each do |child|
96
+ children.concat(result[child]).uniq! if result.key?(child)
97
+ end
98
+ end
99
+
100
+ # Remove the default proc that would create new entries
101
+ result.default_proc = nil
102
+ result
103
+ end
104
+
105
+ # Parse the Torque config into the proper hash of irregular models.
106
+ # This is smart enough to only load necessary models
107
+ def prepare_irregular_models(data_sources)
108
+ entries = Torque::PostgreSQL.config.irregular_models
109
+ entries.slice(*data_sources).each_with_object({}) do |(table, model), hash|
110
+ hash[table] = model.is_a?(Class) ? model : model.constantize
111
+ end
112
+ end
113
+
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ module SchemaReflection
6
+ def add_model_name(connection, table_name, model)
7
+ cache(connection).add_model_name(table_name, model)
8
+ end
9
+
10
+ def dependencies(connection, table_name)
11
+ cache(connection).dependencies(connection, table_name)
12
+ end
13
+
14
+ def associations(connection, table_name)
15
+ cache(connection).associations(connection, table_name)
16
+ end
17
+
18
+ def lookup_model(connection, table_name, scoped_class)
19
+ cache(connection).lookup_model(table_name, scoped_class)
20
+ end
21
+ end
22
+
23
+ ActiveRecord::ConnectionAdapters::SchemaReflection.prepend SchemaReflection
24
+ end
25
+ end
@@ -1,11 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
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
9
+
3
10
  module Torque
4
11
  module PostgreSQL
5
12
  LookupError = Class.new(ArgumentError)
6
13
 
7
14
  # :TODO: Create the +add+ to load inheritance info
8
15
  module SchemaCache
16
+ include Torque::PostgreSQL::SchemaCache::Inheritance
9
17
 
10
18
  def initialize(*) # :nodoc:
11
19
  super
@@ -37,7 +45,7 @@ module Torque
37
45
  @inheritance_associations = coder['inheritance_associations']
38
46
  end
39
47
 
40
- def add(table_name, *) # :nodoc:
48
+ def add(connection_or_table_name, table_name = connection_or_table_name, *) # :nodoc:
41
49
  super
42
50
 
43
51
  # Reset inheritance information when a table is added
@@ -64,8 +72,8 @@ module Torque
64
72
  ].map(&:size).inject(:+)
65
73
  end
66
74
 
67
- def clear_data_source_cache!(name) # :nodoc:
68
- super
75
+ def clear_data_source_cache!(connection_or_name, name = connection_or_name) # :nodoc:
76
+ Torque::PostgreSQL::AR710 ? super : super(name)
69
77
  @data_sources_model_names.delete name
70
78
  @inheritance_dependencies.delete name
71
79
  @inheritance_associations.delete name
@@ -95,88 +103,29 @@ module Torque
95
103
  end
96
104
 
97
105
  # Get all the tables that the given one inherits from
98
- def dependencies(table_name)
99
- reload_inheritance_data!
106
+ def dependencies(conn, table_name = conn)
107
+ reload_inheritance_data!(conn == table_name ? connection : conn)
100
108
  @inheritance_dependencies[table_name]
101
109
  end
102
110
 
103
111
  # Get the list of all tables that are associated (direct or indirect
104
112
  # inheritance) with the provided one
105
- def associations(table_name)
106
- reload_inheritance_data!
113
+ def associations(conn, table_name = conn)
114
+ reload_inheritance_data!(conn == table_name ? connection : conn)
107
115
  @inheritance_associations[table_name]
108
116
  end
109
117
 
110
- # Try to find a model based on a given table
111
- def lookup_model(table_name, scoped_class = '')
112
- scoped_class = scoped_class.name if scoped_class.is_a?(Class)
113
- return @data_sources_model_names[table_name] \
114
- if @data_sources_model_names.key?(table_name)
115
-
116
- # Get all the possible scopes
117
- scopes = scoped_class.scan(/(?:::)?[A-Z][a-z]+/)
118
- scopes.unshift('Object::')
119
-
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
- # Consider the maximum namespaced possible model name
127
- max_name = table_name.tr('_', '/').camelize.split(/(::)/)
128
- max_name[-1] = max_name[-1].singularize
129
-
130
- # Test all the possible names against all the possible scopes
131
- until scopes.size == 0
132
- scope = scopes.join.chomp('::').safe_constantize
133
- model = find_model(max_name, table_name, scope) unless scope.nil?
134
- return @data_sources_model_names[table_name] = model unless model.nil?
135
- scopes.pop
136
- end
137
-
138
- # If this part is reach, no model name was found
139
- raise LookupError.new(<<~MSG.squish)
140
- Unable to find a valid model that is associated with the '#{table_name}' table.
141
- Please, check if they correctly inherit from ActiveRecord::Base
142
- MSG
118
+ # Override the inheritance implementation to pass over the proper cache of
119
+ # the existing association between data sources and model names
120
+ def lookup_model(*args, **xargs)
121
+ super(*args, **xargs, source_to_model: @data_sources_model_names)
143
122
  end
144
123
 
145
124
  private
146
125
 
147
- # Find a model by a given max namespaced class name thath matches the
148
- # given table name
149
- def find_model(max_name, table_name, scope = Object)
150
- pieces = max_name.is_a?(::Array) ? max_name : max_name.split(/(::)/)
151
- ns_places = (1..(max_name.size - 1)).step(2).to_a
152
-
153
- # Generate all possible combinarions
154
- conditions = []
155
- range = Torque::PostgreSQL.config.inheritance.inverse_lookup \
156
- ? 0.upto(ns_places.size) \
157
- : ns_places.size.downto(0)
158
- range.each do |size|
159
- conditions.concat(ns_places.combination(size).to_a)
160
- end
161
-
162
- # Now iterate over
163
- while (condition = conditions.shift)
164
- ns_places.each{ |i| pieces[i] = condition.include?(i) ? '::' : '' }
165
-
166
- candidate = pieces.join
167
- candidate.prepend("#{scope.name}::") unless scope === Object
168
-
169
- klass = candidate.safe_constantize
170
- next if klass.nil?
171
-
172
- # Check if the class match the table name
173
- return klass if klass < ::ActiveRecord::Base && klass.table_name == table_name
174
- end
175
- end
176
-
177
126
  # Reload information about tables inheritance and dependencies, uses a
178
- # cache to not perform additional checkes
179
- def reload_inheritance_data!
127
+ # cache to not perform additional checks
128
+ def reload_inheritance_data!(connection)
180
129
  return if @inheritance_loaded
181
130
  @inheritance_dependencies = connection.inherited_tables
182
131
  @inheritance_associations = generate_associations
@@ -186,38 +135,15 @@ module Torque
186
135
  # Calculates the inverted dependency (association), where even indirect
187
136
  # inheritance comes up in the list
188
137
  def generate_associations
189
- return {} if @inheritance_dependencies.empty?
190
-
191
- result = Hash.new{ |h, k| h[k] = [] }
192
- masters = @inheritance_dependencies.values.flatten.uniq
193
-
194
- # Add direct associations
195
- masters.map do |master|
196
- @inheritance_dependencies.each do |(dependent, associations)|
197
- result[master] << dependent if associations.include?(master)
198
- end
199
- end
200
-
201
- # Add indirect associations
202
- result.each do |master, children|
203
- children.each do |child|
204
- children.concat(result[child]).uniq! if result.key?(child)
205
- end
206
- end
207
-
208
- # Remove the default proc that would create new entries
209
- result.default_proc = nil
210
- result
138
+ super(@inheritance_dependencies)
211
139
  end
212
140
 
213
- # Use this method to also load any irregular model name. This is smart
214
- # enought to only load the sources present on this instance
215
- def prepare_data_sources
216
- super
217
- @data_sources_model_names = Torque::PostgreSQL.config
218
- .irregular_models.slice(*@data_sources.keys).map do |table_name, model_name|
219
- [table_name, (model_name.is_a?(Class) ? model_name : model_name.constantize)]
220
- end.to_h
141
+ # Use this method to also load any irregular model name
142
+ def prepare_data_sources(connection = nil)
143
+ Torque::PostgreSQL::AR710 ? super : super()
144
+
145
+ sources = connection.present? ? tables_to_cache(connection) : @data_sources.keys
146
+ @data_sources_model_names = prepare_irregular_models(sources)
221
147
  end
222
148
 
223
149
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Torque
4
4
  module PostgreSQL
5
- VERSION = '3.2.2'
5
+ VERSION = '3.3.0'
6
6
  end
7
7
  end
@@ -4,8 +4,8 @@ RSpec.describe 'Schema' do
4
4
  let(:connection) { ActiveRecord::Base.connection }
5
5
 
6
6
  before do
7
- connection.instance_variable_set(:@schmeas_blacklist, nil)
8
- connection.instance_variable_set(:@schmeas_whitelist, nil)
7
+ connection.instance_variable_set(:@schemas_blacklist, nil)
8
+ connection.instance_variable_set(:@schemas_whitelist, nil)
9
9
  end
10
10
 
11
11
  context 'on migration' do
@@ -104,7 +104,12 @@ RSpec.describe 'TableInheritance' do
104
104
  end
105
105
 
106
106
  context 'on schema cache' do
107
- subject { ActiveRecord::Base.connection.schema_cache }
107
+ let(:schema_cache) { ActiveRecord::Base.connection.schema_cache }
108
+ let(:schema_cache_connection) { schema_cache.instance_variable_get(:@connection) }
109
+ let(:schema_cache_reflection) { schema_cache.instance_variable_get(:@schema_reflection) }
110
+ let(:new_schema_cache) { schema_cache_reflection.send(:cache, schema_cache_connection) }
111
+
112
+ subject { Torque::PostgreSQL::AR710 ? new_schema_cache : schema_cache }
108
113
 
109
114
  it 'correctly defines the associations' do
110
115
  scenario = {
@@ -131,6 +136,8 @@ RSpec.describe 'TableInheritance' do
131
136
  end
132
137
 
133
138
  context 'on looking up models' do
139
+ let(:prepare_arguments) { Torque::PostgreSQL::AR710 ? [schema_cache_connection] : nil }
140
+
134
141
  after(:all) do
135
142
  schema_cache = ActiveRecord::Base.connection.schema_cache
136
143
  schema_cache.instance_variable_set(:@data_sources, {})
@@ -139,13 +146,13 @@ RSpec.describe 'TableInheritance' do
139
146
 
140
147
  it 'respect irregular names' do
141
148
  Torque::PostgreSQL.config.irregular_models = {
142
- 'posts' => 'ActivityPost',
149
+ 'public.posts' => 'ActivityPost',
143
150
  }
144
151
 
145
- subject.send(:prepare_data_sources)
152
+ subject.send(:prepare_data_sources, *prepare_arguments)
146
153
  list = subject.instance_variable_get(:@data_sources_model_names)
147
- expect(list).to have_key('posts')
148
- expect(list['posts']).to eql(ActivityPost)
154
+ expect(list).to have_key('public.posts')
155
+ expect(list['public.posts']).to eql(ActivityPost)
149
156
  end
150
157
 
151
158
  it 'does not load irregular where the data source is not defined' do
@@ -153,7 +160,7 @@ RSpec.describe 'TableInheritance' do
153
160
  'products' => 'Product',
154
161
  }
155
162
 
156
- subject.send(:prepare_data_sources)
163
+ subject.send(:prepare_data_sources, *prepare_arguments)
157
164
  list = subject.instance_variable_get(:@data_sources_model_names)
158
165
  expect(list).to_not have_key('products')
159
166
  end
@@ -176,6 +183,8 @@ RSpec.describe 'TableInheritance' do
176
183
  let(:child2) { ActivityBook }
177
184
  let(:other) { AuthorJournalist }
178
185
 
186
+ before { ActiveRecord::Base.connection.schema_cache.clear! }
187
+
179
188
  it 'identifies mergeable attributes' do
180
189
  result_base = %w(id author_id title active kind created_at updated_at description url file post_id)
181
190
  expect(base.inheritance_mergeable_attributes.sort).to eql(result_base.sort)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: torque-postgresql
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.2
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Silva
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-20 00:00:00.000000000 Z
11
+ date: 2023-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -64,20 +64,14 @@ dependencies:
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '1.5'
68
- - - ">="
69
- - !ruby/object:Gem::Version
70
- version: 1.5.3
67
+ version: '2.0'
71
68
  type: :development
72
69
  prerelease: false
73
70
  version_requirements: !ruby/object:Gem::Requirement
74
71
  requirements:
75
72
  - - "~>"
76
73
  - !ruby/object:Gem::Version
77
- version: '1.5'
78
- - - ">="
79
- - !ruby/object:Gem::Version
80
- version: 1.5.3
74
+ version: '2.0'
81
75
  - !ruby/object:Gem::Dependency
82
76
  name: dotenv
83
77
  requirement: !ruby/object:Gem::Requirement
@@ -233,6 +227,9 @@ files:
233
227
  - lib/torque/postgresql/relation/inheritance.rb
234
228
  - lib/torque/postgresql/relation/merger.rb
235
229
  - lib/torque/postgresql/schema_cache.rb
230
+ - lib/torque/postgresql/schema_cache/bound_schema_reflection.rb
231
+ - lib/torque/postgresql/schema_cache/inheritance.rb
232
+ - lib/torque/postgresql/schema_cache/schema_reflection.rb
236
233
  - lib/torque/postgresql/table_name.rb
237
234
  - lib/torque/postgresql/version.rb
238
235
  - spec/en.yml
@@ -292,7 +289,7 @@ licenses:
292
289
  metadata:
293
290
  source_code_uri: https://github.com/crashtech/torque-postgresql
294
291
  bug_tracker_uri: https://github.com/crashtech/torque-postgresql/issues
295
- post_install_message:
292
+ post_install_message:
296
293
  rdoc_options:
297
294
  - "--title"
298
295
  - Torque PostgreSQL
@@ -310,7 +307,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
310
307
  version: 1.8.11
311
308
  requirements: []
312
309
  rubygems_version: 3.2.15
313
- signing_key:
310
+ signing_key:
314
311
  specification_version: 4
315
312
  summary: ActiveRecord extension to access PostgreSQL advanced resources
316
313
  test_files: