| 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
132
 | 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.drop_table "postgresql_composites", if_exists: true
    @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::PostgreSQLTestCase
  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.array?
    type = PostgresqlComposite.type_for_attribute("address")
    assert_not type.binary?
  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::PostgreSQLTestCase
  include PostgresqlCompositeBehavior
  class FullAddressType < ActiveRecord::Type::Value
    def type; :full_address end
    def deserialize(value)
      if value =~ /\("?([^",]*)"?,"?([^",]*)"?\)/
        FullAddress.new($1, $2)
      end
    end
    def cast(value)
      value
    end
    def serialize(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.array?
    type = PostgresqlComposite.type_for_attribute("address")
    assert_not type.binary?
  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
 |