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 CHANGED
@@ -1 +1 @@
1
- 0.1.2
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.1.2"
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-08-02}
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
@@ -2,7 +2,7 @@ source 'http://rubygems.org'
2
2
 
3
3
  gem 'rails', '3.0.0'
4
4
  gem 'pg'
5
- gem 'activerecord-postgres-hstore', '0.0.2'
5
+ gem 'activerecord-postgres-hstore', :path => '../'
6
6
 
7
7
  # Use unicorn as the web server
8
8
  # gem 'unicorn'
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 (= 0.0.2)
79
+ activerecord-postgres-hstore!
74
80
  pg
75
81
  rails (= 3.0.0)
@@ -38,5 +38,6 @@ module Benchs
38
38
 
39
39
  # Configure sensitive parameters which will be filtered from the log file.
40
40
  config.filter_parameters += [:password]
41
+ config.active_record.schema_format = :sql
41
42
  end
42
43
  end
@@ -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
- execute File.read("#{Rails.root}/db/development_structure.sql")
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
- migration_template 'setup_hstore.rb', 'db/migrate/setup_hstore.rb'
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 value && ((self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))) || value.is_a?(Hash) || value.is_a?(Array))
81
- if self.class.columns_hash[name].type == :hstore
82
- value = value.to_hstore # Done!
83
- else
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 type_cast_code(var_name)
126
- type == :hstore ? "#{var_name}.from_hstore" : old_type_cast_code(var_name)
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 simplified_type(field_type)
131
- field_type =~ /^hstore$/ ? :hstore : old_simplified_type(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
- alias :old_quote :quote
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 quote(value, column = nil)
148
- if value && column && column.sql_type =~ /^hstore$/
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
- old_quote(value,column)
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 a single quoted hstore string format. This is the format used
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 "''" if empty?
7
- #@todo DIOGO! Check security issues with this quoting pleaz
8
- map{|idx, val| "('#{idx.to_s.escape_quotes}'=>'#{val.to_s.escape_quotes}')" }.join(' || ')
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 %('foo'=>'bar'). I'll call it a "single quoted hstore format".
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
- return true if empty? || self == "''"
15
- # This is what comes from the database
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
- Hash[ scan(/"([^"]+)"=>"([^"]+)"/) ]
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
- def escape_quotes
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
@@ -0,0 +1,9 @@
1
+ class SetupHstore < ActiveRecord::Migration
2
+ def self.up
3
+ execute "CREATE EXTENSION hstore"
4
+ end
5
+
6
+ def self.down
7
+ execute "DROP EXTENSION hstore"
8
+ end
9
+ 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
- ["('a'=>'1') || ('b'=>'2')", "('b'=>'2') || ('a'=>'1')"].should include({:a => 1, :b => 2}.to_hstore)
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("('a'=>'''a''')")
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 eq("('''a'''=>'a')")
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
- "\"'a'\"=>\"a\"".from_hstore.should eq({"'a'" => "a"})
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: 31
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 2
10
- version: 0.1.2
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-08-02 00:00:00 -03:00
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