torque-postgresql 2.2.3 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e9379e388022831a9441cb6b3b646ff8bdea7340cba61f0dd197bcdbf589f72
4
- data.tar.gz: cfb88b94086027202e92a2120ad8ba1a5e939ef089aea5dcdfd85e05a7d122ea
3
+ metadata.gz: c2a382953b6a3c1d87e05837454cdf523f4be237fdd66c85f0b155278eafa203
4
+ data.tar.gz: e72effb133a0ae945b85b1d46de72b16a098eb1d469f3720926b0267e7f3c11b
5
5
  SHA512:
6
- metadata.gz: 809642eb8f6ee574e7b3b9557590baa58624fec54d1c69b6f341999dfc4217aea5010abc1b438119b3213f958918ce00ddd1af1df3a4714321b1f6153f8ee57d
7
- data.tar.gz: e81cf089a977d964326548144b14dd4ab7ce4be0612f2e923e34add1aa951aa2e5467daaa544da42ac17773fb2cc047335a297f3807a1e72f8dc1942f1df4195
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 child.relname AS table_name,
119
- array_agg(parent.relname) AS inheritances
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
- GROUP BY child.relname, pg_inherits.inhrelid
124
- ORDER BY pg_inherits.inhrelid
154
+ ORDER BY inhrelid
125
155
  SQL
126
156
 
127
- tables.map do |(table, refs)|
128
- [table, PG::TextDecoder::Array.new.decode(refs)]
129
- end.to_h
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 subtype,
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
- subtype = options.delete(:subtype)
20
- args.each { |name| column(name, (subtype || name), **options) }
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
- # subtype, which is basically the name of the type defined prior
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 +:subtype+ option to the default set
26
+ # Adds +:enum_type+ option to the default set
26
27
  def prepare_column_options(column)
27
28
  spec = super
28
29
 
29
- if subtype = schema_subtype(column)
30
- spec[:subtype] = subtype
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 schema_subtype(column)
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.sort - @connection.views
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!(&:to_sym)
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 " # These are user-defined types used on this database"
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
- # Wenever it's inherited, add a new list of auxiliary statements
16
- # It also adds an auxiliary statement to load inherited records' relname
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
- next self.class.table_name unless self.class.physically_inheritances?
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
- self.class.connection.select_value(query)
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 name.
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
- # If both are attributes but the left side is not an array, and the
53
- # right side is, use the ANY operation
54
- any_operation = arel_array_to_any(klass_attr, source_attr, klass_type, source_type)
55
- return klass_attr.eq(any_operation) if any_operation
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
- # Decide if should apply a cast to ensure same type comparision
61
- should_cast = klass_type.type.eql?(:integer) && source_type.type.eql?(:integer)
62
- should_cast &= !klass_type.sql_type.eql?(source_type.sql_type)
63
- should_cast |= !(klass_attr.is_a?(AREL_ATTR) && source_attr.is_a?(AREL_ATTR))
64
-
65
- # Apply necessary transformations to values
66
- klass_attr = cast_constraint_to_array(klass_type, klass_attr, should_cast)
67
- source_attr = cast_constraint_to_array(source_type, source_attr, should_cast)
68
-
69
- # Return the overlap condition
70
- klass_attr.overlaps(source_attr)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Torque
4
4
  module PostgreSQL
5
- VERSION = '2.2.3'
5
+ VERSION = '2.3.0'
6
6
  end
7
7
  end
@@ -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)
@@ -0,0 +1,5 @@
1
+ module Internal
2
+ class User < ActiveRecord::Base
3
+ self.schema = 'internal'
4
+ end
5
+ end
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 = 77
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
- # These are user-defined types used on this database
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", subtype: :types
57
- t.enum "conflicts", subtype: :conflicts, array: true
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", subtype: :specialties
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", subtype: :conflicts
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", subtype: :types, array: true
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", subtype: :content_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", subtype: :roles, default: :visitor
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", subtype: :types
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
@@ -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.columns_hash['tag_ids'].default).to eq([])
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.columns_hash['roles'].default).to eq(value)
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.columns_hash['tag_ids'].default).to eq(value)
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.columns_hash['tag_ids'].default).to eq(value)
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
@@ -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
- dump_io = StringIO.new
47
- checker = /t\.enum +"conflicts", +array: true, +subtype: :conflicts/
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
- dump_io = StringIO.new
54
- checker = /t\.enum +"types", +default: \[:A, :B\], +array: true, +subtype: :types/
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
 
@@ -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', subtype: :content_status)
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", +subtype: :content_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, +subtype: :roles/
158
+ expect(dump_io.string).to match /t\.enum +"role", +default: :visitor, +enum_type: :roles/
153
159
  end
154
160
  end
155
161
 
@@ -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
- it 'dumps single inheritance with body' do
77
- dump_io = StringIO.new
78
- ActiveRecord::SchemaDumper.dump(connection, dump_io)
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: :activities'
84
- expect(dump_io.string).to match(/create_table #{parts} do /)
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: :activity_posts'
95
- expect(dump_io.string).to match(/create_table #{parts}(?! do \|t\|)/)
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: (\[:images, :activities\]|\[:activities, :images\])'
106
- expect(dump_io.string).to match(/create_table #{parts}/)
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.2.3
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-03-31 00:00:00.000000000 Z
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
- - carlinhus.fsilva@gmail.com
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