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