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.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +31 -0
  4. data/lib/torque/postgresql/adapter/database_statements.rb +103 -0
  5. data/lib/torque/postgresql/adapter/oid/array.rb +19 -0
  6. data/lib/torque/postgresql/adapter/oid/enum.rb +46 -0
  7. data/lib/torque/postgresql/adapter/oid/interval.rb +94 -0
  8. data/lib/torque/postgresql/adapter/oid.rb +15 -0
  9. data/lib/torque/postgresql/adapter/quoting.rb +23 -0
  10. data/lib/torque/postgresql/adapter/schema_definitions.rb +28 -0
  11. data/lib/torque/postgresql/adapter/schema_dumper.rb +31 -0
  12. data/lib/torque/postgresql/adapter/schema_statements.rb +89 -0
  13. data/lib/torque/postgresql/adapter.rb +29 -0
  14. data/lib/torque/postgresql/attributes/builder/enum.rb +151 -0
  15. data/lib/torque/postgresql/attributes/builder.rb +1 -0
  16. data/lib/torque/postgresql/attributes/enum.rb +231 -0
  17. data/lib/torque/postgresql/attributes/lazy.rb +33 -0
  18. data/lib/torque/postgresql/attributes/type_map.rb +46 -0
  19. data/lib/torque/postgresql/attributes.rb +32 -0
  20. data/lib/torque/postgresql/auxiliary_statement.rb +192 -0
  21. data/lib/torque/postgresql/base.rb +28 -0
  22. data/lib/torque/postgresql/collector.rb +31 -0
  23. data/lib/torque/postgresql/config.rb +50 -0
  24. data/lib/torque/postgresql/migration/command_recorder.rb +31 -0
  25. data/lib/torque/postgresql/migration.rb +1 -0
  26. data/lib/torque/postgresql/relation/auxiliary_statement.rb +65 -0
  27. data/lib/torque/postgresql/relation/distinct_on.rb +43 -0
  28. data/lib/torque/postgresql/relation.rb +62 -0
  29. data/lib/torque/postgresql/schema_dumper.rb +37 -0
  30. data/lib/torque/postgresql/version.rb +5 -0
  31. data/lib/torque/postgresql.rb +18 -0
  32. data/lib/torque-postgresql.rb +1 -0
  33. 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