1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/connection_helper'
module PostgresqlCompositeBehavior
include ConnectionHelper
class PostgresqlComposite < ActiveRecord::Base
self.table_name = "postgresql_composites"
end
def setup
super
@connection = ActiveRecord::Base.connection
@connection.transaction do
@connection.execute <<-SQL
CREATE TYPE full_address AS
(
city VARCHAR(90),
street VARCHAR(90)
);
SQL
@connection.create_table('postgresql_composites') do |t|
t.column :address, :full_address
end
end
end
def teardown
super
@connection.execute 'DROP TABLE IF EXISTS postgresql_composites'
@connection.execute 'DROP TYPE IF EXISTS full_address'
reset_connection
PostgresqlComposite.reset_column_information
end
end
# Composites are mapped to `OID::Identity` by default. The user is informed by a warning like:
# "unknown OID 5653508: failed to recognize type of 'address'. It will be treated as String."
# To take full advantage of composite types, we suggest you register your own +OID::Type+.
# See PostgresqlCompositeWithCustomOIDTest
class PostgresqlCompositeTest < ActiveRecord::TestCase
include PostgresqlCompositeBehavior
def test_column
ensure_warning_is_issued
column = PostgresqlComposite.columns_hash["address"]
assert_nil column.type
assert_equal "full_address", column.sql_type
assert_not column.number?
assert_not column.binary?
assert_not column.array?
end
def test_composite_mapping
ensure_warning_is_issued
@connection.execute "INSERT INTO postgresql_composites VALUES (1, ROW('Paris', 'Champs-Élysées'));"
composite = PostgresqlComposite.first
assert_equal "(Paris,Champs-Élysées)", composite.address
composite.address = "(Paris,Rue Basse)"
composite.save!
assert_equal '(Paris,"Rue Basse")', composite.reload.address
end
private
def ensure_warning_is_issued
warning = capture(:stderr) do
PostgresqlComposite.columns_hash
end
assert_match(/unknown OID \d+: failed to recognize type of 'address'\. It will be treated as String\./, warning)
end
end
class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase
include PostgresqlCompositeBehavior
class FullAddressType < ActiveRecord::Type::Value
def type; :full_address end
def type_cast_from_database(value)
if value =~ /\("?([^",]*)"?,"?([^",]*)"?\)/
FullAddress.new($1, $2)
end
end
def type_cast_from_user(value)
value
end
def type_cast_for_database(value)
return if value.nil?
"(#{value.city},#{value.street})"
end
end
FullAddress = Struct.new(:city, :street)
def setup
super
@connection.type_map.register_type "full_address", FullAddressType.new
end
def test_column
column = PostgresqlComposite.columns_hash["address"]
assert_equal :full_address, column.type
assert_equal "full_address", column.sql_type
assert_not column.number?
assert_not column.binary?
assert_not column.array?
end
def test_composite_mapping
@connection.execute "INSERT INTO postgresql_composites VALUES (1, ROW('Paris', 'Champs-Élysées'));"
composite = PostgresqlComposite.first
assert_equal "Paris", composite.address.city
assert_equal "Champs-Élysées", composite.address.street
composite.address = FullAddress.new("Paris", "Rue Basse")
composite.save!
assert_equal 'Paris', composite.reload.address.city
assert_equal 'Rue Basse', composite.reload.address.street
end
end
|