torque-postgresql 2.2.4 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/lib/torque/postgresql/adapter/database_statements.rb +12 -1
  3. data/lib/torque/postgresql/adapter/oid/enum_set.rb +1 -1
  4. data/lib/torque/postgresql/adapter/oid.rb +0 -3
  5. data/lib/torque/postgresql/adapter/quoting.rb +12 -20
  6. data/lib/torque/postgresql/adapter/schema_creation.rb +1 -2
  7. data/lib/torque/postgresql/adapter/schema_definitions.rb +0 -37
  8. data/lib/torque/postgresql/adapter/schema_dumper.rb +0 -41
  9. data/lib/torque/postgresql/adapter/schema_statements.rb +0 -15
  10. data/lib/torque/postgresql/adapter.rb +0 -9
  11. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +5 -4
  12. data/lib/torque/postgresql/associations/{association.rb → foreign_association.rb} +1 -4
  13. data/lib/torque/postgresql/associations/preloader/association.rb +53 -26
  14. data/lib/torque/postgresql/associations/preloader/loader_query.rb +36 -0
  15. data/lib/torque/postgresql/associations/preloader.rb +1 -0
  16. data/lib/torque/postgresql/associations.rb +6 -1
  17. data/lib/torque/postgresql/attributes/builder/period.rb +6 -2
  18. data/lib/torque/postgresql/config.rb +2 -12
  19. data/lib/torque/postgresql/reflection/abstract_reflection.rb +44 -20
  20. data/lib/torque/postgresql/relation.rb +10 -12
  21. data/lib/torque/postgresql/schema_cache.rb +2 -1
  22. data/lib/torque/postgresql/version.rb +1 -1
  23. data/lib/torque-postgresql.rb +0 -1
  24. data/spec/schema.rb +5 -5
  25. data/spec/spec_helper.rb +0 -1
  26. data/spec/tests/arel_spec.rb +2 -4
  27. data/spec/tests/belongs_to_many_spec.rb +2 -50
  28. data/spec/tests/enum_set_spec.rb +4 -3
  29. data/spec/tests/enum_spec.rb +0 -90
  30. data/spec/tests/has_many_spec.rb +0 -46
  31. metadata +7 -9
  32. data/lib/torque/range.rb +0 -20
  33. data/spec/tests/range_spec.rb +0 -36
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95fa82a4869d180b12518b184191e6877b9e6cf3108fb5e68e55d6a836809389
4
- data.tar.gz: 45c6e24a30b3782ec26ff5caafe01430c24f137c460eb19b6f2a49e0b6609142
3
+ metadata.gz: d4fac19ef8680f477df0f79502331ddda06266658a54c8128321d68145a9f18b
4
+ data.tar.gz: 0e93ec49f80d40ec9ce9b7fcc5ef0eff65a882cad79a63e5429657ad9c66691b
5
5
  SHA512:
6
- metadata.gz: cd7e925ab13b8ae3eb6f0d92fbb994f12c6c121998792263b0752eb9647c0d2e4f72dec8d3973c6e0365fe0c5194a2af5fba9189189743c5bca7b06a4e6dfa41
7
- data.tar.gz: 470bb4e8b0816318ca5ee1b99e1896a8a4f3297af46e653de4ddac5347cf40a781b530a4e2531cf41eae38f7205332e52ced09e5b857a618f965851a6d9d7162
6
+ metadata.gz: 4a9abd492b544296c29e0949e1ad0ad4852a1ed0c48379551e5ffbbaad7018579cad994ac2a841231603bdddd8da4b448d7771a9086312289f818124d842d6a6
7
+ data.tar.gz: 8019255d20eff471ed177078e1bd6042a0a9b95752d32578bdb8ce86afdf9cffbefa8ed181caab0143f0dbbf80cfc39c99fd005cfc2b28089687b26a635268ef
@@ -34,6 +34,15 @@ module Torque
34
34
  execute("SET SESSION IntervalStyle TO 'iso_8601'", 'SCHEMA')
35
35
  end
36
36
 
37
+ # Since enums create new types, type map needs to be rebooted to include
38
+ # the new ones, both normal and array one
39
+ def create_enum(name, *)
40
+ super
41
+
42
+ oid = query_value("SELECT #{quote(name)}::regtype::oid", "SCHEMA").to_i
43
+ load_additional_types([oid])
44
+ end
45
+
37
46
  # Change some of the types being mapped
38
47
  def initialize_type_map(m = type_map)
39
48
  super
@@ -54,7 +63,7 @@ module Torque
54
63
 
55
64
  # Add the composite types to be loaded too.
56
65
  def torque_load_additional_types(oids = nil)
57
- filter = "AND a.typelem::integer IN (%s)" % oids.join(", ") if oids
66
+ filter = ("AND a.typelem::integer IN (%s)" % oids.join(', ')) if oids
58
67
 
59
68
  query = <<-SQL
60
69
  SELECT a.typelem AS oid, t.typname, t.typelem,
@@ -132,7 +141,9 @@ module Torque
132
141
  # Get the list of columns, and their definition, but only from the
133
142
  # actual table, does not include columns that comes from inherited table
134
143
  def column_definitions(table_name) # :nodoc:
144
+ # Only affects inheritance
135
145
  local_condition = 'AND a.attislocal IS TRUE' if @_dump_mode
146
+
136
147
  query(<<-SQL, 'SCHEMA')
137
148
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
138
149
  pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
@@ -14,7 +14,7 @@ module Torque
14
14
  end
