torque-postgresql 0.2.16 → 1.0.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +76 -3
  3. data/lib/torque-postgresql.rb +1 -0
  4. data/lib/torque/postgresql.rb +6 -0
  5. data/lib/torque/postgresql/adapter.rb +2 -4
  6. data/lib/torque/postgresql/adapter/database_statements.rb +23 -9
  7. data/lib/torque/postgresql/adapter/oid.rb +12 -1
  8. data/lib/torque/postgresql/adapter/oid/box.rb +28 -0
  9. data/lib/torque/postgresql/adapter/oid/circle.rb +37 -0
  10. data/lib/torque/postgresql/adapter/oid/enum.rb +9 -5
  11. data/lib/torque/postgresql/adapter/oid/enum_set.rb +44 -0
  12. data/lib/torque/postgresql/adapter/oid/line.rb +59 -0
  13. data/lib/torque/postgresql/adapter/oid/range.rb +52 -0
  14. data/lib/torque/postgresql/adapter/oid/segment.rb +73 -0
  15. data/lib/torque/postgresql/adapter/quoting.rb +21 -0
  16. data/lib/torque/postgresql/adapter/schema_definitions.rb +7 -0
  17. data/lib/torque/postgresql/adapter/schema_dumper.rb +10 -1
  18. data/lib/torque/postgresql/arel.rb +3 -0
  19. data/lib/torque/postgresql/arel/infix_operation.rb +42 -0
  20. data/lib/torque/postgresql/arel/nodes.rb +32 -0
  21. data/lib/torque/postgresql/arel/operations.rb +18 -0
  22. data/lib/torque/postgresql/arel/visitors.rb +28 -2
  23. data/lib/torque/postgresql/associations.rb +8 -0
  24. data/lib/torque/postgresql/associations/association.rb +30 -0
  25. data/lib/torque/postgresql/associations/association_scope.rb +116 -0
  26. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +117 -0
  27. data/lib/torque/postgresql/associations/builder.rb +2 -0
  28. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +121 -0
  29. data/lib/torque/postgresql/associations/builder/has_many.rb +15 -0
  30. data/lib/torque/postgresql/associations/join_dependency/join_association.rb +15 -0
  31. data/lib/torque/postgresql/associations/preloader.rb +25 -0
  32. data/lib/torque/postgresql/associations/preloader/association.rb +64 -0
  33. data/lib/torque/postgresql/attributes.rb +2 -0
  34. data/lib/torque/postgresql/attributes/builder.rb +1 -0
  35. data/lib/torque/postgresql/attributes/builder/enum.rb +23 -15
  36. data/lib/torque/postgresql/attributes/builder/period.rb +452 -0
  37. data/lib/torque/postgresql/attributes/enum.rb +11 -8
  38. data/lib/torque/postgresql/attributes/enum_set.rb +256 -0
  39. data/lib/torque/postgresql/attributes/lazy.rb +1 -1
  40. data/lib/torque/postgresql/attributes/period.rb +31 -0
  41. data/lib/torque/postgresql/attributes/type_map.rb +3 -5
  42. data/lib/torque/postgresql/autosave_association.rb +40 -0
  43. data/lib/torque/postgresql/auxiliary_statement.rb +201 -198
  44. data/lib/torque/postgresql/auxiliary_statement/settings.rb +20 -12
  45. data/lib/torque/postgresql/base.rb +161 -2
  46. data/lib/torque/postgresql/config.rb +91 -9
  47. data/lib/torque/postgresql/geometry_builder.rb +92 -0
  48. data/lib/torque/postgresql/i18n.rb +1 -1
  49. data/lib/torque/postgresql/railtie.rb +18 -5
  50. data/lib/torque/postgresql/reflection.rb +21 -0
  51. data/lib/torque/postgresql/reflection/abstract_reflection.rb +109 -0
  52. data/lib/torque/postgresql/reflection/association_reflection.rb +30 -0
  53. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +44 -0
  54. data/lib/torque/postgresql/reflection/has_many_reflection.rb +13 -0
  55. data/lib/torque/postgresql/reflection/runtime_reflection.rb +12 -0
  56. data/lib/torque/postgresql/reflection/through_reflection.rb +11 -0
  57. data/lib/torque/postgresql/relation.rb +11 -10
  58. data/lib/torque/postgresql/relation/auxiliary_statement.rb +11 -18
  59. data/lib/torque/postgresql/relation/inheritance.rb +2 -2
  60. data/lib/torque/postgresql/relation/merger.rb +11 -7
  61. data/lib/torque/postgresql/schema_cache.rb +1 -1
  62. data/lib/torque/postgresql/version.rb +1 -1
  63. data/lib/torque/range.rb +40 -0
  64. metadata +41 -9
