From 3faf1b7ffbe0131db02b6d1e187dda8334a81b68 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Sun, 3 Feb 2013 13:24:49 +0100 Subject: [PATCH] Add unique support to object ids. [ fix #41 ] --- CHANGELOG.md | 5 +++ lib/moped/bson/object_id.rb | 62 +++++++++++++++++++++++++++---- spec/moped/bson/object_id_spec.rb | 38 ++++++++++++++++--- 3 files changed, 92 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 424cc77..031de4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ * \#137 `IOError` exceptions during connection go through reconnect process properly. (Peter Kieltyka) +* \#41 `Moped::BSON::ObjectId.from_time` now accepts a `unique` option to + ensure the generated id is unique. + + Moped::BSON::ObjectId.from_time(time, unique: true) + * mongoid/mongoid\#2738 Ensure that delete operations don't include special selectors, like $query. diff --git a/lib/moped/bson/object_id.rb b/lib/moped/bson/object_id.rb index eaceece..3b7b63b 100644 --- a/lib/moped/bson/object_id.rb +++ b/lib/moped/bson/object_id.rb @@ -7,19 +7,68 @@ class ObjectId include Comparable class << self + + # Create a new object id from a string. + # + # @example Create an object id from the string. + # Moped::BSON::ObjectId.from_string(id) + # + # @param [ String ] string The string to create from. + # + # @return [ ObjectId ] The new object id. + # + # @since 1.0.0 def from_string(string) raise Errors::InvalidObjectId.new(string) unless legal?(string) from_data [string].pack("H*") end - def from_time(time) - from_data [time.to_i].pack("Nx8") + # Create a new object id from a time. + # + # @example Create an object id from a time. + # Moped::BSON::ObjectId.from_id(time) + # + # @example Create an object id from a time, ensuring uniqueness. + # Moped::BSON::ObjectId.from_id(time, unique: true) + # + # @param [ Time ] time The time to generate from. + # @param [ Hash ] options The options. + # + # @option options [ true, false ] :unique Whether the id should be + # unique. + # + # @return [ ObjectId ] The new object id. + # + # @since 1.0.0 + def from_time(time, options = nil) + unique = (options || {})[:unique] + from_data(unique ? @@generator.next(time.to_i) : [ time.to_i ].pack("Nx8")) end - def legal?(str) - /\A\h{24}\Z/ === str.to_s + # Determine if the string is a legal object id. + # + # @example Is the string a legal object id? + # Moped::BSON::ObjectId.legal?(string) + # + # @param [ String ] The string to test. + # + # @return [ true, false ] If the string is legal. + # + # @since 1.0.0 + def legal?(string) + /\A\h{24}\Z/ === string.to_s end + # Create a new object id from some raw data. + # + # @example Create an object id from raw data. + # Moped::BSON::ObjectId.from_data(data) + # + # @param [ String ] data The raw bytes. + # + # @return [ ObjectId ] The new object id. + # + # @since 1.0.0 def from_data(data) id = allocate id.send(:data=, data) @@ -36,7 +85,6 @@ def data # If @data is defined, then we know we've been loaded in some # non-standard way, so we attempt to repair the data. repair! @data if defined? @data - @raw_data ||= @@generator.next end @@ -98,7 +146,7 @@ def initialize # Return object id data based on the current time, incrementing the # object id counter. - def next + def next(time = nil) @mutex.lock begin counter = @counter = (@counter + 1) % 0xFFFFFF @@ -106,7 +154,7 @@ def next @mutex.unlock rescue nil end - generate(Time.new.to_i, counter) + generate(time || Time.new.to_i, counter) end # Generate object id data for a given time using the provided +counter+. diff --git a/spec/moped/bson/object_id_spec.rb b/spec/moped/bson/object_id_spec.rb index b722a3b..5be333f 100644 --- a/spec/moped/bson/object_id_spec.rb +++ b/spec/moped/bson/object_id_spec.rb @@ -193,14 +193,40 @@ end describe ".from_time" do - it "sets the generation time" do - time = Time.at((Time.now.utc - 64800).to_i).utc - described_class.from_time(time).generation_time.should == time + + context "when no unique option is provided" do + + let(:time) do + Time.at((Time.now.utc - 64800).to_i).utc + end + + it "sets the generation time" do + described_class.from_time(time).generation_time.should eq(time) + end + + it "does not include process or sequence information" do + id = described_class.from_time(Time.now) + id.to_s.should =~ /\A\h{8}0{16}\Z/ + end end - it "does not include process or sequence information" do - id = described_class.from_time(Time.now) - id.to_s.should =~ /\A\h{8}0{16}\Z/ + context "when a unique option is provided" do + + let(:time) do + Time.at((Time.now.utc - 64800).to_i).utc + end + + let(:object_id) do + described_class.from_time(time, unique: true) + end + + let(:non_unique) do + described_class.from_time(time, unique: true) + end + + it "creates a new unique object id" do + object_id.should_not eq(non_unique) + end end end