activerecord-postgres-hstore 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -3,8 +3,3 @@ source "http://rubygems.org"
3
3
  # specify gem dependencies in activerecord-postgres-hstore.gemspec
4
4
  # except the platform-specific dependencies below
5
5
  gemspec
6
-
7
- group :development, :test do
8
- gem 'activerecord-jdbcpostgresql-adapter', :platforms => :jruby
9
- gem 'pg', :platforms => :ruby
10
- end
data/README.md CHANGED
@@ -1,7 +1,4 @@
1
- [![Build Status](https://secure.travis-ci.org/softa/activerecord-postgres-hstore.png?branch=master)](http://travis-ci.org/softa/activerecord-postgres-hstore)
2
-
3
- Goodbye serialize, hello hstore.
4
- --------------------------------
1
+ #Goodbye serialize, hello hstore. [![Build Status](https://secure.travis-ci.org/softa/activerecord-postgres-hstore.png?branch=master)](http://travis-ci.org/softa/activerecord-postgres-hstore)
5
2
 
6
3
  You need dynamic columns in your tables. What do you do?
7
4
 
@@ -9,57 +6,30 @@ You need dynamic columns in your tables. What do you do?
9
6
  * Use a noSQL database just for this issue. Good luck.
10
7
  * Create a serialized column. Nice, insertion will be fine, and reading data from a record too. But, what if you have a condition in your select that includes serialized data? Yeah, regular expressions.
11
8
 
12
- Requirements
13
- ------------
14
-
15
- Postgresql 8.4+ with contrib and Rails 3+. (It
16
- might work on 2.3.x with minor patches…)
17
- On Ubuntu, this is easy: `sudo apt-get install postgresql-contrib-9.1`
9
+ ##Note about 0.7
18
10
 
19
- On Mac you have a couple of options:
11
+ I have decided to clean up the old code and provide only a custom serializer in this new version.
20
12
 
21
- * [the binary package kindly provided by EnterpriseDB](http://www.enterprisedb.com/products-services-training/pgdownload#osx)
22
- * [Homebrew’s](https://github.com/mxcl/homebrew) Postgres installation also includes the contrib packages: `brew install postgres`
23
- * [Postgres.app](http://postgresapp.com/)
13
+ In order to acomplish this I had to drop support for older versions of Rails (3.0 and earlier) and also
14
+ remove some monkey patches that added functionality to the Hash, String, and some ActiveRecord objects.
15
+ This monkey patches provided methods such as Hash\#to\_hstore and String\#from\_hstore.
24
16
 
25
- Notes for Rails 3.1 and above
26
- -----------------------------
27
17
 
28
- The master branch already support a custom serialization coder.
29
- If you want to use it just put in your Gemfile:
30
-
31
- gem 'activerecord-postgres-hstore', github: 'engageis/activerecord-postgres-hstore'
32
-
33
- If you install them gem from the master branch you also have to insert a
34
- line in each model that uses hstore.
35
- Assuming a model called **Person**, with a **data** field on it, the
36
- code should look like:
37
-
38
- class Person < ActiveRecord::Base
39
- serialize :data, ActiveRecord::Coders::Hstore
40
- end
41
-
42
- If you want a default value (say, an empty hash) you should pass it as an argument when you
43
- initialize the serializer, like so:
18
+ **If you rely on this feature please stick to 0.6 version** and there is still a branch named 0.6 to which you can submit your pull requests.
44
19
 
45
- class Person < ActiveRecord::Base
46
- serialize :data, ActiveRecord::Coders::Hstore.new({})
47
- end
20
+ ##Requirements
48
21
 
49
- This way, you will automatically start with an empty hash that you can write attributes to.
22
+ Postgresql 8.4+ with contrib and Rails 3.1+ (If you want to try on older rails versions I recommend the 0.6 and ealier versions of this gem)
23
+ On Ubuntu, this is easy: `sudo apt-get install postgresql-contrib-9.1`
50
24
 
51
- irb(main):001:0> person = Person.new
52
- => #<Person id: nil, name: nil, data: {}, created_at: nil, updated_at: nil>
53
- irb(main):002:0> person.data['favorite_color'] = 'blue'
54
- => "blue"
25
+ On Mac you have a couple of options:
55
26
 
56
- If you skip this step, you will have to manually initialize the value to an empty hash before
57
- writing to the attribute, or else you will get an error:
27
+ * [the binary package kindly provided by EnterpriseDB](http://www.enterprisedb.com/products-services-training/pgdownload#osx)
28
+ * [Homebrew’s](https://github.com/mxcl/homebrew) Postgres installation also includes the contrib packages: `brew install postgres`
29
+ * [Postgres.app](http://postgresapp.com/)
58
30
 
59
- NoMethodError: undefined method `[]=' for nil:NilClass
31
+ ##Install
60
32
 
61
- Install
62
- -------
63
33
 
64
34
  Hstore is a PostgreSQL contrib type, [check it out first](http://www.postgresql.org/docs/9.2/static/hstore.html).
65
35
 
@@ -71,23 +41,6 @@ And run your bundler:
71
41
 
72
42
  `bundle install`
73
43
 
74
- Make sure that you have the desired database, if not create it as the
75
- desired user:
76
-
77
- `createdb hstorage_dev`
78
-
79
- Add the parameters to your database.yml (these are system dependant),
80
- e.g.:
81
-
82
- development:
83
- adapter: postgresql
84
- host: 127.0.0.1
85
- database: hstorage_dev
86
- encoding: unicode
87
- username: postgres
88
- password:
89
- pool: 5
90
-
91
44
  Now you need to create a migration that adds hstore support for your
92
45
  PostgreSQL database:
93
46
 
@@ -105,17 +58,65 @@ Finally you can create your own tables using hstore type. It’s easy:
105
58
  You’re done.
106
59
  Well, not yet. Don’t forget to add indexes. Like this:
107
60
 
108
- `CREATE INDEX people_gist_data ON people USING GIST(data);`
61
+ ```sql CREATE INDEX people_gist_data ON people USING GIST(data);```
109
62
  or
110
- `CREATE INDEX people_gin_data ON people USING GIN(data);`
63
+ ```sql CREATE INDEX people_gin_data ON people USING GIN(data);```
64
+
65
+ This gem provides some functions to generate this kind of index inside your migrations.
66
+ For the model Person we could create an index (defaults to type GIST) over the data field with this migration:
67
+
68
+ ```ruby
69
+ class AddIndexToPeople < ActiveRecord::Migration
70
+ def change
71
+ add_hstore_index :people, :data
72
+ end
73
+ end
74
+ ```
111
75
 
112
76
  To understand the difference between the two types of indexes take a
113
77
  look at [PostgreSQL docs](http://www.postgresql.org/docs/9.2/static/textsearch-indexes.html).
114
78
 
115
- Usage
116
- -----
79
+ ##Usage
80
+
81
+ This gem only provides a custom serialization coder.
82
+ If you want to use it just put in your Gemfile:
83
+
84
+ gem 'activerecord-postgres-hstore'
85
+
86
+ Now add a line (for each hstore column) on the model you have your hstore columns.
87
+ Assuming a model called **Person**, with a **data** field on it, the
88
+ code should look like:
89
+
90
+ ```ruby
91
+ class Person < ActiveRecord::Base
92
+ serialize :data, ActiveRecord::Coders::Hstore
93
+ end
94
+ ```
95
+
96
+ If you want a default value (say, an empty hash) you should pass it as an argument when you
97
+ initialize the serializer, like so:
98
+
99
+ ```ruby
100
+ class Person < ActiveRecord::Base
101
+ serialize :data, ActiveRecord::Coders::Hstore.new({})
102
+ end
103
+ ```
104
+
105
+ This way, you will automatically start with an empty hash that you can write attributes to.
106
+
107
+ irb(main):001:0> person = Person.new
108
+ => #<Person id: nil, name: nil, data: {}, created_at: nil, updated_at: nil>
109
+ irb(main):002:0> person.data['favorite_color'] = 'blue'
110
+ => "blue"
117
111
 
118
- Once you have it installed, you just need to learn a little bit of new
112
+ If you skip this step, you will have to manually initialize the value to an empty hash before
113
+ writing to the attribute, or else you will get an error:
114
+
115
+ NoMethodError: undefined method `[]=' for nil:NilClass
116
+
117
+ ###Querying the database
118
+
119
+ Now you just need to learn a little bit of new
119
120
  sqls for selecting stuff (creating and updating is transparent).
120
121
  Find records that contains a key named 'foo’:
121
122
 
@@ -162,8 +163,7 @@ and with many keys:
162
163
 
163
164
  Person.delete_keys(:data, :foo, :bar)
164
165
 
165
- Caveats
166
- -------
166
+ ##Caveats
167
167
 
168
168
  hstore keys and values have to be strings. This means `true` will become `"true"` and `42` will become `"42"` after you save the record. Only `nil` values are preserved.
169
169
 
@@ -178,8 +178,7 @@ To avoid the above, make sure all named parameters are strings:
178
178
 
179
179
  Have fun.
180
180
 
181
- Test Database
182
- -------------
181
+ ##Test Database
183
182
 
184
183
  To have hstore enabled when you load your database schema (as happens in rake db:test:prepare), you
185
184
  have two options.
@@ -195,14 +194,13 @@ This will change your schema dumps from Ruby to SQL. If you're
195
194
  unsure about the implications of this change, we suggest reading this
196
195
  [Rails Guide](http://guides.rubyonrails.org/migrations.html#schema-dumping-and-you).
197
196
 
198
- Help
199
- ----
197
+ ##Help
200
198
 
201
199
  You can use issues in github for that. Or else you can reach us at
202
200
  twitter: [@dbiazus](https://twitter.com/#!/dbiazus) or [@joaomilho](https://twitter.com/#!/joaomilho)
203
201
 
204
- Note on Patches/Pull Requests
205
- -----------------------------
202
+ ##Note on Patches/Pull Requests
203
+
206
204
 
207
205
  * Fork the project.
208
206
  * Make your feature addition or bug fix.
@@ -210,7 +208,6 @@ Note on Patches/Pull Requests
210
208
  * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
211
209
  * Send me a pull request. Bonus points for topic branches.
212
210
 
213
- Copyright
214
- ---------
211
+ ##Copyright
215
212
 
216
213
  Copyright © 2010 Juan Maiz. See LICENSE for details.
@@ -4,7 +4,7 @@ $:.unshift lib unless $:.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "activerecord-postgres-hstore"
7
- s.version = "0.6.0"
7
+ s.version = "0.7.0"
8
8
 
9
9
  s.platform = Gem::Platform::RUBY
10
10
  s.license = "MIT"
@@ -19,7 +19,6 @@ Gem::Specification.new do |s|
19
19
  s.add_dependency "rails"
20
20
  s.add_dependency "rake"
21
21
  s.add_development_dependency "bundler"
22
- s.add_development_dependency "shoulda"
23
22
  s.add_development_dependency "rdoc"
24
23
  s.add_development_dependency "rspec", "~> 2.11"
25
24
 
@@ -1,11 +1,5 @@
1
1
  require 'active_support'
2
2
 
3
- if RUBY_PLATFORM == "jruby"
4
- require 'activerecord-jdbcpostgresql-adapter'
5
- else
6
- require 'pg'
7
- end
8
-
9
3
  if defined? Rails
10
4
  require "activerecord-postgres-hstore/railties"
11
5
  else
@@ -13,6 +7,4 @@ else
13
7
  require "activerecord-postgres-hstore/activerecord"
14
8
  end
15
9
  end
16
- require "activerecord-postgres-hstore/string"
17
- require "activerecord-postgres-hstore/hash"
18
10
  require "activerecord-postgres-hstore/coder"
@@ -3,7 +3,6 @@ module ActiveRecord
3
3
 
4
4
  # Adds methods for deleting keys in your hstore columns
5
5
  class Base
6
-
7
6
  # Deletes all keys from a specific column in a model. E.g.
8
7
  # Person.delete_key(:info, :father)
9
8
  # The SQL generated will be:
@@ -65,42 +64,9 @@ module ActiveRecord
65
64
  raise "invalid attribute #{attribute}" unless self.class.column_names.include?(attribute.to_s)
66
65
  destroy_keys(attribute, *keys).save
67
66
  end
68
-
69
- if defined? Rails and Rails.version < '3.1.0'
70
- # This method is replaced for Rails 3 compatibility.
71
- # All I do is add the condition when the field is a hash that converts the value
72
- # to hstore format.
73
- # IMHO this should be delegated to the column, so it won't be necessary to rewrite all
74
- # this method.
75
- def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
76
- attrs = {}
77
- attribute_names.each do |name|
78
- if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
79
- if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
80
- value = read_attribute(name)
81
- if self.class.columns_hash[name].type == :hstore && value && value.is_a?(Hash)
82
- value = value.to_hstore # Done!
83
- 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))
84
- value = value.to_yaml
85
- end
86
- attrs[self.class.arel_table[name]] = value
87
- end
88
- end
89
- end
90
- attrs
91
- end
92
- end
93
-
94
- end
95
-
96
- # This erro class is used when the user passes a wrong value to a hstore column.
97
- # Hstore columns accepts hashes or hstore valid strings. It is validated with
98
- # String#valid_hstore? method.
99
- class HstoreTypeMismatch < ActiveRecord::ActiveRecordError
100
67
  end
101
68
 
102
69
  module ConnectionAdapters
103
-
104
70
  module SchemaStatements
105
71
 
106
72
  # Installs hstore by creating the Postgres extension
@@ -175,38 +141,5 @@ module ActiveRecord
175
141
  end
176
142
 
177
143
  end
178
-
179
- class PostgreSQLColumn < Column
180
- # Does the type casting from hstore columns using String#from_hstore or Hash#from_hstore.
181
- def type_cast_code_with_hstore(var_name)
182
- type == :hstore ? "#{var_name}.from_hstore" : type_cast_code_without_hstore(var_name)
183
- end
184
-
185
- # Adds the hstore type for the column.
186
- def simplified_type_with_hstore(field_type)
187
- field_type == 'hstore' ? :hstore : simplified_type_without_hstore(field_type)
188
- end
189
-
190
- alias_method_chain :type_cast_code, :hstore
191
- alias_method_chain :simplified_type, :hstore
192
- end
193
-
194
- class PostgreSQLAdapter < AbstractAdapter
195
- def native_database_types_with_hstore
196
- native_database_types_without_hstore.merge({:hstore => { :name => "hstore" }})
197
- end
198
-
199
- # Quotes correctly a hstore column value.
200
- def quote_with_hstore(value, column = nil)
201
- if value && column && column.sql_type == 'hstore'
202
- raise HstoreTypeMismatch, "#{column.name} must have a Hash or a valid hstore value (#{value})" unless value.kind_of?(Hash) || value.valid_hstore?
203
- return quote_without_hstore(value.to_hstore, column)
204
- end
205
- quote_without_hstore(value,column)
206
- end
207
-
208
- alias_method_chain :quote, :hstore
209
- alias_method_chain :native_database_types, :hstore
210
- end
211
144
  end
212
145
  end
@@ -14,11 +14,47 @@ module ActiveRecord
14
14
  end
15
15
 
16
16
  def dump(obj)
17
- obj.nil? ? (@default.nil? ? nil : @default.to_hstore) : obj.to_hstore
17
+ obj.nil? ? (@default.nil? ? nil : to_hstore(@default)) : to_hstore(obj)
18
18
  end
19
19
 
20
20
  def load(hstore)
21
- hstore.nil? ? @default : hstore.from_hstore
21
+ hstore.nil? ? @default : from_hstore(hstore)
22
+ end
23
+
24
+ private
25
+ # Escapes values such that they will work in an hstore string
26
+ def hstore_escape(str)
27
+ return 'NULL' if str.nil?
28
+ return str if str =~ /^".*"$/
29
+ '"%s"' % str
30
+ end
31
+
32
+ def to_hstore obj
33
+ return "" if obj.empty?
34
+ obj.map do |idx, val|
35
+ "%s=>%s" % [hstore_escape(idx), hstore_escape(val)]
36
+ end * ","
37
+ end
38
+
39
+ def hstore_pair
40
+ quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
41
+ unquoted_string = /[^\s=,][^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
42
+ string = /(#{quoted_string}|#{unquoted_string})/
43
+ /#{string}\s*=>\s*#{string}/
44
+ end
45
+
46
+ def from_hstore hstore
47
+ token_pairs = (hstore.scan(hstore_pair)).map { |k,v| [k,v =~ /^NULL$/i ? nil : v] }
48
+ token_pairs = token_pairs.map { |k,v|
49
+ [k,v].map { |t|
50
+ case t
51
+ when nil then t
52
+ when /\A"(.*)"\Z/m then $1.gsub(/\\(.)/, '\1')
53
+ else t.gsub(/\\(.)/, '\1')
54
+ end
55
+ }
56
+ }
57
+ Hash[ token_pairs ]
22
58
  end
23
59
  end
24
60
  end
@@ -1,27 +1,107 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- describe "ActiverecordCodersHstore" do
4
- it 'should load nil' do
5
- ActiveRecord::Coders::Hstore.load(nil).should be_nil
6
- end
3
+ describe ActiveRecord::Coders::Hstore do
4
+ describe "#load" do
5
+ subject{ ActiveRecord::Coders::Hstore.new.load(value) }
7
6
 
8
- it 'should load an hstore' do
9
- ActiveRecord::Coders::Hstore.load("a=>a").should == { 'a' => 'a' }
10
- end
7
+ context 'when value is nil and we have a default in the constructor' do
8
+ subject{ ActiveRecord::Coders::Hstore.new({'a'=>'a'}).load(nil) }
9
+ it{ should eql({'a'=>'a'}) }
10
+ end
11
+
12
+ context 'when key and value have newline char' do
13
+ let(:value){ "\"foo\nbar\"=>\"\nnewline\"" }
14
+ it{ should eql({"foo\nbar" => "\nnewline"}) }
15
+ end
16
+
17
+ context 'when key and value are empty strings' do
18
+ let(:value){ %q(""=>"") }
19
+ it{ should eql({'' => ''}) }
20
+ end
11
21
 
12
- it 'should dump an hstore' do
13
- ActiveRecord::Coders::Hstore.dump({'a'=>'a'}).should == {'a'=>'a'}.to_hstore
22
+ context 'when value has single quotes' do
23
+ let(:value){ %q("'a'"=>"'a'") }
24
+ it{ should eql({"'a'" => "'a'"}) }
25
+ end
26
+
27
+ context 'when value is empty hash' do
28
+ let(:value){ '' }
29
+ it{ should eql({}) }
30
+ end
31
+
32
+ context 'when value is nil' do
33
+ let(:value){ nil }
34
+ it { should be_nil }
35
+ end
36
+
37
+ context 'when value is a hstore' do
38
+ let(:value){ "a=>a" }
39
+ it{ should eql({ 'a' => 'a' }) }
40
+ end
14
41
  end
15
42
 
16
- it 'should dump nil' do
17
- ActiveRecord::Coders::Hstore.dump(nil).should be_nil
43
+ describe "#dump" do
44
+ subject{ ActiveRecord::Coders::Hstore.new.dump(value) }
45
+
46
+ context 'when value is nil and we have a default in the constructor' do
47
+ subject{ ActiveRecord::Coders::Hstore.new({'a'=>'a'}).dump(nil) }
48
+ it{ should eql('"a"=>"a"') }
49
+ end
50
+
51
+ context 'when key and value have newline char' do
52
+ let(:value){ {"foo\nbar" => "\nnewline"} }
53
+ it{ should eql("\"foo\nbar\"=>\"\nnewline\"") }
54
+ end
55
+
56
+ context 'when key and value are empty strings' do
57
+ let(:value){ {'' => ''} }
58
+ it{ should eql(%q(""=>"")) }
59
+ end
60
+
61
+ context 'when value has single quotes' do
62
+ let(:value){ {"'a'" => "'a'"} }
63
+ it{ should eql(%q("'a'"=>"'a'")) }
64
+ end
65
+
66
+ context 'when value is empty hash' do
67
+ let(:value){ {} }
68
+ it{ should eql('') }
69
+ end
70
+
71
+ context 'when value is nil' do
72
+ let(:value){ nil }
73
+ it{ should be_nil }
74
+ end
75
+
76
+ context "when value is an hstore" do
77
+ let(:value){ {'a' => 'a'} }
78
+ it{ should eql('"a"=>"a"') }
79
+ end
18
80
  end
19
81
 
20
- it 'should dump the default given nil' do
21
- ActiveRecord::Coders::Hstore.new({'a'=>'a'}).dump(nil).should == {'a'=>'a'}.to_hstore
82
+ describe ".load" do
83
+ before do
84
+ @parameter = 'b=>b'
85
+ instance = double("coder instance")
86
+ instance.should_receive(:load).with(@parameter)
87
+ ActiveRecord::Coders::Hstore.should_receive(:new).and_return(instance)
88
+ end
89
+
90
+ it("should instantiate and call load") do
91
+ ActiveRecord::Coders::Hstore.load(@parameter)
92
+ end
22
93
  end
23
94
 
24
- it 'should load the default given nil' do
25
- ActiveRecord::Coders::Hstore.new({'a'=>'a'}).load(nil).should eql({'a'=>'a'})
95
+ describe ".dump" do
96
+ before do
97
+ @parameter = {'b' => 'b'}
98
+ instance = double("coder instance")
99
+ instance.should_receive(:dump).with(@parameter)
100
+ ActiveRecord::Coders::Hstore.should_receive(:new).and_return(instance)
101
+ end
102
+
103
+ it("should instantiate and call dump") do
104
+ ActiveRecord::Coders::Hstore.dump(@parameter)
105
+ end
26
106
  end
27
107
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-postgres-hstore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-02-03 00:00:00.000000000 Z
13
+ date: 2013-02-04 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
@@ -60,22 +60,6 @@ dependencies:
60
60
  - - ! '>='
61
61
  - !ruby/object:Gem::Version
62
62
  version: '0'
63
- - !ruby/object:Gem::Dependency
64
- name: shoulda
65
- requirement: !ruby/object:Gem::Requirement
66
- none: false
67
- requirements:
68
- - - ! '>='
69
- - !ruby/object:Gem::Version
70
- version: '0'
71
- type: :development
72
- prerelease: false
73
- version_requirements: !ruby/object:Gem::Requirement
74
- none: false
75
- requirements:
76
- - - ! '>='
77
- - !ruby/object:Gem::Version
78
- version: '0'
79
63
  - !ruby/object:Gem::Dependency
80
64
  name: rdoc
81
65
  requirement: !ruby/object:Gem::Requirement
@@ -128,13 +112,10 @@ files:
128
112
  - lib/activerecord-postgres-hstore.rb
129
113
  - lib/activerecord-postgres-hstore/activerecord.rb
130
114
  - lib/activerecord-postgres-hstore/coder.rb
131
- - lib/activerecord-postgres-hstore/hash.rb
132
115
  - lib/activerecord-postgres-hstore/railties.rb
133
- - lib/activerecord-postgres-hstore/string.rb
134
116
  - lib/templates/setup_hstore.rb
135
117
  - lib/templates/setup_hstore91.rb
136
118
  - spec/activerecord-coders-hstore_spec.rb
137
- - spec/activerecord-postgres-hstore_spec.rb
138
119
  - spec/spec_helper.rb
139
120
  homepage: http://github.com/softa/activerecord-postgres-hstore
140
121
  licenses:
@@ -1,39 +0,0 @@
1
- class Hash
2
- HSTORE_ESCAPED = /[,\s=>\\]/
3
-
4
- # Escapes values such that they will work in an hstore string
5
- def hstore_escape(str)
6
- if str.nil?
7
- return 'NULL'
8
- end
9
-
10
- str = str.to_s.dup
11
- # backslash is an escape character for strings, and an escape character for gsub, so you need 6 backslashes to get 2 in the output.
12
- # see http://stackoverflow.com/questions/1542214/weird-backslash-substitution-in-ruby for the gory details
13
- str.gsub!(/\\/, '\\\\\\')
14
- # escape backslashes before injecting more backslashes
15
- str.gsub!(/"/, '\"')
16
-
17
- if str =~ HSTORE_ESCAPED or str.empty?
18
- str = '"%s"' % str
19
- end
20
-
21
- return str
22
- end
23
-
24
- # Generates an hstore string format. This is the format used
25
- # to insert or update stuff in the database.
26
- def to_hstore
27
- return "" if empty?
28
-
29
- map do |idx, val|
30
- "%s=>%s" % [hstore_escape(idx), hstore_escape(val)]
31
- end * ","
32
- end
33
-
34
- # If the method from_hstore is called in a Hash, it just returns self.
35
- def from_hstore
36
- self
37
- end
38
-
39
- end
@@ -1,43 +0,0 @@
1
- class String
2
-
3
- # If the value os a column is already a String and it calls to_hstore, it
4
- # just returns self. Validation occurs afterwards.
5
- def to_hstore
6
- self
7
- end
8
-
9
- # Validates the hstore format. Valid formats are:
10
- # * An empty string
11
- # * A string like %("foo"=>"bar"). I'll call it a "double 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
-
14
- def valid_hstore?
15
- pair = hstore_pair
16
- !!match(/^\s*(#{pair}\s*(,\s*#{pair})*)?\s*$/)
17
- end
18
-
19
- # Creates a hash from a valid double quoted hstore format, 'cause this is the format
20
- # that postgresql spits out.
21
- def from_hstore
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 /\A"(.*)"\Z/m then $1.gsub(/\\(.)/, '\1')
28
- else t.gsub(/\\(.)/, '\1')
29
- end
30
- }
31
- }
32
- Hash[ token_pairs ]
33
- end
34
-
35
- private
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
43
- end
@@ -1,104 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
-
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
23
-
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"})
26
- end
27
-
28
- it "should convert hstore string to hash" do
29
- '"a"=>"1", "b"=>"2"'.from_hstore.should eq({'a' => '1', 'b' => '2'})
30
- end
31
-
32
- it "should quote correctly" do
33
- {:a => "'a'"}.to_hstore.should eq(%q(a=>'a'))
34
- end
35
-
36
- it "should quote keys correctly" do
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"))
67
- end
68
-
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)})
71
- end
72
-
73
- it "should quote keys and values correctly with backslashes" do
74
- { %q(\\) => %q(\\) }.to_hstore.should eq(%q("\\\\"=>"\\\\"))
75
- end
76
-
77
- it "should unquote keys and values correctly with backslashes" do
78
- %q("\\\\"=>"\\\\").from_hstore.should eq({ %q(\\) => %q(\\) })
79
- end
80
-
81
- it "should quote keys and values correctly with combinations of backslashes and quotes" do
82
- { %q(' \\ ") => %q(" \\ ') }.to_hstore.should eq(%q("' \\\\ \""=>"\" \\\\ '"))
83
- end
84
-
85
- it "should unquote keys and values correctly with combinations of backslashes and quotes" do
86
- %q("' \\\\ \""=>"\" \\\\ '").from_hstore.should eq({ %q(' \\ ") => %q(" \\ ') })
87
- end
88
-
89
- it "should convert empty hash" do
90
- {}.to_hstore.should eq("")
91
- end
92
-
93
- it "should convert empty string" do
94
- ''.from_hstore.should eq({})
95
- ' '.from_hstore.should eq({})
96
- end
97
-
98
- it "should not change values with line breaks" do
99
- input = { "a" => "foo\n\nbar" }
100
- output = input.to_hstore
101
- output.from_hstore.should eq(input)
102
- end
103
-
104
- end