surus 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ # 0.1.0 (February 2, 2012)
2
+
3
+ * Added array serializers
4
+ * Added array scope helpers
5
+ * Moved everything into Surus namespace
6
+ * Add true and false to types that are successfully round-tripped as hstore keys and values
7
+ * Changed default test database name to surus_test
8
+
9
+ # 0.0.1 (January 31, 2012)
10
+
11
+ * Initial release
data/README.md CHANGED
@@ -3,19 +3,23 @@ Surus
3
3
 
4
4
  # Description
5
5
 
6
- Surus extends ActiveRecord with PostgreSQL specific functionality. At the
7
- moment this is limited to hstore.
6
+ Surus extends ActiveRecord with PostgreSQL specific functionality. It includes
7
+ hstore and array serializers and helper scopes.
8
8
 
9
9
  # Installation
10
10
 
11
11
  gem install surus
12
+
13
+ or add to your Gemfile
14
+
15
+ gem 'surus'
12
16
 
13
17
  # Hstore
14
18
 
15
19
  Hashes can be serialized to an hstore column.
16
20
 
17
21
  class User < ActiveRecord::Base
18
- serialize :properties, Hstore::Serializer.new
22
+ serialize :properties, Surus::Hstore::Serializer.new
19
23
  end
20
24
 
21
25
  Even though the underlying hstore can only use strings for keys and values
@@ -29,6 +33,24 @@ Hstores can be searched with helper scopes.
29
33
  User.hstore_has_key(:properties, "favorite_color")
30
34
  User.hstore_has_all_keys(:properties, "favorite_color", "gender")
31
35
  User.hstore_has_any_keys(:properties, "favorite_color", "favorite_artist")
36
+
37
+ # Array
38
+
39
+ Ruby arrays can be serialized to PostgreSQL arrays. Surus includes support
40
+ for text, integer, float, and decimal arrays.
41
+
42
+ class User < ActiveRecord::Base
43
+ serialize :permissions, Surus::Array::TextSerializer.new
44
+ serialize :favorite_integers, Surus::Array::IntegerSerializer.new
45
+ serialize :favorite_floats, Surus::Array::FloatSerializer.new
46
+ serialize :favorite_decimals, Surus::Array::DecimalSerializer.new
47
+ end
48
+
49
+ Arrays can be searched with helper scopes.
50
+
51
+ User.array_has(:permissions, "admin")
52
+ User.array_has(:permissions, "manage_accounts", "manage_users")
53
+ User.array_has_any(:favorite_integers, 7, 11, 42)
32
54
 
33
55
  # License
34
56
 
@@ -2,3 +2,8 @@ require 'active_record'
2
2
  require 'surus/version'
3
3
  require 'surus/hstore/serializer'
4
4
  require 'surus/hstore/scope'
