torque-postgresql 1.0.1 → 1.1.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.
@@ -24,12 +24,10 @@ module Torque
24
24
 
25
25
  # Provide a method on the given class to setup which enums will be
26
26
  # manually initialized
27
- def include_on(klass)
28
- method_name = Torque::PostgreSQL.config.enum.base_method
29
- klass.singleton_class.class_eval do
30
- define_method(method_name) do |*args, **options|
31
- Torque::PostgreSQL::Attributes::TypeMap.decorate(self, args, **options)
32
- end
27
+ def include_on(klass, method_name = nil)
28
+ method_name ||= Torque::PostgreSQL.config.enum.base_method
29
+ Builder.include_on(klass, method_name, Builder::Enum) do |builder|
30
+ defined_enums[builder.attribute.to_sym] = builder.subtype
33
31
  end
34
32
  end
35
33
 
@@ -225,17 +223,6 @@ module Torque
225
223
  raise EnumError, "Comparison of #{self.class.name} with #{self.inspect} failed"
226
224
  end
227
225
  end
228
-
229
- # Create the methods related to the attribute to handle the enum type
230
- TypeMap.register_type Adapter::OID::Enum do |subtype, attribute, options = nil|
231
- # Generate methods on self class
232
- builder = Builder::Enum.new(self, attribute, subtype, options || {})
233
- break if builder.conflicting?
234
- builder.build
235
-
236
- # Mark the enum as defined
237
- defined_enums[attribute] = subtype.klass
238
- end
239
226
  end
240
227
  end
241
228
  end
@@ -27,13 +27,8 @@ module Torque
27
27
 
28
28
  # Provide a method on the given class to setup which enum sets will be
29
29
  # manually initialized
30
- def include_on(klass)
31
- method_name = Torque::PostgreSQL.config.enum.set_method
32
- klass.singleton_class.class_eval do
33
- define_method(method_name) do |*args, **options|
34
- Torque::PostgreSQL::Attributes::TypeMap.decorate(self, args, **options)
35
- end
36
- end
30
+ def include_on(klass, method_name = nil)
31
+ Enum.include_on(klass, method_name || Torque::PostgreSQL.config.enum.set_method)
37
32
  end
38
33
 
39
34
  # The original Enum implementation, for individual values
@@ -146,7 +141,7 @@ module Torque
146
141
 
147
142
  # Change the inspection to show the enum name
148
143
  def inspect
149
- map(&:inspect).inspect
144
+ "[#{map(&:inspect).join(', ')}]"
150
145
  end
151
146
 
152
147
  # Replace the setter by instantiating the value
@@ -202,7 +197,7 @@ module Torque
202
197
  # Turn all the values into their respective Enum representations
203
198
  def transform_values(values)
204
199
  values = values.first if values.size.eql?(1) && values.first.is_a?(::Enumerable)
205
- values.map(&method(:instantiate))
200
+ values.map(&method(:instantiate)).reject(&:nil?)
206
201
  end
207
202
 
208
203
  # Check for valid '?' and '!' methods
@@ -240,17 +235,6 @@ module Torque
240
235
  raise EnumSetError, "Comparison of #{self.class.name} with #{self.inspect} failed"
241
236
  end
242
237
  end
243
-
244
- # Create the methods related to the attribute to handle the enum type
245
- TypeMap.register_type Adapter::OID::EnumSet do |subtype, attribute, options = nil|
246
- # Generate methods on self class
247
- builder = Builder::Enum.new(self, attribute, subtype, options || {})
248
- break if builder.conflicting?
249
- builder.build
250
-
251
- # Mark the enum as defined
252
- defined_enums[attribute] = subtype.klass
253
- end
254
238
  end
255
239
  end
256
240
  end
@@ -4,28 +4,14 @@ module Torque
4
4
  # For naw, period doesn't have it's own class
5
5
  module Period
6
6
  class << self
7
-
8
7
  # Provide a method on the given class to setup which period columns
9
8
  # will be manually initialized
10
- def include_on(klass)
11
- method_name = Torque::PostgreSQL.config.period.base_method
12
- klass.singleton_class.class_eval do
13
- define_method(method_name) do |*args, **options|
14
- Torque::PostgreSQL::Attributes::TypeMap.decorate(self, args, **options)
15
- end
16
- end
9
+ def include_on(klass, method_name = nil)
10
+ method_name ||= Torque::PostgreSQL.config.period.base_method
11
+ Builder.include_on(klass, method_name, Builder::Period)
17
12
  end