15
15
 
16
16
  def type
17
- :enum_set
17
+ :enum
18
18
  end
19
19
 
20
20
  def deserialize(value)
@@ -19,9 +19,6 @@ module Torque
19
19
  ActiveRecord::Type.register(:enum_set, OID::EnumSet, adapter: :postgresql)
20
20
  ActiveRecord::Type.register(:line, OID::Line, adapter: :postgresql)
21
21
  ActiveRecord::Type.register(:segment, OID::Segment, adapter: :postgresql)
22
-
23
- ActiveRecord::Type.register(:interval, OID::Interval, adapter: :postgresql) \
24
- unless PostgreSQL::AR610
25
22
  end
26
23
  end
27
24
  end
@@ -22,27 +22,19 @@ module Torque
22
22
  end
23
23
 
24
24
  def quote_default_expression(value, column)
25
- return super unless value.class <= Array &&
26
- ((column.is_a?(ColumnDefinition) && column.dig(:options, :array)) ||
27
- (column.is_a?(Column) && column.array?))
28
-
29
- type = column.is_a?(Column) ? column.sql_type_metadata.sql_type : column.sql_type
30
- quote(value) + '::' + type
25
+ return super unless value.class <= Array || value.class <= Set
26
+
27
+ type =
28
+ if column.is_a?(ColumnDefinition) && column.options.try(:[], :array)
29
+ # This is the general way
30
+ lookup_cast_type(column.sql_type)
31
+ elsif column.is_a?(Column) && column.array?
32
+ # When using +change_column_default+
33
+ lookup_cast_type_from_column(column)
34
+ end
35
+
36
+ type.nil? ? super : quote(type.serialize(value.to_a))
31
37
  end
32
-
33
- private
34
-
35
- def _quote(value)
36
- return super unless value.is_a?(Array)
37
-
38
- values = value.map(&method(:quote))
39
- "ARRAY[#{values.join(','.freeze)}]"
40
- end
41
-
42
- def _type_cast(value)
43
- return super unless value.is_a?(Array)
44
- value.map(&method(:quote)).join(','.freeze)
45
- end
46
38
  end
47
39
  end
48
40
  end
@@ -33,8 +33,7 @@ module Torque
33
33
  create_sql << "(#{statements.join(', ')})" \
34
34
  if statements.present? || o.inherits.present?
35
35
 
36
- options = PostgreSQL::AR610 ? o : table_options(o)
37
- add_table_options!(create_sql, options)
36
+ add_table_options!(create_sql, o)
38
37
 
39
38
  if o.inherits.present?
40
39
  tables = o.inherits.map(&method(:quote_table_name))
@@ -3,35 +3,7 @@
3
3
  module Torque
4
4
  module PostgreSQL
5
5
  module Adapter
6
- module ColumnMethods
7
-
8
- # Creates a column with an interval type, allowing span of times and
9
- # dates to be stored without having to store a seconds-based integer
10
- # or any sort of other approach
11
- def interval(*args, **options)
12
- args.each { |name| column(name, :interval, **options) }
13
- end
14
-
15
- # Creates a column with an enum type, needing to specify the enum_type,
16
- # which is basically the name of the type defined prior creating the
17
- # column
18
- def enum(*args, **options)
19
- enum_type = [options.delete(:subtype), options.delete(:enum_type)].compact.first
20
- args.each { |name| column(name, (enum_type || name), **options) }
21
- end
22
-
23
- # Creates a column with an enum array type, needing to specify the
24
- # enum_type, which is basically the name of the type defined prior
25
- # creating the column
26
- def enum_set(*args, **options)
27
- super(*args, **options.merge(array: true))
28
- end
29
-
30
- end
31
-
32
6
  module TableDefinition
33
- include ColumnMethods
34
-
35
7
  attr_reader :inherits
36
8
 
37
9
  def initialize(*args, **options)
@@ -42,16 +14,7 @@ module Torque
42
14
  end
43
15
  end
44
16
 
45
- ActiveRecord::ConnectionAdapters::PostgreSQL::Table.include ColumnMethods
46
17
  ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition.include TableDefinition
47
-
48
- if ActiveRecord::ConnectionAdapters::PostgreSQL.const_defined?('ColumnDefinition')
49
- module ColumnDefinition
50
- attr_accessor :subtype, :enum_type
51
- end
52
-
53
- ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnDefinition.include ColumnDefinition
54
- end
55
18
  end
56
19
  end
57
20
  end
@@ -12,33 +12,13 @@ module Torque
12
12
  stream
13
13
  end
14
14
 
15
- def extensions(stream) # :nodoc:
16
- super
17
- user_defined_types(stream)
18
- end
19
-
20
15
  # Translate +:enum_set+ into +:enum+
21
16
  def schema_type(column)
22
17
  column.type == :enum_set ? :enum : super
23
18
  end
24
19
 
25
- # Adds +:enum_type+ option to the default set
26
- def prepare_column_options(column)
27
- spec = super
28
-
29
- if enum_type = schema_enum_type(column)
30
- spec[:enum_type] = enum_type
31
- end
32
-
33
- spec
34
- end
35
-
36
20
  private
37
21
 
38
- def schema_enum_type(column)
39
- column.sql_type.to_sym.inspect if column.type == :enum || column.type == :enum_set
40
- end
41
-
42
22
  def tables(stream) # :nodoc:
43
23
  inherited_tables = @connection.inherited_tables
44
24
  sorted_tables = @connection.tables.sort - @connection.views
