activerecord-postgres-hstore 0.6.0 → 0.7.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/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