5
+ require 'surus/array/text_serializer'
6
+ require 'surus/array/integer_serializer'
7
+ require 'surus/array/float_serializer'
8
+ require 'surus/array/decimal_serializer'
9
+ require 'surus/array/scope'
@@ -0,0 +1,28 @@
1
+ module Surus
2
+ module Array
3
+ class DecimalSerializer
4
+ ARRAY_REGEX = %r{
5
+ (?<=[\{,]) (?# All elements are prefixed with either the opening brace or a comma)
6
+ \-?\d+\.?\d*
7
+ |
8
+ NULL
9
+ }x
10
+
11
+ def load(string)
12
+ return unless string
13
+ string.scan(ARRAY_REGEX).map do |match|
14
+ match == "NULL" ? nil : BigDecimal(match)
15
+ end
16
+ end
17
+
18
+ def dump(array)
19
+ return unless array
20
+ '{' + array.map { |s| format(s) }.join(",") + '}'
21
+ end
22
+
23
+ def format(value)
24
+ value == nil ? "NULL" : value
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ module Surus
2
+ module Array
3
+ class FloatSerializer
4
+ ARRAY_REGEX = %r{
5
+ (?<=[\{,]) (?# All elements are prefixed with either the opening brace or a comma)
6
+ \-?\d+(?:\.\d+)?(?:e[+-])?\d*
7
+ |
8
+ NULL
9
+ }x
10
+
11
+ def load(string)
12
+ return unless string
13
+ string.scan(ARRAY_REGEX).map do |match|
14
+ match == "NULL" ? nil : Float(match)
15
+ end
16
+ end
17
+
18
+ def dump(array)
19
+ return unless array
20
+ '{' + array.map { |s| format(s) }.join(",") + '}'
21
+ end
22
+
23
+ def format(value)
24
+ value == nil ? "NULL" : value
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ module Surus
2
+ module Array
3
+ class IntegerSerializer
4
+ ARRAY_REGEX = %r{
5
+ (?<=[\{,]) (?# All elements are prefixed with either the opening brace or a comma)
6
+ \-?\d+
7
+ |
8
+ NULL
9
+ }x
10
+
11
+ def load(string)
12
+ return unless string
13
+ string.scan(ARRAY_REGEX).map do |match|
14
+ match == "NULL" ? nil : Integer(match)
15
+ end
16
+ end
17
+
18
+ def dump(array)
19
+ return unless array
20
+ '{' + array.map { |s| format(s) }.join(",") + '}'
21
+ end
22
+
23
+ def format(value)
24
+ value == nil ? "NULL" : value
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ module Surus
2
+ module Array
3
+ module Scope
4
+ def array_has(column, *values)
5
+ where("#{connection.quote_column_name(column)} @> ARRAY[?]", values.flatten)
6
+ end
7
+
8
+ def array_has_any(column, *values)
9
+ where("#{connection.quote_column_name(column)} && ARRAY[?]", values.flatten)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ ActiveRecord::Base.extend Surus::Array::Scope
@@ -0,0 +1,47 @@
1
+ module Surus
2
+ module Array
3
+ class TextSerializer
4
+ ARRAY_REGEX = %r{
5
+ [{,] (?# All elements are prefixed with either the opening brace or a comma)
6
+ (?:
7
+ "
8
+ (?<quoted_string>(?:[^"\\]|\\.)*)
9
+ "
10
+ |
11
+ (?<null>NULL)
12
+ |
13
+ (?<unquoted_string>[^,}]+)
14
+ )
15
+ }x
16
+
17
+ def load(string)
18
+ return unless string
19
+ string.scan(ARRAY_REGEX).map do |quoted_string, null, unquoted_string|
20
+ element = quoted_string || unquoted_string
21
+ element ? unescape(element) : nil
22
+ end
23
+ end
24
+
25
+ def dump(array)
26
+ return unless array
27
+ '{' + array.map { |s| format(s) }.join(",") + '}'
28
+ end
29
+
30
+ def format(value)
31
+ value == nil ? "NULL" : '"' + escape(value) + '"'
32
+ end
33
+
34
+ def escape(value)
35
+ value
36
+ .gsub('\\', '\\\\\\')
37
+ .gsub('"', '\\"')
38
+ end
39
+
40
+ def unescape(value)
41
+ value
42
+ .gsub('\\\\', '\\')
43
+ .gsub('\\"', '"')
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,21 +1,23 @@
1
- module Hstore
2
- module Scope
3
- def hstore_has_pairs(column, hash)
4
- where("#{connection.quote_column_name(column)} @> ?", Serializer.new.dump(hash))
5
- end
6
-
7
- def hstore_has_key(column, key)
8
- where("#{connection.quote_column_name(column)} ? :key", :key => key)
9
- end
10
-
11
- def hstore_has_all_keys(column, *keys)
12
- where("#{connection.quote_column_name(column)} ?& ARRAY[:keys]", :keys => keys.flatten)
13
- end
14
-
15
- def hstore_has_any_keys(column, *keys)
16
- where("#{connection.quote_column_name(column)} ?| ARRAY[:keys]", :keys => keys.flatten)
1
+ module Surus
2
+ module Hstore
3
+ module Scope
4
+ def hstore_has_pairs(column, hash)
5
+ where("#{connection.quote_column_name(column)} @> ?", Serializer.new.dump(hash))
6
+ end
7
+
8
+ def hstore_has_key(column, key)
9
+ where("#{connection.quote_column_name(column)} ? :key", :key => key)
10
+ end
11
+
12
+ def hstore_has_all_keys(column, *keys)
13
+ where("#{connection.quote_column_name(column)} ?& ARRAY[:keys]", :keys => keys.flatten)
14
+ end
15
+
16
+ def hstore_has_any_keys(column, *keys)
17
+ where("#{connection.quote_column_name(column)} ?| ARRAY[:keys]", :keys => keys.flatten)
18
+ end
17
19
  end
18
20
  end
19
21
  end
20
22
 
21
- ActiveRecord::Base.extend Hstore::Scope
23
+ ActiveRecord::Base.extend Surus::Hstore::Scope
@@ -1,140 +1,150 @@
1
- module Hstore
2
- class Serializer
3
- KEY_VALUE_REGEX = %r{
4
- "
5
- ((?:[^"\\]|\\.)*)
6
- "
7
- =>
8
- (
1
+ module Surus
2
+ module Hstore
3
+ class Serializer
4
+ KEY_VALUE_REGEX = %r{
9
5
  "
10
- (?:[^"\\]|\\.)*
6
+ ((?:[^"\\]|\\.)*)
11
7
  "
12
- |(NULL)
13
- )
14
- }x
8
+ =>
9
+ (
10
+ "
11
+ (?:[^"\\]|\\.)*
12
+ "
13
+ |(NULL)
14
+ )
15
+ }x
15
16
 
16
- def load(string)
17
- return unless string
18
- stringified_hash = string.scan(KEY_VALUE_REGEX).each_with_object({}) do |key_value, hash|
19
- key, value = key_value
20
- key = unescape(key)
21
- value = if value == "NULL"
22
- nil
23
- else
24
- unescape(value[1..-2])
17
+ def load(string)
18
+ return unless string
19
+ stringified_hash = string.scan(KEY_VALUE_REGEX).each_with_object({}) do |key_value, hash|
20
+ key, value = key_value
21
+ key = unescape(key)
22
+ value = if value == "NULL"
23
+ nil
24
+ else
25
+ unescape(value[1..-2])
26
+ end
27
+
28
+ hash[key] = value
25
29
  end
30
+
31
+ key_types = stringified_hash.delete "__key_types"
32
+ key_types = YAML.load key_types if key_types
33
+ value_types = stringified_hash.delete "__value_types"
34
+ value_types = YAML.load value_types if value_types
26
35
 
27
- hash[key] = value
36
+ return stringified_hash unless key_types || value_types
37
+
38
+ stringified_hash.each_with_object({}) do |key_value, hash|
39
+ string_key, string_value = key_value
40
+
41
+ key = if key_types && key_types.key?(string_key)
42
+ typecast(string_key, key_types[string_key])
43
+ else
44
+ string_key
45
+ end
46
+
47
+ value = if value_types && value_types.key?(string_key)
48
+ typecast(string_value, value_types[string_key])
49
+ else
50
+ string_value
51
+ end
52
+
53
+ hash[key] = value
54
+ end
28
55
  end
29
-
30
- key_types = stringified_hash.delete "__key_types"
31
- key_types = YAML.load key_types if key_types
32
- value_types = stringified_hash.delete "__value_types"
33
- value_types = YAML.load value_types if value_types
34
56
 
35
- return stringified_hash unless key_types || value_types
36
-
37
- stringified_hash.each_with_object({}) do |key_value, hash|
38
- string_key, string_value = key_value
57
+ def dump(hash)
58
+ return unless hash
39
59
 
40
- key = if key_types && key_types.key?(string_key)
41
- typecast(string_key, key_types[string_key])
42
- else
43
- string_key
44
- end
60
+ key_types = {}
61
+ value_types = {}
45
62
 
46
- value = if value_types && value_types.key?(string_key)
47
- typecast(string_value, value_types[string_key])
48
- else
49
- string_value
63
+ stringified_hash = hash.each_with_object({}) do |key_value, stringified_hash|
64
+ key_string, key_type = stringify(key_value[0])
65
+ value_string, value_type = stringify(key_value[1])
66
+
67
+ stringified_hash[key_string] = value_string
68
+
69
+ key_types[key_string] = key_type unless key_type == "String"
70
+ value_types[key_string] = value_type unless value_type == "String"
50
71
  end
72
+
73
+ # Use YAML for recording types as it is much simpler than trying to
74
+ # handle all the special characters that could be in a key or value
75
+ # and encoding them again to fit into one string. Let YAML handle all
76
+ # the mess for us.
77
+ stringified_hash["__key_types"] = YAML.dump(key_types) if key_types.present?
78
+ stringified_hash["__value_types"] = YAML.dump(value_types) if value_types.present?
51
79
 
52
- hash[key] = value
53
- end
54
- end
55
-
56
- def dump(hash)
57
- return unless hash
80
+ stringified_hash.map do |key, value|
81
+ "#{format_key(key)}=>#{format_value(value)}"
82
+ end.join(", ")
83
+ end
58
84
 
59
- key_types = {}
60
- value_types = {}
85
+ def format_key(key)
86
+ %Q("#{escape(key)}")
87
+ end
61
88
 
62
- stringified_hash = hash.each_with_object({}) do |key_value, stringified_hash|
63
- key_string, key_type = stringify(key_value[0])
64
- value_string, value_type = stringify(key_value[1])
65
-
66
- stringified_hash[key_string] = value_string
67
-
68
- key_types[key_string] = key_type unless key_type == "String"
69
- value_types[key_string] = value_type unless value_type == "String"
89
+ def format_value(value)
90
+ value ? %Q("#{escape(value)}") : "NULL"
70
91
  end
71
-
72
- # Use YAML for recording types as it is much simpler than trying to
73
- # handle all the special characters that could be in a key or value
74
- # and encoding them again to fit into one string. Let YAML handle all
75
- # the mess for us.
76
- stringified_hash["__key_types"] = YAML.dump(key_types) if key_types.present?
77
- stringified_hash["__value_types"] = YAML.dump(value_types) if value_types.present?
78
92
 
79
- stringified_hash.map do |key, value|
80
- "#{format_key(key)}=>#{format_value(value)}"
81
- end.join(", ")
82
- end
83
-
84
- def format_key(key)
85
- %Q("#{escape(key)}")
86
- end
87
-
88
- def format_value(value)
89
- value ? %Q("#{escape(value)}") : "NULL"
90
- end
91
-
92
- # Escape a value for use as a key or value in an hstore
93
- def escape(value)
94
- value
95
- .gsub('\\', '\\\\\\')
96
- .gsub('"', '\\"')
97
- end
98
-
99
- # Unescape a value from a key or value in an hstore
100
- def unescape(value)
101
- value
102
- .gsub('\\\\', '\\')
103
- .gsub('\\"', '"')
104
- end
105
-
106
- # Returns an array of value as a string and value type
107
- def stringify(value)
108
- if value.kind_of?(String)
109
- [value, "String"]
110
- elsif value.kind_of?(Integer)
111
- [value.to_s, "Integer"]
112
- elsif value.kind_of?(Float)
113
- [value.to_s, "Float"]
114
- elsif value.kind_of?(BigDecimal)
115
- [value.to_s, "BigDecimal"]
116
- elsif value.kind_of?(Date)
117
- [value.to_s(:db), "Date"]
118
- elsif value == nil
119
- [nil, "String"] # we don't actually stringify nil because format_value special cases nil
120
- else
121
- [value.to_s, "String"] # coerce to string as we don't know how to reconstitue an unknown class
122
- end
123
- end
124
-
125
- def typecast(value, type)
126
- case type
127
- when "Integer"
128
- Integer(value)
129
- when "Float"
130
- Float(value)
131
- when "BigDecimal"
132
- BigDecimal(value)
133
- when "Date"
134
- Date.parse(value)
135
- else
136
- raise ArgumentError, "Can't typecast: #{type}"
93
+ # Escape a value for use as a key or value in an hstore
94
+ def escape(value)
95
+ value
96
+ .gsub('\\', '\\\\\\')
97
+ .gsub('"', '\\"')
98
+ end
99
+
100
+ # Unescape a value from a key or value in an hstore
101
+ def unescape(value)
102
+ value
103
+ .gsub('\\\\', '\\')
104
+ .gsub('\\"', '"')
105
+ end
106
+
107
+ # Returns an array of value as a string and value type
108
+ def stringify(value)
109
+ if value.kind_of?(String)
110
+ [value, "String"]
111
+ elsif value.kind_of?(Integer)
112
+ [value.to_s, "Integer"]
113
+ elsif value.kind_of?(Float)
114
+ [value.to_s, "Float"]
115
+ elsif value.kind_of?(BigDecimal)
116
+ [value.to_s, "BigDecimal"]
117
+ elsif value.kind_of?(Date)
118
+ [value.to_s(:db), "Date"]
119
+ elsif value.kind_of?(TrueClass)
120
+ [value.to_s, "TrueClass"]
121
+ elsif value.kind_of?(FalseClass)
122
+ [value.to_s, "FalseClass"]
123
+ elsif value == nil
124
+ [nil, "String"] # we don't actually stringify nil because format_value special cases nil
125
+ else
126
+ [value.to_s, "String"] # coerce to string as we don't know how to reconstitue an unknown class
127
+ end
128
+ end
129
+
130
+ def typecast(value, type)
131
+ case type
132
+ when "Integer"
133
+ Integer(value)
134
+ when "Float"
135
+ Float(value)
136
+ when "BigDecimal"
137
+ BigDecimal(value)
138
+ when "Date"
139
+ Date.parse(value)
140
+ when "TrueClass"
141
+ true
142
+ when "FalseClass"
143
+ false
144
+ else
145
+ raise ArgumentError, "Can't typecast: #{type}"
146
+ end
137
147
  end
138
148
  end
139
- end
140
- end
149
+ end
150
+ end
@@ -1,3 +1,3 @@
1
1
  module Surus
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Surus::Array::DecimalSerializer do
4
+ round_trip_examples = [
5
+ [nil, "nil"],
6
+ [[], "empty array"],
7
+ [[BigDecimal("0.0")], "single element"],
8
+ [[BigDecimal("1.0"), BigDecimal("2.0")], "multiple elements"],
9
+ [[BigDecimal("-1.0"), BigDecimal("-2.0")], "negative element"],
10
+ [[BigDecimal("1232493289348929843.323422349274382923")], "high magnitude element"],
11
+ [[BigDecimal("1.0"), BigDecimal("2.0"), BigDecimal("2.0")], "duplicated elements"],
12
+ [[BigDecimal("1.0"), nil], "an element is nil"],
13
+ [(10_000.times.map { |n| n * BigDecimal("1.13312") }).to_a, "huge array"]
14
+ ]
15
+
16
+ round_trip_examples.each do |value, description|
17
+ it "round trips when #{description}" do
18
+ r = DecimalArrayRecord.create! :decimals => value
19
+ r.reload
20
+ r.decimals.should == value
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Surus::Array::FloatSerializer do
4
+ round_trip_examples = [
5
+ [nil, "nil"],
6
+ [[], "empty array"],
7
+ [[0.0], "single element"],
8
+ [[1.0, 2.0], "multiple elements"],
9
+ [[-1.0, -2.0], "negative element"],
10
+ [[4.4325349e+45, 1.2324323e+77, 1.1242342e-57, 3e99], "high magnitude elements"],
11
+ [[1.0, 2.0, 2.0, 2.0], "duplicated elements"],
12
+ [[1.0, nil], "an element is nil"],
13
+ [(10_000.times.map { |n| n * 1.0 }).to_a, "huge array"]
14
+ ]
15
+
16
+ round_trip_examples.each do |value, description|
17
+ it "round trips when #{description}" do
18
+ r = FloatArrayRecord.create! :floats => value
19
+ r.reload
20
+ r.floats.should == value
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Surus::Array::IntegerSerializer do
4
+ round_trip_examples = [
5
+ [nil, "nil"],
6
+ [[], "empty array"],
7
+ [[0], "single element"],
8
+ [[1, 2], "multiple elements"],
9
+ [[-1, -2], "negative element"],
10
+ [[1, 2, 2, 2], "duplicated elements"],
11
+ [[1, nil], "an element is nil"],
12
+ [(1..10_000).to_a, "huge array"]
13
+ ]
14
+
15
+ round_trip_examples.each do |value, description|
16
+ it "round trips when #{description}" do
17
+ r = IntegerArrayRecord.create! :integers => value
18
+ r.reload
19
+ r.integers.should == value
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Surus::Array::Scope do
4
+ let!(:empty) { TextArrayRecord.create! :texts => [] }
5
+
6
+ context "array_has" do
7
+ let!(:match) { TextArrayRecord.create! :texts => %w{a b} }
8
+ let!(:missing_element) { TextArrayRecord.create! :texts => %w{a} }
9
+
10
+ def self.shared_examples
11
+ it { should include(match) }
12
+ it { should_not include(missing_element) }
13
+ it { should_not include(empty) }
14
+ end
15
+
16
+ context "with one element" do
17
+ subject { TextArrayRecord.array_has(:texts, "b").all }
18
+ shared_examples
19
+ end
20
+
21
+ context "with array of elements" do
22
+ subject { TextArrayRecord.array_has(:texts, ["a", "b"]).all }
23
+ shared_examples
24
+ end
25
+
26
+ context "with multiple elements" do
27
+ subject { TextArrayRecord.array_has(:texts, "a", "b").all }
28
+ shared_examples
29
+ end
30
+ end
31
+
32
+ context "array_has_any" do
33
+ let!(:match) { TextArrayRecord.create! :texts => %w{a b} }
34
+ let!(:missing_element) { TextArrayRecord.create! :texts => %w{a} }
35
+
36
+ def self.shared_examples
37
+ it { should include(match) }
38
+ it { should_not include(missing_element) }
39
+ it { should_not include(empty) }
40
+ end
41
+
42
+ context "with one element" do
43
+ subject { TextArrayRecord.array_has_any(:texts, "b").all }
44
+ shared_examples
45
+ end
46
+
47
+ context "with array of elements" do
48
+ subject { TextArrayRecord.array_has_any(:texts, ["b", "c"]).all }
49
+ shared_examples
50
+ end
51
+
52
+ context "with multiple elements" do
53
+ subject { TextArrayRecord.array_has_any(:texts, "b", "c").all }
54
+ shared_examples
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Surus::Array::TextSerializer do
4
+ round_trip_examples = [
5
+ [nil, "nil"],
6
+ [[], "empty array"],
7
+ [["foo"], "single element"],
8
+ [["foo", "bar"], "multiple elements"],
9
+ [["foo", "bar", "bar", "bar"], "duplicated elements"],
10
+ [["foo bar", nil], "an element is nil"],
11
+ [["foo bar", 'NULL'], "an element is the string 'NULL'"],
12
+ [["foo bar", "baz"], "an element has a space"],
13
+ [["foo,bar", "baz"], "an element has a comma (,)"],
14
+ [["foo'bar", "baz"], "an element has a single quote (')"],
15
+ [['foo"bar', "baz"], "an element has a double quote (\")"],
16
+ [['foo\\bar', "baz"], "an element has a backslash (\\)"],
17
+ [['foo{}bar', "{baz}"], "an element has a braces ({})"],
18
+ [[%q~foo \\ / " ; ' ( ) {}bar \\'~, "bar"], "an element many special characters"],
19
+ [("aaa".."zzz").to_a, "huge array"]
20
+ ]
21
+
22
+ round_trip_examples.each do |value, description|
23
+ it "round trips when #{description}" do
24
+ r = TextArrayRecord.create! :texts => value
25
+ r.reload
26
+ r.texts.should == value
27
+ end
28
+ end
29
+ end
@@ -1,4 +1,4 @@
1
1
  test:
2
2
  adapter: postgresql
3
3
  encoding: unicode
4
- database: ar_pg_test
4
+ database: surus_test
@@ -7,3 +7,38 @@ CREATE TABLE hstore_records(
7
7
  properties hstore
8
8
  );
9
9
 
10
+
11
+
12
+ DROP TABLE IF EXISTS text_array_records;
13
+
14
+ CREATE TABLE text_array_records(
15
+ id serial PRIMARY KEY,
16
+ texts text[]
17
+ );
18
+
19
+
20
+
21
+ DROP TABLE IF EXISTS integer_array_records;
22
+
23
+ CREATE TABLE integer_array_records(
24
+ id serial PRIMARY KEY,
25
+ integers integer[]
26
+ );
27
+
28
+
29
+
30
+ DROP TABLE IF EXISTS float_array_records;
31
+
32
+ CREATE TABLE float_array_records(
33
+ id serial PRIMARY KEY,
34
+ floats float[]
35
+ );
36
+
37
+
38
+
39
+ DROP TABLE IF EXISTS float_array_records;
40
+
41
+ CREATE TABLE decimal_array_records(
42
+ id serial PRIMARY KEY,
43
+ decimals decimal[]
44
+ );
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Hstore::Scope do
3
+ describe Surus::Hstore::Scope do
4
4
  let!(:empty) { HstoreRecord.create! :properties => {} }
5
5
 
6
6
  context "hstore_has_pairs" do
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Hstore::Serializer do
3
+ describe Surus::Hstore::Serializer do
4
4
  round_trip_examples = [
5
5
  [nil, "nil"],
6
6
  [{}, "empty hash"],
@@ -44,6 +44,8 @@ describe Hstore::Serializer do
44
44
  [BigDecimal("0"), "BigDecimal 0"],
45
45
  [BigDecimal("1"), "positive BigDecimal"],
46
46
  [BigDecimal("-1"), "negative BigDecimal"],
47
+ [true, "true"],
48
+ [false, "false"],
47
49
  [Date.today, "date"]
48
50
  ].each do |value, description|
49
51
  round_trip_examples << [{"foo" => value}, "value is #{description}"]
@@ -7,8 +7,25 @@ database_config = YAML.load_file(File.expand_path("../database.yml", __FILE__))
7
7
  ActiveRecord::Base.establish_connection database_config["test"]
8
8
 
9
9
 
10
+
10
11
  class HstoreRecord < ActiveRecord::Base
11
- serialize :properties, Hstore::Serializer.new
12
+ serialize :properties, Surus::Hstore::Serializer.new
13
+ end
14
+
15
+ class TextArrayRecord < ActiveRecord::Base
16
+ serialize :texts, Surus::Array::TextSerializer.new
17
+ end
18
+
19
+ class IntegerArrayRecord < ActiveRecord::Base
20
+ serialize :integers, Surus::Array::IntegerSerializer.new
21
+ end
22
+
23
+ class FloatArrayRecord < ActiveRecord::Base
24
+ serialize :floats, Surus::Array::FloatSerializer.new
25
+ end
26
+
27
+ class DecimalArrayRecord < ActiveRecord::Base
28
+ serialize :decimals, Surus::Array::DecimalSerializer.new
12
29
  end
13
30
 
14
31
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: surus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-01 00:00:00.000000000 Z
12
+ date: 2012-02-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pg
16
- requirement: &10920640 !ruby/object:Gem::Requirement
16
+ requirement: &17079080 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *10920640
24
+ version_requirements: *17079080
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: activerecord
27
- requirement: &10916840 !ruby/object:Gem::Requirement
27
+ requirement: &17091280 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 3.1.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *10916840
35
+ version_requirements: *17091280
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &10930900 !ruby/object:Gem::Requirement
38
+ requirement: &17089120 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 2.8.0
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *10930900
46
+ version_requirements: *17089120
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: guard
49
- requirement: &10927660 !ruby/object:Gem::Requirement
49
+ requirement: &17086120 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 0.10.0
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *10927660
57
+ version_requirements: *17086120
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: guard-rspec
60
- requirement: &10943960 !ruby/object:Gem::Requirement
60
+ requirement: &17102600 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,7 +65,7 @@ dependencies:
65
65
  version: 0.6.0
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *10943960
68
+ version_requirements: *17102600
69
69
  description: PostgreSQL extensions for ActiveRecord
70
70
  email:
71
71
  - jack@jackchristensen.com
@@ -75,15 +75,26 @@ extra_rdoc_files: []
75
75
  files:
76
76
  - .gitignore
77
77
  - .rspec
78
+ - CHANGELOG.md
78
79
  - Gemfile
79
80
  - Guardfile
80
81
  - LICENSE
81
82
  - README.md
82
83
  - Rakefile
83
84
  - lib/surus.rb
85
+ - lib/surus/array/decimal_serializer.rb
86
+ - lib/surus/array/float_serializer.rb
87
+ - lib/surus/array/integer_serializer.rb
88
+ - lib/surus/array/scope.rb
89
+ - lib/surus/array/text_serializer.rb
84
90
  - lib/surus/hstore/scope.rb
85
91
  - lib/surus/hstore/serializer.rb
86
92
  - lib/surus/version.rb
93
+ - spec/array/decimal_serializer_spec.rb
94
+ - spec/array/float_serializer_spec.rb
95
+ - spec/array/integer_serializer_spec.rb
96
+ - spec/array/scope_spec.rb
97
+ - spec/array/text_serializer_spec.rb
87
98
  - spec/database.yml
88
99
  - spec/database_structure.sql
89
100
  - spec/hstore/scope_spec.rb