@@ -83,27 +63,6 @@ module Torque
83
63
  functions(stream) if defined?(::Fx::SchemaDumper::Function)
84
64
  triggers(stream) if defined?(::Fx::SchemaDumper::Trigger)
85
65
  end
86
-
87
- # Dump user defined types like enum
88
- def user_defined_types(stream)
89
- types = @connection.user_defined_types('e')
90
- return unless types.any?
91
-
92
- stream.puts " # Custom types defined in this database."
93
- stream.puts " # Note that some types may not work with other database engines. Be careful if changing database."
94
- types.sort_by(&:first).each { |(name, type)| send(type.to_sym, name, stream) }
95
- stream.puts
96
- rescue => e
97
- stream.puts "# Could not dump user-defined types because of following #{e.class}"
98
- stream.puts "# #{e.message}"
99
- stream.puts
100
- end
101
-
102
- # Dump enum custom type
103
- def enum(name, stream)
104
- values = @connection.enum_values(name).map { |v| "\"#{v}\"" }
105
- stream.puts " create_enum \"#{name}\", [#{values.join(', ')}], force: :cascade"
106
- end
107
66
  end
108
67
 
109
68
  ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaDumper.prepend SchemaDumper
@@ -25,21 +25,6 @@ module Torque
25
25
  SQL
26
26
  end
27
27
 
