durable_hash 1.0.1

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