nested-hstore 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Tom Benner
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ Nested Hstore
2
+ =============
3
+ Store nested hashes and other types in ActiveRecord hstores
4
+
5
+ Overview
6
+ --------
7
+
8
+ Postgres hstores offer a number of benefits, but they don't natively support multi-level data. Nested Hstore adds this support to ActiveRecord, letting you treat an hstore like a NoSQL-like document.
9
+
10
+ [Hstore functions](http://www.postgresql.org/docs/9.1/static/hstore.html) are still supported at the root level.
11
+
12
+ It also lets you store data types other than hashes in an hstore. All of the following values will be returned verbatim:
13
+
14
+ ```ruby
15
+ # Nested hash
16
+ post.my_hstore = {
17
+ 'title' => 'My Post',
18
+ 'comment_ids' => [34, 67, 82],
19
+ 'user' => {
20
+ 'id' => 15,
21
+ 'username' => 'janedoe',
22
+ }
23
+ }
24
+
25
+ # Array
26
+ post.my_hstore = [34, 67, 82]
27
+
28
+ # Array of nested hashes
29
+ post.my_hstore = [
30
+ {
31
+ 'id' => 15,
32
+ 'username' => 'janedoe'
33
+ },
34
+ {
35
+ 'id' => 16,
36
+ 'username' => 'johndoe'
37
+ }
38
+ ]
39
+
40
+ # Integer
41
+ post.my_hstore = 43
42
+
43
+ # Float
44
+ post.my_hstore = 43.1
45
+
46
+ # String
47
+ post.my_hstore = 'janedoe'
48
+ ```
49
+
50
+ Installation
51
+ ------------
52
+
53
+ Include it in your Gemfile:
54
+
55
+ gem 'nested-hstore'
56
+
57
+ Set up [activerecord-postgres-hstore](https://github.com/diogob/activerecord-postgres-hstore) if you haven't already. Instead of using ActiveRecord::Coders::Hstore, use:
58
+
59
+ ```ruby
60
+ class Post < ActiveRecord::Base
61
+ serialize :my_store, ActiveRecord::Coders::NestedHstore
62
+ end
63
+ ```
64
+
65
+ License
66
+ -------
67
+
68
+ Nested Hstore is released under the MIT License. Please see the MIT-LICENSE file for details.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ task :default => :test
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.libs << "lib"
9
+ t.libs << "test"
10
+ t.test_files = FileList["test/**/*_test.rb"]
11
+ t.verbose = true
12
+ end
@@ -0,0 +1,20 @@
1
+ module ActiveRecord
2
+ module Coders
3
+ class NestedHstore < Hstore
4
+ def initialize(default=nil)
5
+ super(default)
6
+ @nested_serializer = ::NestedHstore::Serializer.new
7
+ end
8
+
9
+ private
10
+
11
+ def to_hstore obj
12
+ super(@nested_serializer.serialize(obj))
13
+ end
14
+
15
+ def from_hstore hstore
16
+ @nested_serializer.deserialize(super)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ # Dependencies
2
+ require 'activerecord-postgres-hstore'
3
+ require 'active_support/all'
4
+
5
+ # Self
6
+ require 'active_record/coders/nested_hstore'
7
+ require 'nested_hstore/serializer'
8
+ require 'nested_hstore/version'
@@ -0,0 +1,117 @@
1
+ module NestedHstore
2
+ class Serializer
3
+
4
+ def initialize
5
+ @type_key = '__TYPE__'
6
+ @types_map = {
7
+ array: '__ARRAY__',
8
+ float: '__FLOAT__',
9
+ hash: '__HASH__',
10
+ integer: '__INTEGER__',
11
+ string: '__STRING__'
12
+ }
13
+ @types_map_inverted = @types_map.invert
14
+ @value_key = '__VALUE__'
15
+ end
16
+
17
+ def serialize(value)
18
+ return nil if value.nil?
19
+ if value.is_a?(Array)
20
+ type = :array
21
+ hash = array_to_hash(value)
22
+ elsif value.is_a?(Float)
23
+ type = :float
24
+ hash = { @value_key => value }
25
+ elsif value.is_a?(Hash)
26
+ type = :hash
27
+ hash = standardize_value(value)
28
+ elsif value.is_a?(Integer)
29
+ type = :integer
30
+ hash = { @value_key => value }
31
+ elsif value.is_a?(String)
32
+ type = :string
33
+ hash = { @value_key => value }
34
+ else
35
+ raise "Unsupported hstore type: #{value.class}"
36
+ end
37
+ hash_to_hstore(type, hash)
38
+ end
39
+
40
+ def deserialize(hash)
41
+ return nil if hash.nil?
42
+ raise 'Hstore value should be a hash' unless hash.is_a?(Hash)
43
+ type_value = hash.delete(@type_key)
44
+ type = @types_map_inverted[type_value]
45
+ deserialized = case type
46
+ when :array
47
+ hash.values.map { |v| decode_json_if_json(v) }
48
+ when :float
49
+ hash[@value_key].to_f
50
+ when :hash
51
+ hash.each do |k, v|
52
+ hash[k] = decode_json_if_json(v)
53
+ end
54
+ hash
55
+ when :integer
56
+ hash[@value_key].to_i
57
+ when :string
58
+ hash[@value_key]
59
+ end
60
+ deserialized
61
+ end
62
+
63
+ private
64
+
65
+ def hash_to_hstore(type, hash)
66
+ return {} if type == :hash && hash.blank?
67
+
68
+ hstore = hash.dup
69
+ hstore.each do |k, v|
70
+ if v.is_a?(Array) || v.is_a?(Hash)
71
+ hstore[k] = encode_json(v)
72
+ else
73
+ hstore[k] = v.to_s
74
+ end
75
+ end
76
+ hstore.merge(@type_key => @types_map[type])
77
+ end
78
+
79
+ def standardize_value(value)
80
+ if value.is_a?(Array)
81
+ value.map! do |v|
82
+ standardize_value(v)
83
+ end
84
+ elsif value.is_a?(Hash)
85
+ value.each do |k, v|
86
+ value[k] = standardize_value(v)
87
+ end
88
+ # Standardize Times to an ISO string, as that's what DateTime#to_s evaluates to
89
+ elsif value.is_a?(Time) || value.is_a?(ActiveSupport::TimeWithZone)
90
+ value = value.iso8601
91
+ end
92
+ value
93
+ end
94
+
95
+ def array_to_hash(array)
96
+ hash = {}
97
+ array.each_with_index do |value, index|
98
+ hash[index.to_s] = standardize_value(value)
99
+ end
100
+ hash
101
+ end
102
+
103
+ # This isn't ideal: how do we know whether each value in an hstore is JSON or a
104
+ # string/integer/etc?
105
+ def decode_json_if_json(value)
106
+ begin
107
+ ActiveSupport::JSON.decode(value)
108
+ rescue
109
+ value
110
+ end
111
+ end
112
+
113
+ def encode_json(value)
114
+ ActiveSupport::JSON.encode(value)
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,3 @@
1
+ module NestedHstore
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nested-hstore
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tom Benner
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activerecord-postgres-hstore
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: activesupport
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Store nested hashes and other types in ActiveRecord hstores
63
+ email:
64
+ - tombenner@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - lib/active_record/coders/nested_hstore.rb
70
+ - lib/nested-hstore.rb
71
+ - lib/nested_hstore/serializer.rb
72
+ - lib/nested_hstore/version.rb
73
+ - MIT-LICENSE
74
+ - Rakefile
75
+ - README.md
76
+ homepage: https://github.com/tombenner/nested-hstore
77
+ licenses:
78
+ - MIT
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 1.8.24
98
+ signing_key:
99
+ specification_version: 3
100
+ summary: Store nested hashes and other types in ActiveRecord hstores
101
+ test_files: []