activerecord-postgres-composite-types 0.2.4 → 0.2.5

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.
@@ -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