activerecord-postgres-composite-types 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,108 +1,108 @@
1
1
  # ActiveRecord 3.X specific extensions.
2
2
  module ActiveRecord
3
3
 
4
- module ConnectionAdapters
4
+ module ConnectionAdapters
5
5
 
6
- class PostgreSQLAdapter
7
- module OID
8
- class CompositeType < Type
9
- def initialize(composite_type_class)
10
- @composite_type_class = composite_type_class
11
- end
6
+ class PostgreSQLAdapter
7
+ module OID
8
+ class CompositeType < Type
9
+ def initialize(composite_type_class)
10
+ @composite_type_class = composite_type_class
11
+ end
12
12
 
13
- # Casts value (which is a String) to an appropriate instance.
14
- def type_cast(value)
15
- PostgreSQLColumn.string_to_composite_type(@composite_type_class, value)
16
- # @composite_type_class.new(value)
17
- end
13
+ # Casts value (which is a String) to an appropriate instance.
14
+ def type_cast(value)
15
+ PostgreSQLColumn.string_to_composite_type(@composite_type_class, value)
16
+ # @composite_type_class.new(value)
17
+ end
18
18
 
19
- # Casts a Ruby value to something appropriate for writing to the database.
20
- def type_cast_for_write(value)
21
- # Cast Hash and Array to composite type klass
22
- if value.is_a?(@composite_type_class)
23
- value
24
- else
25
- @composite_type_class.new(value)
26
- end
27
- end
28
- end
29
- end
19
+ # Casts a Ruby value to something appropriate for writing to the database.
20
+ def type_cast_for_write(value)
21
+ # Cast Hash and Array to composite type klass
22
+ if value.is_a?(@composite_type_class)
23
+ value
24
+ else
25
+ @composite_type_class.new(value)
26
+ end
27
+ end
28
+ end
29
+ end
30
30
 
31
- class << self
32
- def register_oid_type(klass)
33
- OID.register_type klass.type.to_s, OID::CompositeType.new(klass)
34
- # Dirty. Only this type should be added to type map
35
- klass.connection.send(:reload_type_map) if klass.connected?
36
- end
37
- end
31
+ class << self
32
+ def register_oid_type(klass)
33
+ OID.register_type klass.type.to_s, OID::CompositeType.new(klass)
34
+ # Dirty. Only this type should be added to type map
35
+ klass.connection.send(:reload_type_map) if klass.connected?
36
+ end
37
+ end
38
38
 
39
- def add_composite_type_to_map(type)
40
- oid_type = OID::NAMES[type.to_s]
41
- raise "OID type: '#{type}' not registered" unless oid_type
39
+ def add_composite_type_to_map(type)
40
+ oid_type = OID::NAMES[type.to_s]
41
+ raise "OID type: '#{type}' not registered" unless oid_type
42
42
 
43
- result = execute("SELECT oid, typname, typelem, typdelim, typinput FROM pg_type WHERE typname = '#{type}'", 'SCHEMA')
44
- raise "Composite type: '#{type}' not defined in PostgreSQL database" if result.empty?
45
- row = result[0]
43
+ result = execute("SELECT oid, typname, typelem, typdelim, typinput FROM pg_type WHERE typname = '#{type}'", 'SCHEMA')
44
+ raise "Composite type: '#{type}' not defined in PostgreSQL database" if result.empty?
45
+ row = result[0]
46
46
 
47
- unless type_map.key? row['typelem'].to_i
48
- type_map[row['oid'].to_i] = vector
49
- end
50
- end
47
+ unless type_map.key? row['typelem'].to_i
48
+ type_map[row['oid'].to_i] = vector
49
+ end
50
+ end
51
51
 
52
- # Cast a +value+ to a type that the database understands.
53
- def type_cast_with_composite_types(value, column, array_member = false)
54
- case value
55
- when PostgresCompositeType
56
- PostgreSQLColumn.composite_type_to_string(value, self)
57
- when Array, Hash
58
- if klass = column.composite_type_class
59
- value = klass.new(value)
60
- PostgreSQLColumn.composite_type_to_string(value, self)
61
- else
62
- type_cast_without_composite_types(value, column, array_member)
63
- end
64
- else
65
- type_cast_without_composite_types(value, column, array_member)
66
- end
67
- end
52
+ # Cast a +value+ to a type that the database understands.
53
+ def type_cast_with_composite_types(value, column, array_member = false)
54
+ case value
55
+ when PostgresCompositeType
56
+ PostgreSQLColumn.composite_type_to_string(value, self)
57
+ when Array, Hash
58
+ if klass = column.composite_type_class
59
+ value = klass.new(value)
60
+ PostgreSQLColumn.composite_type_to_string(value, self)
61
+ else
62
+ type_cast_without_composite_types(value, column, array_member)
63
+ end
64
+ else
65
+ type_cast_without_composite_types(value, column, array_member)
66
+ end
67
+ end
68
68
 
