Skip to content

Commit

Permalink
Add Rails 7.1 support (#86)
Browse files Browse the repository at this point in the history
* Add Rails 7.1 support

* Small code simplification
  • Loading branch information
Carlos authored Nov 18, 2023
1 parent 356a190 commit bdeb93a
Show file tree
Hide file tree
Showing 17 changed files with 244 additions and 125 deletions.
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

0 comments on commit bdeb93a

Please sign in to comment.