28
- # Creates a new PostgreSQL enumerator type
29
- #
30
- # Example:
31
- # create_enum 'status', ['foo', 'bar']
32
- # create_enum 'status', ['foo', 'bar'], prefix: true
33
- # create_enum 'status', ['foo', 'bar'], suffix: 'test'
34
- # create_enum 'status', ['foo', 'bar'], force: true
35
- def create_enum(name, values, options = {})
36
- drop_type(name, options) if options[:force]
37
- execute <<-SQL.squish
38
- CREATE TYPE #{quote_type_name(name, options[:schema])} AS ENUM
39
- (#{quote_enum_values(name, values, options).join(', ')})
40
- SQL
41
- end
42
-
43
28
  # Changes the enumerator by adding new values
44
29
  #
45
30
  # Example:
@@ -49,15 +49,6 @@ module Torque
49
49
  end
50
50
  end
51
51
  end
52
-
53
- # Extend the extract default value to support array
54
- def extract_value_from_default(default)
55
- return super unless Torque::PostgreSQL.config.use_extended_defaults
56
- return super unless default&.match(/ARRAY\[(.*?)\](?:::"?([\w. ]+)"?(?:\[\])+)?$/)
57
-
58
- arr = $1.split(/(?!\B\[[^\]]*), ?(?![^\[]*\]\B)/)
59
- DeduplicatableArray.new(arr.map(&method(:extract_value_from_default)))
60
- end
61
52
  end
62
53
 
63
54
  ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend Adapter
@@ -53,8 +53,8 @@ module Torque
53
53
 
54
54
  def load_target
55
55
  if stale_target? || find_target?
56
- new_records = PostgreSQL::AR615 ? target.extract!(&:persisted?) : []
57
- @target = merge_target_lists((find_target || []) + new_records, target)
56
+ persisted_records = (find_target || []) + target.extract!(&:persisted?)
57
+ @target = merge_target_lists(persisted_records, target)
58
58
  end
59
59
 
60
60
  loaded!
@@ -228,8 +228,9 @@ module Torque
228
228
  end
229
229
 
230
230
  def invertible_for?(record)
231
- inverse = inverse_reflection_for(record)
232
- inverse && (inverse.has_many? && inverse.connected_through_array?)
231
+ return unless (inverse = inverse_reflection_for(record))
232
+ collection_class = ::ActiveRecord::Associations::HasManyAssociation
233
+ inverse.is_a?(collection_class) && inverse.connected_through_array?
233
234
  end
234
235
 
235
236
  def stale_state
@@ -3,7 +3,7 @@
3
3
  module Torque
4
4
  module PostgreSQL
5
5
  module Associations
6
- module Association
6
+ module ForeignAssociation
7
7
 
8
8
  # There is no problem of adding temporary items on target because
9
9
  # CollectionProxy will handle memory and persisted relationship
@@ -33,9 +33,6 @@ module Torque
33
33
  end
34
34
 
35
35
  end
36
-
37
- ::ActiveRecord::Associations::Association.prepend(Association)
38
- ::ActiveRecord::Associations::HasManyAssociation.prepend(Association)
39
36
  end
40
37
  end
41
38
  end
@@ -11,8 +11,61 @@ module Torque
11
11
  # For reflections connected through an array, make sure to properly
12
12
  # decuple the list of ids and set them as associated with the owner
13
13
  def run
14
+ return self if run?
14
15
  return super unless connected_through_array?
16
+
17
+ @run = true
15
18
  send("run_array_for_#{@reflection.macro}")
19
+ self
20
+ end
21
+
22
+ # Correctly correlate records when they are connected theough an array
23
+ def set_inverse(record)
24
+ return super unless connected_through_array? && @reflection.macro == :has_many
25
+
26
+ # Only the first owner is associated following the same instruction
27
+ # on the original implementation
28
+ convert_key(record[association_key_name])&.each do |key|
29
+ if owners = owners_by_key[key]
30
+ association = owners.first.association(reflection.name)
31
+ association.set_inverse_instance(record)
32
+ end
33
+ end
34
+ end
35
+
36
+ # Requires a slight change when running on has many since the value
37
+ # of the foreign key being an array
38
+ def load_records(raw_records = nil)
39
+ return super unless connected_through_array? && @reflection.macro == :has_many
40
+
41
+ @records_by_owner = {}.compare_by_identity
42
+ raw_records ||= loader_query.records_for([self])
43
+
44
+ @preloaded_records = raw_records.select do |record|
45
+ assignments = false
46
+
47
+ keys = convert_key(record[association_key_name]) || []
48
+ owners_by_key.values_at(*keys).each do |owner|
49
+ entries = (@records_by_owner[owner] ||= [])
50
+
51
+ if reflection.collection? || entries.empty?
52
+ entries << record
53
+ assignments = true
54
+ end
55
+ end
56
+
57
+ assignments
58
+ end
59
+ end
60
+
61
+ # Make sure to change the process when connected through an array
62
+ def owners_by_key
63
+ return super unless connected_through_array?
64
+ @owners_by_key ||= owners.each_with_object({}) do |owner, result|
65
+ Array.wrap(convert_key(owner[owner_key_name])).each do |key|
66
+ (result[key] ||= []) << owner
67
+ end
68
+ end
16
69
  end
17
70
 
18
71
  private
@@ -41,32 +94,6 @@ module Torque
41
94
  end
42
95
  end
43
96
 
44
- if PostgreSQL::AR604
45
- # This is how Rails 6.0.4 and 6.1 now load the records
46
- def load_records
47
- return super unless connected_through_array?
48
-
49
- @records_by_owner = {}.compare_by_identity
50
- raw_records = owner_keys.empty? ? [] : records_for(owner_keys)
51
-
52
- @preloaded_records = raw_records.select do |record|
53
- assignments = false
54
-
55
- ids = convert_key(record[association_key_name])
56
- owners_by_key.values_at(*ids).flat_map do |owner|
57
- entries = (@records_by_owner[owner] ||= [])
58
-
59
- if reflection.collection? || entries.empty?
60
- entries << record
61
- assignments = true
62
- end
63
- end
64
-
65
- assignments
66
- end
67
- end
68
- end
69
-
70
97
  # Build correctly the constraint condition in order to get the
71
98
  # associated ids
72
99
  def records_for(ids, &block)
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ module Associations
6
+ module Preloader
7
+ module LoaderQuery
8
+ def foreign_column
9
+ @foreign_column ||= scope.columns_hash[association_key_name]
10
+ end
11
+
12
+ def load_records_for_keys(keys, &block)
13
+ condition = query_condition_for(keys)
14
+ scope.where(condition).load(&block)
15
+ end
16
+
17
+ def query_condition_for(keys)
18
+ if connected_through_array?
19
+ value = scope.cast_for_condition(foreign_column, keys.to_a)
20
+ scope.table[association_key_name].overlaps(value)
21
+ else
22
+ { association_key_name => keys }
23
+ end
24
+ end
25
+
26
+ def connected_through_array?
27
+ foreign_column.array?
28
+ end
29
+ end
30
+
31
+ ::ActiveRecord::Associations::Preloader::Association::LoaderQuery
32
+ .prepend(LoaderQuery)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1 +1,2 @@
1
1
  require_relative 'preloader/association'
2
+ require_relative 'preloader/loader_query'
@@ -1,5 +1,10 @@
1
- require_relative 'associations/association'
2
1
  require_relative 'associations/association_scope'
3
2
  require_relative 'associations/belongs_to_many_association'
3
+ require_relative 'associations/foreign_association'
4
+
4
5
  require_relative 'associations/builder'
5
6
  require_relative 'associations/preloader'
7
+
8
+ association_mod = Torque::PostgreSQL::Associations::ForeignAssociation
9
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(association_mod)
10
+ ::ActiveRecord::Associations::BelongsToManyAssociation.prepend(association_mod)
@@ -220,7 +220,7 @@ module Torque
220
220
  @arel_threshold_value ||= begin
221
221
  case threshold
222
222
  when Symbol, String
223
- "arel_attribute('#{threshold}')"
223
+ "arel_table['#{threshold}']"
224
224
  when ActiveSupport::Duration
225
225
  value = "'#{threshold.to_i} seconds'"
226
226
  "::Arel.sql(\"#{value}\").cast(:interval)"
@@ -453,7 +453,11 @@ module Torque
453
453
  attr_value = threshold.present? ? method_names[:real] : attribute
454
454
  default_value = default.inspect
455
455
 
456
- "#{attr_value}.nil? ? #{default_value} : #{attr_value}.include?(value)"
456
+ [
457
+ "return #{default_value} if #{attr_value}.nil?",
458
+ "(#{attr_value}.min.try(:infinite?) || #{attr_value}.min <= value) &&",
459
+ " (#{attr_value}.max.try(:infinite?) || #{attr_value}.max > value)",
460
+ ].join("\n")
457
461
  end
458
462
 
459
463
  def instance_start
@@ -4,11 +4,6 @@ module Torque
4
4
  module PostgreSQL
5
5
  include ActiveSupport::Configurable
6
6
 
7
- # Stores a version check for compatibility purposes
8
- AR604 = (ActiveRecord.gem_version >= Gem::Version.new('6.0.4'))
9
- AR610 = (ActiveRecord.gem_version >= Gem::Version.new('6.1.0'))
10
- AR615 = (ActiveRecord.gem_version >= Gem::Version.new('6.1.5'))
11
-
12
7
  # Use the same logger as the Active Record one
13
8
  def self.logger
14
9
  ActiveRecord::Base.logger
@@ -27,11 +22,6 @@ module Torque
27
22
  # same configuration is set to true
28
23
  config.eager_load = false
29
24
 
30
- # This allows default values to have extended values like arrays and casted
31
- # values. Extended defaults are still experimental, so enable and test it
32
- # before using it in prod
33
- config.use_extended_defaults = false
34
-
35
25
  # Set a list of irregular model name when associated with table names
36
26
  config.irregular_models = {}
37
27
  def config.irregular_models=(hash)
@@ -67,11 +57,11 @@ module Torque
67
57
 
68
58
  # The name of the method to be used on any ActiveRecord::Base to
69
59
  # initialize model-based enum features
70
- enum.base_method = :enum
60
+ enum.base_method = :torque_enum
71
61
 
72
62
  # The name of the method to be used on any ActiveRecord::Base to
73
63
  # initialize model-based enum set features
74
- enum.set_method = :enum_set
64
+ enum.set_method = :torque_enum_set
75
65
 
76
66
  # Indicates if bang methods like 'disabled!' should update the record on
77
67
  # database or not
@@ -6,6 +6,9 @@ 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
+
9
12
  # Check if the foreign key actually exists
10
13
  def connected_through_array?
11
14
  false
@@ -37,36 +40,39 @@ module Torque
37
40
  result
38
41
  end
39
42
 
40
- # Build the id constraint checking if both types are perfect matching.
41
- # The klass attribute (left side) will always be a column attribute
43
+ # Build the id constraint checking if both types are perfect matching
42
44
  def build_id_constraint(klass_attr, source_attr)
43
45
  return klass_attr.eq(source_attr) unless connected_through_array?
44
46
 
45
47
  # Klass and key are associated with the reflection Class
46
- klass_type = klass.columns_hash[join_keys.key.to_s]
47
-
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
48
+ klass_type = klass.columns_hash[join_primary_key.to_s]
49
+ # active_record and foreign_key are associated with the source Class
50
+ source_type = active_record.columns_hash[join_foreign_key.to_s]
51
+
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
55
56
 
56
57
  # If the left side is not an array, just use the IN condition
57
58
  return klass_attr.in(source_attr) unless klass_type.try(:array)
58
59
 
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))
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)
63
71
  end
