durable_hash 1.0.1

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 ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'activerecord', ENV['ACTIVE_RECORD_VERSION']
4
+
5
+ # Add dependencies to develop your gem here.
6
+ # Include everything needed to run rake, tests, features, etc.
7
+ group :development do
8
+ gem "bundler", "~> 1.0.0"
9
+ gem "jeweler", "~> 1.6.0"
10
+ end
11
+
12
+ group :test do
13
+ gem 'sqlite3'
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,22 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activerecord (2.3.10)
5
+ activesupport (= 2.3.10)
6
+ activesupport (2.3.10)
7
+ git (1.2.5)
8
+ jeweler (1.6.0)
9
+ bundler (~> 1.0.0)
10
+ git (>= 1.2.5)
11
+ rake
12
+ rake (0.8.7)
13
+ sqlite3 (1.3.3)
14
+
15
+ PLATFORMS
16
+ ruby
17
+
18
+ DEPENDENCIES
19
+ activerecord
20
+ bundler (~> 1.0.0)
21
+ jeweler (~> 1.6.0)
22
+ sqlite3
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Francis Hwang
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 ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "durable_hash"
18
+ gem.homepage = "http://github.com/fhwang/durable_hash"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Maybe you want something that acts like a hash but is backed by ActiveRecord.}
21
+ gem.description = %Q{Maybe you want something that acts like a hash but is backed by ActiveRecord.}
22
+ gem.email = "francis.hwang@profitably.com"
23
+ gem.authors = ["Francis Hwang"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ ActiveRecordVersions = %w(3.0.7 2.3.10)
29
+
30
+ desc "Run all tests"
31
+ task :test do
32
+ ActiveRecordVersions.each do |ar_version|
33
+ cmd = "ACTIVE_RECORD_VERSION=#{ar_version} ruby test/durable_hash_test.rb"
34
+ puts cmd
35
+ puts `cd . && #{cmd}`
36
+ puts
37
+ end
38
+ end
39
+
40
+ task :default => :test
41
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.1
@@ -0,0 +1,56 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{durable_hash}
8
+ s.version = "1.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Francis Hwang"]
12
+ s.date = %q{2011-05-29}
13
+ s.description = %q{Maybe you want something that acts like a hash but is backed by ActiveRecord.}
14
+ s.email = %q{francis.hwang@profitably.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README"
18
+ ]
19
+ s.files = [
20
+ "Gemfile",
21
+ "Gemfile.lock",
22
+ "LICENSE.txt",
23
+ "README",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "durable_hash.gemspec",
27
+ "init.rb",
28
+ "lib/durable_hash.rb",
29
+ "test/durable_hash_test.rb"
30
+ ]
31
+ s.homepage = %q{http://github.com/fhwang/durable_hash}
32
+ s.licenses = ["MIT"]
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = %q{1.3.7}
35
+ s.summary = %q{Maybe you want something that acts like a hash but is backed by ActiveRecord.}
36
+
37
+ if s.respond_to? :specification_version then
38
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
39
+ s.specification_version = 3
40
+
41
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
42
+ s.add_runtime_dependency(%q<activerecord>, [">= 0"])
43
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
44
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.0"])
45
+ else
46
+ s.add_dependency(%q<activerecord>, [">= 0"])
47
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
48
+ s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
49
+ end
50
+ else
51
+ s.add_dependency(%q<activerecord>, [">= 0"])
52
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
53
+ s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
54
+ end
55
+ end
56
+
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "#{File.dirname(__FILE__)}/lib/durable_hash"
@@ -0,0 +1,115 @@
1
+ module DurableHash
2
+ mattr_accessor :deserializers
3
+ self.deserializers = Hash.new { |h,durable_hash_class|
4
+ h[durable_hash_class] = {}
5
+ }
6
+
7
+ def self.deserializer(durable_hash_class, value_class)
8
+ deserializer = deserializers[durable_hash_class][value_class]
9
+ unless deserializer
10
+ match_class = deserializers[durable_hash_class].keys.detect { |klass|
11
+ value_class < klass
12
+ }
13
+ deserializer = deserializers[durable_hash_class][match_class]
14
+ end
15
+ deserializer
16
+ end
17
+
18
+ mattr_accessor :serializers
19
+ self.serializers = Hash.new { |h,durable_hash_class|
20
+ h[durable_hash_class] = {}
21
+ }
22
+
23
+ def self.serializer(durable_hash_class, value_class)
24
+ serializer = serializers[durable_hash_class][value_class]
25
+ unless serializer
26
+ match_class = serializers[durable_hash_class].keys.detect { |klass|
27
+ value_class < klass
28
+ }
29
+ serializer = serializers[durable_hash_class][match_class]
30
+ end
31
+ serializer
32
+ end
33
+
34
+ def self.included(includer)
35
+ def includer.acts_as_durable_hash
36
+ self.validates_uniqueness_of :key
37
+ self.validates_presence_of :key, :value_class
38
+ self.serialize :value
39
+
40
+ def self.[](key)
41
+ if record = find_by_key(key.to_s)
42
+ record.value
43
+ end
44
+ end
45
+
46
+ def self.[]=(key, value)
47
+ if record = find_by_key(key.to_s)
48
+ record.value = value
49
+ record.save!
50
+ else
51
+ create! :key => key.to_s, :value => value
52
+ end
53
+ end
54
+
55
+ self.before_validation do |record|
56
+ record.value_class = record.value.class.name
57
+ serializer = DurableHash.serializer(record.class, record.value.class)
58
+ record.value = serializer.call(record.value) if serializer
59
+ end
60
+
61
+ if ActiveRecord::VERSION::MAJOR == 3
62
+ define_method(:durable_hash_after_find) do
63
+ if attributes['value_class']
64
+ serializer = DurableHash.deserializer(
65
+ self.class, value_class.constantize
66
+ )
67
+ if serializer
68
+ self.value = serializer.call self.value
69
+ elsif value_class == 'Fixnum'
70
+ self.value = self.value.to_i
71
+ elsif value_class == 'Float'
72
+ self.value = self.value.to_f
73
+ end
74
+ end
75
+ end
76
+ self.after_find :durable_hash_after_find
77
+ else
78
+ define_method(:after_find) do
79
+ if attributes['value_class']
80
+ serializer = DurableHash.deserializer(
81
+ self.class, value_class.constantize
82
+ )
83
+ if serializer
84
+ self.value = serializer.call self.value
85
+ elsif value_class == 'Fixnum'
86
+ self.value = self.value.to_i
87
+ elsif value_class == 'Float'
88
+ self.value = self.value.to_f
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ if block_given?
95
+ yield DurableHash::Builder.new(self)
96
+ end
97
+ end
98
+ end
99
+
100
+ class Builder
101
+ def initialize(klass); @klass = klass; end
102
+
103
+ def deserialize(value_class, &block)
104
+ DurableHash.deserializers[@klass][value_class] = block
105
+ end
106
+
107
+ def serialize(value_class, &block)
108
+ DurableHash.serializers[@klass][value_class] = block
109
+ end
110
+ end
111
+ end
112
+
113
+ class ActiveRecord::Base
114
+ include DurableHash
115
+ end
@@ -0,0 +1,208 @@
1
+ RAILS_ENV = 'test'
2
+ require 'rubygems'
3
+ require 'active_record'
4
+ require 'active_record/base'
5
+ require 'active_support/core_ext/logger'
6
+ require File.dirname(__FILE__) + '/../lib/durable_hash'
7
+ require 'test/unit'
8
+
9
+ # Configure ActiveRecord
10
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/test.log')
11
+ ActiveRecord::Base.establish_connection(
12
+ 'timeout' => 5000, 'adapter' => 'sqlite3', 'database' => 'test/test.sqlite3',
13
+ 'pool' => 5
14
+ )
15
+
16
+ # Create the DB schema
17
+ silence_stream(STDOUT) do
18
+ ActiveRecord::Schema.define do
19
+ create_table 'application_settings', :force => true do |app_setting|
20
+ app_setting.string 'key'
21
+ app_setting.string 'value'
22
+ app_setting.string 'value_class'
23
+ end
24
+
25
+ create_table 'customized_settings', :force => true do |app_setting|
26
+ app_setting.string 'key'
27
+ app_setting.string 'value'
28
+ app_setting.string 'value_class'
29
+ end
30
+ end
31
+ end
32
+
33
+ # Define some sample classes
34
+ class ApplicationSetting < ActiveRecord::Base
35
+ acts_as_durable_hash
36
+ end
37
+
38
+ module WrapperModule
39
+ class Custom
40
+ attr_accessor :value
41
+
42
+ def initialize(value); @value = value; end
43
+ end
44
+
45
+ class SonOfCustom < Custom
46
+ end
47
+ end
48
+
49
+ class CustomizedSetting < ActiveRecord::Base
50
+ acts_as_durable_hash do |dh|
51
+ dh.serialize(WrapperModule::Custom) do |custom|
52
+ custom.value
53
+ end
54
+ dh.deserialize(WrapperModule::Custom) do |data|
55
+ WrapperModule::Custom.new data
56
+ end
57
+ end
58
+ end
59
+
60
+ # Finally some tests
61
+ class EmptyApplicationSettingTestCase < Test::Unit::TestCase
62
+ def setup
63
+ ApplicationSetting.destroy_all
64
+ end
65
+
66
+ def test_should_return_nil_for_most_everything
67
+ assert_nil(ApplicationSetting['foo'])
68
+ end
69
+ end
70
+
71
+ class ApplicationSettingReadingTestCase < Test::Unit::TestCase
72
+ def setup
73
+ as = ApplicationSetting.find_or_create_by_key 'foo'
74
+ as.value = 'bar'
75
+ as.save!
76
+ end
77
+
78
+ def test_should_return_the_value
79
+ assert_equal(ApplicationSetting['foo'], 'bar')
80
+ end
81
+
82
+ def test_should_return_the_value_with_a_symbol_too
83
+ assert_equal(ApplicationSetting[:foo], 'bar')
84
+ end
85
+ end
86
+
87
+ class ApplicationSettingCreatingTestCase < Test::Unit::TestCase
88
+ def setup
89
+ ApplicationSetting.destroy_all
90
+ end
91
+
92
+ def test_it_should_handle_a_write
93
+ ApplicationSetting['foo'] = 'bar'
94
+ assert_equal(ApplicationSetting['foo'], 'bar')
95
+ end
96
+
97
+ def test_should_handle_a_write_with_a_symbol
98
+ ApplicationSetting[:foo] = 'bar'
99
+ assert_equal(ApplicationSetting['foo'], 'bar')
100
+ end
101
+ end
102
+
103
+ class ApplicationSettingUpdatingTestCase < Test::Unit::TestCase
104
+ def setup
105
+ ApplicationSetting.destroy_all
106
+ ApplicationSetting.create! :key => 'foo', :value => 'bar'
107
+ end
108
+
109
+ def test_should_handle_a_write
110
+ ApplicationSetting['foo'] = 'baz'
111
+ assert_equal(ApplicationSetting['foo'], 'baz')
112
+ end
113
+ end
114
+
115
+ class ApplicationSettingUniquenessTestCase < Test::Unit::TestCase
116
+ def setup
117
+ ApplicationSetting.destroy_all
118
+ ApplicationSetting.create! :key => 'foo', :value => 'bar'
119
+ end
120
+
121
+ def test_should_be_set_on_key_automatically
122
+ assert_raises(ActiveRecord::RecordInvalid) {
123
+ ApplicationSetting.create!(:key => 'foo', :value => 'baz')
124
+ }
125
+ end
126
+
127
+ def test_should_prevent_new_instances_from_being_seen_as_valid
128
+ assert(!ApplicationSetting.new(:key => 'foo', :value => 'baz').valid?)
129
+ end
130
+ end
131
+
132
+ class ApplicationSettingWithAnIntegerTestCase < Test::Unit::TestCase
133
+ def setup
134
+ ApplicationSetting.destroy_all
135
+ end
136
+
137
+ def test_should_read_and_write_as_an_integer
138
+ ApplicationSetting['foo'] = 123
139
+ assert_equal(ApplicationSetting['foo'], 123)
140
+ end
141
+ end
142
+
143
+ class ApplicationSettingWithAFloatTestCase < Test::Unit::TestCase
144
+ def setup
145
+ ApplicationSetting.destroy_all
146
+ end
147
+
148
+ def test_should_read_and_write_as_a_float
149
+ ApplicationSetting['foo'] = 123.0
150
+ assert_equal(ApplicationSetting['foo'].class, Float)
151
+ assert_in_delta(123.0, ApplicationSetting['foo'], 0.00001)
152
+ end
153
+ end
154
+
155
+ class ApplicationSettingWithAnArrayTestCase < Test::Unit::TestCase
156
+ def setup
157
+ ApplicationSetting.destroy_all
158
+ end
159
+
160
+ def test_should_read_and_write_as_a_float
161
+ ApplicationSetting['foo'] = [1,2,3]
162
+ assert_equal(ApplicationSetting['foo'], [1,2,3])
163
+ end
164
+ end
165
+
166
+ class ApplicationSettingValidForANewInstanceCase < Test::Unit::TestCase
167
+ def setup
168
+ ApplicationSetting.destroy_all
169
+ end
170
+
171
+ def test_should_not_need_a_value_class_to_be_explicitly_set
172
+ app_setting = ApplicationSetting.new :key => 'username', :value => 'bob'
173
+ assert(app_setting.valid?)
174
+ end
175
+ end
176
+
177
+ class CustomizedSettingCustomSerializationTestCase < Test::Unit::TestCase
178
+ def setup
179
+ CustomizedSetting.destroy_all
180
+ end
181
+
182
+ def test_should_save_and_load_with_a_custom_serialization
183
+ value = WrapperModule::Custom.new('bar')
184
+ CustomizedSetting['foo'] = value
185
+ value_prime = CustomizedSetting['foo']
186
+ # let's make sure there's no in-Ruby caching going on which could give this
187
+ # test a false positive
188
+ assert_not_equal(value.object_id, value_prime.object_id)
189
+ assert_equal(value_prime.class, WrapperModule::Custom)
190
+ assert_equal(value_prime.value, 'bar')
191
+ end
192
+
193
+ def test_should_not_try_to_mess_with_a_normal_value
194
+ CustomizedSetting['baz'] = 'fiz'
195
+ assert_equal(CustomizedSetting['baz'], 'fiz')
196
+ end
197
+
198
+ def test_should_use_custom_serialization_for_any_subclasses_of_Custom_too
199
+ value = WrapperModule::SonOfCustom.new('bar')
200
+ CustomizedSetting['foo'] = value
201
+ value_prime = CustomizedSetting['foo']
202
+ # let's make sure there's no in-Ruby caching going on which could give this
203
+ # test a false positive
204
+ assert_not_equal(value.object_id, value_prime.object_id)
205
+ assert_equal(value_prime.class, WrapperModule::Custom)
206
+ assert_equal(value_prime.value, 'bar')
207
+ end
208
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: durable_hash
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 1
10
+ version: 1.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Francis Hwang
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-05-29 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ hash: 3
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ name: activerecord
33
+ prerelease: false
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ hash: 23
42
+ segments:
43
+ - 1
44
+ - 0
45
+ - 0
46
+ version: 1.0.0
47
+ type: :development
48
+ name: bundler
49
+ prerelease: false
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ hash: 15
58
+ segments:
59
+ - 1
60
+ - 6
61
+ - 0
62
+ version: 1.6.0
63
+ type: :development
64
+ name: jeweler
65
+ prerelease: false
66
+ version_requirements: *id003
67
+ description: Maybe you want something that acts like a hash but is backed by ActiveRecord.
68
+ email: francis.hwang@profitably.com
69
+ executables: []
70
+
71
+ extensions: []
72
+
73
+ extra_rdoc_files:
74
+ - LICENSE.txt
75
+ - README
76
+ files:
77
+ - Gemfile
78
+ - Gemfile.lock
79
+ - LICENSE.txt
80
+ - README
81
+ - Rakefile
82
+ - VERSION
83
+ - durable_hash.gemspec
84
+ - init.rb
85
+ - lib/durable_hash.rb
86
+ - test/durable_hash_test.rb
87
+ has_rdoc: true
88
+ homepage: http://github.com/fhwang/durable_hash
89
+ licenses:
90
+ - MIT
91
+ post_install_message:
92
+ rdoc_options: []
93
+
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ hash: 3
102
+ segments:
103
+ - 0
104
+ version: "0"
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ hash: 3
111
+ segments:
112
+ - 0
113
+ version: "0"
114
+ requirements: []
115
+
116
+ rubyforge_project:
117
+ rubygems_version: 1.3.7
118
+ signing_key:
119
+ specification_version: 3
120
+ summary: Maybe you want something that acts like a hash but is backed by ActiveRecord.
121
+ test_files: []
122
+