activerecord-postgres-hstore 0.1.2 → 0.3.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.
- 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
|