hstore_accessor 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f427874b1399b9aadd39910014acf1374ed8665f
4
+ data.tar.gz: 6801a29b56e216ffb63de7ce1484ee76c20ba7b3
5
+ SHA512:
6
+ metadata.gz: 8702aed6b3f57de15f2175da5e50cbfabe60373b41492ff034196224f08494dc6763ac96d9cca7d21c51d6a1e56a2580d5e96803d675a6f10d59ce2f938bc3b4
7
+ data.tar.gz: 977817e66627297cb37f84f901644db7a2a2d5ecb995864eb68da7d24ebeba8d7fb6db6c87b0309936538eb2ed1ca92f84b5785474fc216dff8b63b4096bcbd8
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 JC Grubbs
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # HstoreAccessor
2
+
3
+ PostgreSQL provides an hstore data type for storing arbitrarily complex
4
+ structures in a column. ActiveRecord 4.0 supports Hstore but casts all
5
+ valus in the store to a string. Further, ActiveRecord does not provide
6
+ discrete fields to access values directly in the hstore column. The
7
+ HstoreAccessor gem solves both of these issues.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'hstore_accessor'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install hstore_accessor
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ class Product < ActiveRecord::Base
27
+
28
+ hstore_accessor :options,
29
+ color: :string,
30
+ weight: :integer,
31
+
32
+
33
+ end
34
+ ```
35
+
36
+ ## Contributing
37
+
38
+ 1. Fork it
39
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
40
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
41
+ 4. Push to the branch (`git push origin my-new-feature`)
42
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "hstore_accessor/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "hstore_accessor"
8
+ spec.version = HstoreAccessor::VERSION
9
+ spec.authors = ["Joe Hirn", "Cory Stephenson", "JC Grubbs"]
10
+ spec.email = ["joe@devmynd.com", "cory@devmynd.com", "jc@devmynd.com"]
11
+ spec.description = %q{Adds typed hstore backed fields to an ActiveRecord model.}
12
+ spec.summary = %q{Adds typed hstore backed fields to an ActiveRecord model.}
13
+ spec.homepage = "http://github.com/devmynd/hstore_accessor"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "pg", ">= 0.14.1"
22
+ spec.add_dependency "activesupport", ">= 3.2.0"
23
+
24
+ spec.add_development_dependency "activerecord", ">= 4.0.0.rc1"
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec"
28
+ end
@@ -0,0 +1,3 @@
1
+ module HstoreAccessor
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,85 @@
1
+ require "hstore_accessor/version"
2
+ require "active_support"
3
+ require "active_record"
4
+
5
+ module HstoreAccessor
6
+
7
+ InvalidDataTypeError = Class.new(StandardError)
8
+
9
+ VALID_TYPES = [:string, :integer, :float, :array, :hash]
10
+
11
+ SEPARATOR = ";|;"
12
+
13
+ DEFAULT_SERIALIZER = ->(val) { val.to_s }
14
+ DEFAULT_DESERIALIZER = ->(val) { val.to_s }
15
+
16
+ SERIALIZERS = {
17
+ :array => ->(val) { val.join(SEPARATOR) },
18
+ :hash => ->(val) { val.to_json }
19
+ }
20
+
21
+ DESERIALIZERS = {
22
+ :array => ->(val) { val.split(SEPARATOR) },
23
+ :hash => ->(val) { JSON.parse(val) },
24
+ :integer => ->(val) { val.to_i },
25
+ :float => ->(val) { val.to_f }
26
+ }
27
+
28
+ def self.included(base)
29
+ base.extend(ClassMethods)
30
+ end
31
+
32
+ def serialize(type, value, serializer=nil)
33
+ serializer ||= (SERIALIZERS[type] || DEFAULT_SERIALIZER)
34
+ serializer.call(value)
35
+ end
36
+
37
+ def deserialize(type, value, deserializer=nil)
38
+ deserializer ||= (DESERIALIZERS[type] || DEFAULT_DESERIALIZER)
39
+ deserializer.call(value)
40
+ end
41
+
42
+ module ClassMethods
43
+
44
+ def hstore_accessor(hstore_attribute, fields)
45
+
46
+ fields.each do |key, type|
47
+
48
+ raise InvalidDataTypeError unless VALID_TYPES.include?(type)
49
+
50
+ define_method("#{key}=") do |value|
51
+ send("#{hstore_attribute}=", (send(hstore_attribute) || {}).merge(key.to_s => serialize(type, value)))
52
+ send("#{hstore_attribute}_will_change!")
53
+ end
54
+
55
+ define_method(key) do
56
+ value = send(hstore_attribute) && send(hstore_attribute)[key.to_s]
57
+ deserialize(type, value)
58
+ end
59
+
60
+ case type
61
+ when :string
62
+ send(:scope, "with_#{key}", -> value { where("#{hstore_attribute} -> '#{key}' = ?", value.to_s)})
63
+ when :integer, :float
64
+ send(:scope, "#{key}_lt", -> value { where("#{hstore_attribute} -> '#{key}' < ?", value.to_s)})
65
+ send(:scope, "#{key}_lte", -> value { where("#{hstore_attribute} -> '#{key}' <= ?", value.to_s)})
66
+ send(:scope, "#{key}_eq", -> value { where("#{hstore_attribute} -> '#{key}' = ?", value.to_s)})
67
+ send(:scope, "#{key}_gte", -> value { where("#{hstore_attribute} -> '#{key}' >= ?", value.to_s)})
68
+ send(:scope, "#{key}_gt", -> value { where("#{hstore_attribute} -> '#{key}' > ?", value.to_s)})
69
+ when :array
70
+ send(:scope, "#{key}_eq", -> value { where("#{hstore_attribute} -> '#{key}' = ?", value.join(SEPARATOR))})
71
+ send(:scope, "#{key}_contains", -> value do
72
+ where("string_to_array(#{hstore_attribute} -> '#{key}', '#{SEPARATOR}') @> string_to_array(?, '#{SEPARATOR}')", Array[value].flatten)
73
+ end)
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+
83
+ ActiveSupport.on_load(:active_record) do
84
+ ActiveRecord::Base.send(:include, HstoreAccessor)
85
+ end
@@ -0,0 +1,165 @@
1
+ require "spec_helper"
2
+ require "active_support/all"
3
+
4
+ class Product < ActiveRecord::Base
5
+ hstore_accessor :options,
6
+ color: :string,
7
+ price: :integer,
8
+ weight: :float,
9
+ tags: :array,
10
+ reviews: :hash
11
+ end
12
+
13
+ describe HstoreAccessor do
14
+
15
+ context "macro" do
16
+
17
+ let(:product) { Product.new }
18
+
19
+ it "creates getters for the hstore fields" do
20
+ [:color, :price, :weight, :tags, :reviews].each do |field|
21
+ expect(product).to respond_to(field)
22
+ end
23
+ end
24
+
25
+ it "creates setters for the hstore fields" do
26
+ [:color, :price, :weight, :tags, :reviews].each do |field|
27
+ expect(product).to respond_to(:"#{field}=")
28
+ end
29
+ end
30
+
31
+ it "raises an InvalidDataTypeError if an invalid type is specified" do
32
+ expect do
33
+ class FakeModel
34
+ include HstoreAccessor
35
+ hstore_accessor :foo, bar: :baz
36
+ end
37
+ end.to raise_error(HstoreAccessor::InvalidDataTypeError)
38
+ end
39
+
40
+ end
41
+
42
+ describe "scopes" do
43
+
44
+ let!(:product_a) { Product.create(color: "green", price: 10, weight: 10.1, tags: ["tag1", "tag2", "tag3"]) }
45
+ let!(:product_b) { Product.create(color: "orange", price: 20, weight: 20.2, tags: ["tag2", "tag3", "tag4"]) }
46
+ let!(:product_c) { Product.create(color: "blue", price: 30, weight: 30.3, tags: ["tag3", "tag4", "tag5"]) }
47
+
48
+ context "for string fields support" do
49
+
50
+ it "equality" do
51
+ expect(Product.with_color("orange").to_a).to eq [product_b]
52
+ end
53
+
54
+ end
55
+
56
+ context "for integer fields support" do
57
+
58
+ it "less than" do
59
+ expect(Product.price_lt(20).to_a).to eq [product_a]
60
+ end
61
+
62
+ it "less than or equal" do
63
+ expect(Product.price_lte(20).to_a).to eq [product_a, product_b]
64
+ end
65
+
66
+ it "equality" do
67
+ expect(Product.price_eq(10).to_a).to eq [product_a]
68
+ end
69
+
70
+ it "greater than or equal" do
71
+ expect(Product.price_gte(20).to_a).to eq [product_b, product_c]
72
+ end
73
+
74
+ it "greater than" do
75
+ expect(Product.price_gt(20).to_a).to eq [product_c]
76
+ end
77
+
78
+ end
79
+
80
+ context "for float fields" do
81
+
82
+ it "less than" do
83
+ expect(Product.weight_lt(20.0).to_a).to eq [product_a]
84
+ end
85
+
86
+ it "less than or equal" do
87
+ expect(Product.weight_lte(20.2).to_a).to eq [product_a, product_b]
88
+ end
89
+
90
+ it "equality" do
91
+ expect(Product.weight_eq(10.1).to_a).to eq [product_a]
92
+ end
93
+
94
+ it "greater than or equal" do
95
+ expect(Product.weight_gte(20.2).to_a).to eq [product_b, product_c]
96
+ end
97
+
98
+ it "greater than" do
99
+ expect(Product.weight_gt(20.5).to_a).to eq [product_c]
100
+ end
101
+
102
+ end
103
+
104
+ context "for array fields" do
105
+
106
+ it "equality" do
107
+ expect(Product.tags_eq(["tag1", "tag2", "tag3"]).to_a).to eq [product_a]
108
+ end
109
+
110
+ it "contains" do
111
+ expect(Product.tags_contains("tag2").to_a).to eq [product_a, product_b]
112
+ expect(Product.tags_contains(["tag2", "tag3"]).to_a).to eq [product_a, product_b]
113
+ end
114
+
115
+ end
116
+
117
+ end
118
+
119
+ context "when assigning values it" do
120
+
121
+ let(:product) { Product.new }
122
+
123
+ it "correctly stores string values" do
124
+ product.color = "blue"
125
+ product.save
126
+ product.reload
127
+ expect(product.color).to eq "blue"
128
+ end
129
+
130
+ it "correctly stores integer values" do
131
+ product.price = 468
132
+ product.save
133
+ product.reload
134
+ expect(product.price).to eq 468
135
+ end
136
+
137
+ it "correctly stores float values" do
138
+ product.weight = 93.45
139
+ product.save
140
+ product.reload
141
+ expect(product.weight).to eq 93.45
142
+ end
143
+
144
+ it "correctly stores array values" do
145
+ product.tags = ["household", "living room", "kitchen"]
146
+ product.save
147
+ product.reload
148
+ expect(product.tags).to eq ["household", "living room", "kitchen"]
149
+ end
150
+
151
+ it "correctly stores hash values" do
152
+ product.reviews = { "user_123" => "4 stars", "user_994" => "3 stars" }
153
+ product.save
154
+ product.reload
155
+ expect(product.reviews).to eq({ "user_123" => "4 stars", "user_994" => "3 stars" })
156
+ end
157
+
158
+ it "setters call the _will_change! method of the store attribute" do
159
+ product.should_receive(:options_will_change!)
160
+ product.color = "green"
161
+ end
162
+
163
+ end
164
+
165
+ end
@@ -0,0 +1,22 @@
1
+ require "hstore_accessor"
2
+
3
+ ActiveRecord::Base.establish_connection(
4
+ adapter: "postgresql",
5
+ database: "hstore_accessor",
6
+ username: "root"
7
+ )
8
+
9
+ ActiveRecord::Base.connection.execute("CREATE EXTENSION hstore;") rescue PG::Error
10
+ ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS products;")
11
+
12
+ ActiveRecord::Base.connection.create_table(:products) do |t|
13
+ t.hstore :options
14
+ end
15
+
16
+ RSpec.configure do |config|
17
+ config.mock_with :rspec
18
+
19
+ config.after do
20
+ Product.delete_all
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hstore_accessor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Joe Hirn
8
+ - Cory Stephenson
9
+ - JC Grubbs
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-06-08 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: pg
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.14.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - '>='
27
+ - !ruby/object:Gem::Version
28
+ version: 0.14.1
29
+ - !ruby/object:Gem::Dependency
30
+ name: activesupport
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - '>='
34
+ - !ruby/object:Gem::Version
35
+ version: 3.2.0
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - '>='
41
+ - !ruby/object:Gem::Version
42
+ version: 3.2.0
43
+ - !ruby/object:Gem::Dependency
44
+ name: activerecord
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - '>='
48
+ - !ruby/object:Gem::Version
49
+ version: 4.0.0.rc1
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - '>='
55
+ - !ruby/object:Gem::Version
56
+ version: 4.0.0.rc1
57
+ - !ruby/object:Gem::Dependency
58
+ name: bundler
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ version: '1.3'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ~>
69
+ - !ruby/object:Gem::Version
70
+ version: '1.3'
71
+ - !ruby/object:Gem::Dependency
72
+ name: rake
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ - !ruby/object:Gem::Dependency
86
+ name: rspec
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ description: Adds typed hstore backed fields to an ActiveRecord model.
100
+ email:
101
+ - joe@devmynd.com
102
+ - cory@devmynd.com
103
+ - jc@devmynd.com
104
+ executables: []
105
+ extensions: []
106
+ extra_rdoc_files: []
107
+ files:
108
+ - .gitignore
109
+ - Gemfile
110
+ - LICENSE.txt
111
+ - README.md
112
+ - Rakefile
113
+ - hstore_accessor.gemspec
114
+ - lib/hstore_accessor.rb
115
+ - lib/hstore_accessor/version.rb
116
+ - spec/hstore_accessor_spec.rb
117
+ - spec/spec_helper.rb
118
+ homepage: http://github.com/devmynd/hstore_accessor
119
+ licenses:
120
+ - MIT
121
+ metadata: {}
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 2.0.2
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: Adds typed hstore backed fields to an ActiveRecord model.
142
+ test_files:
143
+ - spec/hstore_accessor_spec.rb
144
+ - spec/spec_helper.rb