Skip to content

Commit fef6f75

Browse files
committed
Adding in object id functionality
1 parent 0060264 commit fef6f75

File tree

4 files changed

+581
-9
lines changed

4 files changed

+581
-9
lines changed

Diff for: ruby/lib/bson.rb

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ module BSON
1919
#
2020
# @since 2.0.0
2121
NULL_BYTE = 0.chr.force_encoding(BINARY).freeze
22+
23+
# Constant for UTF-8 string encoding.
24+
#
25+
# @since 2.0.0
26+
UTF8 = "UTF-8".freeze
2227
end
2328

2429
require "bson/registry"

Diff for: ruby/lib/bson/object_id.rb

+235-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# encoding: utf-8
2+
require "digest/md5"
3+
require "socket"
4+
25
module BSON
36

47
# Represents object_id data.
@@ -7,25 +10,254 @@ module BSON
710
#
811
# @since 2.0.0
912
class ObjectId
13+
include Comparable
1014

1115
# A object_id is type 0x07 in the BSON spec.
1216
#
1317
# @since 2.0.0
1418
BSON_TYPE = 7.chr.force_encoding(BINARY).freeze
1519

16-
# Encode the object_id type
20+
# Check equality of the object id with another object.
21+
#
22+
# @example Check if the object id is equal to the other.
23+
# object_id == other
24+
#
25+
# @param [ Object ] other The object to check against.
26+
#
27+
# @return [ true, false ] If the objects are equal.
28+
#
29+
# @since 2.0.0
30+
def ==(other)
31+
return false unless other.is_a?(ObjectId)
32+
to_bson == other.to_bson
33+
end
34+
35+
# Check case equality on the object id.
36+
#
37+
# @example Check case equality.
38+
# object_id === other
39+
#
40+
# @param [ Object ] other The object to check against.
41+
#
42+
# @return [ true, false ] If the objects are equal in a case.
43+
#
44+
# @since 2.0.0
45+
def ===(other)
46+
return to_str === other.to_str if other.respond_to?(:to_str)
47+
super
48+
end
49+
50+
# Compare this object id with another object for use in sorting.
1751
#
18-
# @example Encode the object_id.
52+
# @example Compare the object id with the other object.
53+
# object <=> other
54+
#
55+
# @param [ Object ] other The object to compare to.
56+
#
57+
# @return [ Integer ] The result of the comparison.
58+
#
59+
# @since 2.0.0
60+
def <=>(other)
61+
to_bson <=> other.to_bson
62+
end
63+
64+
# Return the UTC time at which this ObjectId was generated. This may
65+
# be used instread of a created_at timestamp since this information
66+
# is always encoded in the object id.
67+
#
68+
# @example Get the generation time.
69+
# object_id.generation_time
70+
#
71+
# @return [ Time ] The time the id was generated.
72+
#
73+
# @since 2.0.0
74+
def generation_time
75+
::Time.at(to_bson.unpack("N")[0]).utc
76+
end
77+
78+
# Get the object id as it's raw BSON data.
79+
#
80+
# @example Get the raw bson bytes.
1981
# object_id.to_bson
2082
#
21-
# @return [ String ] The encoded object_id.
83+
# @note Since Moped's BSON and 10gen BSON before 2.0.0 have different
84+
# internal representations, we will attempt to repair the data for cases
85+
# where the object was instantiated in a non-standard way. (Like a
86+
# Marshal.load)
87+
#
88+
# @return [ String ] The raw bytes.
2289
#
2390
# @see http://bsonspec.org/#/specification
2491
#
2592
# @since 2.0.0
2693
def to_bson
94+
repair!(@data) if defined?(@data)
95+
@raw_data ||= @@generator.next
2796
end
2897

