torque-postgresql 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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