gold-record 0.1.0 → 0.2.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.
data/HISTORY.rdoc CHANGED
@@ -1,4 +1,16 @@
1
- === Version 0.1.0 (git)
1
+ === Version 0.2.0 (2009-12-06)
2
+
3
+ This version replaces ID generation and parsing code from UUIDTools with smaller/faster implementation using pack and SecureRandom directly.
4
+
5
+ NOTE: #to_param now generates hex UUIDs *without* dashes. This is a slight change in behavior from 0.1.0. Hex-encoded UUIDs with dashes are still supported by #find.
6
+
7
+ * #find supports binary, Base64, hex and dash-delimited hex UUID strings as input.
8
+ * Support for varied UUID encodings is implemented in GoldRecord::UUID.coerce.
9
+ * ID parsing is about 36x faster on my machine (see extras/benchmark-uuidtools-vs-pack).
10
+ * Initial support for URL-safe Base64 encoding.
11
+ * #to_param is no longer patched using alias_method_chain. This prevents interfering with models using a specific #to_param implementation.
12
+
13
+ === Version 0.1.0 (2009-10-01)
2
14
 
3
15
  This is the first preview release of GoldRecord, an extension for ActiveRecord that implements unobtrusive support for binary UUIDs.
4
16
 
data/Rakefile CHANGED
@@ -18,9 +18,6 @@ GEM_NAME = File.join(File.dirname(__FILE__), "#{PKG_NAME}-#{PKG_VERSION}.ge
18
18
 
19
19
  RELEASE_NAME = "REL #{PKG_VERSION}"
20
20
 
21
- RUBY_FORGE_PROJECT = "gold-record"
22
- RUBY_FORGE_USER = "ydnar"
23
-
24
21
  MYSQL_DB_USER = 'root'
25
22
 
26
23
  PKG_FILES = FileList[
@@ -62,7 +59,7 @@ task :rebuild_mysql_databases => 'mysql:rebuild_databases'
62
59
 
63
60
  Rake::RDocTask.new { |rdoc|
64
61
  rdoc.rdoc_dir = 'doc'
65
- rdoc.title = "GoldRecord -- Object-relation mapping put on rails"
62
+ rdoc.title = "GoldRecord Unobtrusive binary UUID support for ActiveRecord"
66
63
  rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
67
64
  rdoc.options << '--charset' << 'utf-8'
68
65
  rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
@@ -78,7 +75,7 @@ dist_dirs = [ "lib", "test" ]
78
75
  spec = Gem::Specification.new do |s|
79
76
  s.name = PKG_NAME
80
77
  s.version = PKG_VERSION
81
- s.summary = "Binary UUID support for ActiveRecord"
78
+ s.summary = "Unobtrusive binary UUID support for ActiveRecord"
82
79
  s.description = "Unobtrusive binary UUID support for ActiveRecord. Supports migrations, reflections, assocations and SchemaDumper."
83
80
  s.author = "Randy Reddig"
84
81
  s.email = "randy@shaderlab.com"
@@ -118,6 +115,9 @@ spec = Gem::Specification.new do |s|
118
115
  end
119
116
  end
120
117
 
118
+ "Default gem task"
119
+ task :gem => "gem:build"
120
+
121
121
  namespace :gem do
122
122
  desc "Print gemspec"
123
123
  task :spec do
data/lib/gold_record.rb CHANGED
@@ -21,6 +21,7 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
 
24
+ require 'gold_record/uuid'
24
25
  require 'gold_record/base'
25
26
  require 'gold_record/fixtures'
26
27
  require 'gold_record/connection_adapters/mysql_adapter'
@@ -1,3 +1,4 @@
1
+ require 'active_record'
1
2
  require 'uuidtools'
2
3
 
3
4
  module GoldRecord
@@ -5,21 +6,13 @@ module GoldRecord
5
6
  def gold_record?
6
7
  true
7
8
  end
8
-
9
- def coerce_id(id)
10
- if id.is_a?(String) && id.size == 16
11
- id
12
- else
13
- UUIDTools::UUID.parse(id).raw rescue nil
14
- end
15
- end
16
9
 
17
10
  def find_one_with_uuid(id, options)
18
- find_one_without_uuid(coerce_id(id), options)
11
+ find_one_without_uuid(GoldRecord::UUID.coerce(id), options)
19
12
  end
20
13
 
21
14
  def find_some_with_uuid(ids, options)
22
- ids = ids.map { |id| coerce_id(id) }
15
+ ids = ids.map { |id| GoldRecord::UUID.coerce(id) }
23
16
  ids = ids.uniq # must do this after coercion
24
17
  find_some_without_uuid(ids, options)
25
18
  end
@@ -29,14 +22,13 @@ module GoldRecord
29
22
  def to_uuid
30
23
  UUIDTools::UUID.parse_raw(id) rescue nil
31
24
  end
32
-
33
- def to_param_with_uuid
34
- uuid = to_uuid
35
- uuid ? uuid.to_param : nil
25
+
26
+ def to_param
27
+ (id = self.id) ? GoldRecord::UUID.encode_hex(id) : nil
36
28
  end
37
29
 
38
30
  def generate_id!
39
- self[self.class.primary_key] ||= UUIDTools::UUID.random_create.raw
31
+ self[self.class.primary_key] ||= GoldRecord::UUID.random_generate
40
32
  end
41
33
  end
42
34
 
@@ -48,7 +40,6 @@ module GoldRecord
48
40
  alias_method_chain :find_some, :uuid
49
41
  end
50
42
  include InstanceMethods
51
- alias_method_chain :to_param, :uuid
52
43
  before_create :generate_id!
53
44
  end
54
45
  end
@@ -0,0 +1,40 @@
1
+ require 'securerandom'
2
+
3
+ module GoldRecord
4
+ module UUID
5
+ def self.random_generate
6
+ SecureRandom.random_bytes(16)
7
+ end
8
+
9
+ def self.coerce(value)
10
+ return nil unless value.kind_of?(String)
11
+ if value.size == 22
12
+ value = urlsafe_decode64(value)
13
+ elsif value.size == 32 || value.size == 36
14
+ value = decode_hex(value)
15
+ end
16
+ value = nil unless value.size == 16
17
+ value
18
+ end
19
+
20
+ def self.encode_hex(bin)
21
+ bin.unpack("H*").first
22
+ end
23
+
24
+ def self.decode_hex(str)
25
+ [str.delete("-")].pack("H*")
26
+ end
27
+
28
+ # Slightly modified backport from Ruby 1.9
29
+ # http://www.ruby-doc.org/ruby-1.9/classes/Base64.html
30
+ # Strips padding char (=) and newlines from end of encoded string.
31
+
32
+ def self.urlsafe_encode64(bin)
33
+ [bin].pack("m").tr("+/=", "-_ ").strip.delete("\n")
34
+ end
35
+
36
+ def self.urlsafe_decode64(str)
37
+ "#{str.tr("-_", "+/")}===".unpack("m").first
38
+ end
39
+ end
40
+ end
@@ -1,7 +1,7 @@
1
1
  module GoldRecord
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 1
4
+ MINOR = 2
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
@@ -0,0 +1,43 @@
1
+ require 'cases/helper'
2
+
3
+ class CoerceTest < ActiveRecord::TestCase
4
+ def test_coerce_with_nil
5
+ assert_equal nil, GoldRecord::UUID.coerce(nil)
6
+ end
7
+
8
+ def test_coerce_with_false
9
+ assert_equal nil, GoldRecord::UUID.coerce(false)
10
+ end
11
+
12
+ def test_coerce_with_integer
13
+ assert_equal nil, GoldRecord::UUID.coerce(12345)
14
+ end
15
+
16
+ def test_coerce_with_empty_string
17
+ assert_equal nil, GoldRecord::UUID.coerce("")
18
+ end
19
+
20
+ def test_coerce_with_bogus_string
21
+ assert_equal nil, GoldRecord::UUID.coerce("BOGUS")
22
+ end
23
+
24
+ def test_coerce_with_22_character_bogus_string
25
+ assert_equal nil, GoldRecord::UUID.coerce("~" * 22)
26
+ end
27
+
28
+ def test_coerce_with_36_character_bogus_string
29
+ assert_equal nil, GoldRecord::UUID.coerce("~" * 36)
30
+ end
31
+
32
+ def test_coerce_with_binary_uuid
33
+ assert_equal NULL_UUID_RAW, GoldRecord::UUID.coerce(NULL_UUID_RAW)
34
+ end
35
+
36
+ def test_coerce_with_hex_uuid
37
+ assert_equal NULL_UUID_RAW, GoldRecord::UUID.coerce(NULL_UUID_HEX)
38
+ end
39
+
40
+ def test_coerce_with_base64_uuid
41
+ assert_equal NULL_UUID_RAW, GoldRecord::UUID.coerce(NULL_UUID_BASE64)
42
+ end
43
+ end
@@ -7,11 +7,21 @@ class FindTest < ActiveRecord::TestCase
7
7
  def test_find_with_one_binary_uuid
8
8
  artist = Artist.find identify(:michael_jackson)
9
9
  assert artist.instance_of?(Artist)
10
+ assert_equal "Michael Jackson", artist.name
10
11
  end
11
12
 
12
13
  def test_find_with_one_hex_uuid
13
14
  artist = Artist.find identify_hex(:michael_jackson)
14
15
  assert artist.instance_of?(Artist)
16
+ assert_equal "Michael Jackson", artist.name
17
+ end
18
+
19
+ def test_find_with_one_base64_uuid
20
+ assert_equal GoldRecord::UUID.coerce(identify(:michael_jackson)),
21
+ GoldRecord::UUID.coerce(identify_base64(:michael_jackson))
22
+ artist = Artist.find identify_base64(:michael_jackson)
23
+ assert artist.instance_of?(Artist)
24
+ assert_equal "Michael Jackson", artist.name
15
25
  end
16
26
 
17
27
  def test_find_with_multiple_binary_uuids
@@ -38,11 +48,23 @@ class FindTest < ActiveRecord::TestCase
38
48
  end
39
49
  end
40
50
 
51
+ def test_find_with_multiple_base64_uuids
52
+ artists = Artist.find([
53
+ identify_base64(:method_man),
54
+ identify_base64(:beatles),
55
+ identify_base64(:warren_g),
56
+ ])
57
+ assert_equal 3, artists.size
58
+ artists.each do |artist|
59
+ assert artist.instance_of?(Artist)
60
+ end
61
+ end
62
+
41
63
  def test_find_with_multiple_mixed_uuids
42
64
  artists = Artist.find([
43
- identify_hex(:beatles),
44
- identify(:michael_jackson),
45
- identify_hex(:warren_g),
65
+ identify(:beatles),
66
+ identify_hex(:michael_jackson),
67
+ identify_base64(:warren_g),
46
68
  ])
47
69
  assert_equal 3, artists.size
48
70
  artists.each do |artist|
@@ -58,6 +80,9 @@ class FindTest < ActiveRecord::TestCase
58
80
  identify_hex(:beatles),
59
81
  identify_hex(:method_man),
60
82
  identify_hex(:warren_g),
83
+ identify_base64(:beatles),
84
+ identify_base64(:method_man),
85
+ identify_base64(:warren_g),
61
86
  ])
62
87
  assert_equal 3, artists.size
63
88
  artists.each do |artist|
data/test/cases/helper.rb CHANGED
@@ -21,8 +21,9 @@ Fixtures.send :include, GoldRecord::Fixtures
21
21
 
22
22
  # UUID composed of all null bytes
23
23
  NULL_UUID_RAW = ("\000" * 16).freeze
24
- NULL_UUID = UUIDTools::UUID.parse_raw(NULL_UUID_RAW)
25
- NULL_UUID_HEX = NULL_UUID.to_s
24
+ NULL_UUID = UUIDTools::UUID.parse_raw(NULL_UUID_RAW).freeze
25
+ NULL_UUID_HEX = "00000000-0000-0000-0000-000000000000".freeze
26
+ NULL_UUID_BASE64 = ("A" * 22).freeze
26
27
 
27
28
  # Show backtraces for deprecated behavior for quicker cleanup.
28
29
  ActiveSupport::Deprecation.debug = true
@@ -79,7 +80,19 @@ class ActiveSupport::TestCase
79
80
  Fixtures.identify(label)
80
81
  end
81
82
 
83
+ def identify_uuid(label)
84
+ UUIDTools::UUID.parse_raw(identify(label))
85
+ end
86
+
82
87
  def identify_hex(label)
83
- UUIDTools::UUID.parse_raw(identify(label)).to_s
88
+ identify_uuid(label).to_s
89
+ end
90
+
91
+ def identify_base64(label)
92
+ raw = [identify_hex(label).gsub(/-/, '')].pack("H*")
93
+ # puts "Label: #{label}"
94
+ # puts "UUID: #{identify_uuid(label)}"
95
+ # puts "Raw: #{raw.unpack("H*").first}"
96
+ GoldRecord::UUID.urlsafe_encode64(raw)
84
97
  end
85
98
  end
@@ -0,0 +1,65 @@
1
+ require 'cases/helper'
2
+
3
+ class HexTest < ActiveRecord::TestCase
4
+ def test_encode_with_nil
5
+ assert_raise NoMethodError do
6
+ GoldRecord::UUID.encode_hex(nil)
7
+ end
8
+ end
9
+
10
+ def test_encode_with_false
11
+ assert_raise NoMethodError do
12
+ GoldRecord::UUID.encode_hex(false)
13
+ end
14
+ end
15
+
16
+ def test_encode_with_number
17
+ assert_raise NoMethodError do
18
+ GoldRecord::UUID.encode_hex(12345)
19
+ end
20
+ end
21
+
22
+ def test_encode_with_null_string
23
+ assert_equal "0" * 32, GoldRecord::UUID.encode_hex("\000" * 16)
24
+ end
25
+
26
+ def test_decode_with_nil
27
+ assert_raise NoMethodError do
28
+ GoldRecord::UUID.decode_hex(nil)
29
+ end
30
+ end
31
+
32
+ def test_decode_with_false
33
+ assert_raise NoMethodError do
34
+ GoldRecord::UUID.decode_hex(false)
35
+ end
36
+ end
37
+
38
+ def test_decode_with_number
39
+ assert_raise NoMethodError do
40
+ GoldRecord::UUID.decode_hex(12345)
41
+ end
42
+ end
43
+
44
+ def test_decode_with_null_string
45
+ assert_equal "\000" * 16, GoldRecord::UUID.decode_hex("0" * 32)
46
+ end
47
+
48
+ def test_decode_with_short_strings
49
+ assert_equal "\000", GoldRecord::UUID.decode_hex("0")
50
+ assert_equal "\000", GoldRecord::UUID.decode_hex("00")
51
+ assert_equal "\000\000", GoldRecord::UUID.decode_hex("000")
52
+ assert_equal "\000\000", GoldRecord::UUID.decode_hex("0000")
53
+ assert_equal "\000\000\000", GoldRecord::UUID.decode_hex("00000")
54
+ assert_equal "\000\000\000", GoldRecord::UUID.decode_hex("000000")
55
+ end
56
+
57
+ def test_symmetry
58
+ [
59
+ "a" * 32,
60
+ "b" * 32,
61
+ ].each do |value|
62
+ assert_equal value, GoldRecord::UUID.encode_hex(GoldRecord::UUID.decode_hex(value))
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,16 @@
1
+ require 'cases/helper'
2
+
3
+ class RandomGenerateTest < ActiveRecord::TestCase
4
+ def test_size_of_generated_string
5
+ assert_equal 16, GoldRecord::UUID.random_generate.size
6
+ end
7
+
8
+ def test_randomness # This is kind of weak.
9
+ cache = {}
10
+ 10000.times do
11
+ value = GoldRecord::UUID.random_generate
12
+ assert !cache.has_key?(value)
13
+ cache[value] = true
14
+ end
15
+ end
16
+ end
@@ -15,6 +15,6 @@ class ToParamTest < ActiveRecord::TestCase
15
15
  artist = Artist.first
16
16
  param = artist.to_param
17
17
  assert param.instance_of?(String)
18
- assert param.match(/\A[0-9a-f\-]{36}\z/)
18
+ assert param.match(/\A[0-9a-f]{32}\z/)
19
19
  end
20
20
  end
@@ -0,0 +1,91 @@
1
+ require 'cases/helper'
2
+
3
+ class UrlSafeBase64Test < ActiveRecord::TestCase
4
+ def test_encode_with_nil
5
+ assert_raise TypeError do
6
+ GoldRecord::UUID.urlsafe_encode64(nil)
7
+ end
8
+ end
9
+
10
+ def test_encode_with_false
11
+ assert_raise TypeError do
12
+ GoldRecord::UUID.urlsafe_encode64(false)
13
+ end
14
+ end
15
+
16
+ def test_encode_with_number
17
+ assert_raise TypeError do
18
+ GoldRecord::UUID.urlsafe_encode64(12345)
19
+ end
20
+ end
21
+
22
+ def test_encode_with_null_string
23
+ assert_equal "A" * 22, GoldRecord::UUID.urlsafe_encode64("\000" * 16)
24
+ end
25
+
26
+ def test_encode_omits_padding_characters
27
+ assert_no_match /[=]/, GoldRecord::UUID.urlsafe_encode64("\001" * 16)
28
+ end
29
+
30
+ def test_encode_omits_newlines
31
+ assert_no_match /[\r\n]/, GoldRecord::UUID.urlsafe_encode64("\002" * 16)
32
+ end
33
+
34
+ def test_translation_of_plus_characters
35
+ encoded = GoldRecord::UUID.urlsafe_encode64("\x7e" * 16)
36
+ assert_no_match /[+]/, encoded
37
+ assert_equal "fn5-fn5-fn5-fn5-fn5-fg", encoded
38
+ end
39
+
40
+ def test_translation_of_slash_characters
41
+ encoded = GoldRecord::UUID.urlsafe_encode64("\x7f" * 16)
42
+ assert_no_match /\//, encoded
43
+ assert_equal "f39_f39_f39_f39_f39_fw", encoded
44
+ end
45
+
46
+ def test_decode_with_nil
47
+ assert_raise NoMethodError do
48
+ GoldRecord::UUID.urlsafe_decode64(nil)
49
+ end
50
+ end
51
+
52
+ def test_decode_with_false
53
+ assert_raise NoMethodError do
54
+ GoldRecord::UUID.urlsafe_decode64(false)
55
+ end
56
+ end
57
+
58
+ def test_decode_with_number
59
+ assert_raise NoMethodError do
60
+ GoldRecord::UUID.urlsafe_decode64(12345)
61
+ end
62
+ end
63
+
64
+ def test_decode_with_null_string
65
+ assert_equal "\000" * 16, GoldRecord::UUID.urlsafe_decode64("A" * 22)
66
+ end
67
+
68
+ def test_decode_with_short_strings
69
+ assert_equal "", GoldRecord::UUID.urlsafe_decode64("A")
70
+ assert_equal "\000", GoldRecord::UUID.urlsafe_decode64("AA")
71
+ assert_equal "\000\000", GoldRecord::UUID.urlsafe_decode64("AAA")
72
+ assert_equal "\000\000\000", GoldRecord::UUID.urlsafe_decode64("AAAA")
73
+ assert_equal "\000\000\000", GoldRecord::UUID.urlsafe_decode64("AAAAA")
74
+ assert_equal "\000\000\000\000", GoldRecord::UUID.urlsafe_decode64("AAAAAA")
75
+ end
76
+
77
+ def test_symmetry
78
+ [
79
+ "A" * 22,
80
+ "BBBBBBBBBBBBBBBBBBBBBA",
81
+ "CCCCCCCCCCCCCCCCCCCCCA",
82
+ "aaaaaaaaaaaaaaaaaaaaaQ",
83
+ "bbbbbbbbbbbbbbbbbbbbbQ",
84
+ "cccccccccccccccccccccQ",
85
+ "---------------------w",
86
+ "_____________________w",
87
+ ].each do |value|
88
+ assert_equal value, GoldRecord::UUID.urlsafe_encode64(GoldRecord::UUID.urlsafe_decode64(value))
89
+ end
90
+ end
91
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gold-record
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Randy Reddig
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-01 00:00:00 +00:00
12
+ date: 2009-12-06 00:00:00 +00:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -61,6 +61,7 @@ files:
61
61
  - lib/gold_record/base.rb
62
62
  - lib/gold_record/connection_adapters/mysql_adapter.rb
63
63
  - lib/gold_record/fixtures.rb
64
+ - lib/gold_record/uuid.rb
64
65
  - lib/gold_record/version.rb
65
66
  - lib/gold_record.rb
66
67
  - test/cases/aaa_create_tables_test.rb
@@ -72,12 +73,15 @@ files:
72
73
  - test/cases/associations/habtm_uuid_to_uuid_association_test.rb
73
74
  - test/cases/associations/has_many_integer_assocation_test.rb
74
75
  - test/cases/associations/has_many_uuid_association_test.rb
75
- - test/cases/coerce_id_test.rb
76
+ - test/cases/coerce_test.rb
76
77
  - test/cases/find_test.rb
77
78
  - test/cases/generate_id_test.rb
78
79
  - test/cases/helper.rb
80
+ - test/cases/hex_test.rb
81
+ - test/cases/random_generate_test.rb
79
82
  - test/cases/to_param_test.rb
80
83
  - test/cases/to_uuid_test.rb
84
+ - test/cases/urlsafe_base64_test.rb
81
85
  - test/cases/zzz_migration_test.rb
82
86
  - test/config.rb
83
87
  - test/connection.rb
@@ -122,6 +126,6 @@ rubyforge_project: gold-record
122
126
  rubygems_version: 1.3.5
123
127
  signing_key:
124
128
  specification_version: 3
125
- summary: Binary UUID support for ActiveRecord
129
+ summary: Unobtrusive binary UUID support for ActiveRecord
126
130
  test_files: []
127
131
 
@@ -1,20 +0,0 @@
1
- require 'cases/helper'
2
- require 'models/artist'
3
-
4
- class CoerceIdTest < ActiveRecord::TestCase
5
- def test_coerce_id_with_nil
6
- assert_nil Artist.coerce_id(nil)
7
- end
8
-
9
- def test_coerce_id_with_bogus_data
10
- assert_nil Artist.coerce_id("BOGUS")
11
- end
12
-
13
- def test_coerce_id_with_binary_uuid
14
- assert_equal NULL_UUID_RAW, Artist.coerce_id(NULL_UUID_RAW)
15
- end
16
-
17
- def test_coerce_id_with_hex_uuid
18
- assert_equal NULL_UUID_RAW, Artist.coerce_id(NULL_UUID.to_s)
19
- end
20
- end