Skip to content

Add Rails 7.1 support #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ references:
matrix:
parameters:
ruby-version: ['2.7', '3.0', '3.1', '3.2']
bundle-version: ['Gemfile.rails-7.0']
bundle-version: ['Gemfile.rails-7.0', 'Gemfile.rails-7.1']

workflows:
commit:
jobs:
- <<: *matrix_build
commit:
jobs:
- <<: *matrix_build
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ To install torque-postgresql you need to add the following to your Gemfile:
```ruby
gem 'torque-postgresql', '~> 2.0' # For Rails >= 6.0 < 6.1
gem 'torque-postgresql', '~> 2.0.4' # For Rails >= 6.1
gem 'torque-postgresql', '~> 3.0' # For Rails >= 7.0
gem 'torque-postgresql', '~> 3.0' # For Rails >= 7.0 < 7.1
gem 'torque-postgresql', '~> 3.3' # For Rails >= 7.1
```

Also, run:
Expand Down
2 changes: 1 addition & 1 deletion gemfiles/Gemfile.rails-7.0
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
source 'https://rubygems.org'

gem 'rails', '~> 7.0'
gem 'rails', '~> 7.0', '< 7.1'
gem 'pg', '~> 1.4.0'
gem "byebug"

Expand Down
7 changes: 7 additions & 0 deletions gemfiles/Gemfile.rails-7.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
source 'https://rubygems.org'

gem 'rails', '~> 7.1'
gem 'pg', '~> 1.4.0'
gem "byebug"

gemspec path: "../"
6 changes: 3 additions & 3 deletions lib/torque/postgresql/adapter/database_statements.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,11 @@ def user_defined_schemas
# Build the query for allowed schemas
def user_defined_schemas_sql
conditions = []
conditions << <<-SQL if schemas_blacklist.any?
nspname NOT LIKE ANY (ARRAY['#{schemas_blacklist.join("', '")}'])
conditions << <<-SQL.squish if schemas_blacklist.any?
nspname NOT LIKE ALL (ARRAY['#{schemas_blacklist.join("', '")}'])
SQL

conditions << <<-SQL if schemas_whitelist.any?
conditions << <<-SQL.squish if schemas_whitelist.any?
nspname LIKE ANY (ARRAY['#{schemas_whitelist.join("', '")}'])
SQL

Expand Down
2 changes: 1 addition & 1 deletion lib/torque/postgresql/adapter/schema_creation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def visit_TableDefinition(o)
statements.concat(o.indexes.map { |c, o| index_in_create(o.name, c, o) })
end

if supports_foreign_keys?
if @conn.supports_foreign_keys?
statements.concat(o.foreign_keys.map { |fk| accept fk })
end

Expand Down
6 changes: 6 additions & 0 deletions lib/torque/postgresql/adapter/schema_statements.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ def data_source_sql(name = nil, type: nil)
super.sub('SELECT c.relname FROM', "SELECT n.nspname || '.' || c.relname FROM")
end

# Add schema and inherits as one of the valid options for table
# definition
def valid_table_definition_options
super + [:schema, :inherits]
end

private

# Remove the schema from the sequence name
Expand Down
7 changes: 5 additions & 2 deletions lib/torque/postgresql/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ module Torque
module PostgreSQL
include ActiveSupport::Configurable

# Stores a version check for compatibility purposes
AR710 = (ActiveRecord.gem_version >= Gem::Version.new('7.1.0'))

# Use the same logger as the Active Record one
def self.logger
ActiveRecord::Base.logger
Expand All @@ -17,8 +20,8 @@ def self.logger
send("#{name}=", klass)
end

# Set if any information that requires querying and searching or collectiong
# information shuld be eager loaded. This automatically changes when rails
# Set if any information that requires querying and searching or collecting
# information should be eager loaded. This automatically changes when rails
# same configuration is set to true
config.eager_load = false

Expand Down
2 changes: 1 addition & 1 deletion lib/torque/postgresql/reflection/association_reflection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def initialize(name, scope, options, active_record)
private

# Check if the foreign key should be pluralized
def derive_foreign_key
def derive_foreign_key(*, **)
result = super
result = ActiveSupport::Inflector.pluralize(result) \
if collection? && connected_through_array?
Expand Down
130 changes: 28 additions & 102 deletions lib/torque/postgresql/schema_cache.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
# frozen_string_literal: true

require 'torque/postgresql/schema_cache/inheritance'

if Torque::PostgreSQL::AR710
require 'torque/postgresql/schema_cache/schema_reflection'
require 'torque/postgresql/schema_cache/bound_schema_reflection'
end

module Torque
module PostgreSQL
LookupError = Class.new(ArgumentError)

# :TODO: Create the +add+ to load inheritance info
module SchemaCache
include Torque::PostgreSQL::SchemaCache::Inheritance

def initialize(*) # :nodoc:
super
Expand Down Expand Up @@ -37,7 +45,7 @@ def init_with(coder) # :nodoc:
@inheritance_associations = coder['inheritance_associations']
end

def add(table_name, *) # :nodoc:
def add(connection_or_table_name, table_name = connection_or_table_name, *) # :nodoc:
super

# Reset inheritance information when a table is added
Expand All @@ -64,8 +72,8 @@ def size # :nodoc:
].map(&:size).inject(:+)
end

def clear_data_source_cache!(name) # :nodoc:
super
def clear_data_source_cache!(connection_or_name, name = connection_or_name) # :nodoc:
Torque::PostgreSQL::AR710 ? super : super(name)
@data_sources_model_names.delete name
@inheritance_dependencies.delete name
@inheritance_associations.delete name
Expand Down Expand Up @@ -95,88 +103,29 @@ def add_model_name(table_name, model)
end

