hstore_accessor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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