64
72
 
65
- if PostgreSQL::AR610
66
- # TODO: Deprecate this method
67
- def join_keys
68
- OpenStruct.new(key: join_primary_key, foreign_key: join_foreign_key)
69
- end
73
+ # TODO: Deprecate this method
74
+ def join_keys
75
+ OpenStruct.new(key: join_primary_key, foreign_key: join_foreign_key)
70
76
  end
71
77
 
72
78
  private
@@ -77,6 +83,24 @@ module Torque
77
83
 
78
84
  build_id_constraint(klass_attr, source_attr)
79
85
  end
86
+
87
+ # Prepare a value for an array constraint overlap condition
88
+ def cast_constraint_to_array(type, value, should_cast)
89
+ base_ready = type.try(:array) && value.is_a?(AREL_ATTR)
90
+ return value if base_ready && (type.sql_type.eql?(ARR_NO_CAST) || !should_cast)
91
+
92
+ value = ::Arel::Nodes.build_quoted(Array.wrap(value)) unless base_ready
93
+ value = value.cast(ARR_CAST) if should_cast
94
+ value
95
+ end
96
+
97
+ # Check if it's possible to turn both attributes into an ANY condition
98
+ def arel_array_to_any(klass_attr, source_attr, klass_type, source_type)
99
+ return unless !klass_type.try(:array) && source_type.try(:array) &&
100
+ klass_attr.is_a?(AREL_ATTR) && source_attr.is_a?(AREL_ATTR)
101
+
102
+ ::Arel::Nodes::NamedFunction.new('ANY', [source_attr])
103
+ end
80
104
  end
81
105
 
82
106
  ::ActiveRecord::Reflection::AbstractReflection.prepend(AbstractReflection)
@@ -19,6 +19,8 @@ module Torque
19
19
  MULTI_VALUE_METHODS = [:distinct_on, :auxiliary_statements, :cast_records, :select_extra]
20
20
  VALUE_METHODS = SINGLE_VALUE_METHODS + MULTI_VALUE_METHODS
21
21
 
22
+ ARColumn = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Column
23
+
22
24
  # :nodoc:
23
25
  def select_extra_values; get_value(:select_extra); end
24
26
  # :nodoc:
@@ -74,6 +76,14 @@ module Torque
74
76
  end
75
77
  end
76
78
 
79
+ # Serialize the given value so it can be used in a condition tha involves
80
+ # the given column
81
+ def cast_for_condition(column, value)
82
+ column = columns_hash[column.to_s] unless column.is_a?(ARColumn)
83
+ caster = connection.lookup_cast_type_from_column(column)
84
+ connection.type_cast(caster.serialize(value))
85
+ end
86
+
77
87
  private
78
88
 
79
89
  def build_arel(*)
@@ -140,18 +150,6 @@ module Torque
140
150
  ActiveRecord::QueryMethods::VALID_UNSCOPING_VALUES += %i[cast_records itself_only
141
151
  distinct_on auxiliary_statements]
142
152
 
