surus 0.0.1 → 0.1.0

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