# Get all the tables that the given one inherits from
def dependencies(table_name)
reload_inheritance_data!
def dependencies(conn, table_name = conn)
reload_inheritance_data!(conn == table_name ? connection : conn)
@inheritance_dependencies[table_name]
end

# Get the list of all tables that are associated (direct or indirect
# inheritance) with the provided one
def associations(table_name)
reload_inheritance_data!
def associations(conn, table_name = conn)
reload_inheritance_data!(conn == table_name ? connection : conn)
@inheritance_associations[table_name]
end

# Try to find a model based on a given table
def lookup_model(table_name, scoped_class = '')
scoped_class = scoped_class.name if scoped_class.is_a?(Class)
return @data_sources_model_names[table_name] \
if @data_sources_model_names.key?(table_name)

# Get all the possible scopes
scopes = scoped_class.scan(/(?:::)?[A-Z][a-z]+/)
scopes.unshift('Object::')

# Check if the table name comes with a schema
if table_name.include?('.')
schema, table_name = table_name.split('.')
scopes.insert(1, schema.camelize) if schema != 'public'
end

# Consider the maximum namespaced possible model name
max_name = table_name.tr('_', '/').camelize.split(/(::)/)
max_name[-1] = max_name[-1].singularize

# Test all the possible names against all the possible scopes
until scopes.size == 0
scope = scopes.join.chomp('::').safe_constantize
model = find_model(max_name, table_name, scope) unless scope.nil?
return @data_sources_model_names[table_name] = model unless model.nil?
scopes.pop
end

# If this part is reach, no model name was found
raise LookupError.new(<<~MSG.squish)
Unable to find a valid model that is associated with the '#{table_name}' table.
Please, check if they correctly inherit from ActiveRecord::Base
MSG
# Override the inheritance implementation to pass over the proper cache of
# the existing association between data sources and model names
def lookup_model(*args, **xargs)
super(*args, **xargs, source_to_model: @data_sources_model_names)
end

private

# Find a model by a given max namespaced class name thath matches the
# given table name
def find_model(max_name, table_name, scope = Object)
pieces = max_name.is_a?(::Array) ? max_name : max_name.split(/(::)/)
ns_places = (1..(max_name.size - 1)).step(2).to_a

# Generate all possible combinarions
conditions = []
range = Torque::PostgreSQL.config.inheritance.inverse_lookup \
? 0.upto(ns_places.size) \
: ns_places.size.downto(0)
range.each do |size|
conditions.concat(ns_places.combination(size).to_a)
end

# Now iterate over
while (condition = conditions.shift)
ns_places.each{ |i| pieces[i] = condition.include?(i) ? '::' : '' }

candidate = pieces.join
candidate.prepend("#{scope.name}::") unless scope === Object

klass = candidate.safe_constantize
next if klass.nil?

# Check if the class match the table name
return klass if klass < ::ActiveRecord::Base && klass.table_name == table_name
end
end

# Reload information about tables inheritance and dependencies, uses a
# cache to not perform additional checkes
def reload_inheritance_data!
# cache to not perform additional checks
def reload_inheritance_data!(connection)
return if @inheritance_loaded
@inheritance_dependencies = connection.inherited_tables
@inheritance_associations = generate_associations
Expand All @@ -186,38 +135,15 @@ def reload_inheritance_data!
# Calculates the inverted dependency (association), where even indirect
# inheritance comes up in the list
def generate_associations
return {} if @inheritance_dependencies.empty?

result = Hash.new{ |h, k| h[k] = [] }
masters = @inheritance_dependencies.values.flatten.uniq

# Add direct associations
masters.map do |master|
@inheritance_dependencies.each do |(dependent, associations)|
result[master] << dependent if associations.include?(master)
end
end

# Add indirect associations
result.each do |master, children|
children.each do |child|
children.concat(result[child]).uniq! if result.key?(child)
end
end

# Remove the default proc that would create new entries
result.default_proc = nil
result
super(@inheritance_dependencies)
end

# Use this method to also load any irregular model name. This is smart
# enought to only load the sources present on this instance
def prepare_data_sources
super
@data_sources_model_names = Torque::PostgreSQL.config
.irregular_models.slice(*@data_sources.keys).map do |table_name, model_name|
[table_name, (model_name.is_a?(Class) ? model_name : model_name.constantize)]
end.to_h
# Use this method to also load any irregular model name
def prepare_data_sources(connection = nil)
Torque::PostgreSQL::AR710 ? super : super()

sources = connection.present? ? tables_to_cache(connection) : @data_sources.keys
@data_sources_model_names = prepare_irregular_models(sources)
end

end
Expand Down
25 changes: 25 additions & 0 deletions lib/torque/postgresql/schema_cache/bound_schema_reflection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Torque
module PostgreSQL
module BoundSchemaReflection
def add_model_name(table_name, model)
@schema_reflection.add_model_name(@connection, table_name, model)
end

def dependencies(table_name)
@schema_reflection.dependencies(@connection, table_name)
end

def associations(table_name)
@schema_reflection.associations(@connection, table_name)
end

def lookup_model(table_name, scoped_class = '')
@schema_reflection.lookup_model(@connection, table_name, scoped_class)
end
end

ActiveRecord::ConnectionAdapters::BoundSchemaReflection.prepend BoundSchemaReflection
end
end
Loading