143
- unless AR610
144
- Relation::SINGLE_VALUE_METHODS.each do |value|
145
- ActiveRecord::QueryMethods::DEFAULT_VALUES[value] = nil \
146
- if ActiveRecord::QueryMethods::DEFAULT_VALUES[value].nil?
147
- end
148
-
149
- Relation::MULTI_VALUE_METHODS.each do |value|
150
- ActiveRecord::QueryMethods::DEFAULT_VALUES[value] ||= \
151
- ActiveRecord::QueryMethods::FROZEN_EMPTY_ARRAY
152
- end
153
- end
154
-
155
153
  $VERBOSE = warn_level
156
154
  end
157
155
  end
@@ -109,6 +109,7 @@ module Torque
109
109
 
110
110
  # Try to find a model based on a given table
111
111
  def lookup_model(table_name, scoped_class = '')
112
+ # byebug if table_name == 'activities'
112
113
  scoped_class = scoped_class.name if scoped_class.is_a?(Class)
113
114
  return @data_sources_model_names[table_name] \
114
115
  if @data_sources_model_names.key?(table_name)
@@ -123,7 +124,7 @@ module Torque
123
124
 
124
125
  # Test all the possible names against all the possible scopes
125
126
  until scopes.size == 0
126
- scope = scopes.join.safe_constantize
127
+ scope = scopes.join.chomp('::').safe_constantize
127
128
  model = find_model(max_name, table_name, scope) unless scope.nil?
128
129
  return @data_sources_model_names[table_name] = model unless model.nil?
129
130
  scopes.pop
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Torque
4
4
  module PostgreSQL
5
- VERSION = '2.2.4'
5
+ VERSION = '3.0.0'
6
6
  end
7
7
  end
@@ -1,2 +1 @@
1
1
  require 'torque/postgresql'
2
- require 'torque/range'
data/spec/schema.rb CHANGED
@@ -22,11 +22,11 @@ ActiveRecord::Schema.define(version: version) do
22
22
 
23
23
  # Custom types defined in this database.
24
24
  # Note that some types may not work with other database engines. Be careful if changing database.
25
- create_enum "content_status", ["created", "draft", "published", "archived"], force: :cascade
26
- create_enum "specialties", ["books", "movies", "plays"], force: :cascade
27
- create_enum "roles", ["visitor", "assistant", "manager", "admin"], force: :cascade
28
- create_enum "conflicts", ["valid", "invalid", "untrusted"], force: :cascade
29
- create_enum "types", ["A", "B", "C", "D"], force: :cascade
25
+ create_enum "content_status", ["created", "draft", "published", "archived"]
26
+ create_enum "specialties", ["books", "movies", "plays"]
27
+ create_enum "roles", ["visitor", "assistant", "manager", "admin"]
28
+ create_enum "conflicts", ["valid", "invalid", "untrusted"]
29
+ create_enum "types", ["A", "B", "C", "D"]
30
30
 
31
31
  create_table "geometries", force: :cascade do |t|
32
32
  t.point "point"
data/spec/spec_helper.rb CHANGED
@@ -39,7 +39,6 @@ RSpec.configure do |config|
39
39
 
40
40
  # Handles acton before rspec initialize
41
41
  config.before(:suite) do
42
- ActiveSupport::Deprecation.silenced = true
43
42
  DatabaseCleaner.clean_with(:truncation)
44
43
  end
45
44
 
@@ -51,8 +51,6 @@ RSpec.describe 'Arel' do
51
51
  context 'on default value' do
52
52
  let(:connection) { ActiveRecord::Base.connection }
53
53
 
54
- before(:context) { Torque::PostgreSQL.config.use_extended_defaults = true }
55
- after(:context) { Torque::PostgreSQL.config.use_extended_defaults = false }
56
54
  after { Author.reset_column_information }
57
55
 
58
56
  it 'does not break the change column default value method' do
@@ -76,7 +74,7 @@ RSpec.describe 'Arel' do
76
74
 
77
75
  expect do
78
76
  connection.create_enum(:samples, %i[a b c d])
79
- connection.add_column(:authors, :samples, :samples, array: true, default: value)
77
+ connection.add_column(:authors, :samples, :enum, enum_type: :samples, array: true, default: value)
80
78
  end.not_to raise_error
81
79
 
82
80
  expect(Author.new.samples).to eq(value)
@@ -84,7 +82,7 @@ RSpec.describe 'Arel' do
84
82
 
85
83
  it 'works with an array with enum values for an existing enum' do
86
84
  value = ['visitor', 'assistant']
87
- expect { connection.add_column(:authors, :roles, :roles, array: true, default: value) }.not_to raise_error
85
+ expect { connection.add_column(:authors, :roles, :enum, enum_type: :roles, array: true, default: value) }.not_to raise_error
88
86
  expect(Author.new.roles).to eq(value)
89
87
  end
90
88
 
@@ -344,7 +344,8 @@ RSpec.describe 'BelongsToMany' do
344
344
  subject.tags.concat(records)
345
345
 
346
346
  entries = Video.all
347
- ActiveRecord::Associations::Preloader.new.preload(entries, :tags, Tag.all)
347
+ arguments = { records: entries, associations: :tags, available_records: Tag.all.to_a }
348
+ ActiveRecord::Associations::Preloader.new(**arguments).call
348
349
  entries = entries.load
349
350
 
350
351
  expect(entries.size).to be_eql(1)
@@ -392,53 +393,4 @@ RSpec.describe 'BelongsToMany' do
392
393
  end
393
394
  end
394
395
  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
444
396
  end
@@ -31,7 +31,8 @@ RSpec.describe 'Enum' do
31
31
  it 'can be defined as an array' do