18
-
19
13
  end
20
14
  end
21
-
22
- # Create the methods related to the attribute to handle the enum type
23
- TypeMap.register_type Adapter::OID::Range do |subtype, attribute, options = nil|
24
- # Generate methods on self class
25
- builder = Builder::Period.new(self, attribute, subtype, options || {})
26
- break if builder.conflicting?
27
- builder.build
28
- end
29
15
  end
30
16
  end
31
17
  end
@@ -137,19 +137,23 @@ module Torque
137
137
  # initialize model-based period features
138
138
  period.base_method = :period_for
139
139
 
140
+ # The default name for a threshold attribute, which will automatically
141
+ # enable threshold features
142
+ period.auto_threshold = :threshold
143
+
140
144
  # Define the list of methods that will be created by default while setting
141
145
  # up a new period field
142
146
  period.method_names = {
143
- current_on: '%s_on', # 0
144
- current: 'current_%s', # 1
145
- not_current: 'not_current_%s', # 2
146
- containing: '%s_containing', # 3
147
- not_containing: '%s_not_containing', # 4
148
- overlapping: '%s_overlapping', # 5
149
- not_overlapping: '%s_not_overlapping', # 6
150
- starting_after: '%s_starting_after', # 7
151
- starting_before: '%s_starting_before', # 8
152
- finishing_after: '%s_finishing_after', # 9
147
+ current_on: '%s_on', # 00
148
+ current: 'current_%s', # 01
149
+ not_current: 'not_current_%s', # 02
150
+ containing: '%s_containing', # 03
151
+ not_containing: '%s_not_containing', # 04
152
+ overlapping: '%s_overlapping', # 05
153
+ not_overlapping: '%s_not_overlapping', # 06
154
+ starting_after: '%s_starting_after', # 07
155
+ starting_before: '%s_starting_before', # 08
156
+ finishing_after: '%s_finishing_after', # 09
153
157
  finishing_before: '%s_finishing_before', # 10
154
158
 
155
159
  real_containing: '%s_real_containing', # 11
@@ -163,14 +167,35 @@ module Torque
163
167
  not_containing_date: '%s_not_containing_date', # 18
164
168
  overlapping_date: '%s_overlapping_date', # 19
165
169
  not_overlapping_date: '%s_not_overlapping_date', # 20
170
+ real_containing_date: '%s_real_containing_date', # 21
171
+ real_overlapping_date: '%s_real_overlapping_date', # 22
172
+
173
+ current?: 'current_%s?', # 23
174
+ current_on?: 'current_%s_on?', # 24
175
+ start: '%s_start', # 25
176
+ finish: '%s_finish', # 26
177
+ real: 'real_%s', # 27
178
+ real_start: '%s_real_start', # 28
179
+ real_finish: '%s_real_finish', # 29
180
+ }
166
181
 
167
- current?: 'current_%s?', # 21
168
- current_on?: 'current_%s_on?', # 22
169
- start: '%s_start', # 23
170
- finish: '%s_finish', # 24
171
- real: 'real_%s', # 25
172
- real_start: '%s_real_start', # 26
173
- real_finish: '%s_real_finish', # 27
182
+ # If the period is marked as direct access, without the field name,
183
+ # then these method names will replace the default ones
184
+ period.direct_method_names = {
185
+ current_on: 'happening_in',
186
+ containing: 'during',
187
+ not_containing: 'not_during',
188
+ real_containing: 'real_during',
189
+
190
+ containing_date: 'during_date',
191
+ not_containing_date: 'not_during_date',
192
+
193
+ current_on?: 'happening_in?',
194
+ start: 'start_at',
195
+ finish: 'finish_at',
196
+ real: 'real_time',
197
+ real_start: 'real_start_at',
198
+ real_finish: 'real_finish_at',
174
199
  }
175
200
 
176
201
  end
@@ -33,7 +33,28 @@ module Torque
33
33
  @inheritance_merged_attributes ||= begin
34
34
  list = attribute_names
35
35
  list += casted_dependents.values.map(&:attribute_names)
36
- list.flatten.to_set.freeze
36
+ list.flatten.uniq.freeze
37
+ end
38
+ end
39
+
40
+ # Get the list of attributes that can be merged while querying because
41
+ # they all have the same type
42
+ def inheritance_mergeable_attributes
43
+ @inheritance_mergeable_attributes ||= begin
44
+ base = inheritance_merged_attributes - attribute_names
45
+ types = base.zip(base.size.times.map { [] }).to_h
46
+
47
+ casted_dependents.values.each do |klass|
48
+ klass.attribute_types.each do |column, type|
49
+ types[column]&.push(type)
50
+ end
51
+ end
52
+
53
+ result = types.select do
54
+ |_, types| types.each_with_object(types.shift).all?(&:==)
55
+ end.keys + attribute_names
56
+
57
+ result.freeze
37
58
  end
