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 +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
|
-
[](http://travis-ci.org/softa/activerecord-postgres-hstore)
|
2
|
-
|
3
|
-
Goodbye serialize, hello hstore.
|
4
|
-
--------------------------------
|
1
|
+
#Goodbye serialize, hello hstore. [](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
|