69
- alias_method_chain :type_cast, :composite_types
70
- end
69
+ alias_method_chain :type_cast, :composite_types
70
+ end
71
71
 
72
- class PostgreSQLColumn < Column
73
- # Casts value (which is a String) to an appropriate instance.
74
- def type_cast_with_composite_types(value)
75
- if composite_type_klass = PostgreSQLAdapter.composite_type_classes[type]
76
- self.class.string_to_composite_type(composite_type_klass, value)
77
- else
78
- type_cast_without_composite_types(value)
79
- end
80
- end
72
+ class PostgreSQLColumn < Column
73
+ # Casts value (which is a String) to an appropriate instance.
74
+ def type_cast_with_composite_types(value)
75
+ if composite_type_klass = PostgreSQLAdapter.composite_type_classes[type]
76
+ self.class.string_to_composite_type(composite_type_klass, value)
77
+ else
78
+ type_cast_without_composite_types(value)
79
+ end
80
+ end
81
81
 
82
- alias_method_chain :type_cast, :composite_types
82
+ alias_method_chain :type_cast, :composite_types
83
83
 
84
- def self.composite_type_to_string(object, adapter)
85
- quoted_values = object.class.columns.collect do |column|
86
- value = object.send(column.name)
87
- if String === value
88
- if value == "NULL"
89
- "\"#{value}\""
90
- else
91
- quote_and_escape(adapter.type_cast(value, column, true))
92
- end
93
- else
94
- res = adapter.type_cast(value, column, true)
95
- if value.class < PostgresCompositeType
96
- quote_and_escape(res)
97
- else
98
- res
99
- end
100
- end
101
- end
102
- "(#{quoted_values.join(',')})"
103
- end
84
+ def self.composite_type_to_string(object, adapter)
85
+ quoted_values = object.class.columns.collect do |column|
86
+ value = object.send(column.name)
87
+ if String === value
88
+ if value == "NULL"
89
+ "\"#{value}\""
90
+ else
91
+ quote_and_escape(adapter.type_cast(value, column, true))
92
+ end
93
+ else
94
+ res = adapter.type_cast(value, column, true)
95
+ if value.class < PostgresCompositeType
96
+ quote_and_escape(res)
97
+ else
98
+ res
99
+ end
100
+ end
101
+ end
102
+ "(#{quoted_values.join(',')})"
103
+ end
104
104
 
105
105
 
106
- end
107
- end
106
+ end
107
+ end
108
108
  end
@@ -1,95 +1,46 @@
1
1
  module ActiveRecord
