torque-postgresql 2.2.3 → 2.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 +4 -4
- data/README.rdoc +17 -0
- data/lib/torque/postgresql/adapter/database_statements.rb +61 -7
- data/lib/torque/postgresql/adapter/schema_definitions.rb +5 -5
- data/lib/torque/postgresql/adapter/schema_dumper.rb +25 -7
- data/lib/torque/postgresql/adapter/schema_statements.rb +40 -0
- data/lib/torque/postgresql/adapter.rb +2 -2
- data/lib/torque/postgresql/base.rb +23 -13
- data/lib/torque/postgresql/config.rb +13 -0
- data/lib/torque/postgresql/migration/command_recorder.rb +12 -2
- data/lib/torque/postgresql/reflection/abstract_reflection.rb +13 -39
- data/lib/torque/postgresql/schema_cache.rb +6 -0
- data/lib/torque/postgresql/table_name.rb +41 -0
- data/lib/torque/postgresql/version.rb +1 -1
- data/lib/torque/postgresql.rb +2 -1
- data/lib/torque/range.rb +2 -0
- data/spec/models/internal/user.rb +5 -0
- data/spec/schema.rb +21 -10
- data/spec/spec_helper.rb +2 -5
- data/spec/tests/arel_spec.rb +16 -5
- data/spec/tests/belongs_to_many_spec.rb +49 -0
- data/spec/tests/enum_set_spec.rb +10 -9
- data/spec/tests/enum_spec.rb +9 -3
- data/spec/tests/has_many_spec.rb +46 -0
- data/spec/tests/schema_spec.rb +92 -0
- data/spec/tests/table_inheritance_spec.rb +11 -15
- metadata +20 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c2a382953b6a3c1d87e05837454cdf523f4be237fdd66c85f0b155278eafa203
|
|
4
|
+
data.tar.gz: e72effb133a0ae945b85b1d46de72b16a098eb1d469f3720926b0267e7f3c11b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 554eb1ae3c30abd27acbbd9ce7e2f78d37e42c730ed811b5d617209cbb12a2c1bd34163f63ab25273f542e1cb6bfc36ee6d64484fd66f8a5b489e4aaea6ae628
|
|
7
|
+
data.tar.gz: d018ee26970f64a83e552f1929e55e281235054064771a851e5b4fc714e69a95e6247e95338deffe8b1732d6ae2dad1bc2b5602146a1c4fb4d763ad19fc13c97
|
data/README.rdoc
CHANGED
|
@@ -128,6 +128,23 @@ reconfigured on the model, and then can be used during querying process.
|
|
|
128
128
|
|
|
129
129
|
{Learn more}[link:classes/Torque/PostgreSQL/AuxiliaryStatement.html]
|
|
130
130
|
|
|
131
|
+
* Multiple Schemas
|
|
132
|
+
|
|
133
|
+
Allows models and modules to have a schema associated with them, so that
|
|
134
|
+
developers can better organize their tables into schemas and build features in
|
|
135
|
+
a way that the database can better represent how they are separated.
|
|
136
|
+
|
|
137
|
+
create_schema "internal", force: :cascade
|
|
138
|
+
|
|
139
|
+
module Internal
|
|
140
|
+
class User < ActiveRecord::Base
|
|
141
|
+
self.schema = 'internal'
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
Internal::User.all
|
|
146
|
+
|
|
147
|
+
{Learn more}[link:classes/Torque/PostgreSQL/Adapter/DatabaseStatements.html]
|
|
131
148
|
|
|
132
149
|
== Download and installation
|
|
133
150
|
|
|
@@ -12,6 +12,26 @@ module Torque
|
|
|
12
12
|
@_dump_mode = !!!@_dump_mode
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
# List of schemas blocked by the application in the current connection
|
|
16
|
+
def schemas_blacklist
|
|
17
|
+
@schemas_blacklist ||= Torque::PostgreSQL.config.schemas.blacklist +
|
|
18
|
+
(@config.dig(:schemas, 'blacklist') || [])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# List of schemas used by the application in the current connection
|
|
22
|
+
def schemas_whitelist
|
|
23
|
+
@schemas_whitelist ||= Torque::PostgreSQL.config.schemas.whitelist +
|
|
24
|
+
(@config.dig(:schemas, 'whitelist') || [])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# A list of schemas on the search path sanitized
|
|
28
|
+
def schemas_search_path_sanitized
|
|
29
|
+
@schemas_search_path_sanitized ||= begin
|
|
30
|
+
db_user = @config[:username] || ENV['USER'] || ENV['USERNAME']
|
|
31
|
+
schema_search_path.split(',').map { |item| item.strip.sub('"$user"', db_user) }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
15
35
|
# Check if a given type is valid.
|
|
16
36
|
def valid_type?(type)
|
|
17
37
|
super || extended_types.include?(type)
|
|
@@ -22,6 +42,17 @@ module Torque
|
|
|
22
42
|
EXTENDED_DATABASE_TYPES
|
|
23
43
|
end
|
|
24
44
|
|
|
45
|
+
# Checks if a given schema exists in the database. If +filtered+ is
|
|
46
|
+
# given as false, then it will check regardless of whitelist and
|
|
47
|
+
# blacklist
|
|
48
|
+
def schema_exists?(name, filtered: true)
|
|
49
|
+
return user_defined_schemas.include?(name.to_s) if filtered
|
|
50
|
+
|
|
51
|
+
query_value(<<-SQL) == 1
|
|
52
|
+
SELECT 1 FROM pg_catalog.pg_namespace WHERE nspname = '#{name}'
|
|
53
|
+
SQL
|
|
54
|
+
end
|
|
55
|
+
|
|
25
56
|
# Returns true if type exists.
|
|
26
57
|
def type_exists?(name)
|
|
27
58
|
user_defined_types.key? name.to_s
|
|
@@ -115,18 +146,41 @@ module Torque
|
|
|
115
146
|
# Get the list of inherited tables associated with their parent tables
|
|
116
147
|
def inherited_tables
|
|
117
148
|
tables = query(<<-SQL, 'SCHEMA')
|
|
118
|
-
SELECT
|
|
119
|
-
|
|
149
|
+
SELECT inhrelid::regclass AS table_name,
|
|
150
|
+
inhparent::regclass AS inheritances
|
|
120
151
|
FROM pg_inherits
|
|
121
152
|
JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
|
|
122
153
|
JOIN pg_class child ON pg_inherits.inhrelid = child.oid
|
|
123
|
-
|
|
124
|
-
ORDER BY pg_inherits.inhrelid
|
|
154
|
+
ORDER BY inhrelid
|
|
125
155
|
SQL
|
|
126
156
|
|
|
127
|
-
tables.
|
|
128
|
-
[
|
|
129
|
-
end
|
|
157
|
+
tables.each_with_object({}) do |(child, parent), result|
|
|
158
|
+
(result[child] ||= []) << parent
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Get the list of schemas that were created by the user
|
|
163
|
+
def user_defined_schemas
|
|
164
|
+
query_values(user_defined_schemas_sql, 'SCHEMA')
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Build the query for allowed schemas
|
|
168
|
+
def user_defined_schemas_sql
|
|
169
|
+
conditions = []
|
|
170
|
+
conditions << <<-SQL if schemas_blacklist.any?
|
|
171
|
+
nspname NOT LIKE ANY (ARRAY['#{schemas_blacklist.join("', '")}'])
|
|
172
|
+
SQL
|
|
173
|
+
|
|
174
|
+
conditions << <<-SQL if schemas_whitelist.any?
|
|
175
|
+
nspname LIKE ANY (ARRAY['#{schemas_whitelist.join("', '")}'])
|
|
176
|
+
SQL
|
|
177
|
+
|
|
178
|
+
<<-SQL.squish
|
|
179
|
+
SELECT nspname
|
|
180
|
+
FROM pg_catalog.pg_namespace
|
|
181
|
+
WHERE 1=1 AND #{conditions.join(' AND ')}
|
|
182
|
+
ORDER BY oid
|
|
183
|
+
SQL
|
|
130
184
|
end
|
|
131
185
|
|
|
132
186
|
# Get the list of columns, and their definition, but only from the
|
|
@@ -12,16 +12,16 @@ module Torque
|
|
|
12
12
|
args.each { |name| column(name, :interval, **options) }
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
# Creates a column with an enum type, needing to specify the
|
|
15
|
+
# Creates a column with an enum type, needing to specify the enum_type,
|
|
16
16
|
# which is basically the name of the type defined prior creating the
|
|
17
17
|
# column
|
|
18
18
|
def enum(*args, **options)
|
|
19
|
-
|
|
20
|
-
args.each { |name| column(name, (
|
|
19
|
+
enum_type = [options.delete(:subtype), options.delete(:enum_type)].compact.first
|
|
20
|
+
args.each { |name| column(name, (enum_type || name), **options) }
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# Creates a column with an enum array type, needing to specify the
|
|
24
|
-
#
|
|
24
|
+
# enum_type, which is basically the name of the type defined prior
|
|
25
25
|
# creating the column
|
|
26
26
|
def enum_set(*args, **options)
|
|
27
27
|
super(*args, **options.merge(array: true))
|
|
@@ -47,7 +47,7 @@ module Torque
|
|
|
47
47
|
|
|
48
48
|
if ActiveRecord::ConnectionAdapters::PostgreSQL.const_defined?('ColumnDefinition')
|
|
49
49
|
module ColumnDefinition
|
|
50
|
-
attr_accessor :subtype
|
|
50
|
+
attr_accessor :subtype, :enum_type
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnDefinition.include ColumnDefinition
|
|
@@ -14,6 +14,7 @@ module Torque
|
|
|
14
14
|
|
|
15
15
|
def extensions(stream) # :nodoc:
|
|
16
16
|
super
|
|
17
|
+
user_defined_schemas(stream)
|
|
17
18
|
user_defined_types(stream)
|
|
18
19
|
end
|
|
19
20
|
|
|
@@ -22,12 +23,12 @@ module Torque
|
|
|
22
23
|
column.type == :enum_set ? :enum : super
|
|
23
24
|
end
|
|
24
25
|
|
|
25
|
-
# Adds +:
|
|
26
|
+
# Adds +:enum_type+ option to the default set
|
|
26
27
|
def prepare_column_options(column)
|
|
27
28
|
spec = super
|
|
28
29
|
|
|
29
|
-
if
|
|
30
|
-
spec[:
|
|
30
|
+
if enum_type = schema_enum_type(column)
|
|
31
|
+
spec[:enum_type] = enum_type
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
spec
|
|
@@ -35,13 +36,15 @@ module Torque
|
|
|
35
36
|
|
|
36
37
|
private
|
|
37
38
|
|
|
38
|
-
def
|
|
39
|
+
def schema_enum_type(column)
|
|
39
40
|
column.sql_type.to_sym.inspect if column.type == :enum || column.type == :enum_set
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
def tables(stream) # :nodoc:
|
|
43
44
|
inherited_tables = @connection.inherited_tables
|
|
44
|
-
sorted_tables = @connection.tables
|
|
45
|
+
sorted_tables = (@connection.tables - @connection.views).sort_by do |table_name|
|
|
46
|
+
table_name.split(/(?:public)?\./).reverse
|
|
47
|
+
end
|
|
45
48
|
|
|
46
49
|
stream.puts " # These are the common tables"
|
|
47
50
|
(sorted_tables - inherited_tables.keys).each do |table_name|
|
|
@@ -58,7 +61,7 @@ module Torque
|
|
|
58
61
|
|
|
59
62
|
# Add the inherits setting
|
|
60
63
|
sub_stream.rewind
|
|
61
|
-
inherits.map!(
|
|
64
|
+
inherits.map! { |parent| parent.to_s.sub(/\Apublic\./, '') }
|
|
62
65
|
inherits = inherits.first if inherits.size === 1
|
|
63
66
|
inherits = ", inherits: #{inherits.inspect} do |t|"
|
|
64
67
|
table_dump = sub_stream.read.gsub(/ do \|t\|$/, inherits)
|
|
@@ -84,12 +87,27 @@ module Torque
|
|
|
84
87
|
triggers(stream) if defined?(::Fx::SchemaDumper::Trigger)
|
|
85
88
|
end
|
|
86
89
|
|
|
90
|
+
# Make sure to remove the schema from the table name
|
|
91
|
+
def remove_prefix_and_suffix(table)
|
|
92
|
+
super(table.sub(/\A[a-z0-9_]*\./, ''))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Dump user defined schemas
|
|
96
|
+
def user_defined_schemas(stream)
|
|
97
|
+
return if (list = (@connection.user_defined_schemas - ['public'])).empty?
|
|
98
|
+
|
|
99
|
+
stream.puts " # Custom schemas defined in this database."
|
|
100
|
+
list.each { |name| stream.puts " create_schema \"#{name}\", force: :cascade" }
|
|
101
|
+
stream.puts
|
|
102
|
+
end
|
|
103
|
+
|
|
87
104
|
# Dump user defined types like enum
|
|
88
105
|
def user_defined_types(stream)
|
|
89
106
|
types = @connection.user_defined_types('e')
|
|
90
107
|
return unless types.any?
|
|
91
108
|
|
|
92
|
-
stream.puts " #
|
|
109
|
+
stream.puts " # Custom types defined in this database."
|
|
110
|
+
stream.puts " # Note that some types may not work with other database engines. Be careful if changing database."
|
|
93
111
|
types.sort_by(&:first).each { |(name, type)| send(type.to_sym, name, stream) }
|
|
94
112
|
stream.puts
|
|
95
113
|
rescue => e
|
|
@@ -7,6 +7,21 @@ module Torque
|
|
|
7
7
|
|
|
8
8
|
TableDefinition = ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition
|
|
9
9
|
|
|
10
|
+
# Create a new schema
|
|
11
|
+
def create_schema(name, options = {})
|
|
12
|
+
drop_schema(name, options) if options[:force]
|
|
13
|
+
|
|
14
|
+
check = 'IF NOT EXISTS' if options.fetch(:check, true)
|
|
15
|
+
execute("CREATE SCHEMA #{check} #{quote_schema_name(name.to_s)}")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Drop an existing schema
|
|
19
|
+
def drop_schema(name, options = {})
|
|
20
|
+
force = options.fetch(:force, '').upcase
|
|
21
|
+
check = 'IF EXISTS' if options.fetch(:check, true)
|
|
22
|
+
execute("DROP SCHEMA #{check} #{quote_schema_name(name.to_s)} #{force}")
|
|
23
|
+
end
|
|
24
|
+
|
|
10
25
|
# Drops a type.
|
|
11
26
|
def drop_type(name, options = {})
|
|
12
27
|
force = options.fetch(:force, '').upcase
|
|
@@ -79,12 +94,37 @@ module Torque
|
|
|
79
94
|
|
|
80
95
|
# Rewrite the method that creates tables to easily accept extra options
|
|
81
96
|
def create_table(table_name, **options, &block)
|
|
97
|
+
table_name = "#{options[:schema]}.#{table_name}" if options[:schema].present?
|
|
98
|
+
|
|
82
99
|
options[:id] = false if options[:inherits].present? &&
|
|
83
100
|
options[:primary_key].blank? && options[:id].blank?
|
|
84
101
|
|
|
85
102
|
super table_name, **options, &block
|
|
86
103
|
end
|
|
87
104
|
|
|
105
|
+
# Add the schema option when extracting table options
|
|
106
|
+
def table_options(table_name)
|
|
107
|
+
parts = table_name.split('.').reverse
|
|
108
|
+
return super unless parts.size == 2 && parts[1] != 'public'
|
|
109
|
+
|
|
110
|
+
(super || {}).merge(schema: parts[1])
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# When dumping the schema we need to add all schemas, not only those
|
|
114
|
+
# active for the current +schema_search_path+
|
|
115
|
+
def quoted_scope(name = nil, type: nil)
|
|
116
|
+
return super unless name.nil?
|
|
117
|
+
|
|
118
|
+
super.merge(schema: "ANY ('{#{user_defined_schemas.join(',')}}')")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Fix the query to include the schema on tables names when dumping
|
|
122
|
+
def data_source_sql(name = nil, type: nil)
|
|
123
|
+
return super unless name.nil?
|
|
124
|
+
|
|
125
|
+
super.sub('SELECT c.relname FROM', "SELECT n.nspname || '.' || c.relname FROM")
|
|
126
|
+
end
|
|
127
|
+
|
|
88
128
|
private
|
|
89
129
|
|
|
90
130
|
def quote_enum_values(name, values, options)
|
|
@@ -31,9 +31,9 @@ module Torque
|
|
|
31
31
|
)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
# Add `inherits` to the list of extracted table options
|
|
34
|
+
# Add `inherits` and `schema` to the list of extracted table options
|
|
35
35
|
def extract_table_options!(options)
|
|
36
|
-
super.merge(options.extract!(:inherits))
|
|
36
|
+
super.merge(options.extract!(:inherits, :schema))
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
# Allow filtered bulk insert by adding the where clause. This method is
|
|
@@ -5,15 +5,27 @@ module Torque
|
|
|
5
5
|
module Base
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
|
+
##
|
|
9
|
+
# :singleton-method: schema
|
|
10
|
+
# :call-seq: schema
|
|
11
|
+
#
|
|
12
|
+
# The schema to which the table belongs to.
|
|
13
|
+
|
|
8
14
|
included do
|
|
9
15
|
mattr_accessor :belongs_to_many_required_by_default, instance_accessor: false
|
|
16
|
+
class_attribute :schema, instance_writer: false
|
|
10
17
|
end
|
|
11
18
|
|
|
12
19
|
module ClassMethods
|
|
13
20
|
delegate :distinct_on, :with, :itself_only, :cast_records, to: :all
|
|
14
21
|
|
|
15
|
-
#
|
|
16
|
-
|
|
22
|
+
# Make sure that table name is an instance of TableName class
|
|
23
|
+
def reset_table_name
|
|
24
|
+
self.table_name = TableName.new(self, super)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Whenever the base model is inherited, add a list of auxiliary
|
|
28
|
+
# statements like the one that loads inherited records' relname
|
|
17
29
|
def inherited(subclass)
|
|
18
30
|
super
|
|
19
31
|
|
|
@@ -24,6 +36,11 @@ module Torque
|
|
|
24
36
|
|
|
25
37
|
# Define helper methods to return the class of the given records
|
|
26
38
|
subclass.auxiliary_statement record_class do |cte|
|
|
39
|
+
ActiveSupport::Deprecation.warn(<<~MSG.squish)
|
|
40
|
+
Inheritance does not use this auxiliary statement and it can be removed.
|
|
41
|
+
You can replace it with `model.select_extra_values << 'tableoid::regclass'`.
|
|
42
|
+
MSG
|
|
43
|
+
|
|
27
44
|
pg_class = ::Arel::Table.new('pg_class')
|
|
28
45
|
arel_query = ::Arel::SelectManager.new(pg_class)
|
|
29
46
|
arel_query.project(pg_class['oid'], pg_class['relname'].as(record_class.to_s))
|
|
@@ -36,18 +53,11 @@ module Torque
|
|
|
36
53
|
# Define the dynamic attribute that returns the same information as
|
|
37
54
|
# the one provided by the auxiliary statement
|
|
38
55
|
subclass.dynamic_attribute(record_class) do
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
pg_class = ::Arel::Table.new('pg_class')
|
|
42
|
-
source = ::Arel::Table.new(subclass.table_name, as: 'source')
|
|
43
|
-
quoted_id = ::Arel::Nodes::Quoted.new(id)
|
|
44
|
-
|
|
45
|
-
query = ::Arel::SelectManager.new(pg_class)
|
|
46
|
-
query.join(source).on(pg_class['oid'].eq(source['tableoid']))
|
|
47
|
-
query.where(source[subclass.primary_key].eq(quoted_id))
|
|
48
|
-
query.project(pg_class['relname'])
|
|
56
|
+
klass = self.class
|
|
57
|
+
next klass.table_name unless klass.physically_inheritances?
|
|
49
58
|
|
|
50
|
-
|
|
59
|
+
query = klass.unscoped.where(subclass.primary_key => id)
|
|
60
|
+
query.pluck(klass.arel_table['tableoid'].cast('regclass')).first
|
|
51
61
|
end
|
|
52
62
|
end
|
|
53
63
|
|
|
@@ -40,6 +40,19 @@ module Torque
|
|
|
40
40
|
end.to_h
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
# Configure multiple schemas
|
|
44
|
+
config.nested(:schemas) do |schemas|
|
|
45
|
+
|
|
46
|
+
# Defines a list of LIKE-based schemas to not consider for a multiple
|
|
47
|
+
# schema database
|
|
48
|
+
schemas.blacklist = %w[information_schema pg_%]
|
|
49
|
+
|
|
50
|
+
# Defines a list of LIKE-based schemas to consider for a multiple schema
|
|
51
|
+
# database
|
|
52
|
+
schemas.whitelist = %w[public]
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
|
|
43
56
|
# Configure associations features
|
|
44
57
|
config.nested(:associations) do |assoc|
|
|
45
58
|
|
|
@@ -5,16 +5,26 @@ 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
|
|
13
|
+
# Inverts the type rename operation
|
|
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
|
+
|
|
18
28
|
# Records the creation of the enum to be reverted.
|
|
19
29
|
def create_enum(*args, &block)
|
|
20
30
|
record(:create_enum, args, &block)
|
|
@@ -6,9 +6,6 @@ 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
|
-
|
|
12
9
|
# Check if the foreign key actually exists
|
|
13
10
|
def connected_through_array?
|
|
14
11
|
false
|
|
@@ -40,34 +37,29 @@ module Torque
|
|
|
40
37
|
result
|
|
41
38
|
end
|
|
42
39
|
|
|
43
|
-
# Build the id constraint checking if both types are perfect matching
|
|
40
|
+
# Build the id constraint checking if both types are perfect matching.
|
|
41
|
+
# The klass attribute (left side) will always be a column attribute
|
|
44
42
|
def build_id_constraint(klass_attr, source_attr)
|
|
45
43
|
return klass_attr.eq(source_attr) unless connected_through_array?
|
|
46
44
|
|
|
47
45
|
# Klass and key are associated with the reflection Class
|
|
48
46
|
klass_type = klass.columns_hash[join_keys.key.to_s]
|
|
49
|
-
# active_record and foreign_key are associated with the source Class
|
|
50
|
-
source_type = active_record.columns_hash[join_keys.foreign_key.to_s]
|
|
51
47
|
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
56
55
|
|
|
57
56
|
# If the left side is not an array, just use the IN condition
|
|
58
57
|
return klass_attr.in(source_attr) unless klass_type.try(:array)
|
|
59
58
|
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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)
|
|
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))
|
|
71
63
|
end
|
|
72
64
|
|
|
73
65
|
if PostgreSQL::AR610
|
|
@@ -85,24 +77,6 @@ module Torque
|
|
|
85
77
|
|
|
86
78
|
build_id_constraint(klass_attr, source_attr)
|
|
87
79
|
end
|
|
88
|
-
|
|
89
|
-
# Prepare a value for an array constraint overlap condition
|
|
90
|
-
def cast_constraint_to_array(type, value, should_cast)
|
|
91
|
-
base_ready = type.try(:array) && value.is_a?(AREL_ATTR)
|
|
92
|
-
return value if base_ready && (type.sql_type.eql?(ARR_NO_CAST) || !should_cast)
|
|
93
|
-
|
|
94
|
-
value = ::Arel::Nodes.build_quoted(Array.wrap(value)) unless base_ready
|
|
95
|
-
value = value.cast(ARR_CAST) if should_cast
|
|
96
|
-
value
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Check if it's possible to turn both attributes into an ANY condition
|
|
100
|
-
def arel_array_to_any(klass_attr, source_attr, klass_type, source_type)
|
|
101
|
-
return unless !klass_type.try(:array) && source_type.try(:array) &&
|
|
102
|
-
klass_attr.is_a?(AREL_ATTR) && source_attr.is_a?(AREL_ATTR)
|
|
103
|
-
|
|
104
|
-
::Arel::Nodes::NamedFunction.new('ANY', [source_attr])
|
|
105
|
-
end
|
|
106
80
|
end
|
|
107
81
|
|
|
108
82
|
::ActiveRecord::Reflection::AbstractReflection.prepend(AbstractReflection)
|
|
@@ -117,6 +117,12 @@ module Torque
|
|
|
117
117
|
scopes = scoped_class.scan(/(?:::)?[A-Z][a-z]+/)
|
|
118
118
|
scopes.unshift('Object::')
|
|
119
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
|
+
|
|
120
126
|
# Consider the maximum namespaced possible model name
|
|
121
127
|
max_name = table_name.tr('_', '/').camelize.split(/(::)/)
|
|
122
128
|
max_name[-1] = max_name[-1].singularize
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Torque
|
|
4
|
+
module PostgreSQL
|
|
5
|
+
class TableName < Delegator
|
|
6
|
+
def initialize(klass, table_name)
|
|
7
|
+
@klass = klass
|
|
8
|
+
@table_name = table_name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def schema
|
|
12
|
+
return @schema if defined?(@schema)
|
|
13
|
+
|
|
14
|
+
@schema = ([@klass] + @klass.module_parents[0..-2]).find do |klass|
|
|
15
|
+
next unless klass.respond_to?(:schema)
|
|
16
|
+
break klass.schema
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_s
|
|
21
|
+
schema.nil? ? @table_name : "#{schema}.#{@table_name}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
alias __getobj__ to_s
|
|
25
|
+
|
|
26
|
+
def ==(other)
|
|
27
|
+
other.to_s =~ /("?#{schema | search_path_schemes.join('|')}"?\.)?"?#{@table_name}"?/
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def __setobj__(value)
|
|
31
|
+
@table_name = value
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def search_path_schemes
|
|
37
|
+
klass.connection.schemas_search_path_sanitized
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/torque/postgresql.rb
CHANGED
|
@@ -20,12 +20,13 @@ 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'
|
|
24
23
|
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'
|
|
30
31
|
|
|
31
32
|
require 'torque/postgresql/railtie' if defined?(Rails)
|
data/lib/torque/range.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module Torque
|
|
2
2
|
module Range
|
|
3
3
|
def intersection(other)
|
|
4
|
+
ActiveSupport::Deprecation.warn('Range extensions will be removed in future versions')
|
|
4
5
|
raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range)
|
|
5
6
|
|
|
6
7
|
new_min = self.cover?(other.min) ? other.min : other.cover?(min) ? min : nil
|
|
@@ -11,6 +12,7 @@ module Torque
|
|
|
11
12
|
alias_method :&, :intersection
|
|
12
13
|
|
|
13
14
|
def union(other)
|
|
15
|
+
ActiveSupport::Deprecation.warn('Range extensions will be removed in future versions')
|
|
14
16
|
raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range)
|
|
15
17
|
|
|
16
18
|
([min, other.min].min)..([max, other.max].max)
|
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 =
|
|
13
|
+
version = 1
|
|
14
14
|
|
|
15
15
|
return if ActiveRecord::Migrator.current_version == version
|
|
16
16
|
ActiveRecord::Schema.define(version: version) do
|
|
@@ -20,7 +20,11 @@ ActiveRecord::Schema.define(version: version) do
|
|
|
20
20
|
enable_extension "pgcrypto"
|
|
21
21
|
enable_extension "plpgsql"
|
|
22
22
|
|
|
23
|
-
#
|
|
23
|
+
# Custom schemas used in this database.
|
|
24
|
+
create_schema "internal", force: :cascade
|
|
25
|
+
|
|
26
|
+
# Custom types defined in this database.
|
|
27
|
+
# Note that some types may not work with other database engines. Be careful if changing database.
|
|
24
28
|
create_enum "content_status", ["created", "draft", "published", "archived"], force: :cascade
|
|
25
29
|
create_enum "specialties", ["books", "movies", "plays"], force: :cascade
|
|
26
30
|
create_enum "roles", ["visitor", "assistant", "manager", "admin"], force: :cascade
|
|
@@ -53,8 +57,8 @@ ActiveRecord::Schema.define(version: version) do
|
|
|
53
57
|
t.bigint "tag_ids", array: true
|
|
54
58
|
t.string "title"
|
|
55
59
|
t.string "url"
|
|
56
|
-
t.enum "type",
|
|
57
|
-
t.enum "conflicts",
|
|
60
|
+
t.enum "type", enum_type: :types
|
|
61
|
+
t.enum "conflicts", enum_type: :conflicts, array: true
|
|
58
62
|
t.datetime "created_at", null: false
|
|
59
63
|
t.datetime "updated_at", null: false
|
|
60
64
|
end
|
|
@@ -62,13 +66,13 @@ ActiveRecord::Schema.define(version: version) do
|
|
|
62
66
|
create_table "authors", force: :cascade do |t|
|
|
63
67
|
t.string "name"
|
|
64
68
|
t.string "type"
|
|
65
|
-
t.enum "specialty",
|
|
69
|
+
t.enum "specialty", enum_type: :specialties
|
|
66
70
|
end
|
|
67
71
|
|
|
68
72
|
create_table "texts", force: :cascade do |t|
|
|
69
73
|
t.integer "user_id"
|
|
70
74
|
t.string "content"
|
|
71
|
-
t.enum "conflict",
|
|
75
|
+
t.enum "conflict", enum_type: :conflicts
|
|
72
76
|
end
|
|
73
77
|
|
|
74
78
|
create_table "comments", force: :cascade do |t|
|
|
@@ -84,7 +88,7 @@ ActiveRecord::Schema.define(version: version) do
|
|
|
84
88
|
create_table "courses", force: :cascade do |t|
|
|
85
89
|
t.string "title", null: false
|
|
86
90
|
t.interval "duration"
|
|
87
|
-
t.enum "types",
|
|
91
|
+
t.enum "types", enum_type: :types, array: true
|
|
88
92
|
t.datetime "created_at", null: false
|
|
89
93
|
t.datetime "updated_at", null: false
|
|
90
94
|
end
|
|
@@ -98,7 +102,7 @@ ActiveRecord::Schema.define(version: version) do
|
|
|
98
102
|
t.integer "activity_id"
|
|
99
103
|
t.string "title"
|
|
100
104
|
t.text "content"
|
|
101
|
-
t.enum "status",
|
|
105
|
+
t.enum "status", enum_type: :content_status
|
|
102
106
|
t.index ["author_id"], name: "index_posts_on_author_id", using: :btree
|
|
103
107
|
end
|
|
104
108
|
|
|
@@ -111,16 +115,23 @@ ActiveRecord::Schema.define(version: version) do
|
|
|
111
115
|
|
|
112
116
|
create_table "users", force: :cascade do |t|
|
|
113
117
|
t.string "name", null: false
|
|
114
|
-
t.enum "role",
|
|
118
|
+
t.enum "role", enum_type: :roles, default: :visitor
|
|
119
|
+
t.datetime "created_at", null: false
|
|
120
|
+
t.datetime "updated_at", null: false
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
create_table "users", schema: "internal", force: :cascade do |t|
|
|
124
|
+
t.string "email"
|
|
115
125
|
t.datetime "created_at", null: false
|
|
116
126
|
t.datetime "updated_at", null: false
|
|
127
|
+
t.index ["email"], name: "index_internal_users_on_email", unique: true
|
|
117
128
|
end
|
|
118
129
|
|
|
119
130
|
create_table "activities", force: :cascade do |t|
|
|
120
131
|
t.integer "author_id"
|
|
121
132
|
t.string "title"
|
|
122
133
|
t.boolean "active"
|
|
123
|
-
t.enum "kind",
|
|
134
|
+
t.enum "kind", enum_type: :types
|
|
124
135
|
t.datetime "created_at", null: false
|
|
125
136
|
t.datetime "updated_at", null: false
|
|
126
137
|
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,6 +39,7 @@ RSpec.configure do |config|
|
|
|
39
39
|
|
|
40
40
|
# Handles acton before rspec initialize
|
|
41
41
|
config.before(:suite) do
|
|
42
|
+
ActiveSupport::Deprecation.silenced = true
|
|
42
43
|
DatabaseCleaner.clean_with(:truncation)
|
|
43
44
|
end
|
|
44
45
|
|
|
@@ -46,10 +47,6 @@ RSpec.configure do |config|
|
|
|
46
47
|
DatabaseCleaner.strategy = :transaction
|
|
47
48
|
end
|
|
48
49
|
|
|
49
|
-
config.before(:each, js: true) do
|
|
50
|
-
DatabaseCleaner.strategy = :truncation
|
|
51
|
-
end
|
|
52
|
-
|
|
53
50
|
config.before(:each) do
|
|
54
51
|
DatabaseCleaner.start
|
|
55
52
|
end
|
data/spec/tests/arel_spec.rb
CHANGED
|
@@ -68,26 +68,37 @@ RSpec.describe 'Arel' do
|
|
|
68
68
|
|
|
69
69
|
it 'works properly when column is an array' do
|
|
70
70
|
expect { connection.add_column(:authors, :tag_ids, :bigint, array: true, default: []) }.not_to raise_error
|
|
71
|
-
expect(Author.
|
|
71
|
+
expect(Author.new.tag_ids).to eq([])
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
it 'works with an array with enum values' do
|
|
74
|
+
it 'works with an array with enum values for a new enum' do
|
|
75
|
+
value = ['a', 'b']
|
|
76
|
+
|
|
77
|
+
expect do
|
|
78
|
+
connection.create_enum(:samples, %i[a b c d])
|
|
79
|
+
connection.add_column(:authors, :samples, :samples, array: true, default: value)
|
|
80
|
+
end.not_to raise_error
|
|
81
|
+
|
|
82
|
+
expect(Author.new.samples).to eq(value)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'works with an array with enum values for an existing enum' do
|
|
75
86
|
value = ['visitor', 'assistant']
|
|
76
87
|
expect { connection.add_column(:authors, :roles, :roles, array: true, default: value) }.not_to raise_error
|
|
77
|
-
expect(Author.
|
|
88
|
+
expect(Author.new.roles).to eq(value)
|
|
78
89
|
end
|
|
79
90
|
|
|
80
91
|
it 'works with multi dimentional array' do
|
|
81
92
|
value = [['1', '2'], ['3', '4']]
|
|
82
93
|
expect { connection.add_column(:authors, :tag_ids, :string, array: true, default: value) }.not_to raise_error
|
|
83
|
-
expect(Author.
|
|
94
|
+
expect(Author.new.tag_ids).to eq(value)
|
|
84
95
|
end
|
|
85
96
|
|
|
86
97
|
it 'works with change column default value' do
|
|
87
98
|
value = ['2', '3']
|
|
88
99
|
connection.add_column(:authors, :tag_ids, :string, array: true)
|
|
89
100
|
expect { connection.change_column_default(:authors, :tag_ids, { from: nil, to: value }) }.not_to raise_error
|
|
90
|
-
expect(Author.
|
|
101
|
+
expect(Author.new.tag_ids).to eq(value)
|
|
91
102
|
end
|
|
92
103
|
end
|
|
93
104
|
|
|
@@ -392,4 +392,53 @@ RSpec.describe 'BelongsToMany' do
|
|
|
392
392
|
end
|
|
393
393
|
end
|
|
394
394
|
end
|
|
395
|
+
|
|
396
|
+
context 'using uuid' do
|
|
397
|
+
let(:connection) { ActiveRecord::Base.connection }
|
|
398
|
+
let(:game) { Class.new(ActiveRecord::Base) }
|
|
399
|
+
let(:player) { Class.new(ActiveRecord::Base) }
|
|
400
|
+
let(:other) { player.create }
|
|
401
|
+
|
|
402
|
+
# TODO: Set as a shred example
|
|
403
|
+
before do
|
|
404
|
+
connection.create_table(:players, id: :uuid) { |t| t.string :name }
|
|
405
|
+
connection.create_table(:games, id: :uuid) { |t| t.uuid :player_ids, array: true }
|
|
406
|
+
|
|
407
|
+
options = { anonymous_class: player, foreign_key: :player_ids }
|
|
408
|
+
options[:inverse_of] = false if Torque::PostgreSQL::AR610
|
|
409
|
+
|
|
410
|
+
game.table_name = 'games'
|
|
411
|
+
player.table_name = 'players'
|
|
412
|
+
game.belongs_to_many :players, **options
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
subject { game.create }
|
|
416
|
+
|
|
417
|
+
it 'loads associated records' do
|
|
418
|
+
subject.update(player_ids: [other.id])
|
|
419
|
+
expect(subject.players.to_sql).to be_eql(<<-SQL.squish)
|
|
420
|
+
SELECT "players".* FROM "players" WHERE "players"."id" IN ('#{other.id}')
|
|
421
|
+
SQL
|
|
422
|
+
|
|
423
|
+
expect(subject.players.load).to be_a(ActiveRecord::Associations::CollectionProxy)
|
|
424
|
+
expect(subject.players.to_a).to be_eql([other])
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
it 'can preload records' do
|
|
428
|
+
records = 5.times.map { player.create }
|
|
429
|
+
subject.players.concat(records)
|
|
430
|
+
|
|
431
|
+
entries = game.all.includes(:players).load
|
|
432
|
+
|
|
433
|
+
expect(entries.size).to be_eql(1)
|
|
434
|
+
expect(entries.first.players).to be_loaded
|
|
435
|
+
expect(entries.first.players.size).to be_eql(5)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
it 'can joins records' do
|
|
439
|
+
query = game.all.joins(:players)
|
|
440
|
+
expect(query.to_sql).to match(/INNER JOIN "players"/)
|
|
441
|
+
expect { query.load }.not_to raise_error
|
|
442
|
+
end
|
|
443
|
+
end
|
|
395
444
|
end
|
data/spec/tests/enum_set_spec.rb
CHANGED
|
@@ -29,7 +29,7 @@ RSpec.describe 'Enum' do
|
|
|
29
29
|
subject { table_definition.new(connection, 'articles') }
|
|
30
30
|
|
|
31
31
|
it 'can be defined as an array' do
|
|
32
|
-
subject.enum(:content_status, array: true)
|
|
32
|
+
subject.enum(:content_status, array: true, enum_type: :content_status)
|
|
33
33
|
expect(subject['content_status'].name).to be_eql('content_status')
|
|
34
34
|
expect(subject['content_status'].type).to be_eql(:content_status)
|
|
35
35
|
|
|
@@ -42,18 +42,19 @@ RSpec.describe 'Enum' do
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
context 'on schema' do
|
|
45
|
+
let(:dump_result) do
|
|
46
|
+
ActiveRecord::SchemaDumper.dump(connection, (dump_result = StringIO.new))
|
|
47
|
+
dump_result.string
|
|
48
|
+
end
|
|
49
|
+
|
|
45
50
|
it 'can be used on tables' do
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
ActiveRecord::SchemaDumper.dump(connection, dump_io)
|
|
49
|
-
expect(dump_io.string).to match checker
|
|
51
|
+
checker = /t\.enum +"conflicts", +array: true, +enum_type: :conflicts/
|
|
52
|
+
expect(dump_result).to match checker
|
|
50
53
|
end
|
|
51
54
|
|
|
52
55
|
xit 'can have a default value as an array of symbols' do
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
ActiveRecord::SchemaDumper.dump(connection, dump_io)
|
|
56
|
-
expect(dump_io.string).to match checker
|
|
56
|
+
checker = /t\.enum +"types", +default: \[:A, :B\], +array: true, +enum_type: :types/
|
|
57
|
+
expect(dump_result).to match checker
|
|
57
58
|
end
|
|
58
59
|
end
|
|
59
60
|
|
data/spec/tests/enum_spec.rb
CHANGED
|
@@ -99,13 +99,19 @@ RSpec.describe 'Enum' do
|
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
it 'can be used in a multiple form' do
|
|
102
|
-
subject.enum('foo', 'bar', 'baz',
|
|
102
|
+
subject.enum('foo', 'bar', 'baz', enum_type: :content_status)
|
|
103
103
|
expect(subject['foo'].type).to be_eql(:content_status)
|
|
104
104
|
expect(subject['bar'].type).to be_eql(:content_status)
|
|
105
105
|
expect(subject['baz'].type).to be_eql(:content_status)
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
it 'can have custom type' do
|
|
109
|
+
subject.enum('foo', enum_type: :content_status)
|
|
110
|
+
expect(subject['foo'].name).to be_eql('foo')
|
|
111
|
+
expect(subject['foo'].type).to be_eql(:content_status)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'can use the deprecated subtype option' do
|
|
109
115
|
subject.enum('foo', subtype: :content_status)
|
|
110
116
|
expect(subject['foo'].name).to be_eql('foo')
|
|
111
117
|
expect(subject['foo'].type).to be_eql(:content_status)
|
|
@@ -143,13 +149,13 @@ RSpec.describe 'Enum' do
|
|
|
143
149
|
it 'can be used on tables too' do
|
|
144
150
|
dump_io = StringIO.new
|
|
145
151
|
ActiveRecord::SchemaDumper.dump(connection, dump_io)
|
|
146
|
-
expect(dump_io.string).to match /t\.enum +"status", +
|
|
152
|
+
expect(dump_io.string).to match /t\.enum +"status", +enum_type: :content_status/
|
|
147
153
|
end
|
|
148
154
|
|
|
149
155
|
it 'can have a default value as symbol' do
|
|
150
156
|
dump_io = StringIO.new
|
|
151
157
|
ActiveRecord::SchemaDumper.dump(connection, dump_io)
|
|
152
|
-
expect(dump_io.string).to match /t\.enum +"role", +default: :visitor, +
|
|
158
|
+
expect(dump_io.string).to match /t\.enum +"role", +default: :visitor, +enum_type: :roles/
|
|
153
159
|
end
|
|
154
160
|
end
|
|
155
161
|
|
data/spec/tests/has_many_spec.rb
CHANGED
|
@@ -411,4 +411,50 @@ RSpec.describe 'HasMany' do
|
|
|
411
411
|
expect { query.load }.not_to raise_error
|
|
412
412
|
end
|
|
413
413
|
end
|
|
414
|
+
|
|
415
|
+
context 'using uuid' do
|
|
416
|
+
let(:connection) { ActiveRecord::Base.connection }
|
|
417
|
+
let(:game) { Class.new(ActiveRecord::Base) }
|
|
418
|
+
let(:player) { Class.new(ActiveRecord::Base) }
|
|
419
|
+
|
|
420
|
+
# TODO: Set as a shred example
|
|
421
|
+
before do
|
|
422
|
+
connection.create_table(:players, id: :uuid) { |t| t.string :name }
|
|
423
|
+
connection.create_table(:games, id: :uuid) { |t| t.uuid :player_ids, array: true }
|
|
424
|
+
|
|
425
|
+
options = { anonymous_class: game, foreign_key: :player_ids }
|
|
426
|
+
options[:inverse_of] = false if Torque::PostgreSQL::AR610
|
|
427
|
+
|
|
428
|
+
game.table_name = 'games'
|
|
429
|
+
player.table_name = 'players'
|
|
430
|
+
player.has_many :games, array: true, **options
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
subject { player.create }
|
|
434
|
+
|
|
435
|
+
it 'loads associated records' do
|
|
436
|
+
expect(subject.games.to_sql).to match(Regexp.new(<<-SQL.squish))
|
|
437
|
+
SELECT "games"\\.\\* FROM "games"
|
|
438
|
+
WHERE \\(?"games"\\."player_ids" && ARRAY\\['#{subject.id}'\\]::uuid\\[\\]\\)?
|
|
439
|
+
SQL
|
|
440
|
+
|
|
441
|
+
expect(subject.games.load).to be_a(ActiveRecord::Associations::CollectionProxy)
|
|
442
|
+
expect(subject.games.to_a).to be_eql([])
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
it 'can preload records' do
|
|
446
|
+
5.times { game.create(player_ids: [subject.id]) }
|
|
447
|
+
entries = player.all.includes(:games).load
|
|
448
|
+
|
|
449
|
+
expect(entries.size).to be_eql(1)
|
|
450
|
+
expect(entries.first.games).to be_loaded
|
|
451
|
+
expect(entries.first.games.size).to be_eql(5)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
it 'can joins records' do
|
|
455
|
+
query = player.all.joins(:games)
|
|
456
|
+
expect(query.to_sql).to match(/INNER JOIN "games"/)
|
|
457
|
+
expect { query.load }.not_to raise_error
|
|
458
|
+
end
|
|
459
|
+
end
|
|
414
460
|
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe 'Schema' do
|
|
4
|
+
let(:connection) { ActiveRecord::Base.connection }
|
|
5
|
+
|
|
6
|
+
before do
|
|
7
|
+
connection.instance_variable_set(:@schmeas_blacklist, nil)
|
|
8
|
+
connection.instance_variable_set(:@schmeas_whitelist, nil)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
context 'on migration' do
|
|
12
|
+
it 'can check for existance' do
|
|
13
|
+
expect(connection.schema_exists?(:information_schema)).to be_falsey
|
|
14
|
+
expect(connection.schema_exists?(:information_schema, filtered: false)).to be_truthy
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'can be created' do
|
|
18
|
+
expect(connection.schema_exists?(:legacy, filtered: false)).to be_falsey
|
|
19
|
+
connection.create_schema(:legacy)
|
|
20
|
+
expect(connection.schema_exists?(:legacy, filtered: false)).to be_truthy
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'can be deleted' do
|
|
24
|
+
expect(connection.schema_exists?(:legacy, filtered: false)).to be_falsey
|
|
25
|
+
|
|
26
|
+
connection.create_schema(:legacy)
|
|
27
|
+
expect(connection.schema_exists?(:legacy, filtered: false)).to be_truthy
|
|
28
|
+
|
|
29
|
+
connection.drop_schema(:legacy)
|
|
30
|
+
expect(connection.schema_exists?(:legacy, filtered: false)).to be_falsey
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'works with whitelist' do
|
|
34
|
+
expect(connection.schema_exists?(:legacy)).to be_falsey
|
|
35
|
+
connection.create_schema(:legacy)
|
|
36
|
+
|
|
37
|
+
expect(connection.schema_exists?(:legacy)).to be_falsey
|
|
38
|
+
expect(connection.schema_exists?(:legacy, filtered: false)).to be_truthy
|
|
39
|
+
|
|
40
|
+
connection.schemas_whitelist.push('legacy')
|
|
41
|
+
expect(connection.schema_exists?(:legacy)).to be_truthy
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context 'on schema' do
|
|
46
|
+
let(:dump_result) do
|
|
47
|
+
ActiveRecord::SchemaDumper.dump(connection, (dump_result = StringIO.new))
|
|
48
|
+
dump_result.string
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'does not add when there is no extra schemas' do
|
|
52
|
+
connection.drop_schema(:internal, force: :cascade)
|
|
53
|
+
expect(dump_result).not_to match /Custom schemas defined in this database/
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'does not include tables from blacklisted schemas' do
|
|
57
|
+
connection.schemas_blacklist.push('internal')
|
|
58
|
+
expect(dump_result).not_to match /create_table \"users\",.*schema: +"internal"/
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
context 'with internal schema whitelisted' do
|
|
62
|
+
before { connection.schemas_whitelist.push('internal') }
|
|
63
|
+
|
|
64
|
+
it 'dumps the schemas' do
|
|
65
|
+
expect(dump_result).to match /create_schema \"internal\"/
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'shows the internal users table in the connection tables list' do
|
|
69
|
+
expect(connection.tables).to include('internal.users')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'dumps tables on whitelisted schemas' do
|
|
73
|
+
expect(dump_result).to match /create_table \"users\",.*schema: +"internal"/
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context 'on relation' do
|
|
79
|
+
let(:model) { Internal::User }
|
|
80
|
+
|
|
81
|
+
it 'adds the schema to the query' do
|
|
82
|
+
expect(model.all.to_sql).to match(/FROM "internal"."users"/)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'can load the schema from the module' do
|
|
86
|
+
allow(Internal).to receive(:schema).and_return('internal')
|
|
87
|
+
allow(model).to receive(:schema).and_return(nil)
|
|
88
|
+
|
|
89
|
+
expect(model.all.to_sql).to match(/FROM "internal"."users"/)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -73,37 +73,33 @@ RSpec.describe 'TableInheritance' do
|
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
context 'on schema' do
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
let(:dump_result) do
|
|
77
|
+
ActiveRecord::SchemaDumper.dump(connection, (dump_result = StringIO.new))
|
|
78
|
+
dump_result.string
|
|
79
|
+
end
|
|
79
80
|
|
|
81
|
+
it 'dumps single inheritance with body' do
|
|
80
82
|
parts = '"activity_books"'
|
|
81
83
|
parts << ', id: false'
|
|
82
84
|
parts << ', force: :cascade'
|
|
83
|
-
parts << ', inherits:
|
|
84
|
-
expect(
|
|
85
|
+
parts << ', inherits: "activities"'
|
|
86
|
+
expect(dump_result).to match(/create_table #{parts} do /)
|
|
85
87
|
end
|
|
86
88
|
|
|
87
89
|
it 'dumps single inheritance without body' do
|
|
88
|
-
dump_io = StringIO.new
|
|
89
|
-
ActiveRecord::SchemaDumper.dump(connection, dump_io)
|
|
90
|
-
|
|
91
90
|
parts = '"activity_post_samples"'
|
|
92
91
|
parts << ', id: false'
|
|
93
92
|
parts << ', force: :cascade'
|
|
94
|
-
parts << ', inherits:
|
|
95
|
-
expect(
|
|
93
|
+
parts << ', inherits: "activity_posts"'
|
|
94
|
+
expect(dump_result).to match(/create_table #{parts}(?! do \|t\|)/)
|
|
96
95
|
end
|
|
97
96
|
|
|
98
97
|
it 'dumps multiple inheritance' do
|
|
99
|
-
dump_io = StringIO.new
|
|
100
|
-
ActiveRecord::SchemaDumper.dump(connection, dump_io)
|
|
101
|
-
|
|
102
98
|
parts = '"activity_posts"'
|
|
103
99
|
parts << ', id: false'
|
|
104
100
|
parts << ', force: :cascade'
|
|
105
|
-
parts << ', inherits: (\[
|
|
106
|
-
expect(
|
|
101
|
+
parts << ', inherits: (\["images", "activities"\]|\["activities", "images"\])'
|
|
102
|
+
expect(dump_result).to match(/create_table #{parts}/)
|
|
107
103
|
end
|
|
108
104
|
end
|
|
109
105
|
|
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: 2.
|
|
4
|
+
version: 2.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Carlos Silva
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-
|
|
11
|
+
date: 2022-12-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -17,6 +17,9 @@ dependencies:
|
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: '6.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '7.0'
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -24,6 +27,9 @@ dependencies:
|
|
|
24
27
|
- - ">="
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
29
|
version: '6.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '7.0'
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
34
|
name: pg
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -155,7 +161,7 @@ dependencies:
|
|
|
155
161
|
description: Add support to complex resources of PostgreSQL, like data types, array
|
|
156
162
|
associations, and auxiliary statements (CTE)
|
|
157
163
|
email:
|
|
158
|
-
-
|
|
164
|
+
- me@carlosfsilva.com
|
|
159
165
|
executables: []
|
|
160
166
|
extensions: []
|
|
161
167
|
extra_rdoc_files: []
|
|
@@ -231,6 +237,7 @@ files:
|
|
|
231
237
|
- lib/torque/postgresql/relation/inheritance.rb
|
|
232
238
|
- lib/torque/postgresql/relation/merger.rb
|
|
233
239
|
- lib/torque/postgresql/schema_cache.rb
|
|
240
|
+
- lib/torque/postgresql/table_name.rb
|
|
234
241
|
- lib/torque/postgresql/version.rb
|
|
235
242
|
- lib/torque/range.rb
|
|
236
243
|
- spec/en.yml
|
|
@@ -254,6 +261,7 @@ files:
|
|
|
254
261
|
- spec/models/course.rb
|
|
255
262
|
- spec/models/geometry.rb
|
|
256
263
|
- spec/models/guest_comment.rb
|
|
264
|
+
- spec/models/internal/user.rb
|
|
257
265
|
- spec/models/item.rb
|
|
258
266
|
- spec/models/post.rb
|
|
259
267
|
- spec/models/question.rb
|
|
@@ -281,13 +289,18 @@ files:
|
|
|
281
289
|
- spec/tests/quoting_spec.rb
|
|
282
290
|
- spec/tests/range_spec.rb
|
|
283
291
|
- spec/tests/relation_spec.rb
|
|
292
|
+
- spec/tests/schema_spec.rb
|
|
284
293
|
- spec/tests/table_inheritance_spec.rb
|
|
285
294
|
homepage: https://github.com/crashtech/torque-postgresql
|
|
286
295
|
licenses:
|
|
287
296
|
- MIT
|
|
288
|
-
metadata:
|
|
297
|
+
metadata:
|
|
298
|
+
source_code_uri: https://github.com/crashtech/torque-postgresql
|
|
299
|
+
bug_tracker_uri: https://github.com/crashtech/torque-postgresql/issues
|
|
289
300
|
post_install_message:
|
|
290
|
-
rdoc_options:
|
|
301
|
+
rdoc_options:
|
|
302
|
+
- "--title"
|
|
303
|
+
- Torque PostgreSQL
|
|
291
304
|
require_paths:
|
|
292
305
|
- lib
|
|
293
306
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
@@ -327,6 +340,7 @@ test_files:
|
|
|
327
340
|
- spec/models/course.rb
|
|
328
341
|
- spec/models/geometry.rb
|
|
329
342
|
- spec/models/guest_comment.rb
|
|
343
|
+
- spec/models/internal/user.rb
|
|
330
344
|
- spec/models/item.rb
|
|
331
345
|
- spec/models/post.rb
|
|
332
346
|
- spec/models/question.rb
|
|
@@ -354,4 +368,5 @@ test_files:
|
|
|
354
368
|
- spec/tests/quoting_spec.rb
|
|
355
369
|
- spec/tests/range_spec.rb
|
|
356
370
|
- spec/tests/relation_spec.rb
|
|
371
|
+
- spec/tests/schema_spec.rb
|
|
357
372
|
- spec/tests/table_inheritance_spec.rb
|