38
59
  end
39
60
 
@@ -128,7 +149,7 @@ module Torque
128
149
  record_class = _record_class_attribute.to_s
129
150
 
130
151
  return super unless record.key?(record_class) &&
131
- record[auto_cast] === true && record[record_class] != table_name
152
+ record.delete(auto_cast) && record[record_class] != table_name
132
153
 
133
154
  klass = casted_dependents[record[record_class]]
134
155
  raise_unable_to_cast(record[record_class]) if klass.nil?
@@ -139,8 +160,17 @@ module Torque
139
160
  # Filter the record attributes to be loaded to not included those from
140
161
  # another inherited dependent
141
162
  def filter_attributes_for_cast(record, klass)
142
- remove_attrs = (inheritance_merged_attributes - klass.attribute_names)
143
- record.reject!{ |attribute| remove_attrs.include?(attribute) }
163
+ new_record = record.slice(*klass.attribute_names)
164
+ table = new_record[_record_class_attribute.to_s] = klass.table_name
165
+
166
+ # Recover aliased attributes
167
+ (klass.attribute_names - inheritance_mergeable_attributes).each do |attribute|
168
+ new_record[attribute] = record["#{table}__#{attribute}"]
169
+ end
170
+
171
+ # Add any additional columns and replace the record with the new record data
172
+ new_record.merge!(record.slice(*(record.keys - inheritance_merged_attributes)))
173
+ record.replace(new_record)
144
174
  end
145
175
 
146
176
  end
@@ -18,12 +18,16 @@ module Torque
18
18
  Associations::BelongsToManyAssociation
19
19
  end
20
20
 
21
+ def foreign_key
22
+ @foreign_key ||= options[:primary_key] || derive_foreign_key.freeze
23
+ end
24
+
21
25
  def association_foreign_key
22
26
  @association_foreign_key ||= foreign_key
23
27
  end
24
28
 
25
29
  def active_record_primary_key
26
- @active_record_primary_key ||= options[:primary_key] || derive_primary_key
30
+ @active_record_primary_key ||= options[:foreign_key] || derive_primary_key
27
31
  end
28
32
 
29
33
  private
@@ -33,12 +37,13 @@ module Torque
33
37
  end
34
38
 
35
39
  def derive_primary_key
36
- ActiveSupport::Inflector.pluralize(klass.name.foreign_key)
40
+ "#{name.to_s.singularize}_ids"
37
41
  end
38
42
  end
39
43
 
40
- ::ActiveRecord::Reflection::AssociationReflection::VALID_AUTOMATIC_INVERSE_MACROS.push(:belongs_to_many)
41
44
  ::ActiveRecord::Reflection.const_set(:BelongsToManyReflection, BelongsToManyReflection)
45
+ ::ActiveRecord::Reflection::AssociationReflection::VALID_AUTOMATIC_INVERSE_MACROS
46
+ .push(:belongs_to_many)
42
47
  end
43
48
  end
44
49
  end
@@ -68,15 +68,22 @@ module Torque
68
68
  def build_inheritances(arel)
69
69
  return unless self.cast_records_value.present?
70
70
 
71
+ mergeable = inheritance_mergeable_attributes
72
+
71
73
  columns = build_inheritances_joins(arel, self.cast_records_value)
72
74
  columns = columns.map do |column, arel_tables|
73
75
  next arel_tables.first[column] if arel_tables.size == 1
74
- list = arel_tables.each_with_object(column).map(&:[])
75
- ::Arel::Nodes::NamedFunction.new('COALESCE', list).as(column)
76
+
77
+ if mergeable.include?(column)
78
+ list = arel_tables.each_with_object(column).map(&:[])
79
+ ::Arel::Nodes::NamedFunction.new('COALESCE', list).as(column)
80
+ else
81
+ arel_tables.map { |table| table[column].as("#{table.left.name}__#{column}") }
82
+ end
76
83
  end
77
84
 
78
85
  columns.push(build_auto_caster_marker(arel, self.cast_records_value))
79
- self.select_extra_values += columns if columns.any?
86
+ self.select_extra_values += columns.flatten if columns.any?
80
87
  end
