activerecord-postgres-hstore 0.1.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/activerecord-postgres-hstore.gemspec +3 -2
- data/app/Gemfile +1 -1
- data/app/Gemfile.lock +8 -2
- data/app/config/application.rb +1 -0
- data/app/db/development_structure.sql +12 -10
- data/app/db/schema.rb +13 -1
- data/lib/activerecord-postgres-hstore.rb +8 -2
- data/lib/activerecord-postgres-hstore/activerecord.rb +20 -25
- data/lib/activerecord-postgres-hstore/hash.rb +17 -4
- data/lib/activerecord-postgres-hstore/string.rb +22 -14
- data/lib/templates/setup_hstore91.rb +9 -0
- data/spec/activerecord-postgres-hstore_spec.rb +56 -7
- metadata +6 -5
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{activerecord-postgres-hstore}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Juan Maiz", "Diogo Biazus"]
|
12
|
-
s.date = %q{2011-
|
12
|
+
s.date = %q{2011-12-28}
|
13
13
|
s.description = %q{This gem adds support for the postgres hstore type. It is the _just right_ alternative for storing hashes instead of using seralization or dynamic tables.}
|
14
14
|
s.email = %q{juanmaiz@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -84,6 +84,7 @@ Gem::Specification.new do |s|
|
|
84
84
|
"lib/activerecord-postgres-hstore/hash.rb",
|
85
85
|
"lib/activerecord-postgres-hstore/string.rb",
|
86
86
|
"lib/templates/setup_hstore.rb",
|
87
|
+
"lib/templates/setup_hstore91.rb",
|
87
88
|
"spec/activerecord-postgres-hstore_spec.rb",
|
88
89
|
"spec/spec_helper.rb"
|
89
90
|
]
|
data/app/Gemfile
CHANGED
data/app/Gemfile.lock
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../
|
3
|
+
specs:
|
4
|
+
activerecord-postgres-hstore (0.1.2)
|
5
|
+
activerecord
|
6
|
+
rake
|
7
|
+
|
1
8
|
GEM
|
2
9
|
remote: http://rubygems.org/
|
3
10
|
specs:
|
@@ -24,7 +31,6 @@ GEM
|
|
24
31
|
activesupport (= 3.0.0)
|
25
32
|
arel (~> 1.0.0)
|
26
33
|
tzinfo (~> 0.3.23)
|
27
|
-
activerecord-postgres-hstore (0.0.2)
|
28
34
|
activeresource (3.0.0)
|
29
35
|
activemodel (= 3.0.0)
|
30
36
|
activesupport (= 3.0.0)
|
@@ -70,6 +76,6 @@ PLATFORMS
|
|
70
76
|
ruby
|
71
77
|
|
72
78
|
DEPENDENCIES
|
73
|
-
activerecord-postgres-hstore
|
79
|
+
activerecord-postgres-hstore!
|
74
80
|
pg
|
75
81
|
rails (= 3.0.0)
|
data/app/config/application.rb
CHANGED
@@ -267,6 +267,15 @@ CREATE FUNCTION hs_contains(hstore, hstore) RETURNS boolean
|
|
267
267
|
AS '$libdir/hstore', 'hs_contains';
|
268
268
|
|
269
269
|
|
270
|
+
--
|
271
|
+
-- Name: hstore(text, text); Type: FUNCTION; Schema: public; Owner: -
|
272
|
+
--
|
273
|
+
|
274
|
+
CREATE FUNCTION hstore(text, text) RETURNS hstore
|
275
|
+
LANGUAGE c IMMUTABLE
|
276
|
+
AS '$libdir/hstore', 'tconvert';
|
277
|
+
|
278
|
+
|
270
279
|
--
|
271
280
|
-- Name: isdefined(hstore, text); Type: FUNCTION; Schema: public; Owner: -
|
272
281
|
--
|
@@ -553,13 +562,6 @@ ALTER TABLE ONLY foos
|
|
553
562
|
ADD CONSTRAINT foos_pkey PRIMARY KEY (id);
|
554
563
|
|
555
564
|
|
556
|
-
--
|
557
|
-
-- Name: bars_gin_data; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
558
|
-
--
|
559
|
-
|
560
|
-
CREATE INDEX bars_gin_data ON bars USING gin (data);
|
561
|
-
|
562
|
-
|
563
565
|
--
|
564
566
|
-- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
565
567
|
--
|
@@ -571,8 +573,8 @@ CREATE UNIQUE INDEX unique_schema_migrations ON schema_migrations USING btree (v
|
|
571
573
|
-- PostgreSQL database dump complete
|
572
574
|
--
|
573
575
|
|
574
|
-
INSERT INTO schema_migrations (version) VALUES ('20100906191151');
|
575
|
-
|
576
576
|
INSERT INTO schema_migrations (version) VALUES ('20100906191457');
|
577
577
|
|
578
|
-
INSERT INTO schema_migrations (version) VALUES ('20100906191506');
|
578
|
+
INSERT INTO schema_migrations (version) VALUES ('20100906191506');
|
579
|
+
|
580
|
+
INSERT INTO schema_migrations (version) VALUES ('20100906191151');
|
data/app/db/schema.rb
CHANGED
@@ -11,5 +11,17 @@
|
|
11
11
|
# It's strongly recommended to check this file into your version control system.
|
12
12
|
|
13
13
|
ActiveRecord::Schema.define(:version => 20100906191506) do
|
14
|
-
|
14
|
+
|
15
|
+
create_table "bars", :force => true do |t|
|
16
|
+
t.hstore "data"
|
17
|
+
t.datetime "created_at"
|
18
|
+
t.datetime "updated_at"
|
19
|
+
end
|
20
|
+
|
21
|
+
create_table "foos", :force => true do |t|
|
22
|
+
t.text "data"
|
23
|
+
t.datetime "created_at"
|
24
|
+
t.datetime "updated_at"
|
25
|
+
end
|
26
|
+
|
15
27
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'rails'
|
2
2
|
require 'rails/generators'
|
3
3
|
require 'rails/generators/migration'
|
4
|
+
require 'pg'
|
4
5
|
|
5
6
|
# = Hstore Railtie
|
6
7
|
#
|
@@ -39,10 +40,15 @@ class Hstore < Rails::Railtie
|
|
39
40
|
end
|
40
41
|
|
41
42
|
def create_migration_file
|
42
|
-
|
43
|
+
pgversion = ActiveRecord::Base.connection.send(:postgresql_version)
|
44
|
+
if pgversion < 90100
|
45
|
+
migration_template 'setup_hstore.rb', 'db/migrate/setup_hstore.rb'
|
46
|
+
else
|
47
|
+
migration_template 'setup_hstore91.rb', 'db/migrate/setup_hstore.rb'
|
48
|
+
end
|
43
49
|
end
|
44
50
|
|
45
51
|
end
|
46
52
|
end
|
47
53
|
require "activerecord-postgres-hstore/string"
|
48
|
-
require "activerecord-postgres-hstore/hash"
|
54
|
+
require "activerecord-postgres-hstore/hash"
|
@@ -77,12 +77,10 @@ module ActiveRecord
|
|
77
77
|
if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
|
78
78
|
if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
|
79
79
|
value = read_attribute(name)
|
80
|
-
if
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
value = value.to_yaml
|
85
|
-
end
|
80
|
+
if self.class.columns_hash[name].type == :hstore && value && value.is_a?(Hash)
|
81
|
+
value = value.to_hstore # Done!
|
82
|
+
elsif value && self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time) || value.is_a?(Hash) || value.is_a?(Array))
|
83
|
+
value = value.to_yaml
|
86
84
|
end
|
87
85
|
attrs[self.class.arel_table[name]] = value
|
88
86
|
end
|
@@ -118,39 +116,36 @@ module ActiveRecord
|
|
118
116
|
end
|
119
117
|
|
120
118
|
class PostgreSQLColumn < Column
|
121
|
-
alias :old_type_cast_code :type_cast_code
|
122
|
-
alias :old_simplified_type :simplified_type
|
123
|
-
|
124
119
|
# Does the type casting from hstore columns using String#from_hstore or Hash#from_hstore.
|
125
|
-
def
|
126
|
-
type == :hstore ? "#{var_name}.from_hstore" :
|
120
|
+
def type_cast_code_with_hstore(var_name)
|
121
|
+
type == :hstore ? "#{var_name}.from_hstore" : type_cast_code_without_hstore(var_name)
|
127
122
|
end
|
128
123
|
|
129
124
|
# Adds the hstore type for the column.
|
130
|
-
def
|
131
|
-
field_type
|
125
|
+
def simplified_type_with_hstore(field_type)
|
126
|
+
field_type == 'hstore' ? :hstore : simplified_type_without_hstore(field_type)
|
132
127
|
end
|
133
|
-
|
128
|
+
|
129
|
+
alias_method_chain :type_cast_code, :hstore
|
130
|
+
alias_method_chain :simplified_type, :hstore
|
134
131
|
end
|
135
132
|
|
136
133
|
class PostgreSQLAdapter < AbstractAdapter
|
137
|
-
|
138
|
-
|
139
|
-
alias :old_columns :columns
|
140
|
-
alias :old_native_database_types :native_database_types
|
141
|
-
|
142
|
-
def native_database_types
|
143
|
-
old_native_database_types.merge({:hstore => { :name => "hstore" }})
|
134
|
+
def native_database_types_with_hstore
|
135
|
+
native_database_types_without_hstore.merge({:hstore => { :name => "hstore" }})
|
144
136
|
end
|
145
137
|
|
146
138
|
# Quotes correctly a hstore column value.
|
147
|
-
def
|
148
|
-
if value && column && column.sql_type
|
139
|
+
def quote_with_hstore(value, column = nil)
|
140
|
+
if value && column && column.sql_type == 'hstore'
|
149
141
|
raise HstoreTypeMismatch, "#{column.name} must have a Hash or a valid hstore value (#{value})" unless value.kind_of?(Hash) || value.valid_hstore?
|
150
|
-
return value.to_hstore
|
142
|
+
return quote_without_hstore(value.to_hstore, column)
|
151
143
|
end
|
152
|
-
|
144
|
+
quote_without_hstore(value,column)
|
153
145
|
end
|
146
|
+
|
147
|
+
alias_method_chain :quote, :hstore
|
148
|
+
alias_method_chain :native_database_types, :hstore
|
154
149
|
end
|
155
150
|
end
|
156
151
|
end
|
@@ -1,11 +1,24 @@
|
|
1
1
|
class Hash
|
2
2
|
|
3
|
-
# Generates
|
3
|
+
# Generates an hstore string format. This is the format used
|
4
4
|
# to insert or update stuff in the database.
|
5
5
|
def to_hstore
|
6
|
-
return "
|
7
|
-
|
8
|
-
map{|idx, val|
|
6
|
+
return "" if empty?
|
7
|
+
|
8
|
+
map { |idx, val|
|
9
|
+
iv = [idx,val].map { |_|
|
10
|
+
e = _.to_s.gsub(/"/, '\"')
|
11
|
+
if _.nil?
|
12
|
+
'NULL'
|
13
|
+
elsif e =~ /[,\s=>]/ || e.blank?
|
14
|
+
'"%s"' % e
|
15
|
+
else
|
16
|
+
e
|
17
|
+
end
|
18
|
+
}
|
19
|
+
|
20
|
+
"%s=>%s" % iv
|
21
|
+
} * ","
|
9
22
|
end
|
10
23
|
|
11
24
|
# If the method from_hstore is called in a Hash, it just returns self.
|
@@ -9,27 +9,35 @@ class String
|
|
9
9
|
# Validates the hstore format. Valid formats are:
|
10
10
|
# * An empty string
|
11
11
|
# * A string like %("foo"=>"bar"). I'll call it a "double quoted hstore format".
|
12
|
-
# * A string like %(
|
12
|
+
# * A string like %(foo=>bar). Postgres doesn't emit this but it does accept it as input, we should accept any input Postgres does
|
13
|
+
|
13
14
|
def valid_hstore?
|
14
|
-
|
15
|
-
#
|
16
|
-
dbl_quotes_re = /"([^"]+)"=>"([^"]+)"/
|
17
|
-
# TODO
|
18
|
-
# This is what comes from the plugin
|
19
|
-
# this is a big problem, 'cause regexes does not know how to count...
|
20
|
-
# how should i very values quoted with two single quotes? using .+ sux.
|
21
|
-
sngl_quotes_re = /'(.+)'=>'(.+)'/
|
22
|
-
self.match(dbl_quotes_re) || self.match(sngl_quotes_re)
|
15
|
+
pair = hstore_pair
|
16
|
+
!!match(/^\s*(#{pair}\s*(,\s*#{pair})*)?\s*$/)
|
23
17
|
end
|
24
18
|
|
25
19
|
# Creates a hash from a valid double quoted hstore format, 'cause this is the format
|
26
20
|
# that postgresql spits out.
|
27
21
|
def from_hstore
|
28
|
-
|
22
|
+
token_pairs = (scan(hstore_pair)).map { |k,v| [k,v =~ /^NULL$/i ? nil : v] }
|
23
|
+
token_pairs = token_pairs.map { |k,v|
|
24
|
+
[k,v].map { |t|
|
25
|
+
case t
|
26
|
+
when nil then t
|
27
|
+
when /^"(.*)"$/ then $1.gsub(/\\(.)/, '\1')
|
28
|
+
else t.gsub(/\\(.)/, '\1')
|
29
|
+
end
|
30
|
+
}
|
31
|
+
}
|
32
|
+
Hash[ token_pairs ]
|
29
33
|
end
|
30
34
|
|
31
|
-
|
32
|
-
self.gsub(/'/,"''")
|
33
|
-
end
|
35
|
+
private
|
34
36
|
|
37
|
+
def hstore_pair
|
38
|
+
quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
|
39
|
+
unquoted_string = /[^\s=,][^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
|
40
|
+
string = /(#{quoted_string}|#{unquoted_string})/
|
41
|
+
/#{string}\s*=>\s*#{string}/
|
42
|
+
end
|
35
43
|
end
|
@@ -1,9 +1,28 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
3
|
describe "ActiverecordPostgresHstore" do
|
4
|
+
it "should recognize a valid hstore string" do
|
5
|
+
"".valid_hstore?.should be_true
|
6
|
+
"a=>b".valid_hstore?.should be_true
|
7
|
+
'"a"=>"b"'.valid_hstore?.should be_true
|
8
|
+
'"a" => "b"'.valid_hstore?.should be_true
|
9
|
+
'"a"=>"b","c"=>"d"'.valid_hstore?.should be_true
|
10
|
+
'"a"=>"b", "c"=>"d"'.valid_hstore?.should be_true
|
11
|
+
'"a" => "b", "c"=>"d"'.valid_hstore?.should be_true
|
12
|
+
'"a"=>"b","c" => "d"'.valid_hstore?.should be_true
|
13
|
+
'k => v'.valid_hstore?.should be_true
|
14
|
+
'foo => bar, baz => whatever'.valid_hstore?.should be_true
|
15
|
+
'"1-a" => "anything at all"'.valid_hstore?.should be_true
|
16
|
+
'key => NULL'.valid_hstore?.should be_true
|
17
|
+
%q(c=>"}", "\"a\""=>"b \"a b").valid_hstore?.should be_true
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should not recognize an invalid hstore string" do
|
21
|
+
'"a"=>"b",Hello?'.valid_hstore?.should be_false
|
22
|
+
end
|
4
23
|
|
5
|
-
it "should convert hash to hstore string" do
|
6
|
-
|
24
|
+
it "should convert hash to hstore string and back (sort of)" do
|
25
|
+
{:a => 1, :b => 2}.to_hstore.from_hstore.should eq({"a" => "1", "b" => "2"})
|
7
26
|
end
|
8
27
|
|
9
28
|
it "should convert hstore string to hash" do
|
@@ -11,23 +30,53 @@ describe "ActiverecordPostgresHstore" do
|
|
11
30
|
end
|
12
31
|
|
13
32
|
it "should quote correctly" do
|
14
|
-
{:a => "'a'"}.to_hstore.should eq(
|
33
|
+
{:a => "'a'"}.to_hstore.should eq(%q(a=>'a'))
|
15
34
|
end
|
16
35
|
|
17
36
|
it "should quote keys correctly" do
|
18
|
-
{"'a'" => "a"}.to_hstore.should
|
37
|
+
{"'a'" => "a"}.to_hstore.should eq(%q('a'=>a))
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should preserve null values on store" do
|
41
|
+
# NULL=>b will be interpreted as the string pair "NULL"=>"b"
|
42
|
+
|
43
|
+
{'a' => nil,nil=>'b'}.to_hstore.should eq(%q(a=>NULL,NULL=>b))
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should preserve null values on load" do
|
47
|
+
'a=>null,b=>NuLl,c=>"NuLl",null=>c'.from_hstore.should eq({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'})
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should quote tokens with nothing space comma equals or greaterthan" do
|
51
|
+
{' '=>''}.to_hstore.should eq(%q(" "=>""))
|
52
|
+
{','=>''}.to_hstore.should eq(%q(","=>""))
|
53
|
+
{'='=>''}.to_hstore.should eq(%q("="=>""))
|
54
|
+
{'>'=>''}.to_hstore.should eq(%q(">"=>""))
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should unquote keys correctly with single quotes" do
|
58
|
+
"\"'a'\"=>\"a\"". from_hstore.should eq({"'a'" => "a"})
|
59
|
+
'\=a=>q=w'. from_hstore.should eq({"=a"=>"q=w"})
|
60
|
+
'"=a"=>q\=w'. from_hstore.should eq({"=a"=>"q=w"});
|
61
|
+
'"\"a"=>q>w'. from_hstore.should eq({"\"a"=>"q>w"});
|
62
|
+
'\"a=>q"w'. from_hstore.should eq({"\"a"=>"q\"w"})
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should quote keys and values correctly with combinations of single and double quotes" do
|
66
|
+
{ %q("a') => %q(b "a' b) }.to_hstore.should eq(%q(\"a'=>"b \"a' b"))
|
19
67
|
end
|
20
68
|
|
21
|
-
it "should unquote keys correctly" do
|
22
|
-
"\"
|
69
|
+
it "should unquote keys and values correctly with combinations of single and double quotes" do
|
70
|
+
%q("\"a'"=>"b \"a' b").from_hstore.should eq({%q("a') => %q(b "a' b)})
|
23
71
|
end
|
24
72
|
|
25
73
|
it "should convert empty hash" do
|
26
|
-
{}.to_hstore.should eq("
|
74
|
+
{}.to_hstore.should eq("")
|
27
75
|
end
|
28
76
|
|
29
77
|
it "should convert empty string" do
|
30
78
|
''.from_hstore.should eq({})
|
79
|
+
' '.from_hstore.should eq({})
|
31
80
|
end
|
32
81
|
|
33
82
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-postgres-hstore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 19
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 3
|
9
|
+
- 0
|
10
|
+
version: 0.3.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Juan Maiz
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2011-
|
19
|
+
date: 2011-12-28 00:00:00 -02:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
@@ -198,6 +198,7 @@ files:
|
|
198
198
|
- lib/activerecord-postgres-hstore/hash.rb
|
199
199
|
- lib/activerecord-postgres-hstore/string.rb
|
200
200
|
- lib/templates/setup_hstore.rb
|
201
|
+
- lib/templates/setup_hstore91.rb
|
201
202
|
- spec/activerecord-postgres-hstore_spec.rb
|
202
203
|
- spec/spec_helper.rb
|
203
204
|
has_rdoc: true
|