torque-postgresql 0.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +31 -0
- data/lib/torque/postgresql/adapter/database_statements.rb +103 -0
- data/lib/torque/postgresql/adapter/oid/array.rb +19 -0
- data/lib/torque/postgresql/adapter/oid/enum.rb +46 -0
- data/lib/torque/postgresql/adapter/oid/interval.rb +94 -0
- data/lib/torque/postgresql/adapter/oid.rb +15 -0
- data/lib/torque/postgresql/adapter/quoting.rb +23 -0
- data/lib/torque/postgresql/adapter/schema_definitions.rb +28 -0
- data/lib/torque/postgresql/adapter/schema_dumper.rb +31 -0
- data/lib/torque/postgresql/adapter/schema_statements.rb +89 -0
- data/lib/torque/postgresql/adapter.rb +29 -0
- data/lib/torque/postgresql/attributes/builder/enum.rb +151 -0
- data/lib/torque/postgresql/attributes/builder.rb +1 -0
- data/lib/torque/postgresql/attributes/enum.rb +231 -0
- data/lib/torque/postgresql/attributes/lazy.rb +33 -0
- data/lib/torque/postgresql/attributes/type_map.rb +46 -0
- data/lib/torque/postgresql/attributes.rb +32 -0
- data/lib/torque/postgresql/auxiliary_statement.rb +192 -0
- data/lib/torque/postgresql/base.rb +28 -0
- data/lib/torque/postgresql/collector.rb +31 -0
- data/lib/torque/postgresql/config.rb +50 -0
- data/lib/torque/postgresql/migration/command_recorder.rb +31 -0
- data/lib/torque/postgresql/migration.rb +1 -0
- data/lib/torque/postgresql/relation/auxiliary_statement.rb +65 -0
- data/lib/torque/postgresql/relation/distinct_on.rb +43 -0
- data/lib/torque/postgresql/relation.rb +62 -0
- data/lib/torque/postgresql/schema_dumper.rb +37 -0
- data/lib/torque/postgresql/version.rb +5 -0
- data/lib/torque/postgresql.rb +18 -0
- data/lib/torque-postgresql.rb +1 -0
- metadata +236 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
require_relative 'adapter/database_statements'
|
3
|
+
require_relative 'adapter/oid'
|
4
|
+
require_relative 'adapter/quoting'
|
5
|
+
require_relative 'adapter/schema_definitions'
|
6
|
+
require_relative 'adapter/schema_dumper'
|
7
|
+
require_relative 'adapter/schema_statements'
|
8
|
+
|
9
|
+
module Torque
|
10
|
+
module PostgreSQL
|
11
|
+
module Adapter
|
12
|
+
|
13
|
+
include Quoting
|
14
|
+
include ColumnDumper
|
15
|
+
include DatabaseStatements
|
16
|
+
include SchemaStatements
|
17
|
+
|
18
|
+
# Get the current PostgreSQL version as a Gem Version.
|
19
|
+
def version
|
20
|
+
@version ||= Gem::Version.new(
|
21
|
+
select_value('SELECT version()')
|
22
|
+
.match(/#{Adapter::ADAPTER_NAME} ([\d\.]+)/)[1])
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend Adapter
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module Torque
|
2
|
+
module PostgreSQL
|
3
|
+
module Attributes
|
4
|
+
module Builder
|
5
|
+
class Enum
|
6
|
+
|
7
|
+
attr_accessor :klass, :attribute, :subtype, :initial, :options, :values
|
8
|
+
|
9
|
+
# Start a new builder of methods for composite values on
|
10
|
+
# ActiveRecord::Base
|
11
|
+
def initialize(klass, attribute, subtype, initial, options)
|
12
|
+
@klass = klass
|
13
|
+
@attribute = attribute.to_s
|
14
|
+
@subtype = subtype
|
15
|
+
@initial = initial
|
16
|
+
@options = options
|
17
|
+
|
18
|
+
@values = subtype.klass.values
|
19
|
+
|
20
|
+
if @options[:only]
|
21
|
+
@values &= Array(@options[:only]).map(&:to_s)
|
22
|
+
end
|
23
|
+
|
24
|
+
if @options[:except]
|
25
|
+
@values -= Array(@options[:except]).map(&:to_s)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get the list of methods based on enum values
|
30
|
+
def values_methods
|
31
|
+
return @values_methods if defined?(@values_methods)
|
32
|
+
|
33
|
+
prefix = options.fetch(:prefix, nil).try(:<<, '_')
|
34
|
+
suffix = options.fetch(:suffix, nil).try(:prepend, '_')
|
35
|
+
|
36
|
+
prefix = attribute + '_' if prefix == true
|
37
|
+
suffix = '_' + attribute if suffix == true
|
38
|
+
|
39
|
+
base = "#{prefix}%s#{suffix}"
|
40
|
+
|
41
|
+
@values_methods = begin
|
42
|
+
values.map do |val|
|
43
|
+
val = val.tr('-', '_')
|
44
|
+
scope = base % val
|
45
|
+
ask = scope + '?'
|
46
|
+
bang = scope + '!'
|
47
|
+
[val, [scope, ask, bang]]
|
48
|
+
end.to_h
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Check if any of the methods that will be created get in conflict
|
53
|
+
# with the base class methods
|
54
|
+
def conflicting?
|
55
|
+
return false if options[:force] == true
|
56
|
+
|
57
|
+
dangerous?(attribute.pluralize, true)
|
58
|
+
dangerous?(attribute + '_text')
|
59
|
+
|
60
|
+
values_methods.each do |attr, list|
|
61
|
+
list.map(&method(:dangerous?))
|
62
|
+
end
|
63
|
+
|
64
|
+
return false
|
65
|
+
rescue Interrupt => err
|
66
|
+
if !initial
|
67
|
+
raise ArgumentError, <<-MSG.gsub(/^ +| +$|\n/, '')
|
68
|
+
#{subtype.class.name} was not able to generate requested
|
69
|
+
methods because the method #{err} already exists in
|
70
|
+
#{klass.name}.
|
71
|
+
MSG
|
72
|
+
else
|
73
|
+
warn <<-MSG.gsub(/^ +| +$|\n/, '')
|
74
|
+
#{subtype.class.name} was not able to autoload on
|
75
|
+
#{klass.name} because the method #{err} already exists.
|
76
|
+
MSG
|
77
|
+
return true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Create all methods needed
|
82
|
+
def build
|
83
|
+
plural
|
84
|
+
text
|
85
|
+
all_values
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
# Check if the method already exists in the reference class
|
91
|
+
def dangerous?(method_name, class_method = false)
|
92
|
+
if class_method
|
93
|
+
if klass.dangerous_class_method?(method_name)
|
94
|
+
raise Interrupt, method_name.to_s
|
95
|
+
end
|
96
|
+
else
|
97
|
+
if klass.dangerous_attribute_method?(method_name)
|
98
|
+
raise Interrupt, method_name.to_s
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Create the method that allow access to the list of values
|
104
|
+
def plural
|
105
|
+
klass.singleton_class.module_eval <<-STR, __FILE__, __LINE__ + 1
|
106
|
+
def #{attribute.pluralize} # def statuses
|
107
|
+
::#{subtype.klass.name}.values # ::Enum::Status.values
|
108
|
+
end # end
|
109
|
+
STR
|
110
|
+
end
|
111
|
+
|
112
|
+
# Create the method that turn the attribute value into text using
|
113
|
+
# the model scope
|
114
|
+
def text
|
115
|
+
klass.module_eval <<-STR, __FILE__, __LINE__ + 1
|
116
|
+
def #{attribute}_text # def status_text
|
117
|
+
#{attribute}.text('#{attribute}', self) # status.text('status', self)
|
118
|
+
end # end
|
119
|
+
STR
|
120
|
+
end
|
121
|
+
|
122
|
+
# Create all the methods that represent actions related to the
|
123
|
+
# attribute value
|
124
|
+
def all_values
|
125
|
+
values_methods.each do |val, list|
|
126
|
+
klass.singleton_class.module_eval <<-STR, __FILE__, __LINE__ + 1
|
127
|
+
def #{list[0]} # def disabled
|
128
|
+
where(#{attribute}: '#{val}') # where(status: 'disabled')
|
129
|
+
end # end
|
130
|
+
STR
|
131
|
+
klass.module_eval <<-STR, __FILE__, __LINE__ + 1
|
132
|
+
def #{list[1]} # def disabled?
|
133
|
+
#{attribute}.#{val}? # status.disabled?
|
134
|
+
end # end
|
135
|
+
|
136
|
+
def #{list[2]} # def disabled!
|
137
|
+
if enum_save_on_bang # if enum_save_on_bang
|
138
|
+
update!(#{attribute}: '#{val}') # update!(status: 'disabled')
|
139
|
+
else # else
|
140
|
+
#{attribute}.#{val}! # status.disabled!
|
141
|
+
end # end
|
142
|
+
end # end
|
143
|
+
STR
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'builder/enum'
|
@@ -0,0 +1,231 @@
|
|
1
|
+
module Torque
|
2
|
+
module PostgreSQL
|
3
|
+
module Attributes
|
4
|
+
class Enum < String
|
5
|
+
include Comparable
|
6
|
+
|
7
|
+
class EnumError < ArgumentError; end
|
8
|
+
|
9
|
+
LAZY_VALUE = 0.chr
|
10
|
+
|
11
|
+
class << self
|
12
|
+
delegate :each, to: :values
|
13
|
+
|
14
|
+
# Find or create the class that will handle the value
|
15
|
+
def lookup(name)
|
16
|
+
const = name.camelize
|
17
|
+
namespace = Torque::PostgreSQL.config.enum.namespace
|
18
|
+
|
19
|
+
return namespace.const_get(const) if namespace.const_defined?(const)
|
20
|
+
namespace.const_set(const, Class.new(Enum))
|
21
|
+
end
|
22
|
+
|
23
|
+
# You can specify the connection name for each enum
|
24
|
+
def connection_specification_name
|
25
|
+
return self == Enum ? 'primary' : superclass.connection_specification_name
|
26
|
+
end
|
27
|
+
|
28
|
+
# Overpass new so blank values return only nil
|
29
|
+
def new(value)
|
30
|
+
return Lazy.new(self, LAZY_VALUE) if value.blank?
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
# Load the list of values in a lazy way
|
35
|
+
def values
|
36
|
+
@values ||= self == Enum ? nil : begin
|
37
|
+
conn_name = connection_specification_name
|
38
|
+
conn = connection(conn_name)
|
39
|
+
conn.enum_values(type_name)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Fetch a value from the list
|
44
|
+
# see https://github.com/rails/rails/blob/v5.0.0/activerecord/lib/active_record/fixtures.rb#L656
|
45
|
+
# see https://github.com/rails/rails/blob/v5.0.0/activerecord/lib/active_record/validations/uniqueness.rb#L101
|
46
|
+
def fetch(value, *)
|
47
|
+
return nil unless values.include?(value)
|
48
|
+
send(value)
|
49
|
+
end
|
50
|
+
alias [] fetch
|
51
|
+
|
52
|
+
# Get the type name from its class name
|
53
|
+
def type_name
|
54
|
+
@type_name ||= self.name.demodulize.underscore
|
55
|
+
end
|
56
|
+
|
57
|
+
# Check if the value is valid
|
58
|
+
def valid?(value)
|
59
|
+
return false if self == Enum
|
60
|
+
return true if value.equal?(LAZY_VALUE)
|
61
|
+
self.values.include?(value.to_s)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# Allows checking value existance
|
67
|
+
def respond_to_missing?(method_name, include_private = false)
|
68
|
+
valid?(method_name)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Allow fast creation of values
|
72
|
+
def method_missing(method_name, *arguments)
|
73
|
+
return super if self == Enum
|
74
|
+
self.valid?(method_name) ? new(method_name.to_s) : super
|
75
|
+
end
|
76
|
+
|
77
|
+
# Get a connection based on its name
|
78
|
+
def connection(name)
|
79
|
+
ActiveRecord::Base.connection_handler.retrieve_connection(name)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
# Extension of the ActiveRecord::Base to initiate the enum features
|
85
|
+
module Base
|
86
|
+
|
87
|
+
method_name = Torque::PostgreSQL.config.enum.base_method
|
88
|
+
module_eval <<-STR, __FILE__, __LINE__ + 1
|
89
|
+
def #{method_name}(*args, **options)
|
90
|
+
args.each do |attribute|
|
91
|
+
type = attribute_types[attribute.to_s]
|
92
|
+
TypeMap.lookup(type, self, attribute.to_s, false, options)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
STR
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
# Override string initializer to check for a valid value
|
100
|
+
def initialize(value)
|
101
|
+
str_value = value.is_a?(Numeric) ? self.class.values[value.to_i] : value.to_s
|
102
|
+
raise_invalid(value) unless self.class.valid?(str_value)
|
103
|
+
super(str_value)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Allow comparison between values of the same enum
|
107
|
+
def <=>(other)
|
108
|
+
raise_comparison(other) if other.is_a?(Enum) && other.class != self.class
|
109
|
+
|
110
|
+
case other
|
111
|
+
when Numeric, Enum then to_i <=> other.to_i
|
112
|
+
when String, Symbol then to_i <=> self.class.values.index(other.to_s)
|
113
|
+
else raise_comparison(other)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Only allow value comparison with values of the same class
|
118
|
+
def ==(other)
|
119
|
+
(self <=> other) == 0
|
120
|
+
rescue EnumError
|
121
|
+
false
|
122
|
+
end
|
123
|
+
alias eql? ==
|
124
|
+
|
125
|
+
# Since it can have a lazy value, nil can be true here
|
126
|
+
def nil?
|
127
|
+
self == LAZY_VALUE
|
128
|
+
end
|
129
|
+
alias empty? nil?
|
130
|
+
|
131
|
+
# It only accepts if the other value is valid
|
132
|
+
def replace(value)
|
133
|
+
raise_invalid(value) unless self.class.valid?(value)
|
134
|
+
super
|
135
|
+
end
|
136
|
+
|
137
|
+
# Get a translated version of the value
|
138
|
+
def text(attr = nil, model = nil)
|
139
|
+
keys = i18n_keys(attr, model) << self.underscore.humanize
|
140
|
+
I18n.t(keys.shift, default: keys)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Change the string result for lazy value
|
144
|
+
def to_s
|
145
|
+
nil? ? '' : super
|
146
|
+
end
|
147
|
+
|
148
|
+
# Get the index of the value
|
149
|
+
def to_i
|
150
|
+
self.class.values.index(self)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Change the inspection to show the enum name
|
154
|
+
def inspect
|
155
|
+
nil? ? 'nil' : "#<#{self.class.name} #{super}>"
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
# Get the i18n keys to check
|
161
|
+
def i18n_keys(attr = nil, model = nil)
|
162
|
+
values = { type: self.class.type_name, value: to_s }
|
163
|
+
list_from = :i18n_type_scopes
|
164
|
+
|
165
|
+
if attr && model
|
166
|
+
values[:attr] = attr
|
167
|
+
values[:model] = model.class.model_name.i18n_key
|
168
|
+
list_from = :i18n_scopes
|
169
|
+
end
|
170
|
+
|
171
|
+
Torque::PostgreSQL.config.enum.send(list_from).map do |key|
|
172
|
+
(key % values).to_sym
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Check for valid '?' and '!' methods
|
177
|
+
def respond_to_missing?(method_name, include_private = false)
|
178
|
+
name = method_name.to_s
|
179
|
+
|
180
|
+
return true if name.chomp!('?')
|
181
|
+
name.chomp!('!') && self.class.valid?(name)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Allow '_' to be associated to '-'
|
185
|
+
def method_missing(method_name, *arguments)
|
186
|
+
name = method_name.to_s
|
187
|
+
|
188
|
+
if name.chomp!('?')
|
189
|
+
self == name.tr('_', '-') || self == name
|
190
|
+
elsif name.chomp!('!')
|
191
|
+
replace(name)
|
192
|
+
else
|
193
|
+
super
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Throw an exception for invalid valus
|
198
|
+
def raise_invalid(value)
|
199
|
+
if value.is_a?(Numeric)
|
200
|
+
raise EnumError, "#{value.inspect} is out of bounds of #{self.class.name}"
|
201
|
+
else
|
202
|
+
raise EnumError, "#{value.inspect} is not valid for #{self.class.name}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Throw an exception for comparasion between different enums
|
207
|
+
def raise_comparison(other)
|
208
|
+
raise EnumError, "Comparison of #{self.class.name} with #{self.inspect} failed"
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
# Extend ActiveRecord::Base so it can have the initializer
|
214
|
+
ActiveRecord::Base.extend Enum::Base
|
215
|
+
|
216
|
+
# Create the methods related to the attribute to handle the enum type
|
217
|
+
TypeMap.register_type Adapter::OID::Enum do |subtype, attribute, initial = false, options = nil|
|
218
|
+
return if initial && !Torque::PostgreSQL.config.enum.initializer
|
219
|
+
options = {} if options.nil?
|
220
|
+
|
221
|
+
# Generate methods on self class
|
222
|
+
builder = Builder::Enum.new(self, attribute, subtype, initial, options)
|
223
|
+
return if builder.conflicting?
|
224
|
+
builder.build
|
225
|
+
|
226
|
+
# Mark the enum as defined
|
227
|
+
defined_enums[attribute] = subtype.klass
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Torque
|
2
|
+
module PostgreSQL
|
3
|
+
module Attributes
|
4
|
+
class Lazy < BasicObject
|
5
|
+
|
6
|
+
def initialize(klass, *values)
|
7
|
+
@klass, @values = klass, values
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
other.nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
def nil?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def inspect
|
19
|
+
'nil'
|
20
|
+
end
|
21
|
+
|
22
|
+
def __class__
|
23
|
+
Lazy
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(name, *args, &block)
|
27
|
+
@klass.new(*@values).send(name, *args, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|