81
88
 
82
89
  # Build as many left outer join as necessary for each dependent table
@@ -1,5 +1,5 @@
1
1
  module Torque
2
2
  module PostgreSQL
3
- VERSION = '1.0.1'
3
+ VERSION = '1.1.0'
4
4
  end
5
5
  end
data/lib/torque/range.rb CHANGED
@@ -16,24 +16,6 @@ module Torque
16
16
  ([min, other.min].min)..([max, other.max].max)
17
17
  end
18
18
  alias_method :|, :union
19
-
20
- def subtract(other)
21
- raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range)
22
- return if other.eql?(self)
23
-
24
- other = intersection(other)
25
- return self if other.nil?
26
-
27
- min.eql?(other.min) ? other.max..max : min..other.min
28
- end
29
- alias_method :-, :subtract
30
-
31
- def add(other)
32
- raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range)
33
-
34
- intersection(other) ? union(other) : self
35
- end
36
- alias_method :+, :add
37
19
  end
38
20
 
39
21
  ::Range.include(Range)
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: 1.0.1
4
+ version: 1.1.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: 2019-08-20 00:00:00.000000000 Z
11
+ date: 2019-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -212,7 +212,6 @@ files:
212
212
  - lib/torque/postgresql/attributes/enum_set.rb
213
213
  - lib/torque/postgresql/attributes/lazy.rb
214
214
  - lib/torque/postgresql/attributes/period.rb
215
- - lib/torque/postgresql/attributes/type_map.rb
216
215
  - lib/torque/postgresql/autosave_association.rb
217
216
  - lib/torque/postgresql/auxiliary_statement.rb
218
217
  - lib/torque/postgresql/auxiliary_statement/settings.rb
@@ -1,82 +0,0 @@
1
- module Torque
2
- module PostgreSQL
3
- module Attributes
4
- # Remove type map and add decorators direct to ActiveRecord::Base
5
- module TypeMap
6
-
7
- class << self
8
-
9
- # Reader of the list of tyes
10
- def types
11
- @types ||= {}
12
- end
13
-
14
- # Store which elements should be initialized
15
- def decorable
16
- @decorable ||= Hash.new{ |h, k| h[k] = [] }
17
- end
18
-
19
- # List of options for each individual attribute on each klass
20
- def options
21
- @options ||= Hash.new{ |h, k| h[k] = {} }
22
- end
23
-
24
- # Mark the list of attributes on the given class that can be decorated
25
- def decorate(klass, *attributes, **set_options)
26
- attributes.flatten.each do |attribute|
27
- decorable[klass] << attribute.to_s
28
- options[klass][attribute.to_s] = set_options.deep_dup
29
- end
30
- end
31
-
32
- # Force the list of attributes on the given class to be decorated by
33
- # this type mapper
34
- def decorate!(klass, *attributes, **options)
35
- decorate(klass, *attributes, **options)
36
- attributes.flatten.map do |attribute|
37
- type = klass.attribute_types[attribute.to_s]
38
- lookup(type, klass, attribute.to_s)
39
- end
40
- end
41
-
42
- # Register a type that can be processed by a given block
43
- def register_type(key, &block)
44
- raise_type_defined(key) if present?(key)
45
- types[key] = block
46
- end
47
-
48
- # Search for a type match and process it if any
49
- def lookup(key, klass, attribute, *args)
50
- return unless present?(key) && decorable?(key, klass, attribute)
51
-
52
- set_options = options[klass][attribute]
53
- args.unshift(set_options) unless set_options.nil?
54
- klass.instance_exec(key, attribute, *args, &types[key.class])
55
- rescue LocalJumpError
56
- # There's a bug or misbehavior that blocks being called through
57
- # instance_exec don't accept neither return nor break
58
- return false
59
- end
60
-
61
- # Check if the given type class is registered
62
- def present?(key)
63
- types.key?(key.class)
64
- end
65
-
66
- # Check whether the given attribute on the given klass is
67
- # decorable by this type mapper
68
- def decorable?(key, klass, attribute)
69
- decorable.key?(klass) && decorable[klass].include?(attribute.to_s)
70
- end
71
-
72
- # Message when trying to define multiple types
73
- def raise_type_defined(key)
74
- raise ArgumentError, <<-MSG.squish
75
- Type #{key} is already defined here: #{types[key].source_location.join(':')}
76
- MSG
77
- end
78
- end
79
- end
80
- end
81
- end
82
- end