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.
- checksums.yaml +4 -4
- data/README.rdoc +76 -3
- data/lib/torque-postgresql.rb +1 -0
- data/lib/torque/postgresql.rb +6 -0
- data/lib/torque/postgresql/adapter.rb +2 -4
- data/lib/torque/postgresql/adapter/database_statements.rb +23 -9
- data/lib/torque/postgresql/adapter/oid.rb +12 -1
- data/lib/torque/postgresql/adapter/oid/box.rb +28 -0
- data/lib/torque/postgresql/adapter/oid/circle.rb +37 -0
- data/lib/torque/postgresql/adapter/oid/enum.rb +9 -5
- data/lib/torque/postgresql/adapter/oid/enum_set.rb +44 -0
- data/lib/torque/postgresql/adapter/oid/line.rb +59 -0
- data/lib/torque/postgresql/adapter/oid/range.rb +52 -0
- data/lib/torque/postgresql/adapter/oid/segment.rb +73 -0
- data/lib/torque/postgresql/adapter/quoting.rb +21 -0
- data/lib/torque/postgresql/adapter/schema_definitions.rb +7 -0
- data/lib/torque/postgresql/adapter/schema_dumper.rb +10 -1
- data/lib/torque/postgresql/arel.rb +3 -0
- data/lib/torque/postgresql/arel/infix_operation.rb +42 -0
- data/lib/torque/postgresql/arel/nodes.rb +32 -0
- data/lib/torque/postgresql/arel/operations.rb +18 -0
- data/lib/torque/postgresql/arel/visitors.rb +28 -2
- data/lib/torque/postgresql/associations.rb +8 -0
- data/lib/torque/postgresql/associations/association.rb +30 -0
- data/lib/torque/postgresql/associations/association_scope.rb +116 -0
- data/lib/torque/postgresql/associations/belongs_to_many_association.rb +117 -0
- data/lib/torque/postgresql/associations/builder.rb +2 -0
- data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +121 -0
- data/lib/torque/postgresql/associations/builder/has_many.rb +15 -0
- data/lib/torque/postgresql/associations/join_dependency/join_association.rb +15 -0
- data/lib/torque/postgresql/associations/preloader.rb +25 -0
- data/lib/torque/postgresql/associations/preloader/association.rb +64 -0
- data/lib/torque/postgresql/attributes.rb +2 -0
- data/lib/torque/postgresql/attributes/builder.rb +1 -0
- data/lib/torque/postgresql/attributes/builder/enum.rb +23 -15
- data/lib/torque/postgresql/attributes/builder/period.rb +452 -0
- data/lib/torque/postgresql/attributes/enum.rb +11 -8
- data/lib/torque/postgresql/attributes/enum_set.rb +256 -0
- data/lib/torque/postgresql/attributes/lazy.rb +1 -1
- data/lib/torque/postgresql/attributes/period.rb +31 -0
- data/lib/torque/postgresql/attributes/type_map.rb +3 -5
- data/lib/torque/postgresql/autosave_association.rb +40 -0
- data/lib/torque/postgresql/auxiliary_statement.rb +201 -198
- data/lib/torque/postgresql/auxiliary_statement/settings.rb +20 -12
- data/lib/torque/postgresql/base.rb +161 -2
- data/lib/torque/postgresql/config.rb +91 -9
- data/lib/torque/postgresql/geometry_builder.rb +92 -0
- data/lib/torque/postgresql/i18n.rb +1 -1
- data/lib/torque/postgresql/railtie.rb +18 -5
- data/lib/torque/postgresql/reflection.rb +21 -0
- data/lib/torque/postgresql/reflection/abstract_reflection.rb +109 -0
- data/lib/torque/postgresql/reflection/association_reflection.rb +30 -0
- data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +44 -0
- data/lib/torque/postgresql/reflection/has_many_reflection.rb +13 -0
- data/lib/torque/postgresql/reflection/runtime_reflection.rb +12 -0
- data/lib/torque/postgresql/reflection/through_reflection.rb +11 -0
- data/lib/torque/postgresql/relation.rb +11 -10
- data/lib/torque/postgresql/relation/auxiliary_statement.rb +11 -18
- data/lib/torque/postgresql/relation/inheritance.rb +2 -2
- data/lib/torque/postgresql/relation/merger.rb +11 -7
- data/lib/torque/postgresql/schema_cache.rb +1 -1
- data/lib/torque/postgresql/version.rb +1 -1
- data/lib/torque/range.rb +40 -0
- 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,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 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
|
@@ -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
|
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.
|
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
|
-
|
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
|
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)
|
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], ->
|
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!
|
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
|
-
|
154
|
-
|
155
|
-
|
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
|