32
32
  subject.enum(:content_status, array: true, enum_type: :content_status)
33
33
  expect(subject['content_status'].name).to be_eql('content_status')
34
- expect(subject['content_status'].type).to be_eql(:content_status)
34
+ expect(subject['content_status'].type).to be_eql(:enum)
35
+ expect(subject['content_status'].options[:enum_type]).to be_eql(:content_status)
35
36
 
36
37
  array = subject['content_status'].respond_to?(:options) \
37
38
  ? subject['content_status'].options[:array] \
@@ -44,14 +45,14 @@ RSpec.describe 'Enum' do
44
45
  context 'on schema' do
45
46
  it 'can be used on tables' do
46
47
  dump_io = StringIO.new
47
- checker = /t\.enum +"conflicts", +array: true, +enum_type: :conflicts/
48
+ checker = /t\.enum +"conflicts", +array: true, +enum_type: "conflicts"/
48
49
  ActiveRecord::SchemaDumper.dump(connection, dump_io)
49
50
  expect(dump_io.string).to match checker
50
51
  end
51
52
 
52
53
  xit 'can have a default value as an array of symbols' do
53
54
  dump_io = StringIO.new
54
- checker = /t\.enum +"types", +default: \[:A, :B\], +array: true, +enum_type: :types/
55
+ checker = /t\.enum +"types", +default: \[:A, :B\], +array: true, +enum_type: "types"/
55
56
  ActiveRecord::SchemaDumper.dump(connection, dump_io)
56
57
  expect(dump_io.string).to match checker
57
58
  end
@@ -26,12 +26,6 @@ RSpec.describe 'Enum' do
26
26
  end
27
27
 
28
28
  context 'on migration' do
29
- it 'can be created' do
30
- connection.create_enum(:status, %i(foo bar))
31
- expect(connection.type_exists?(:status)).to be_truthy
32
- expect(connection.enum_values(:status)).to be_eql(['foo', 'bar'])
33
- end
34
-
35
29
  it 'can be deleted' do
36
30
  connection.create_enum(:status, %i(foo bar))
37
31
  expect(connection.type_exists?(:status)).to be_truthy
@@ -46,16 +40,6 @@ RSpec.describe 'Enum' do
46
40
  expect(connection.type_exists?(:status)).to be_truthy
47
41
  end
48
42
 
49
- it 'can have prefix' do
50
- connection.create_enum(:status, %i(foo bar), prefix: true)
51
- expect(connection.enum_values(:status)).to be_eql(['status_foo', 'status_bar'])
52
- end
53
-
54
- it 'can have suffix' do
55
- connection.create_enum(:status, %i(foo bar), suffix: 'tst')
56
- expect(connection.enum_values(:status)).to be_eql(['foo_tst', 'bar_tst'])
57
- end
58
-
59
43
  it 'inserts values at the end' do
60
44
  connection.create_enum(:status, %i(foo bar))
61
45
  connection.add_enum_values(:status, %i(baz qux))
@@ -85,80 +69,6 @@ RSpec.describe 'Enum' do
85
69
  end
86
70
  end
87
71
 
88
- context 'on table definition' do
89
- subject { table_definition.new(connection, 'articles') }
90
-
91
- it 'has the enum method' do
92
- expect(subject).to respond_to(:enum)
93
- end
94
-
95
- it 'can be used in a single form' do
96
- subject.enum('content_status')
97
- expect(subject['content_status'].name).to be_eql('content_status')
98
- expect(subject['content_status'].type).to be_eql(:content_status)
99
- end
100
-
101
- it 'can be used in a multiple form' do
102
- subject.enum('foo', 'bar', 'baz', enum_type: :content_status)
103
- expect(subject['foo'].type).to be_eql(:content_status)
104
- expect(subject['bar'].type).to be_eql(:content_status)
105
- expect(subject['baz'].type).to be_eql(:content_status)
106
- end
107
-
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
115
- subject.enum('foo', subtype: :content_status)
116
- expect(subject['foo'].name).to be_eql('foo')
117
- expect(subject['foo'].type).to be_eql(:content_status)
118
- end
119
-
120
- it 'raises StatementInvalid when type isn\'t defined' do
121
- subject.enum('foo')
122
- creation = connection.send(:schema_creation).accept subject
123
- expect{ connection.execute creation }.to raise_error(ActiveRecord::StatementInvalid)
124
- end
125
- end
126
-
127
- context 'on schema' do
128
- it 'dumps when has it' do
129
- dump_io = StringIO.new
130
- ActiveRecord::SchemaDumper.dump(connection, dump_io)
131
- expect(dump_io.string).to match /create_enum \"content_status\", \[/
132
- end
133
-
134
- it 'sorts the enum entries to better consistency' do
135
- dump_io = StringIO.new
136
- ActiveRecord::SchemaDumper.dump(connection, dump_io)
137
- items = dump_io.string.scan(/create_enum "(\w+)"/).flatten
138
- expect(items).to be_eql(items.sort)
139
- end
140
-
141
- it 'do not dump when has none' do
142
- connection.drop_type(:content_status, force: :cascade)
143
-
144
- dump_io = StringIO.new
145
- ActiveRecord::SchemaDumper.dump(connection, dump_io)
146
- expect(dump_io.string).not_to match /create_enum \"content_status\", \[/
147
- end
148
-
149
- it 'can be used on tables too' do
150
- dump_io = StringIO.new
151
- ActiveRecord::SchemaDumper.dump(connection, dump_io)
152
- expect(dump_io.string).to match /t\.enum +"status", +enum_type: :content_status/
153
- end
154
-
155
- it 'can have a default value as symbol' do
156
- dump_io = StringIO.new
157
- ActiveRecord::SchemaDumper.dump(connection, dump_io)
158
- expect(dump_io.string).to match /t\.enum +"role", +default: :visitor, +enum_type: :roles/
159
- end
160
- end
161
-
162
72
  context 'on value' do
