activerecord-postgres-composite-types 0.2.5 → 0.2.6
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.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/Gemfile +2 -2
- data/VERSION +1 -1
- data/activerecord-postgres-composite-types.gemspec +15 -9
- data/lib/activerecord-postgres-composite-types/active_record.rb +23 -36
- data/lib/activerecord-postgres-composite-types/{active_record_3.rb → active_record_3_2.rb} +31 -8
- data/lib/activerecord-postgres-composite-types/{active_record_4.rb → active_record_4_1.rb} +12 -36
- data/lib/activerecord-postgres-composite-types/active_record_4_2.rb +38 -0
- data/lib/activerecord-postgres-composite-types/oid/composite_type_4_1.rb +29 -0
- data/lib/activerecord-postgres-composite-types/oid/composite_type_4_2.rb +60 -0
- data/lib/postgres_composite_type.rb +49 -10
- data/test/composite_types.rb +1 -1
- data/test/helper.rb +4 -0
- data/test/internal/db/schema.rb +6 -0
- data/test/test_composite_type_class.rb +6 -1
- data/test/test_nested_types.rb +5 -5
- data/test/test_postgres_composite_types.rb +7 -0
- metadata +26 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 147af26bcacdef7cd83aa2c4bb67dad6b0b6747f
|
|
4
|
+
data.tar.gz: 4f8e376d697804a4be2e04d3931047b1922b92ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7a503873a92e2c34cd620e52b1526a006910ecaa1a9b3ade3ab0498ecc771118eb3d9353cf2dd7df965895f41a6a8016388506194474e29a972cb762f11c196c
|
|
7
|
+
data.tar.gz: 47e52eda590974f4b530428313145ca0d8d210059e2a1bbd98d23e4bd817ae27e54dccf5aa7e2da592175751eec9834a154836caadee839cdae06eb94914f81c
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.2.
|
|
1
|
+
0.2.6
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
|
4
4
|
# -*- encoding: utf-8 -*-
|
|
5
|
-
# stub: activerecord-postgres-composite-types 0.2.
|
|
5
|
+
# stub: activerecord-postgres-composite-types 0.2.6 ruby lib
|
|
6
6
|
|
|
7
7
|
Gem::Specification.new do |s|
|
|
8
8
|
s.name = "activerecord-postgres-composite-types"
|
|
9
|
-
s.version = "0.2.
|
|
9
|
+
s.version = "0.2.6"
|
|
10
10
|
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
12
12
|
s.require_paths = ["lib"]
|
|
13
13
|
s.authors = ["Rafal Bigaj"]
|
|
14
|
-
s.date = "
|
|
14
|
+
s.date = "2015-03-20"
|
|
15
15
|
s.description = "This gem adds support to the ActiveRecord (3.x and 4.x) for composite types."
|
|
16
16
|
s.email = "rafal.bigaj@puzzleflow.com"
|
|
17
17
|
s.extra_rdoc_files = [
|
|
@@ -29,9 +29,12 @@ Gem::Specification.new do |s|
|
|
|
29
29
|
"activerecord-postgres-composite-types.gemspec",
|
|
30
30
|
"lib/activerecord-postgres-composite-types.rb",
|
|
31
31
|
"lib/activerecord-postgres-composite-types/active_record.rb",
|
|
32
|
-
"lib/activerecord-postgres-composite-types/
|
|
33
|
-
"lib/activerecord-postgres-composite-types/
|
|
32
|
+
"lib/activerecord-postgres-composite-types/active_record_3_2.rb",
|
|
33
|
+
"lib/activerecord-postgres-composite-types/active_record_4_1.rb",
|
|
34
|
+
"lib/activerecord-postgres-composite-types/active_record_4_2.rb",
|
|
34
35
|
"lib/activerecord-postgres-composite-types/composite_type_parser.rb",
|
|
36
|
+
"lib/activerecord-postgres-composite-types/oid/composite_type_4_1.rb",
|
|
37
|
+
"lib/activerecord-postgres-composite-types/oid/composite_type_4_2.rb",
|
|
35
38
|
"lib/activerecord-postgres-composite-types/railtie.rb",
|
|
36
39
|
"lib/postgres_composite_type.rb",
|
|
37
40
|
"test/composite_types.rb",
|
|
@@ -45,14 +48,14 @@ Gem::Specification.new do |s|
|
|
|
45
48
|
]
|
|
46
49
|
s.homepage = "http://github.com/puzzleflow/activerecord-postgres-composite-types"
|
|
47
50
|
s.licenses = ["MIT"]
|
|
48
|
-
s.rubygems_version = "2.4.
|
|
51
|
+
s.rubygems_version = "2.4.5"
|
|
49
52
|
s.summary = "ActiveRecord composite types support"
|
|
50
53
|
|
|
51
54
|
if s.respond_to? :specification_version then
|
|
52
55
|
s.specification_version = 4
|
|
53
56
|
|
|
54
57
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
|
55
|
-
s.add_runtime_dependency(%q<activerecord>, ["
|
|
58
|
+
s.add_runtime_dependency(%q<activerecord>, ["= 4.1.9"])
|
|
56
59
|
s.add_runtime_dependency(%q<pg>, [">= 0.17.0"])
|
|
57
60
|
s.add_development_dependency(%q<test-unit>, ["~> 2.1"])
|
|
58
61
|
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
|
@@ -62,8 +65,9 @@ Gem::Specification.new do |s|
|
|
|
62
65
|
s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
|
|
63
66
|
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
|
64
67
|
s.add_development_dependency(%q<combustion>, ["~> 0.5.2"])
|
|
68
|
+
s.add_development_dependency(%q<tzinfo-data>, [">= 0"])
|
|
65
69
|
else
|
|
66
|
-
s.add_dependency(%q<activerecord>, ["
|
|
70
|
+
s.add_dependency(%q<activerecord>, ["= 4.1.9"])
|
|
67
71
|
s.add_dependency(%q<pg>, [">= 0.17.0"])
|
|
68
72
|
s.add_dependency(%q<test-unit>, ["~> 2.1"])
|
|
69
73
|
s.add_dependency(%q<shoulda>, [">= 0"])
|
|
@@ -73,9 +77,10 @@ Gem::Specification.new do |s|
|
|
|
73
77
|
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
|
74
78
|
s.add_dependency(%q<simplecov>, [">= 0"])
|
|
75
79
|
s.add_dependency(%q<combustion>, ["~> 0.5.2"])
|
|
80
|
+
s.add_dependency(%q<tzinfo-data>, [">= 0"])
|
|
76
81
|
end
|
|
77
82
|
else
|
|
78
|
-
s.add_dependency(%q<activerecord>, ["
|
|
83
|
+
s.add_dependency(%q<activerecord>, ["= 4.1.9"])
|
|
79
84
|
s.add_dependency(%q<pg>, [">= 0.17.0"])
|
|
80
85
|
s.add_dependency(%q<test-unit>, ["~> 2.1"])
|
|
81
86
|
s.add_dependency(%q<shoulda>, [">= 0"])
|
|
@@ -85,6 +90,7 @@ Gem::Specification.new do |s|
|
|
|
85
90
|
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
|
86
91
|
s.add_dependency(%q<simplecov>, [">= 0"])
|
|
87
92
|
s.add_dependency(%q<combustion>, ["~> 0.5.2"])
|
|
93
|
+
s.add_dependency(%q<tzinfo-data>, [">= 0"])
|
|
88
94
|
end
|
|
89
95
|
end
|
|
90
96
|
|
|
@@ -9,38 +9,14 @@ module ActiveRecord
|
|
|
9
9
|
|
|
10
10
|
class PostgreSQLAdapter
|
|
11
11
|
|
|
12
|
-
# Quotes the column value to help prevent {SQL injection attacks}
|
|
13
|
-
def quote_with_composite_types(value, column = nil)
|
|
14
|
-
if value.class < PostgresCompositeType
|
|
15
|
-
"'#{PostgreSQLColumn.composite_type_to_string(value, self).gsub(/'/, "''")}'"
|
|
16
|
-
else
|
|
17
|
-
quote_without_composite_types(value, column)
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
alias_method_chain :quote, :composite_types
|
|
22
|
-
|
|
23
12
|
class << self
|
|
24
13
|
def register_composite_type_class(klass)
|
|
25
14
|
self.composite_type_classes[klass.type] = klass
|
|
26
15
|
TableDefinition.register_composite_type klass.type
|
|
27
16
|
Table.register_composite_type klass.type
|
|
28
|
-
register_arel_visitor klass
|
|
29
17
|
register_oid_type klass
|
|
30
18
|
end
|
|
31
19
|
|
|
32
|
-
def register_arel_visitor(klass)
|
|
33
|
-
Arel::Visitors::Visitor.class_eval <<-RUBY
|
|
34
|
-
def visit_#{klass.name.gsub('::', '_')}(o, a=nil)
|
|
35
|
-
"'" + ActiveRecord::ConnectionAdapters::PostgreSQLColumn::composite_type_to_string(o, o.class.connection) + "'" + '::#{klass.type}'
|
|
36
|
-
end
|
|
37
|
-
RUBY
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def register_oid_type(klass)
|
|
41
|
-
# only AR 4.X
|
|
42
|
-
end
|
|
43
|
-
|
|
44
20
|
# removes composite types definition (for testing)
|
|
45
21
|
def unregister_composite_types(*composite_types)
|
|
46
22
|
composite_types.each { |type| unregister_composite_type type }
|
|
@@ -86,7 +62,8 @@ module ActiveRecord
|
|
|
86
62
|
|
|
87
63
|
column = klass.columns[i]
|
|
88
64
|
raise "Invalid column index: #{i}" unless column
|
|
89
|
-
|
|
65
|
+
|
|
66
|
+
cv = column.type_cast_from_database(value)
|
|
90
67
|
if cv.is_a?(String)
|
|
91
68
|
# unquote
|
|
92
69
|
cv = cv.upcase == 'NULL' ? nil : cv.gsub(/\A"(.*)"\Z/m) { $1.gsub(/\\(.)/, '\1') }
|
|
@@ -94,18 +71,24 @@ module ActiveRecord
|
|
|
94
71
|
cv
|
|
95
72
|
end
|
|
96
73
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def simplified_type_with_composite_types(field_type)
|
|
100
|
-
type = field_type.to_sym
|
|
101
|
-
if PostgreSQLAdapter.composite_type_classes.has_key?(type)
|
|
102
|
-
type
|
|
103
|
-
else
|
|
104
|
-
simplified_type_without_composite_types(field_type)
|
|
105
|
-
end
|
|
74
|
+
unless method_defined?(:type_cast_from_database) # AR ver < 4.2
|
|
75
|
+
alias_method :type_cast_from_database, :type_cast
|
|
106
76
|
end
|
|
107
77
|
|
|
108
|
-
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
if private_method_defined?(:simplified_type) # up to v4.1
|
|
81
|
+
def simplified_type_with_composite_types(field_type)
|
|
82
|
+
type = field_type.to_sym
|
|
83
|
+
if PostgreSQLAdapter.composite_type_classes.has_key?(type)
|
|
84
|
+
type
|
|
85
|
+
else
|
|
86
|
+
simplified_type_without_composite_types(field_type)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
alias_method_chain :simplified_type, :composite_types
|
|
91
|
+
end
|
|
109
92
|
end
|
|
110
93
|
|
|
111
94
|
class << TableDefinition
|
|
@@ -160,4 +143,8 @@ module ActiveRecord
|
|
|
160
143
|
|
|
161
144
|
end
|
|
162
145
|
|
|
163
|
-
|
|
146
|
+
begin
|
|
147
|
+
require_relative "active_record_#{ActiveRecord::VERSION::STRING[0..2].sub('.', '_')}"
|
|
148
|
+
rescue LoadError
|
|
149
|
+
raise "Unsupported ActiveRecord version: #{ActiveRecord::VERSION::STRING}"
|
|
150
|
+
end
|
|
@@ -4,24 +4,43 @@ module ActiveRecord
|
|
|
4
4
|
module ConnectionAdapters
|
|
5
5
|
|
|
6
6
|
class PostgreSQLAdapter
|
|
7
|
-
|
|
7
|
+
# Quotes the column value to help prevent {SQL injection attacks}
|
|
8
|
+
def quote_with_composite_types(value, column = nil)
|
|
9
|
+
if value.class < PostgresCompositeType
|
|
10
|
+
"'#{PostgreSQLColumn.composite_type_to_string(value, self).gsub(/'/, "''")}'"
|
|
11
|
+
else
|
|
12
|
+
quote_without_composite_types(value, column)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
alias_method_chain :quote, :composite_types
|
|
16
|
+
|
|
17
|
+
# Cast a +value+ to a type that the database understands.
|
|
8
18
|
def type_cast_with_composite_types(value, column)
|
|
9
19
|
case value
|
|
10
20
|
when PostgresCompositeType
|
|
11
21
|
PostgreSQLColumn.composite_type_to_string(value, self)
|
|
12
22
|
when Array, Hash
|
|
13
|
-
if klass = column.composite_type_class
|
|
23
|
+
if (klass = column.composite_type_class)
|
|
14
24
|
value = klass.new(value)
|
|
15
25
|
PostgreSQLColumn.composite_type_to_string(value, self)
|
|
16
26
|
else
|
|
17
27
|
type_cast_without_composite_types(value, column)
|
|
18
28
|
end
|
|
19
|
-
|
|
29
|
+
else
|
|
20
30
|
type_cast_without_composite_types(value, column)
|
|
21
31
|
end
|
|
22
32
|
end
|
|
23
|
-
|
|
24
33
|
alias_method_chain :type_cast, :composite_types
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
def register_oid_type(klass)
|
|
37
|
+
Arel::Visitors::Visitor.module_eval <<-RUBY, __FILE__, __LINE__
|
|
38
|
+
def visit_#{klass.name.gsub('::', '_')}(o, a=nil)
|
|
39
|
+
o.quoted_value
|
|
40
|
+
end
|
|
41
|
+
RUBY
|
|
42
|
+
end
|
|
43
|
+
end
|
|
25
44
|
end
|
|
26
45
|
|
|
27
46
|
class PostgreSQLColumn < Column
|
|
@@ -29,13 +48,12 @@ module ActiveRecord
|
|
|
29
48
|
|
|
30
49
|
# Casts value (which is a String) to an appropriate instance.
|
|
31
50
|
def type_cast_with_composite_types(value)
|
|
32
|
-
if composite_type_klass = PostgreSQLAdapter.composite_type_classes[type]
|
|
51
|
+
if (composite_type_klass = PostgreSQLAdapter.composite_type_classes[type])
|
|
33
52
|
self.class.string_to_composite_type(composite_type_klass, value)
|
|
34
53
|
else
|
|
35
54
|
type_cast_without_composite_types(value)
|
|
36
55
|
end
|
|
37
56
|
end
|
|
38
|
-
|
|
39
57
|
alias_method_chain :type_cast, :composite_types
|
|
40
58
|
|
|
41
59
|
# quote_and_escape - Rails 4 code
|
|
@@ -63,7 +81,12 @@ module ActiveRecord
|
|
|
63
81
|
quote_and_escape(adapter.type_cast(value, column))
|
|
64
82
|
end
|
|
65
83
|
else
|
|
66
|
-
|
|
84
|
+
res = adapter.type_cast(value, column)
|
|
85
|
+
if value.class < PostgresCompositeType
|
|
86
|
+
quote_and_escape(res)
|
|
87
|
+
else
|
|
88
|
+
res
|
|
89
|
+
end
|
|
67
90
|
end
|
|
68
91
|
end
|
|
69
92
|
"(#{quoted_values.join(',')})"
|
|
@@ -86,7 +109,7 @@ module ActiveRecord
|
|
|
86
109
|
extend ActiveSupport::Concern
|
|
87
110
|
|
|
88
111
|
def type_cast_attribute_for_write(column, value)
|
|
89
|
-
if column && klass = column.composite_type_class
|
|
112
|
+
if column && !value.nil? && (klass = column.composite_type_class)
|
|
90
113
|
# Cast Hash and Array to composite type klass
|
|
91
114
|
if value.is_a?(klass)
|
|
92
115
|
value
|
|
@@ -1,33 +1,12 @@
|
|
|
1
|
-
# ActiveRecord
|
|
1
|
+
# ActiveRecord 4.0, 4.1 specific extensions.
|
|
2
|
+
|
|
3
|
+
require_relative 'oid/composite_type_4_1'
|
|
4
|
+
|
|
2
5
|
module ActiveRecord
|
|
3
6
|
|
|
4
7
|
module ConnectionAdapters
|
|
5
8
|
|
|
6
9
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
10
|
class << self
|
|
32
11
|
def register_oid_type(klass)
|
|
33
12
|
OID.register_type klass.type.to_s, OID::CompositeType.new(klass)
|
|
@@ -36,18 +15,15 @@ module ActiveRecord
|
|
|
36
15
|
end
|
|
37
16
|
end
|
|
38
17
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
unless type_map.key? row['typelem'].to_i
|
|
48
|
-
type_map[row['oid'].to_i] = vector
|
|
49
|
-
end
|
|
18
|
+
# Quotes the column value to help prevent {SQL injection attacks}
|
|
19
|
+
def quote_with_composite_types(value, column = nil)
|
|
20
|
+
if value.class < PostgresCompositeType
|
|
21
|
+
"'#{PostgreSQLColumn.composite_type_to_string(value, self).gsub(/'/, "''")}'"
|
|
22
|
+
else
|
|
23
|
+
quote_without_composite_types(value, column)
|
|
24
|
+
end
|
|
50
25
|
end
|
|
26
|
+
alias_method_chain :quote, :composite_types
|
|
51
27
|
|
|
52
28
|
# Cast a +value+ to a type that the database understands.
|
|
53
29
|
def type_cast_with_composite_types(value, column, array_member = false)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# ActiveRecord 4.2 specific extensions.
|
|
2
|
+
|
|
3
|
+
require_relative 'oid/composite_type_4_2'
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
|
|
7
|
+
module ConnectionAdapters
|
|
8
|
+
|
|
9
|
+
class PostgreSQLAdapter
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
mattr_accessor :ordered_composite_type_classes
|
|
13
|
+
self.ordered_composite_type_classes = []
|
|
14
|
+
|
|
15
|
+
def register_oid_type(klass)
|
|
16
|
+
self.ordered_composite_type_classes << klass
|
|
17
|
+
# Dirty. Only this type should be added to type map
|
|
18
|
+
klass.connection.send(:reload_type_map) if klass.connected?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Rails 4.2
|
|
23
|
+
def initialize_type_map_with_composite_types(m)
|
|
24
|
+
initialize_type_map_without_composite_types(m)
|
|
25
|
+
self.class.ordered_composite_type_classes.each do |klass|
|
|
26
|
+
m.register_type klass.type.to_s, OID::CompositeType.new(klass)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
alias_method_chain :initialize_type_map, :composite_types
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class PostgreSQLColumn < Column
|
|
33
|
+
def self.composite_type_to_string(object, adapter)
|
|
34
|
+
PostgreSQL::OID::CompositeType.new(object.class).type_cast_for_database(object)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module ConnectionAdapters
|
|
3
|
+
class PostgreSQLAdapter
|
|
4
|
+
module OID
|
|
5
|
+
class CompositeType < Type
|
|
6
|
+
def initialize(composite_type_class)
|
|
7
|
+
@composite_type_class = composite_type_class
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Casts value (which is a String) to an appropriate instance.
|
|
11
|
+
def type_cast(value)
|
|
12
|
+
PostgreSQLColumn.string_to_composite_type(@composite_type_class, value)
|
|
13
|
+
# @composite_type_class.new(value)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Casts a Ruby value to something appropriate for writing to the database.
|
|
17
|
+
def type_cast_for_write(value)
|
|
18
|
+
# Cast Hash and Array to composite type klass
|
|
19
|
+
if value.is_a?(@composite_type_class) || value.nil?
|
|
20
|
+
value
|
|
21
|
+
else
|
|
22
|
+
@composite_type_class.new(value)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module ConnectionAdapters
|
|
3
|
+
module PostgreSQL
|
|
4
|
+
module OID
|
|
5
|
+
class CompositeType < OID::Array
|
|
6
|
+
|
|
7
|
+
def initialize(composite_type_class)
|
|
8
|
+
@composite_type_class = composite_type_class
|
|
9
|
+
@delimiter = ','
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def type
|
|
13
|
+
@composite_type_class.type
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def type_cast_from_database(value)
|
|
17
|
+
PostgreSQLColumn.string_to_composite_type(@composite_type_class, value)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def type_cast_from_user(value)
|
|
21
|
+
if value.is_a?(@composite_type_class) || value.nil?
|
|
22
|
+
value
|
|
23
|
+
else
|
|
24
|
+
@composite_type_class.new(value)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def type_cast_for_database(object)
|
|
30
|
+
return object if object.is_a?(String) # already quoted by AREL visitor
|
|
31
|
+
return "NULL" if object == nil
|
|
32
|
+
quoted_values = object.class.columns.collect do |column|
|
|
33
|
+
value = object.send(column.name)
|
|
34
|
+
if String === value
|
|
35
|
+
quote_and_escape(column.type_cast_for_database(value))
|
|
36
|
+
else
|
|
37
|
+
res = column.type_cast_for_database(value)
|
|
38
|
+
if value.class < PostgresCompositeType
|
|
39
|
+
quote_and_escape(res)
|
|
40
|
+
else
|
|
41
|
+
res
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
"(#{quoted_values.join(',')})"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Overwrite OID::Array method - regular brackets () instead of {} have to be escaped
|
|
49
|
+
def string_requires_quoting?(string)
|
|
50
|
+
string.empty? ||
|
|
51
|
+
string == "NULL" ||
|
|
52
|
+
string =~ /[\(\)"\\\s]/ ||
|
|
53
|
+
string.include?(delimiter)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -50,14 +50,18 @@ class PostgresCompositeType
|
|
|
50
50
|
def initialize_column_definition
|
|
51
51
|
unless @columns
|
|
52
52
|
@columns = self.connection.columns(type)
|
|
53
|
-
|
|
53
|
+
attr_reader *@columns.map(&:name)
|
|
54
|
+
@columns.each do |column|
|
|
55
|
+
define_method("#{column.name}=") do |value|
|
|
56
|
+
instance_variable_set "@#{column.name}", type_cast_field_from_user(column, value)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
54
59
|
end
|
|
55
60
|
end
|
|
56
61
|
end
|
|
57
62
|
|
|
58
63
|
def initialize(value)
|
|
59
64
|
self.class.initialize_column_definition
|
|
60
|
-
|
|
61
65
|
case value
|
|
62
66
|
when String
|
|
63
67
|
ActiveRecord::ConnectionAdapters::PostgreSQLColumn.string_to_composite_type(self.class, value)
|
|
@@ -80,14 +84,45 @@ class PostgresCompositeType
|
|
|
80
84
|
0
|
|
81
85
|
end
|
|
82
86
|
|
|
87
|
+
# Used for AREL eq node
|
|
88
|
+
def quoted_value
|
|
89
|
+
"'" + ActiveRecord::ConnectionAdapters::PostgreSQLColumn::composite_type_to_string(self, self.class.connection) + "'::#{self.class.type}"
|
|
90
|
+
end
|
|
91
|
+
|
|
83
92
|
private
|
|
84
93
|
|
|
94
|
+
if ActiveRecord::ConnectionAdapters::PostgreSQLColumn.method_defined?(:type_cast_from_user) # v4.2 and later
|
|
95
|
+
def type_cast_field_from_user(column, value)
|
|
96
|
+
column.type_cast_from_user(value)
|
|
97
|
+
end
|
|
98
|
+
elsif ActiveRecord::ConnectionAdapters::PostgreSQLColumn.method_defined?(:type_cast_for_write) # v4.0 and v4.1
|
|
99
|
+
def type_cast_field_from_user(column, value)
|
|
100
|
+
column.type_cast_for_write(value)
|
|
101
|
+
end
|
|
102
|
+
else # v3.X
|
|
103
|
+
def type_cast_field_from_user(column, value)
|
|
104
|
+
klass = column.composite_type_class
|
|
105
|
+
if klass
|
|
106
|
+
case value
|
|
107
|
+
when Hash, Array
|
|
108
|
+
klass.new(value)
|
|
109
|
+
when String
|
|
110
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLColumn.string_to_composite_type(klass, value)
|
|
111
|
+
else
|
|
112
|
+
value
|
|
113
|
+
end
|
|
114
|
+
else
|
|
115
|
+
value
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
85
120
|
def set_attributes(values)
|
|
86
121
|
values.each do |name, value|
|
|
87
|
-
if Hash === value || Array === value
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
end
|
|
122
|
+
#if Hash === value || Array === value
|
|
123
|
+
# klass = self.class.columns.find(name).first.try(:composite_type_class)
|
|
124
|
+
# value = klass.new(value) if klass
|
|
125
|
+
#end
|
|
91
126
|
send "#{name}=", value
|
|
92
127
|
end
|
|
93
128
|
end
|
|
@@ -95,12 +130,16 @@ class PostgresCompositeType
|
|
|
95
130
|
def set_values(values)
|
|
96
131
|
raise "Invalid values count: #{values.size}, expected: #{self.class.columns.size}" if values.size != self.class.columns.size
|
|
97
132
|
self.class.columns.each.with_index do |column, i|
|
|
98
|
-
if Hash === values[i] || Array === values[i]
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
end
|
|
133
|
+
#if Hash === values[i] || Array === values[i]
|
|
134
|
+
# klass = column.composite_type_class
|
|
135
|
+
# values[i] = klass.new(values[i]) if klass
|
|
136
|
+
#end
|
|
102
137
|
send "#{column.name}=", values[i]
|
|
103
138
|
end
|
|
104
139
|
end
|
|
105
140
|
|
|
141
|
+
ActiveRecord::PredicateBuilder.register_handler(PostgresCompositeType, ->(attribute, value) {
|
|
142
|
+
quoted = Arel::Nodes::SqlLiteral.new(value.quoted_value)
|
|
143
|
+
Arel::Nodes::Equality.new(attribute, quoted)
|
|
144
|
+
}) if ActiveRecord::PredicateBuilder.respond_to?(:register_handler) # v.4.X
|
|
106
145
|
end
|
data/test/composite_types.rb
CHANGED
data/test/helper.rb
CHANGED
|
@@ -24,6 +24,8 @@ rescue Bundler::BundlerError => e
|
|
|
24
24
|
exit e.status_code
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
require 'shoulda'
|
|
28
|
+
|
|
27
29
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
28
30
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
29
31
|
|
|
@@ -36,6 +38,8 @@ end
|
|
|
36
38
|
Combustion.path = 'test/internal'
|
|
37
39
|
Combustion.initialize! :active_record
|
|
38
40
|
|
|
41
|
+
ActiveRecord::Base.default_timezone = :utc
|
|
42
|
+
|
|
39
43
|
class Test::Unit::TestCase
|
|
40
44
|
def connection
|
|
41
45
|
ActiveRecord::Base.connection
|
data/test/internal/db/schema.rb
CHANGED
|
@@ -7,6 +7,10 @@ ActiveRecord::Schema.define do
|
|
|
7
7
|
execute "CREATE TYPE nested_type AS (comp compfoo, color rgb_color)"
|
|
8
8
|
execute "CREATE TYPE nested_nested_type AS (nested nested_type, color rgb_color)"
|
|
9
9
|
|
|
10
|
+
create_table :my_table, :id => false do |t|
|
|
11
|
+
t.column :value, :my_type
|
|
12
|
+
end
|
|
13
|
+
|
|
10
14
|
create_table :foos, :id => false do |t|
|
|
11
15
|
t.column :comp, :compfoo, default: "(0,\"\")"
|
|
12
16
|
end
|
|
@@ -22,3 +26,5 @@ ActiveRecord::Schema.define do
|
|
|
22
26
|
execute "INSERT INTO foos VALUES ((0,'abc')), ((1,'a/b''c\\d e f'))"
|
|
23
27
|
execute "INSERT INTO bars VALUES (((0,'abc'),'red')), (((1,'cba'),'blue'))"
|
|
24
28
|
end
|
|
29
|
+
|
|
30
|
+
ActiveRecord::Base.connection.send(:reload_type_map) rescue nil # only v4.X
|
|
@@ -4,6 +4,10 @@ class TestCompositeTypeClass < Test::Unit::TestCase
|
|
|
4
4
|
|
|
5
5
|
PostgreSQLColumn = ActiveRecord::ConnectionAdapters::PostgreSQLColumn
|
|
6
6
|
|
|
7
|
+
setup do
|
|
8
|
+
@my_type_column = connection.columns(:my_table).first
|
|
9
|
+
end
|
|
10
|
+
|
|
7
11
|
should "define accessors" do
|
|
8
12
|
assert MyType.method_defined?(:name)
|
|
9
13
|
assert MyType.method_defined?(:name=)
|
|
@@ -36,7 +40,8 @@ class TestCompositeTypeClass < Test::Unit::TestCase
|
|
|
36
40
|
|
|
37
41
|
should "cast to qouted string" do
|
|
38
42
|
value = MyType.new(number: 1, name: '"\'a\'bc[]*/\"', date: Time.parse('2014-08-27 12:00:00 UTC'))
|
|
39
|
-
|
|
43
|
+
quoted = connection.quote(value, @my_type_column).sub(':00.000000', ':00 UTC') # On AR ver < 4.2 time is quoted to format with milliseconds
|
|
44
|
+
assert_equal %Q{'("\\\"''a''bc[]*/\\\\\\\"",1,2014-08-27 12:00:00 UTC)'}, quoted
|
|
40
45
|
end
|
|
41
46
|
|
|
42
47
|
should "parse string and return array" do
|
data/test/test_nested_types.rb
CHANGED
|
@@ -46,19 +46,19 @@ class TestNestedTypes < Test::Unit::TestCase
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
should "parser should work when nested attribute contains parenthesis" do
|
|
49
|
-
|
|
50
|
-
Bar2.all.to_a.last.nested
|
|
51
|
-
|
|
49
|
+
Bar2.create!(nested: {nested: {comp: [1, 'dc)))a'], color: 'blue'}, color: 'red'})
|
|
50
|
+
assert_equal 'dc)))a', Bar2.all.to_a.last.nested.nested.comp.f2
|
|
51
|
+
assert Bar2.where(nested: NestedNestedType.new(nested: {comp: [1, 'dc)))a'], color: 'blue'}, color: 'red')).exists?
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
should "parser should work when nested attribute contains double quote" do
|
|
55
|
-
|
|
55
|
+
Bar2.create!(nested: {nested: {comp: [1, "dc\"a"], color: 'blue'}, color: 'blue'})
|
|
56
56
|
assert_equal 'blue', Bar2.all.to_a.last.nested.color
|
|
57
57
|
assert_equal 'dc"a', Bar2.all.to_a.last.nested.nested.comp.f2
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
should "parser should work when nested attribute contains backslash" do
|
|
61
|
-
|
|
61
|
+
Bar2.create!(nested: {nested: {comp: [1, "dc\\a"], color: 'blue'}, color: 'green'})
|
|
62
62
|
assert_equal 'green', Bar2.all.to_a.last.nested.color
|
|
63
63
|
assert_equal 'dc\\a', Bar2.all.to_a.last.nested.nested.comp.f2
|
|
64
64
|
end
|
|
@@ -45,4 +45,11 @@ class TestPostgresCompositeTypes < Test::Unit::TestCase
|
|
|
45
45
|
assert_equal 'text 3', foo.comp.f2
|
|
46
46
|
assert Foo.where(comp: Compfoo.new({f1: 111, f2: 'text 3'})).exists?
|
|
47
47
|
end
|
|
48
|
+
|
|
49
|
+
should 'make object nil' do
|
|
50
|
+
foo = Foo.create!(comp: [333, 'ala ma kota'])
|
|
51
|
+
foo.comp = nil
|
|
52
|
+
|
|
53
|
+
assert_nil foo.comp
|
|
54
|
+
end
|
|
48
55
|
end
|
metadata
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-postgres-composite-types
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rafal Bigaj
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2015-03-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- -
|
|
17
|
+
- - '='
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
19
|
+
version: 4.1.9
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- -
|
|
24
|
+
- - '='
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
26
|
+
version: 4.1.9
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: pg
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -150,6 +150,20 @@ dependencies:
|
|
|
150
150
|
- - "~>"
|
|
151
151
|
- !ruby/object:Gem::Version
|
|
152
152
|
version: 0.5.2
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: tzinfo-data
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - ">="
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '0'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - ">="
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '0'
|
|
153
167
|
description: This gem adds support to the ActiveRecord (3.x and 4.x) for composite
|
|
154
168
|
types.
|
|
155
169
|
email: rafal.bigaj@puzzleflow.com
|
|
@@ -169,9 +183,12 @@ files:
|
|
|
169
183
|
- activerecord-postgres-composite-types.gemspec
|
|
170
184
|
- lib/activerecord-postgres-composite-types.rb
|
|
171
185
|
- lib/activerecord-postgres-composite-types/active_record.rb
|
|
172
|
-
- lib/activerecord-postgres-composite-types/
|
|
173
|
-
- lib/activerecord-postgres-composite-types/
|
|
186
|
+
- lib/activerecord-postgres-composite-types/active_record_3_2.rb
|
|
187
|
+
- lib/activerecord-postgres-composite-types/active_record_4_1.rb
|
|
188
|
+
- lib/activerecord-postgres-composite-types/active_record_4_2.rb
|
|
174
189
|
- lib/activerecord-postgres-composite-types/composite_type_parser.rb
|
|
190
|
+
- lib/activerecord-postgres-composite-types/oid/composite_type_4_1.rb
|
|
191
|
+
- lib/activerecord-postgres-composite-types/oid/composite_type_4_2.rb
|
|
175
192
|
- lib/activerecord-postgres-composite-types/railtie.rb
|
|
176
193
|
- lib/postgres_composite_type.rb
|
|
177
194
|
- test/composite_types.rb
|
|
@@ -202,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
202
219
|
version: '0'
|
|
203
220
|
requirements: []
|
|
204
221
|
rubyforge_project:
|
|
205
|
-
rubygems_version: 2.4.
|
|
222
|
+
rubygems_version: 2.4.5
|
|
206
223
|
signing_key:
|
|
207
224
|
specification_version: 4
|
|
208
225
|
summary: ActiveRecord composite types support
|