2
- module ConnectionAdapters
3
- class PostgreSQLColumn
4
- class CompositeTypeParser
5
- DOUBLE_QUOTE = '"'
6
- BACKSLASH = "\\"
7
- COMMA = ','
8
- BRACKET_OPEN = '('
9
- BRACKET_CLOSE = ')'
10
-
11
- def self.parse_data(string)
12
- new.parse_data(string)
13
- end
14
-
15
- def parse_data(string)
16
- local_index = 0
17
- array = []
18
- while (local_index < string.length)
19
- case string[local_index]
20
- when BRACKET_OPEN
21
- local_index, array = parse_composite_type_contents(array, string, local_index + 1)
22
- when BRACKET_CLOSE
23
- return array
24
- end
25
- local_index += 1
26
- end
27
-
28
- array
29
- end
30
-
31
- private
32
- def parse_composite_type_contents(array, string, index)
33
- is_escaping = false
34
- is_quoted = false
35
- was_quoted = false
36
- current_item = ''
37
-
38
- local_index = index
39
- while local_index
40
- token = string[local_index]
41
- if is_escaping
42
- current_item << token
43
- is_escaping = false
44
- else
45
- if is_quoted
46
- case token
47
- when DOUBLE_QUOTE
48
- is_quoted = false
49
- was_quoted = true
50
- when BACKSLASH
51
- is_escaping = true
52
- else
53
- current_item << token
54
- end
55
- else
56
- case token
57
- when BACKSLASH
58
- is_escaping = true
59
- when COMMA
60
- add_item_to_array(array, current_item, was_quoted)
61
- current_item = ''
62
- was_quoted = false
63
- when DOUBLE_QUOTE
64
- is_quoted = true
65
- when BRACKET_OPEN
66
- internal_items = []
67
- local_index, internal_items = parse_composite_type_contents(internal_items, string, local_index + 1)
68
- array.push(internal_items)
69
- when BRACKET_CLOSE
70
- add_item_to_array(array, current_item, was_quoted)
71
- return local_index, array
72
- else
73
- current_item << token
74
- end
75
- end
76
- end
77
-
78
- local_index += 1
79
- end
80
- return local_index, array
81
- end
82
-
83
- def add_item_to_array(array, current_item, quoted)
84
- return if !quoted && current_item.length == 0
85
-
86
- if !quoted && current_item == 'NULL'
87
- array.push nil
88
- else
89
- array.push current_item
90
- end
91
- end
92
- end
93
- end
94
- end
2
+ module ConnectionAdapters
3
+ class PostgreSQLColumn
4
+ class CompositeTypeParser
5
+ class Splitter < StringScanner
6
+ OPEN_PAREN = /\(/.freeze
7
+ CLOSE_PAREN = /\)/.freeze
8
+ UNQUOTED_RE = /[^,)]*/.freeze
9
+ SEP_RE = /[,)]/.freeze
10
+ QUOTE_RE = /"/.freeze
11
+ QUOTE_SEP_RE = /"[,)]/.freeze
12
+ QUOTED_RE = /(\\.|""|[^"])*/.freeze
13
+ REPLACE_RE = /\\(.)|"(")/.freeze
14
+ REPLACE_WITH = '\1\2'.freeze
15
+
16
+ # Split the stored string into an array of strings, handling
17
+ # the different types of quoting.
18
+ def parse
19
+ return @result if @result
20
+ values = []
21
+ skip(OPEN_PAREN)
22
+ if skip(CLOSE_PAREN)
23
+ values << nil
24
+ else
25
+ until eos?
26
+ if skip(QUOTE_RE)
27
+ values << scan(QUOTED_RE).gsub(REPLACE_RE, REPLACE_WITH)
28
+ skip(QUOTE_SEP_RE)
29
+ else
30
+ v = scan(UNQUOTED_RE)
31
+ values << (v unless v.empty?)
32
+ skip(SEP_RE)
33
+ end
34
+ end
35
+ end
36
+ values
37
+ end
38
+ end
39
+
40
+ def self.parse_data(string)
41
+ Splitter.new(string).parse
42
+ end
43
+ end
44
+ end
45
+ end
95
46
  end
@@ -6,12 +6,12 @@ require 'rails'
6
6
 
7
7
  class PostgresCompositeTypesRailtie < Rails::Railtie
8
8
 
9
- initializer 'activerecord-postgres-composite-types' do
10
- ActiveSupport.on_load :active_record do
11
- require "activerecord-postgres-composite-types/active_record"
12
- require 'postgres_composite_type'
13
- end
14
- end
9
+ initializer 'activerecord-postgres-composite-types' do
10
+ ActiveSupport.on_load :active_record do
11
+ require "activerecord-postgres-composite-types/active_record"
12
+ require 'postgres_composite_type'
13
+ end
14
+ end
15
15
 
16
16
  end
17
17
 
@@ -1,106 +1,106 @@
1
1
  require 'activerecord-postgres-composite-types/active_record'
2
2
 
3
3
  class PostgresCompositeType
4
- include Comparable
4
+ include Comparable
5
5
 
6
- class << self
7
- # The PostgreSQL type name as symbol
8
- attr_reader :type
9
- # Column definition read from db schema
10
- attr_reader :columns
6
+ class << self
7
+ # The PostgreSQL type name as symbol
8
+ attr_reader :type
9
+ # Column definition read from db schema
10
+ attr_reader :columns
11
11
 
12
- # Link PostgreSQL type given by the name with this class.
13
- # Usage:
14
- #
15
- # class ComplexType < PostgresCompositeType
16
- # register_type :complex
17
- # end
18
- #
19
- # @param [Symbol] :type the PostgreSQL type name
20
- def register_type(type)
21
- @type = type.to_sym
22
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.register_composite_type_class(self)
23
- end
12
+ # Link PostgreSQL type given by the name with this class.
13
+ # Usage:
14
+ #
15
+ # class ComplexType < PostgresCompositeType
16
+ # register_type :complex
17
+ # end
18
+ #
19
+ # @param [Symbol] :type the PostgreSQL type name
20
+ def register_type(type)
21
+ @type = type.to_sym
22
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.register_composite_type_class(self)
23
+ end
24
24
 
25
- # Be default the ActiveRecord::Base connection is used when reading type definition.
26
- # If you want to use connection linked with another class use this method.
27
- # Usage
28
- #
29
- # class ComplexType < PostgresCompositeType
30
- # register_type :complex
31
- # use_connection_class MyRecordConnectedToDifferentDB
32
- # end
33
- #
34
- # @param [Class] :active_record_class the ActiveRecord model class
35
- def use_connection_class(active_record_class)
36
- @connection_class = active_record_class
37
- end
25
+ # Be default the ActiveRecord::Base connection is used when reading type definition.
26
+ # If you want to use connection linked with another class use this method.
27
+ # Usage
28
+ #
29
+ # class ComplexType < PostgresCompositeType
30
+ # register_type :complex
31
+ # use_connection_class MyRecordConnectedToDifferentDB
32
+ # end
33
+ #
34
+ # @param [Class] :active_record_class the ActiveRecord model class
35
+ def use_connection_class(active_record_class)
36
+ @connection_class = active_record_class
37
+ end
38
38
 
39
- # :nodoc:
40
- def connection
41
- (@connection_class || ActiveRecord::Base).connection
42
- end
39
+ # :nodoc:
40
+ def connection
41
+ (@connection_class || ActiveRecord::Base).connection
42
+ end
43
43
 
44
- # :nodoc:
45
- def connected?
46
- (@connection_class || ActiveRecord::Base).connected?
47
- end
44
+ # :nodoc:
45
+ def connected?
46
+ (@connection_class || ActiveRecord::Base).connected?
47
+ end
48
48
 
49
- # :nodoc:
50
- def initialize_column_definition
51
- unless @columns
52
- @columns = self.connection.columns(type)
53
- attr_accessor *@columns.map(&:name)
54
- end
55
- end
56
- end
49
+ # :nodoc:
50
+ def initialize_column_definition
51
+ unless @columns
52
+ @columns = self.connection.columns(type)
53
+ attr_accessor *@columns.map(&:name)
54
+ end
55
+ end
56
+ end
57
57
 
58
- def initialize(value)
59
- self.class.initialize_column_definition
58
+ def initialize(value)
59
+ self.class.initialize_column_definition
60
60
 
61
- case value
62
- when String
63
- ActiveRecord::ConnectionAdapters::PostgreSQLColumn.string_to_composite_type(self.class, value)
64
- when Array
65
- set_values value
66
- when Hash
67
- set_attributes value
68
- else
69
- raise "Unexpected value: #{value.inspect}"
70
- end
71
- end
61
+ case value
62
+ when String
63
+ ActiveRecord::ConnectionAdapters::PostgreSQLColumn.string_to_composite_type(self.class, value)
64
+ when Array
65
+ set_values value
66
+ when Hash
67
+ set_attributes value
68
+ else
69
+ raise "Unexpected value: #{value.inspect}"
70
+ end
71
+ end
72
72
 
73
- def <=>(another)
74
- return nil if (self.class <=> another.class) == nil
75
- self.class.columns.each do |column|
76
- v1 = self.send(column.name)
77
- v2 = another.send(column.name)
78
- return v1 <=> v2 unless v1 == v2
79
- end
80
- 0
81
- end
73
+ def <=>(another)
74
+ return nil if (self.class <=> another.class) == nil
75
+ self.class.columns.each do |column|
76
+ v1 = self.send(column.name)
77
+ v2 = another.send(column.name)
78
+ return v1 <=> v2 unless v1 == v2
79
+ end
80
+ 0
81
+ end
82
82
 
83
- private
83
+ private
84
84
 
85
- def set_attributes(values)
86
- values.each do |name, value|
87
- if Hash === value || Array === value
88
- klass = self.class.columns.find(name).first.try(:composite_type_class)
89
- value = klass.new(value) if klass
90
- end
91
- send "#{name}=", value
92
- end
93
- end
85
+ def set_attributes(values)
86
+ values.each do |name, value|
87
+ if Hash === value || Array === value
88
+ klass = self.class.columns.find(name).first.try(:composite_type_class)
89
+ value = klass.new(value) if klass
90
+ end
91
+ send "#{name}=", value
92
+ end
93
+ end
94
94
 
95
- def set_values(values)
96
- raise "Invalid values count: #{values.size}, expected: #{self.class.columns.size}" if values.size != self.class.columns.size
97
- self.class.columns.each.with_index do |column, i|
98
- if Hash === values[i] || Array === values[i]
99
- klass = column.composite_type_class
100
- values[i] = klass.new(values[i]) if klass
101
- end
102
- send "#{column.name}=", values[i]
103
- end
104
- end
95
+ def set_values(values)
96
+ raise "Invalid values count: #{values.size}, expected: #{self.class.columns.size}" if values.size != self.class.columns.size
97
+ self.class.columns.each.with_index do |column, i|
98
+ if Hash === values[i] || Array === values[i]
99
+ klass = column.composite_type_class
100
+ values[i] = klass.new(values[i]) if klass
101
+ end
102
+ send "#{column.name}=", values[i]
103
+ end
104
+ end
105
105
 
106
106
  end