activerecord2-hstore 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/Gemfile +7 -0
- data/README.markdown +162 -0
- data/Rakefile +4 -0
- data/activerecord2-hstore.gemspec +26 -0
- data/lib/activerecord2-hstore/hash.rb +24 -0
- data/lib/activerecord2-hstore/hstore.rb +78 -0
- data/lib/activerecord2-hstore/string.rb +24 -0
- data/lib/activerecord2-hstore/version.rb +5 -0
- data/lib/activerecord2-hstore.rb +12 -0
- data/spec/hash_spec.rb +33 -0
- data/spec/hstore_spec.rb +179 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/string_spec.rb +23 -0
- metadata +144 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
activerecord2-hstore
|
2
|
+
====================
|
3
|
+
|
4
|
+
What is this?
|
5
|
+
-------------
|
6
|
+
This is a gem that provides some very basic functionality for working with
|
7
|
+
Postgresql's Hstore columns in ActiveRecord 2.
|
8
|
+
|
9
|
+
Documentation about the Postgresql Hstore feature can be found
|
10
|
+
**[here](http://www.postgresql.org/docs/9.1/static/hstore.html)**.
|
11
|
+
|
12
|
+
Requirements (aka boring stuff)
|
13
|
+
-------------------------------
|
14
|
+
This gem requires:
|
15
|
+
|
16
|
+
* ActiveRecord and ActiveSupport 2.3.x
|
17
|
+
* pg gem
|
18
|
+
* rspec (for running tests, duh)
|
19
|
+
* Ruby (Tested on 1.8.7 MRI, 1.9.2, and REE)
|
20
|
+
* Postgresql (Tested on 9.1)
|
21
|
+
|
22
|
+
Setup (aka more boring stuff)
|
23
|
+
-----------------------------
|
24
|
+
Install the gem like you would normally install any gem.
|
25
|
+
|
26
|
+
Enable the hstore extension on the postgres database(s) you wish to use.
|
27
|
+
Ether by creating a migration in your project that runs this SQL statement
|
28
|
+
or manually running it directly in psql console...
|
29
|
+
|
30
|
+
CREATE EXTENSION IF NOT EXISTS hstore;
|
31
|
+
|
32
|
+
Instead of me trying to hack ActiveRecord to add an actual hstore column type,
|
33
|
+
and risk breaking the universe, just manually write a migration that adds a
|
34
|
+
hstore column to your table. Here, I'll even give you an example:
|
35
|
+
|
36
|
+
ALTER TABLE my_table ADD COLUMN some_field hstore;
|
37
|
+
|
38
|
+
I recommend you add an index to that column. Supported indexes are BTREE, HASH,
|
39
|
+
GIST, and GIN. I'll leave you to handle that on your own.
|
40
|
+
|
41
|
+
Usage (aka the stuff you care about)
|
42
|
+
------------------------------------
|
43
|
+
Now that the boring stuff is out of the way, you need to tell your model that
|
44
|
+
you have a hstore column.
|
45
|
+
|
46
|
+
Assume you have the following table:
|
47
|
+
|
48
|
+
peeps
|
49
|
+
id: integer
|
50
|
+
name: string
|
51
|
+
infos: hstore
|
52
|
+
|
53
|
+
You can tell your model infos is a hstore column thusly...
|
54
|
+
|
55
|
+
class Peep < ActiveRecord::Base
|
56
|
+
hstore_column :infos
|
57
|
+
end
|
58
|
+
|
59
|
+
What does that one line get you?
|
60
|
+
|
61
|
+
* A getter that returns the hstore column as a hash...
|
62
|
+
|
63
|
+
@peep.infos
|
64
|
+
>> {"age" => "25", "haircolor" => "black", "height" => "5'3\"", "likes" => "Cuddling while watching TV"}
|
65
|
+
|
66
|
+
* A setter that takes a hash and converts it to a hstore string (or the method can just take a hstore string)
|
67
|
+
|
68
|
+
@peeps.infos = some_hash
|
69
|
+
|
70
|
+
* These name scopes:
|
71
|
+
* infos\_has\_key (takes a string)
|
72
|
+
* infos\_has\_all\_keys (takes a string or an array of strings)
|
73
|
+
* infos\_has\_any\_keys (takes a string or an array of strings)
|
74
|
+
|
75
|
+
So what about querying the data in that colum? Well, you can always use the
|
76
|
+
standard condisions key in ActiveRecord's find method and use the proper
|
77
|
+
syntax from the Postgresql documentation. But if you're like me and like
|
78
|
+
using searchlogic, that's not an option. So in the same line in your model,
|
79
|
+
you can specifiy some keys you'd want to filter by...
|
80
|
+
|
81
|
+
class Peep < ActiveRecord::Base
|
82
|
+
hstore_column :infos, [:age, :haircolor, :likes]
|
83
|
+
end
|
84
|
+
|
85
|
+
Passing in an array of hstore keys will give you the following named scopes
|
86
|
+
to play with...
|
87
|
+
|
88
|
+
* infos\_age\_eq
|
89
|
+
* infos\_age\_neq
|
90
|
+
* infos\_age\_eq\_any
|
91
|
+
* infos\_age\_neq\_any
|
92
|
+
* infos\_age\_like
|
93
|
+
* infos\_age\_beigns\_with
|
94
|
+
* infos\_age\_ends\_with
|
95
|
+
* (Repeat list for "haircolor" and "likes")
|
96
|
+
|
97
|
+
Which means you can then do...
|
98
|
+
|
99
|
+
Peep.infos_likes_eq("Cuddling while watching TV")
|
100
|
+
Peep.searchlogic(:infos_age_neq => "23")
|
101
|
+
|
102
|
+
But Wait, There's More!
|
103
|
+
-----------------------
|
104
|
+
|
105
|
+
The gem also adds a helper method to the Hash and String objects for converting
|
106
|
+
hashes to hstore strings and back again.
|
107
|
+
|
108
|
+
These methods were originally implemented in a gem by
|
109
|
+
[softa](https://github.com/softa/activerecord-postgres-hstore) to add hstore
|
110
|
+
to ActiveReocord 3 and tweaked slightly for this gem.
|
111
|
+
|
112
|
+
Converting a hash into a hstore string that can be used to direclty store data
|
113
|
+
in a query...
|
114
|
+
|
115
|
+
{"something something" => "something", :dark => "side"}.to_hstore
|
116
|
+
>> "\"something something\"=>something,dark=>side"
|
117
|
+
|
118
|
+
Converting a hstore string that is returned from the database into a hash so
|
119
|
+
you can actually do something with it...
|
120
|
+
|
121
|
+
"\"something something\"=>something,dark=>side".from_hstore
|
122
|
+
>> {"something something" => "something", "dark" => "side"}
|
123
|
+
|
124
|
+
Note that taking a string from hstore into a hash will produce a hash where its
|
125
|
+
keys and values are all strings.
|
126
|
+
|
127
|
+
|
128
|
+
Running Tests
|
129
|
+
-------------
|
130
|
+
For the tests to run, it's assumed there is a Postgres database called
|
131
|
+
activerecord2\_hstore\_test. The specs will create a test table and populate
|
132
|
+
it with data for you. If you want to use a different database, then edit
|
133
|
+
spec/hstore\_spec.rb to your liking.
|
134
|
+
|
135
|
+
Then just run...
|
136
|
+
|
137
|
+
rake spec
|
138
|
+
|
139
|
+
Background / Why make this? / Me Rambling
|
140
|
+
-----------------------------------------
|
141
|
+
At my current employor, I'm helping to support a rather large Rails 2.3 app
|
142
|
+
(that unfortunatly will be stuck in 2.3 for quite some time) that runs on
|
143
|
+
Postgresql. The app's primary purpose is reporting on data from its data
|
144
|
+
warehouse of well... data. Because it's on Postgresql, the development team
|
145
|
+
was interested in using some of Postgresql's special features such as array
|
146
|
+
columns and hstore datatypes. We need the ability to easily take a hash
|
147
|
+
and store that as a hstore and read the column out as a hash. Also because
|
148
|
+
we're big on reporting and use searchlogic for filtering out data, we need
|
149
|
+
some way to search a hstore field for key with certain values.
|
150
|
+
|
151
|
+
To accomplish the first goal, I first needed a way to convert a Postgresql
|
152
|
+
hstore string into a Ruby hash and back to a string. I'm using the hash and
|
153
|
+
string methods from **[softa's gem](https://github.com/softa/activerecord-postgres-hstore)**
|
154
|
+
that provides hstore support for ActiveRecord 3. With those methods, I created
|
155
|
+
a way for you to tell ActiveRecord what columns are hstore columns and this
|
156
|
+
gem will override the default column getter method to return a hash and the
|
157
|
+
setter method to accept a hash which it then converts into a hstore string.
|
158
|
+
|
159
|
+
Part two of this gem is determining a way to query a hstore field. I decided
|
160
|
+
to have the gem generate searchlogic-like named scopes by sepcifying in the
|
161
|
+
model what keys in the hstore column you'll want to filter on. The gem will
|
162
|
+
create scopes in the style of "mycolumn\_key\_eq", "mycolumn\_key\_like", etc.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "activerecord2-hstore/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "activerecord2-hstore"
|
7
|
+
s.version = Activerecord2::Hstore::VERSION
|
8
|
+
s.authors = ["Tony Drake"]
|
9
|
+
s.email = ["t27duck@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/t27duck/activerecord2-hstore"
|
11
|
+
s.summary = %q{Some basic support to help integrate Postgresql's HStore into a Rails 2.3 app}
|
12
|
+
s.description = %q{Allows you to mark a column in a model as a HStore column letting you pass in a hash for its setter and retrieve a hash from its getter method. Also provides a series to named scopes for easy querying of data.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "activerecord2-hstore"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rspec", ">=2.0.0"
|
23
|
+
s.add_runtime_dependency "activerecord", "~> 2.3.0"
|
24
|
+
s.add_runtime_dependency "activesupport", "~> 2.3.0"
|
25
|
+
s.add_runtime_dependency "pg"
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Hash
|
2
|
+
# Generates an hstore string format. This is the format used to insert or update stuff in the database.
|
3
|
+
#
|
4
|
+
# Original implementation from:
|
5
|
+
# https://github.com/softa/activerecord-postgres-hstore/blob/master/lib/activerecord-postgres-hstore/hash.rb
|
6
|
+
def to_hstore
|
7
|
+
return "" if empty?
|
8
|
+
|
9
|
+
map { |key, value|
|
10
|
+
pair = [key, value].map { |element|
|
11
|
+
item = element.to_s.gsub(/"/, '\"')
|
12
|
+
if element.nil?
|
13
|
+
'NULL'
|
14
|
+
elsif item =~ /[,\s=>]/ || item.blank?
|
15
|
+
'"%s"' % item
|
16
|
+
else
|
17
|
+
item
|
18
|
+
end
|
19
|
+
}
|
20
|
+
|
21
|
+
"%s=>%s" % pair
|
22
|
+
} * ","
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Hstore
|
2
|
+
module ActiveRecord
|
3
|
+
# Called when included in ActiveRecord. Extends the Extensions module.
|
4
|
+
def self.included(base)
|
5
|
+
base.extend Extensions
|
6
|
+
end
|
7
|
+
|
8
|
+
module Extensions
|
9
|
+
# Creates a series of methods and named scopes for a hstore column. This is the primary
|
10
|
+
# method you should use to invoke this gem in a model.
|
11
|
+
#
|
12
|
+
# USAGE IN A MODEL:
|
13
|
+
# class Foo < ActiveRecord::Base
|
14
|
+
# hstore_column :some_column, [:array_of_possible_keys, :you_may_want, :to_query_on]
|
15
|
+
# end
|
16
|
+
def hstore_column(column, keys=[])
|
17
|
+
create_getter_and_setter(column)
|
18
|
+
create_hstore_key_availability_scopes(column)
|
19
|
+
Array(keys).each{ |key| create_hstore_key_search_scopes(column, key) }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Creates and overrides ActiveRecord's getter and setter methods for the hstore column.
|
23
|
+
# The getter returns a hash. The setter accepts and converts either a hash or a valid
|
24
|
+
# hstore string.
|
25
|
+
def create_getter_and_setter(column)
|
26
|
+
define_method column.to_sym do
|
27
|
+
read_attribute(column.to_sym).to_s.from_hstore
|
28
|
+
end
|
29
|
+
|
30
|
+
define_method "#{column}=".to_sym do |value|
|
31
|
+
value = value.is_a?(String) ? value : value.to_hstore
|
32
|
+
write_attribute(column.to_sym, value)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates named scopes for the hstore column allowing you to determine if the column
|
37
|
+
# contains a given key or set of keys.
|
38
|
+
def create_hstore_key_availability_scopes(column)
|
39
|
+
named_scope "#{column}_has_key".to_sym, Proc.new{ |key|
|
40
|
+
{ :conditions => ["#{column} ? :key", {:key => key}] }
|
41
|
+
}
|
42
|
+
named_scope "#{column}_has_all_keys".to_sym, Proc.new{ |keys|
|
43
|
+
{ :conditions => ["#{column} ?& ARRAY[:keys]", {:keys => Array(keys)}] }
|
44
|
+
}
|
45
|
+
named_scope "#{column}_has_any_key".to_sym, Proc.new{ |keys|
|
46
|
+
{ :conditions => ["#{column} ?| ARRAY[:keys]", {:keys => Array(keys)}] }
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
# Creates a slew of searchlogic-like named scopes to query for a key on a hstore column
|
51
|
+
def create_hstore_key_search_scopes(column, key)
|
52
|
+
named_scope "#{column}_#{key}_eq".to_sym, Proc.new{ |value|
|
53
|
+
{ :conditions => ["#{column} -> '#{key}' = ?", value.to_s] }
|
54
|
+
}
|
55
|
+
named_scope "#{column}_#{key}_neq".to_sym, Proc.new{ |value|
|
56
|
+
{ :conditions => ["#{column} -> '#{key}' != ?", value.to_s] }
|
57
|
+
}
|
58
|
+
named_scope "#{column}_#{key}_eq_any".to_sym, Proc.new{ |value|
|
59
|
+
{ :conditions => ["#{column} -> '#{key}' IN(?)", value.map{|v| v.to_s} ] }
|
60
|
+
}
|
61
|
+
named_scope "#{column}_#{key}_neq_any".to_sym, Proc.new{ |value|
|
62
|
+
{ :conditions => ["#{column} -> '#{key}' NOT IN(?)", value.map{|v| v.to_s} ] }
|
63
|
+
}
|
64
|
+
named_scope "#{column}_#{key}_like".to_sym, Proc.new{ |value|
|
65
|
+
{ :conditions => ["#{column} -> '#{key}' ILIKE(?)", "%#{value.to_s}%"] }
|
66
|
+
}
|
67
|
+
named_scope "#{column}_#{key}_begins_with".to_sym, Proc.new{ |value|
|
68
|
+
{ :conditions => ["#{column} -> '#{key}' ILIKE(?)", "#{value.to_s}%"] }
|
69
|
+
}
|
70
|
+
named_scope "#{column}_#{key}_ends_with".to_sym, Proc.new{ |value|
|
71
|
+
{ :conditions => ["#{column} -> '#{key}' ILIKE(?)", "%#{value.to_s}"] }
|
72
|
+
}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class String
|
2
|
+
# Generates a hash from an hstore string format.
|
3
|
+
#
|
4
|
+
# Original implementation from:
|
5
|
+
# https://github.com/softa/activerecord-postgres-hstore/blob/master/lib/activerecord-postgres-hstore/string.rb
|
6
|
+
def from_hstore
|
7
|
+
quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
|
8
|
+
unquoted_string = /[^\s=,][^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
|
9
|
+
string = /(#{quoted_string}|#{unquoted_string})/
|
10
|
+
hstore_pair = /#{string}\s*=>\s*#{string}/
|
11
|
+
|
12
|
+
token_pairs = (scan(hstore_pair)).map { |key, value| [key, value =~ /^NULL$/i ? nil : value] }
|
13
|
+
token_pairs = token_pairs.map { |key, value|
|
14
|
+
[key, value].map { |element|
|
15
|
+
case element
|
16
|
+
when nil then element
|
17
|
+
when /^"(.*)"$/ then $1.gsub(/\\(.)/, '\1')
|
18
|
+
else element.gsub(/\\(.)/, '\1')
|
19
|
+
end
|
20
|
+
}
|
21
|
+
}
|
22
|
+
Hash[ token_pairs ]
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "activerecord2-hstore/version"
|
2
|
+
require "activerecord2-hstore/string"
|
3
|
+
require "activerecord2-hstore/hash"
|
4
|
+
require "activerecord2-hstore/hstore"
|
5
|
+
|
6
|
+
module Activerecord2
|
7
|
+
module Hstore
|
8
|
+
# Your code goes here...
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
ActiveRecord::Base.send(:include, Hstore::ActiveRecord)
|
data/spec/hash_spec.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Hash do
|
4
|
+
describe "Converting a hash to a hstore string" do
|
5
|
+
it "#to_hstore" do
|
6
|
+
{:a=>"b"}.to_hstore.should == "a=>b"
|
7
|
+
{"a"=>"b"}.to_hstore.should == "a=>b"
|
8
|
+
{:a=>:b}.to_hstore.should == "a=>b"
|
9
|
+
{"a"=>"2"}.to_hstore.should == "a=>2"
|
10
|
+
{:a=>2}.to_hstore.should == "a=>2"
|
11
|
+
{2=>2}.to_hstore.should == "2=>2"
|
12
|
+
{"a b"=>"b"}.to_hstore.should == "\"a b\"=>b"
|
13
|
+
{"a b"=>"b c"}.to_hstore.should == "\"a b\"=>\"b c\""
|
14
|
+
{"a"=>"b c"}.to_hstore.should == "a=>\"b c\""
|
15
|
+
end
|
16
|
+
|
17
|
+
it "converts a multiball hash to a hstore string" do
|
18
|
+
all_should_match({"a"=>"b", "c"=>"d"}, [/a=>b/, /c=>d/])
|
19
|
+
all_should_match({:a=>"b", "c"=>:d}, [/a=>b/, /c=>d/])
|
20
|
+
all_should_match({"a 1"=>"b 1", "c"=>"d"}, [/\"a 1\"=>\"b 1\"/, /c=>d/])
|
21
|
+
all_should_match({"a"=>"b", "c"=>"d 1"}, [/a=>b/, /c=>\"d 1\"/])
|
22
|
+
all_should_match({"a"=>"b", "c 1"=>"d"}, [/a=>b/, /\"c 1\"=>d/])
|
23
|
+
all_should_match({:a=>:b, "c 1"=>:"d 1"}, [/a=>b/, /\"c 1\"=>\"d 1\"/])
|
24
|
+
all_should_match({"a"=>"b", :c=>:d, "e 1"=>"f 1"}, [/a=>b/, /c=>d/, /\"e 1\"=>\"f 1\"/])
|
25
|
+
end
|
26
|
+
|
27
|
+
def all_should_match(hash, expects)
|
28
|
+
expects.each do |e|
|
29
|
+
hash.to_hstore.should match(e)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/spec/hstore_spec.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
ActiveRecord::Base.establish_connection(
|
4
|
+
:adapter => "postgresql",
|
5
|
+
:database => "activerecord2_hstore_test",
|
6
|
+
:encoding => "unicode"
|
7
|
+
)
|
8
|
+
|
9
|
+
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS my_hstores")
|
10
|
+
ActiveRecord::Base.connection.create_table(:my_hstores) do |t|
|
11
|
+
t.string :label
|
12
|
+
end
|
13
|
+
ActiveRecord::Base.connection.execute("CREATE EXTENSION IF NOT EXISTS hstore")
|
14
|
+
ActiveRecord::Base.connection.execute("ALTER TABLE my_hstores ADD COLUMN some_field hstore")
|
15
|
+
|
16
|
+
class MyHstore < ActiveRecord::Base
|
17
|
+
hstore_column :some_field, [:key]
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Hstore do
|
21
|
+
before(:each) do
|
22
|
+
ActiveRecord::Base.connection.increment_open_transactions
|
23
|
+
ActiveRecord::Base.connection.begin_db_transaction
|
24
|
+
end
|
25
|
+
|
26
|
+
after(:each) do
|
27
|
+
ActiveRecord::Base.connection.rollback_db_transaction
|
28
|
+
ActiveRecord::Base.connection.decrement_open_transactions
|
29
|
+
end
|
30
|
+
|
31
|
+
it "creates the correct named scopes" do
|
32
|
+
%w(eq neq eq_any neq_any like begins_with ends_with).each do |predicate|
|
33
|
+
MyHstore.should respond_to("some_field_key_#{predicate}")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "available methods" do
|
38
|
+
let(:subject) do
|
39
|
+
MyHstore.new
|
40
|
+
end
|
41
|
+
|
42
|
+
it "creates a getter and setter for the some_field" do
|
43
|
+
subject.should respond_to(:some_field)
|
44
|
+
subject.should respond_to(:some_field=)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "uses ActiveRecord's read_attribute for the getter method" do
|
48
|
+
subject.should_receive(:read_attribute).with(:some_field).and_return("a=>b")
|
49
|
+
subject.some_field
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "setter method" do
|
53
|
+
it "uses ActiveRecord's write_attribute" do
|
54
|
+
subject.should_receive(:write_attribute)
|
55
|
+
subject.some_field = {:a => :b}
|
56
|
+
end
|
57
|
+
|
58
|
+
it "can also accept a hstore string instead of a hash" do
|
59
|
+
subject.stub(:write_attribute)
|
60
|
+
subject.some_field = "a=>b"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "querying data" do
|
66
|
+
before(:each) do
|
67
|
+
MyHstore.create( :label=> "A", :some_field => {"key" => "1"}.to_hstore )
|
68
|
+
MyHstore.create( :label=> "B", :some_field => {:key => 1, "a" => "b"}.to_hstore )
|
69
|
+
MyHstore.create( :label=> "C", :some_field => {"key" => "2"}.to_hstore )
|
70
|
+
MyHstore.create( :label=> "D", :some_field => {"key" => "3"}.to_hstore )
|
71
|
+
MyHstore.create( :label=> "E", :some_field => {"key" => "123456789"}.to_hstore )
|
72
|
+
MyHstore.create( :label=> "F", :some_field => {"c" => "x"}.to_hstore )
|
73
|
+
end
|
74
|
+
|
75
|
+
it "queries for the records containing a specific key" do
|
76
|
+
result = MyHstore.some_field_has_key("a")
|
77
|
+
result.map(&:label).should include "B"
|
78
|
+
%w(A C D E F).each do |key|
|
79
|
+
result.map(&:label).should_not include key
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "queries for the records containing a set keys" do
|
84
|
+
result = MyHstore.some_field_has_all_keys(["a", "key"])
|
85
|
+
result.map(&:label).should include "B"
|
86
|
+
%w(A C D E F).each do |key|
|
87
|
+
result.map(&:label).should_not include key
|
88
|
+
end
|
89
|
+
|
90
|
+
result = MyHstore.some_field_has_all_keys("a")
|
91
|
+
result.map(&:label).should include "B"
|
92
|
+
%w(A C D E F).each do |key|
|
93
|
+
result.map(&:label).should_not include key
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
it "queries for the records containing any set keys" do
|
98
|
+
result = MyHstore.some_field_has_any_key(["a", "c"])
|
99
|
+
%w(B F).each do |key|
|
100
|
+
result.map(&:label).should include key
|
101
|
+
end
|
102
|
+
%w(A C D E).each do |key|
|
103
|
+
result.map(&:label).should_not include key
|
104
|
+
end
|
105
|
+
|
106
|
+
result = MyHstore.some_field_has_any_key("a")
|
107
|
+
result.map(&:label).should include "B"
|
108
|
+
%w(A C D E F).each do |key|
|
109
|
+
result.map(&:label).should_not include key
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
it "queries the right data for the named scope *_eq" do
|
114
|
+
result = MyHstore.some_field_key_eq("1")
|
115
|
+
%w(A B).each do |key|
|
116
|
+
result.map(&:label).should include key
|
117
|
+
end
|
118
|
+
%w(C D E F).each do |key|
|
119
|
+
result.map(&:label).should_not include key
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
it "queries the right data for the named scope *_neq" do
|
124
|
+
result = MyHstore.some_field_key_neq("1")
|
125
|
+
%w(A B F).each do |key|
|
126
|
+
result.map(&:label).should_not include key
|
127
|
+
end
|
128
|
+
%w(C D E).each do |key|
|
129
|
+
result.map(&:label).should include key
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it "queries the right data for the named scope *_eq_any" do
|
134
|
+
result = MyHstore.some_field_key_eq_any(["2", "3"])
|
135
|
+
%w(A B E F).each do |key|
|
136
|
+
result.map(&:label).should_not include key
|
137
|
+
end
|
138
|
+
%w(C D).each do |key|
|
139
|
+
result.map(&:label).should include key
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it "queries the right data for the named scope *_neq_any" do
|
144
|
+
result = MyHstore.some_field_key_neq_any(["2", "3"])
|
145
|
+
%w(A B E).each do |key|
|
146
|
+
result.map(&:label).should include key
|
147
|
+
end
|
148
|
+
%w(C D F).each do |key|
|
149
|
+
result.map(&:label).should_not include key
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it "queries the right data for the named scope *_like" do
|
154
|
+
result = MyHstore.some_field_key_like("456")
|
155
|
+
result.map(&:label).should include "E"
|
156
|
+
%w(A B C D F).each do |key|
|
157
|
+
result.map(&:label).should_not include key
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
it "queries the right data for the named scope *_begins_with" do
|
162
|
+
result = MyHstore.some_field_key_begins_with("123")
|
163
|
+
result.map(&:label).should include "E"
|
164
|
+
%w(A B C D F).each do |key|
|
165
|
+
result.map(&:label).should_not include key
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
it "queries the right data for the named scope *_ends_with" do
|
170
|
+
result = MyHstore.some_field_key_ends_with("789")
|
171
|
+
result.map(&:label).should include "E"
|
172
|
+
%w(A B C D F).each do |key|
|
173
|
+
result.map(&:label).should_not include key
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/string_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe String do
|
4
|
+
describe "#from_hstore" do
|
5
|
+
it "handles a single key value pair" do
|
6
|
+
"a=>b".from_hstore.should == {"a" => "b"}
|
7
|
+
"\"a\"=>\"b\"".from_hstore.should == {"a" => "b"}
|
8
|
+
"\"a 1\"=>b".from_hstore.should == {"a 1" => "b"}
|
9
|
+
"\"a 1\"=>\"b 1\"".from_hstore.should == {"a 1" => "b 1"}
|
10
|
+
"1=>b".from_hstore.should == {"1" => "b"}
|
11
|
+
"1=>1".from_hstore.should == {"1" => "1"}
|
12
|
+
end
|
13
|
+
|
14
|
+
it "converts a multikeyed hstore string to a hash" do
|
15
|
+
"a=>b,c=>d".from_hstore.should == {"a" => "b", "c" => "d"}
|
16
|
+
"\"a\"=>\"b\",\"c\"=>\"d\"".from_hstore.should == {"a" => "b", "c" => "d"}
|
17
|
+
"\"a 1\"=>b,c=>d".from_hstore.should == {"a 1" => "b", "c" => "d"}
|
18
|
+
"\"a 1\"=>b,c=>\"d 1\"".from_hstore.should == {"a 1" => "b", "c" => "d 1"}
|
19
|
+
"\"a 1\"=>\"b 1\",c=>\"d 1\"".from_hstore.should == {"a 1" => "b 1", "c" => "d 1"}
|
20
|
+
"1=>b,c=>\"d 1\"".from_hstore.should == {"1" => "b", "c" => "d 1"}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activerecord2-hstore
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Tony Drake
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-02-16 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 15
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
- 0
|
32
|
+
- 0
|
33
|
+
version: 2.0.0
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: activerecord
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 2
|
47
|
+
- 3
|
48
|
+
- 0
|
49
|
+
version: 2.3.0
|
50
|
+
type: :runtime
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: activesupport
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 2
|
63
|
+
- 3
|
64
|
+
- 0
|
65
|
+
version: 2.3.0
|
66
|
+
type: :runtime
|
67
|
+
version_requirements: *id003
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: pg
|
70
|
+
prerelease: false
|
71
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
type: :runtime
|
81
|
+
version_requirements: *id004
|
82
|
+
description: Allows you to mark a column in a model as a HStore column letting you pass in a hash for its setter and retrieve a hash from its getter method. Also provides a series to named scopes for easy querying of data.
|
83
|
+
email:
|
84
|
+
- t27duck@gmail.com
|
85
|
+
executables: []
|
86
|
+
|
87
|
+
extensions: []
|
88
|
+
|
89
|
+
extra_rdoc_files: []
|
90
|
+
|
91
|
+
files:
|
92
|
+
- .gitignore
|
93
|
+
- .rspec
|
94
|
+
- Gemfile
|
95
|
+
- README.markdown
|
96
|
+
- Rakefile
|
97
|
+
- activerecord2-hstore.gemspec
|
98
|
+
- lib/activerecord2-hstore.rb
|
99
|
+
- lib/activerecord2-hstore/hash.rb
|
100
|
+
- lib/activerecord2-hstore/hstore.rb
|
101
|
+
- lib/activerecord2-hstore/string.rb
|
102
|
+
- lib/activerecord2-hstore/version.rb
|
103
|
+
- spec/hash_spec.rb
|
104
|
+
- spec/hstore_spec.rb
|
105
|
+
- spec/spec_helper.rb
|
106
|
+
- spec/string_spec.rb
|
107
|
+
homepage: https://github.com/t27duck/activerecord2-hstore
|
108
|
+
licenses: []
|
109
|
+
|
110
|
+
post_install_message:
|
111
|
+
rdoc_options: []
|
112
|
+
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
hash: 3
|
121
|
+
segments:
|
122
|
+
- 0
|
123
|
+
version: "0"
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
hash: 3
|
130
|
+
segments:
|
131
|
+
- 0
|
132
|
+
version: "0"
|
133
|
+
requirements: []
|
134
|
+
|
135
|
+
rubyforge_project: activerecord2-hstore
|
136
|
+
rubygems_version: 1.8.10
|
137
|
+
signing_key:
|
138
|
+
specification_version: 3
|
139
|
+
summary: Some basic support to help integrate Postgresql's HStore into a Rails 2.3 app
|
140
|
+
test_files:
|
141
|
+
- spec/hash_spec.rb
|
142
|
+
- spec/hstore_spec.rb
|
143
|
+
- spec/spec_helper.rb
|
144
|
+
- spec/string_spec.rb
|