98+
# Get the string representation of the object id.
99+
#
100+
# @example Get the object id as a string.
101+
# object_id.to_s
102+
#
103+
# @return [ String ] The object id as a string.
104+
#
105+
# @since 2.0.0
106+
def to_s
107+
to_bson.unpack("H*")[0].force_encoding(UTF8)
108+
end
109+
alias :to_str :to_s
110+
111+
# Raised when trying to create an object id with invalid data.
112+
#
113+
# @since 2.0.0
114+
class Invalid < RuntimeError; end
115+
116+
private
117+
118+
def data=(data)
119+
@raw_data = data
120+
end
121+
122+
class << self
123+
124+
# Create a new object id from raw bytes.
125+
#
126+
# @example Create an object id from raw bytes.
127+
# BSON::ObjectId.from_data(data)
128+
#
129+
# @param [ String ] data The raw bytes.
130+
#
131+
# @return [ ObjectId ] The new object id.
132+
#
133+
# @since 2.0.0
134+
def from_data(data)
135+
object_id = allocate
136+
object_id.send(:data=, data)
137+
object_id
138+
end
139+
140+
# Create a new object id from a string.
141+
#
142+
# @example Create an object id from the string.
143+
# BSON::ObjectId.from_string(id)
144+
#
145+
# @param [ String ] string The string to create the id from.
146+
#
147+
# @raise [ BSON::ObjectId::Invalid ] If the provided string is invalid.
148+
#
149+
# @return [ BSON::ObjectId ] The new object id.
150+
#
151+
# @since 2.0.0
152+
def from_string(string)
153+
unless legal?(string)
154+
raise Invalid.new("'#{string}' is an invalid ObjectId.")
155+
end
156+
from_data([ string ].pack("H*"))
157+
end
158+
159+
# Create a new object id from a time.
160+
#
161+
# @example Create an object id from a time.
162+
# BSON::ObjectId.from_id(time)
163+
#
164+
# @example Create an object id from a time, ensuring uniqueness.
165+
# BSON::ObjectId.from_id(time, unique: true)
166+
#
167+
# @param [ Time ] time The time to generate from.
168+
# @param [ Hash ] options The options.
169+
#
170+
# @option options [ true, false ] :unique Whether the id should be
171+
# unique.
172+
#
173+
# @return [ ObjectId ] The new object id.
174+
#
175+
# @since 2.0.0
176+
def from_time(time, options = {})
177+
from_data(options[:unique] ? @@generator.next(time.to_i) : [ time.to_i ].pack("Nx8"))
178+
end
179+
180+
# Determine if the provided string is a legal object id.
181+
#
182+
# @example Is the string a legal object id?
183+
# BSON::ObjectId.legal?(string)
184+
#
185+
# @param [ String ] The string to check.
186+
#
187+
# @return [ true, false ] If the string is legal.
188+
#
189+
# @since 2.0.0
190+
def legal?(string)
191+
/\A\h{24}\Z/ === string.to_s
192+
end
193+
end
194+
195+
# Inner class that encapsulates the behaviour of actually generating each
196+
# part of the ObjectId.
197+
#
198+
# @api private
199+
#
200+
# @since 2.0.0
201+
class Generator
202+
203+
attr_reader :machine_id
204+
205+
# Instantiate the new object id generator. Will set the machine id once
206+
# on the initial instantiation.
207+
#
208+
# @example Instantiate the generator.
209+
# BSON::ObjectId::Generator.new
210+
#
211+
# @since 2.0.0
212+
def initialize
213+
@counter = 0
214+
@machine_id = Digest::MD5.digest(Socket.gethostname).unpack("N")[0]
215+
@mutex = Mutex.new
216+
end
217+
218+
# Return object id data based on the current time, incrementing the
219+
# object id counter. Will use the provided time if not nil.
220+
#
221+
# @example Get the next object id data.
222+
# generator.next
223+
#
224+
# @param [ Time ] time The optional time to generate with.
225+
#
226+
# @return [ String ] The raw object id bytes.
227+
#
228+
# @since 2.0.0
229+
def next(time = nil)
230+
@mutex.lock
231+
begin
232+
count = @counter = (@counter + 1) % 0xFFFFFF
233+
ensure
234+
@mutex.unlock rescue nil
235+
end
236+
generate(time || ::Time.new.to_i, count)
237+
end
238+
239+
# Generate object id data for a given time using the provided counter.
240+
#
241+
# @example Generate the object id bytes.
242+
# generator.generate(time)
243+
#
244+
# @param [ Integer ] time The time since epoch in seconds.
245+
# @param [ Integer ] counter The optional counter.
246+
#
247+
# @return [ String ] The raw object id bytes.
248+
#
249+
# @since 2.0.0
250+
def generate(time, counter = 0)
251+
process_thread_id = "#{Process.pid}#{Thread.current.object_id}".hash % 0xFFFF
252+
[ time, machine_id, process_thread_id, counter << 8 ].pack("N NX lXX NX")
253+
end
254+
end
255+
256+
# We keep one global generator for object ids.
257+
#
258+
# @since 2.0.0
259+
@@generator = Generator.new
260+
29261
# Register this type when the module is loaded.
30262
#
31263
# @since 2.0.0

Diff for: ruby/lib/bson/string.rb

-5
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,6 @@ module String
1414
# @since 2.0.0
1515
BSON_TYPE = 2.chr.force_encoding(BINARY).freeze
1616

17-
# Constant for UTF-8 string encoding.
18-
#
19-
# @since 2.0.0
20-
UTF8 = "UTF-8".freeze
21-
2217
# Get the string as encoded BSON.
2318
#
2419
# @example Get the string as encoded BSON.

0 commit comments

Comments
 (0)