torque-postgresql 0.1.0

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