@@ -0,0 +1,117 @@
1
+ require 'active_record/associations/collection_association'
2
+ # FIXME: build, create
3
+ module Torque
4
+ module PostgreSQL
5
+ module Associations
6
+ class BelongsToManyAssociation < ::ActiveRecord::Associations::CollectionAssociation
7
+ include ::ActiveRecord::Associations::ForeignAssociation
8
+
9
+ def handle_dependency
10
+ case options[:dependent]
11
+ when :restrict_with_exception
12
+ raise ::ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
13
+
14
+ when :restrict_with_error
15
+ unless empty?
16
+ record = owner.class.human_attribute_name(reflection.name).downcase
17
+ owner.errors.add(:base, :'restrict_dependent_destroy.has_many', record: record)
18
+ throw(:abort)
19
+ end
20
+
21
+ when :destroy
22
+ # No point in executing the counter update since we're going to destroy the parent anyway
23
+ load_target.each { |t| t.destroyed_by_association = reflection }
24
+ destroy_all
25
+ else
26
+ delete_all
27
+ end
28
+ end
29
+
30
+ def ids_reader
31
+ owner[reflection.active_record_primary_key]
32
+ end
33
+
34
+ def ids_writer(new_ids)
35
+ column = reflection.active_record_primary_key
36
+ owner.update_column(column, owner[column] = new_ids.presence)
37
+ @association_scope = nil
38
+ end
39
+
40
+ def insert_record(record, *)
41
+ super
42
+
43
+ attribute = (ids_reader || owner[reflection.active_record_primary_key] = [])
44
+ attribute.push(record[klass_fk])
45
+ record
46
+ end
47
+
48
+ def empty?
49
+ size.zero?
50
+ end
51
+
52
+ def include?(record)
53
+ list = owner[reflection.active_record_primary_key]
54
+ ids_reader && ids_reader.include?(record[klass_fk])
55
+ end
56
+
57
+ private
58
+
59
+ # Returns the number of records in this collection, which basically
60
+ # means count the number of entries in the +primary_key+
61
+ def count_records
62
+ ids_reader&.size || (@target ||= []).size
63
+ end
64
+
65
+ # When the idea is to nulligy the association, then just set the owner
66
+ # +primary_key+ as empty
67
+ def delete_count(method, scope, ids = nil)
68
+ ids ||= scope.pluck(klass_fk)
69
+ scope.delete_all if method == :delete_all
70
+ remove_stash_records(ids)
71
+ end
72
+
73
+ def delete_or_nullify_all_records(method)
74
+ delete_count(method, scope)
75
+ end
76
+
77
+ # Deletes the records according to the <tt>:dependent</tt> option.
78
+ def delete_records(records, method)
79
+ ids = Array.wrap(records).each_with_object(klass_fk).map(&:[])
80
+
81
+ if method == :destroy
82
+ records.each(&:destroy!)
83
+ remove_stash_records(ids)
84
+ else
85
+ scope = self.scope.where(klass_fk => records)
86
+ delete_count(method, scope, ids)
87
+ end
88
+ end
89
+
90
+ def concat_records(*)
91
+ result = super
92
+ ids_writer(ids_reader)
93
+ result
94
+ end
95
+
96
+ def remove_stash_records(ids)
97
+ return if ids_reader.nil?
98
+ ids_writer(ids_reader - Array.wrap(ids))
99
+ end
100
+
101
+ def klass_fk
102
+ reflection.foreign_key
103
+ end
104
+
105
+ def difference(a, b)
106
+ a - b
107
+ end
108
+
109
+ def intersection(a, b)
110
+ a & b
111
+ end
112
+ end
113
+
114
+ ::ActiveRecord::Associations.const_set(:BelongsToManyAssociation, BelongsToManyAssociation)
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'builder/belongs_to_many'
2
+ require_relative 'builder/has_many'
@@ -0,0 +1,121 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Associations
4
+ module Builder
5
+ class BelongsToMany < ::ActiveRecord::Associations::Builder::CollectionAssociation
6
+ def self.macro
7
+ :belongs_to_many
8
+ end
9
+
10
+ def self.valid_options(options)
11
+ super + [:touch, :optional, :default, :dependent, :primary_key, :required]
12
+ end
13
+
14
+ def self.valid_dependent_options
15
+ [:restrict_with_error, :restrict_with_exception]
16
+ end
17
+
18
+ def self.define_callbacks(model, reflection)
19
+ super
20
+ add_touch_callbacks(model, reflection) if reflection.options[:touch]
21
+ add_default_callbacks(model, reflection) if reflection.options[:default]
22
+ end
23
+
24
+ def self.define_readers(mixin, name)
25
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
26
+ def #{name}
27
+ association(:#{name}).reader
28
+ end
29
+ CODE
30
+ end
31
+
32
+ def self.define_writers(mixin, name)
33
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
34
+ def #{name}=(value)
35
+ association(:#{name}).writer(value)
36
+ end
37
+ CODE
38
+ end
39
+
40
+ def self.add_default_callbacks(model, reflection)
41
+ model.before_validation ->(o) do
42
+ o.association(reflection.name).default(&reflection.options[:default])
43
+ end
44
+ end
45
+
46
+ def self.add_touch_callbacks(model, reflection)
47
+ foreign_key = reflection.foreign_key
48
+ n = reflection.name
49
+ touch = reflection.options[:touch]
50
+
51
+ callback = ->(changes_method) do
52
+ ->(record) do
53
+ BelongsToMany.touch_record(record, record.send(changes_method), foreign_key,
54
+ n, touch, belongs_to_touch_method)
55
+ end
56
+ end
57
+
58
+ unless reflection.counter_cache_column
59
+ model.after_create callback.call(:saved_changes), if: :saved_changes?
60
+ model.after_destroy callback.call(:changes_to_save)
61
+ end
62
+
63
+ model.after_update callback.call(:saved_changes), if: :saved_changes?
64
+ model.after_touch callback.call(:changes_to_save)
65
+ end
66
+
67
+ def self.touch_record(o, changes, foreign_key, name, touch, touch_method) # :nodoc:
68
+ old_foreign_ids = changes[foreign_key] && changes[foreign_key].first
69
+
70
+ if old_foreign_ids.present?
71
+ association = o.association(name)
72
+ reflection = association.reflection
73
+ klass = association.klass
74
+
75
+ primary_key = reflection.association_primary_key(klass)
76
+ old_records = klass.find_by(primary_key => old_foreign_ids)
77
+
78
+ old_records&.map do |old_record|
79
+ if touch != true
80
+ old_record.send(touch_method, touch)
81
+ else
82
+ old_record.send(touch_method)
83
+ end
84
+ end
85
+ end
86
+
87
+ o.send(name)&.map do |record|
88
+ if record && record.persisted?
89
+ if touch != true
90
+ record.send(touch_method, touch)
91
+ else
92
+ record.send(touch_method)
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ def self.define_validations(model, reflection)
99
+ if reflection.options.key?(:required)
100
+ reflection.options[:optional] = !reflection.options.delete(:required)
101
+ end
102
+
103
+ if reflection.options[:optional].nil?
104
+ required = model.belongs_to_many_required_by_default
105
+ else
106
+ required = !reflection.options[:optional]
107
+ end
108
+
109
+ super
110
+
111
+ if required
112
+ model.validates_presence_of reflection.name, message: :required
113
+ end
114
+ end
115
+ end
116
+
117
+ ::ActiveRecord::Associations::Builder.const_set(:BelongsToMany, BelongsToMany)
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,15 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Associations
4
+ module Builder
5
+ module HasMany
6
+ def valid_options(options)
7
+ super + [:array]
8
+ end
9
+ end
10
+
11
+ ::ActiveRecord::Associations::Builder::HasMany.extend(HasMany)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Associations
4
+ module JoinDependency
5
+ module JoinAssociation
6
+ def build_constraint(_, table, _, foreign_table, _)
7
+ reflection.build_join_constraint(table, foreign_table)
8
+ end
9
+ end
10
+
11
+ ::ActiveRecord::Associations::JoinDependency::JoinAssociation.prepend(JoinAssociation)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ require_relative 'preloader/association'
2
+
3
+ unless Torque::PostgreSQL::AR521
4
+ module Torque
5
+ module PostgreSQL
6
+ module Associations
7
+ module Preloader
8
+ BelongsToMany = Class.new(::ActiveRecord::Associations::Preloader::HasMany)
9
+
10
+ def preloader_for(reflection, owners, *)
11
+ return AlreadyLoaded \
12
+ if owners.first.association(reflection.name).loaded?
13
+
14
+ return BelongsToMany \
15
+ if reflection.macro.eql?(:belongs_to_many)
16
+
17
+ super
18
+ end
19
+ end
20
+
21
+ ::ActiveRecord::Associations::Preloader.prepend(Preloader)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,64 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Associations
4
+ module Preloader
5
+ module Association
6
+
7
+ delegate :connected_through_array?, to: :@reflection
8
+
9
+ # For reflections connected through an array, make sure to properly
10
+ # decuple the list of ids and set them as associated with the owner
11
+ def run(preloader)
12
+ return super unless connected_through_array?
13
+ send("run_array_for_#{@reflection.macro}")
14
+ end
15
+
16
+ private
17
+
18
+ # Specific run for belongs_many association
19
+ def run_array_for_belongs_to_many
20
+ # Add reverse to has_many
21
+ records = load_records
22
+ owners.each do |owner|
23
+ items = records.values_at(*Array.wrap(owner[owner_key_name]))
24
+ associate_records_to_owner(owner, items.flatten)
25
+ end
26
+ end
27
+
28
+ # Specific run for has_many association
29
+ def run_array_for_has_many
30
+ # Add reverse to belongs_to_many
31
+ records = Hash.new { |h, k| h[k] = [] }
32
+ load_records.each do |ids, record|
33
+ ids.each { |id| records[id].concat(Array.wrap(record)) }
34
+ end
35
+
36
+ owners.each do |owner|
37
+ associate_records_to_owner(owner, records[owner[owner_key_name]] || [])
38
+ end
39
+ end
40
+
41
+ # Build correctly the constraint condition in order to get the
42
+ # associated ids
43
+ def records_for(ids, &block)
44
+ return super unless connected_through_array?
45
+ condition = scope.arel_attribute(association_key_name)
46
+ condition = reflection.build_id_constraint(condition, ids.flatten.uniq)
47
+ scope.where(condition).load(&block)
48
+ end
49
+
50
+ unless Torque::PostgreSQL::AR521
51
+ def associate_records_to_owner(owner, records)
52
+ association = owner.association(reflection.name)
53
+ association.loaded!
54
+ association.target.concat(records)
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ ::ActiveRecord::Associations::Preloader::Association.prepend(Association)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -4,6 +4,8 @@ require_relative 'attributes/lazy'
4
4
  require_relative 'attributes/builder'
5
5
 
6
6
  require_relative 'attributes/enum'
7
+ require_relative 'attributes/enum_set'
8
+ require_relative 'attributes/period'
7
9
 
8
10
  module Torque
9
11
  module PostgreSQL
@@ -1 +1,2 @@
1
1
  require_relative 'builder/enum'
2
+ require_relative 'builder/period'
@@ -3,11 +3,9 @@ module Torque
3
3
  module Attributes
4
4
  module Builder
5
5
  class Enum
6
-
7
6
  attr_accessor :klass, :attribute, :subtype, :options, :values, :enum_module
8
7
 
9
- # Start a new builder of methods for composite values on
10
- # ActiveRecord::Base
8
+ # Start a new builder of methods for enum values on ActiveRecord::Base
11
9
  def initialize(klass, attribute, subtype, options)
12
10
  @klass = klass
13
11
  @attribute = attribute.to_s
@@ -65,7 +63,7 @@ module Torque
65
63
 
66
64
  return false
67
65
  rescue Interrupt => err
68
- raise ArgumentError, <<-MSG.strip.gsub(/\n +/, ' ')
66
+ raise ArgumentError, <<-MSG.squish
69
67
  #{subtype.class.name} was not able to generate requested
70
68
  methods because the method #{err} already exists in
71
69
  #{klass.name}.
@@ -77,7 +75,7 @@ module Torque
77
75
  @enum_module = Module.new
78
76
 
79
77
  plural
80
- text
78
+ stringify
81
79
  all_values
82
80
 
83
81
  klass.include enum_module
@@ -103,6 +101,8 @@ module Torque
103
101
  def plural
104
102
  attr = attribute
105
103
  enum_klass = subtype.klass
104
+
105
+ # TODO: Rewrite these as string
106
106
  enum_module.const_set('ClassMethods', Module.new)
107
107
  enum_module::ClassMethods.module_eval do
108
108
  # def self.statuses() statuses end
@@ -126,11 +126,13 @@ module Torque
126
126
 
127
127
  # Create the method that turn the attribute value into text using
128
128
  # the model scope
129
- def text
129
+ def stringify
130
130
  attr = attribute
131
+
132
+ # TODO: Rewrite these as string
131
133
  enum_module.module_eval do
132
134
  # def status_text() status.text('status', self) end
133
- define_method("#{attr}_text") { send(attr).text(attr, self) }
135
+ define_method("#{attr}_text") { send(attr)&.text(attr, self) }
134
136
  end
135
137
  end
136
138
 
@@ -139,27 +141,33 @@ module Torque
139
141
  def all_values
140
142
  attr = attribute
141
143
  vals = values_methods
144
+
145
+ enum_klass = subtype.klass
142
146
  model_klass = klass
147
+
148
+ # TODO: Rewrite these as string
143
149
  enum_module.module_eval do
144
150
  vals.each do |val, list|
145
151
  # scope :disabled, -> { where(status: 'disabled') }
146
- model_klass.scope list[0], -> { where(attr => val) }
152
+ model_klass.scope list[0], -> do
153
+ where(enum_klass.scope(arel_table[attr], val))
154
+ end
147
155
 
148
156
  # def disabled? status.disabled? end
149
157
  define_method(list[1]) { send(attr).public_send("#{val}?") }
150
158
 
151
- # def disabled! enum_save_on_bang ? update!(status: 'disabled') : status.disabled! end
159
+ # def disabled!
160
+ # changed = send(attr).public_send("#{val}!")
161
+ # save! if changed && enum_save_on_bang
162
+ # true
152
163
  define_method(list[2]) do
153
- if enum_save_on_bang
154
- update!(attr => val)
155
- else
156
- send(attr).public_send("#{val}!")
157
- end
164
+ changed = send(attr).public_send("#{val}!")
165
+ return save! if changed && enum_save_on_bang
166
+ true
158
167
  end
159
168
  end
160
169
  end
161
170
  end
162
-
163
171
  end
164
172
  end
165
173
  end