torque-postgresql 0.2.16 → 1.0.0

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