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 +0 -5
- data/README.md +74 -77
- data/activerecord-postgres-hstore.gemspec +1 -2
- data/lib/activerecord-postgres-hstore.rb +0 -8
- data/lib/activerecord-postgres-hstore/activerecord.rb +0 -67
- data/lib/activerecord-postgres-hstore/coder.rb +38 -2
- data/spec/activerecord-coders-hstore_spec.rb +95 -15
- metadata +2 -21
- data/lib/activerecord-postgres-hstore/hash.rb +0 -39
- data/lib/activerecord-postgres-hstore/string.rb +0 -43
- data/spec/activerecord-postgres-hstore_spec.rb +0 -104
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
|
-
|
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
|
-
|
11
|
+
I have decided to clean up the old code and provide only a custom serializer in this new version.
|
20
12
|
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
-
|
46
|
-
serialize :data, ActiveRecord::Coders::Hstore.new({})
|
47
|
-
end
|
20
|
+
##Requirements
|
48
21
|
|
49
|
-
|
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
|
-
|
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
|
-
|
57
|
-
|
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
|
-
|
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
|
-
|
61
|
+
```sql CREATE INDEX people_gist_data ON people USING GIST(data);```
|
109
62
|
or
|
110
|
-
|
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
|
-
|
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.
|
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
|
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
|
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
|
4
|
-
|
5
|
-
ActiveRecord::Coders::Hstore.load(
|
6
|
-
end
|
3
|
+
describe ActiveRecord::Coders::Hstore do
|
4
|
+
describe "#load" do
|
5
|
+
subject{ ActiveRecord::Coders::Hstore.new.load(value) }
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
13
|
-
|
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
|
-
|
17
|
-
ActiveRecord::Coders::Hstore.dump(
|
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
|
-
|
21
|
-
|
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
|
-
|
25
|
-
|
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.
|
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-
|
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
|