163
73
  subject { Enum::ContentStatus }
164
74
  let(:values) { %w(created draft published archived) }
@@ -411,50 +411,4 @@ 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
460
414
  end
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.4
4
+ version: 3.0.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-04-11 00:00:00.000000000 Z
11
+ date: 2022-04-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '6.0'
19
+ version: '7.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '6.0'
26
+ version: '7.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: pg
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -189,14 +189,15 @@ files:
189
189
  - lib/torque/postgresql/arel/select_manager.rb
190
190
  - lib/torque/postgresql/arel/visitors.rb
191
191
  - lib/torque/postgresql/associations.rb
192
- - lib/torque/postgresql/associations/association.rb
193
192
  - lib/torque/postgresql/associations/association_scope.rb
194
193
  - lib/torque/postgresql/associations/belongs_to_many_association.rb
195
194
  - lib/torque/postgresql/associations/builder.rb
196
195
  - lib/torque/postgresql/associations/builder/belongs_to_many.rb
197
196
  - lib/torque/postgresql/associations/builder/has_many.rb
197
+ - lib/torque/postgresql/associations/foreign_association.rb
198
198
  - lib/torque/postgresql/associations/preloader.rb
199
199
  - lib/torque/postgresql/associations/preloader/association.rb
200
+ - lib/torque/postgresql/associations/preloader/loader_query.rb
200
201
  - lib/torque/postgresql/attributes.rb
201
202
  - lib/torque/postgresql/attributes/builder.rb
202
203
  - lib/torque/postgresql/attributes/builder/enum.rb
@@ -232,7 +233,6 @@ files:
232
233
  - lib/torque/postgresql/relation/merger.rb
233
234
  - lib/torque/postgresql/schema_cache.rb
234
235
  - lib/torque/postgresql/version.rb
235
- - lib/torque/range.rb
236
236
  - spec/en.yml
237
237
  - spec/factories/authors.rb
238
238
  - spec/factories/comments.rb
@@ -279,7 +279,6 @@ files:
279
279
  - spec/tests/lazy_spec.rb
280
280
  - spec/tests/period_spec.rb
281
281
  - spec/tests/quoting_spec.rb
282
- - spec/tests/range_spec.rb
283
282
  - spec/tests/relation_spec.rb
284
283
  - spec/tests/table_inheritance_spec.rb
285
284
  homepage: https://github.com/crashtech/torque-postgresql
@@ -294,7 +293,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
294
293
  requirements:
295
294
  - - ">="
296
295
  - !ruby/object:Gem::Version
297
- version: '2.6'
296
+ version: 2.7.2
298
297
  required_rubygems_version: !ruby/object:Gem::Requirement
299
298
  requirements:
300
299
  - - ">="
@@ -352,6 +351,5 @@ test_files:
352
351
  - spec/tests/lazy_spec.rb
353
352
  - spec/tests/period_spec.rb
354
353
  - spec/tests/quoting_spec.rb
355
- - spec/tests/range_spec.rb
356
354
  - spec/tests/relation_spec.rb
357
355
  - spec/tests/table_inheritance_spec.rb
data/lib/torque/range.rb DELETED
@@ -1,20 +0,0 @@
1
- module Torque
2
- module Range
3
- def intersection(other)
4
- raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range)
5
-
6
- new_min = self.cover?(other.min) ? other.min : other.cover?(min) ? min : nil
7
- new_max = self.cover?(other.max) ? other.max : other.cover?(max) ? max : nil
8
-
9
- new_min && new_max ? new_min..new_max : nil
10
- end
11
- alias_method :&, :intersection
12
-
13
- def union(other)
14
- raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range)
15
-
16
- ([min, other.min].min)..([max, other.max].max)
17
- end
18
- alias_method :|, :union
19
- end
20
- end
@@ -1,36 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RSpec.xdescribe 'Range' do
4
- let(:sample) { (5..15) }
5
-
6
- it 'has intersection' do
7
- expect { sample.intersection(10) }.to raise_error(ArgumentError, /Range/)
8
-
9
- result = sample & (10..15)
10
- expect(result).to be_a(Range)
11
- expect(result.min).to be_eql(10)
12
- expect(result.max).to be_eql(15)
13
-
14
- result = sample & (15..20)
15
- expect(result).to be_a(Range)
16
- expect(result.min).to be_eql(15)
17
- expect(result.max).to be_eql(15)
18
-
19
- result = sample & (0..10)
20
- expect(result).to be_a(Range)
21
- expect(result.min).to be_eql(5)
22
- expect(result.max).to be_eql(10)
23
-
24
- result = sample & (-10..0)
25
- expect(result).to be_nil
26
- end
27
-
28
- it 'has union' do
29
- expect { sample.union(10) }.to raise_error(ArgumentError, /Range/)
30
-
31
- result = sample | (0..10)
32
- expect(result).to be_a(Range)
33
- expect(result.min).to be_eql(0)
34
- expect(result.max).to be_eql(15)
35
- end
36
- end