11# encoding: utf-8
2+ require "digest/md5"
3+ require "socket"
4+
25module 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
0 commit comments