objectid_columns 1.0.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.
@@ -0,0 +1,40 @@
1
+ # This file adds very useful convenience methods to certain classes:
2
+
3
+ # To each of the BSON classes, we add:
4
+ #
5
+ # * #to_binary, which returns a String containing a pure-binary (12-byte) representation of the ObjectId; and
6
+ # * #to_bson_id, which simply returns +self+ -- this is so we can call this method on any object passed in where we're
7
+ # expecting an ObjectId, and, if you're already supplying an ObjectId object, it will just work.
8
+ ObjectidColumns.available_objectid_columns_bson_classes.each do |klass|
9
+ klass.class_eval do
10
+ def to_binary
11
+ [ to_s ].pack("H*")
12
+ end
13
+
14
+ def to_bson_id
15
+ self
16
+ end
17
+ end
18
+ end
19
+
20
+ # To String, we add #to_bson_id. This method knows how to convert a String that is in either the hex or pure-binary
21
+ # forms to an actual ObjectId object. Note that we use the method ObjectidColumns.construct_objectid to actually create
22
+ # the object; this way, it will follow any preference for exactly what BSON class to use that's set there.
23
+ #
24
+ # If your String is in binary form, it must have its encoding set to Encoding::BINARY (which is aliased to
25
+ # Encoding::ASCII_8BIT); if it's not in this encoding, it may well be coming from a source that doesn't support
26
+ # transparent binary data (for example, UTF-8 doesn't -- certain byte combinations are illegal in UTF-8 and will cause
27
+ # string manipulation to fail), which is a real problem.
28
+ String.class_eval do
29
+ BSON_HEX_ID_REGEX = /^[0-9a-f]{24}$/i
30
+
31
+ def to_bson_id
32
+ if self =~ BSON_HEX_ID_REGEX
33
+ ObjectidColumns.construct_objectid(self)
34
+ elsif length == 12 && ((! respond_to?(:encoding)) || (encoding == Encoding::BINARY)) # :respond_to? is for Ruby 1.8.7 support
35
+ ObjectidColumns.construct_objectid(unpack("H*").first)
36
+ else
37
+ raise ArgumentError, "#{inspect} does not seem to be a valid BSON ID; it is in neither the valid hex (exactly 24 hex characters, any encoding) nor the valid binary (12 characters, binary/ASCII-8BIT encoding) form"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,47 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/object'
3
+ require 'objectid_columns/objectid_columns_manager'
4
+
5
+ module ObjectidColumns
6
+ # This module gets mixed into an ActiveRecord class when you say +has_objectid_column(s)+ or
7
+ # +has_objectid_primary_key+. It delegates everything it does to the ObjectidColumnsManager; we do it this way so
8
+ # that we can define all kinds of helper methods on the ObjectidColumnsManager without polluting the method
9
+ # namespace on the actual ActiveRecord class.
10
+ module HasObjectidColumns
11
+ extend ActiveSupport::Concern
12
+
13
+ # Reads the current value of the given +column_name+ (which must be an ObjectId column) as an ObjectId object.
14
+ def read_objectid_column(column_name)
15
+ self.class.objectid_columns_manager.read_objectid_column(self, column_name)
16
+ end
17
+
18
+ # Writes a new value to the given +column_name+ (which must be an ObjectId column), accepting a String (in either
19
+ # hex or binary formats) or an ObjectId object, and transforming it to whatever storage format is correct for
20
+ # that column.
21
+ def write_objectid_column(column_name, new_value)
22
+ self.class.objectid_columns_manager.write_objectid_column(self, column_name, new_value)
23
+ end
24
+
25
+ # Called as a +before_create+ hook, if (and only if) this class has declared +has_objectid_primary_key+ -- sets
26
+ # the primary key to a newly-generated ObjectId, unless it has one already.
27
+ def assign_objectid_primary_key
28
+ self.id ||= ObjectidColumns.new_objectid
29
+ end
30
+
31
+ module ClassMethods
32
+ # Does this class have any ObjectId columns? It does if this module has been included, since it only gets included
33
+ # if you declare an ObjectId column.
34
+ def has_objectid_columns?
35
+ true
36
+ end
37
+
38
+ # Delegate all of the interesting work to the ObjectidColumnsManager.
39
+ delegate :has_objectid_columns, :has_objectid_column, :has_objectid_primary_key,
40
+ :translate_objectid_query_pair, :to => :objectid_columns_manager
41
+
42
+ def objectid_columns_manager
43
+ @objectid_columns_manager ||= ::ObjectidColumns::ObjectidColumnsManager.new(self)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,298 @@
1
+ require 'objectid_columns/dynamic_methods_module'
2
+
3
+ module ObjectidColumns
4
+ # The ObjectidColumnsManager does all the real work of the ObjectidColumns gem, in many ways -- it takes care of
5
+ # reading ObjectId values and transforming them to objects, transforming supplied data to the right format when
6
+ # writing them, handling primary-key definitions and queries.
7
+ #
8
+ # This is a separate class, rather than being mixed into the actual ActiveRecord class, so that we can add methods
9
+ # and define constants here without polluting the namespace of the underlying class.
10
+ class ObjectidColumnsManager
11
+ # NOTE: These constants are used in a metaprogrammed fashion in #has_objectid_columns, below. If you rename them,
12
+ # you must change that, too.
13
+ BINARY_OBJECTID_LENGTH = 12
14
+ STRING_OBJECTID_LENGTH = 24
15
+
16
+ # Creates a new instance. There should only ever be a single instance for a given ActiveRecord class, accessible
17
+ # via ObjectidColumns::HasObjectidColumns.objectid_columns_manager.
18
+ def initialize(active_record_class)
19
+ raise ArgumentError, "You must supply a Class, not: #{active_record_class.inspect}" unless active_record_class.kind_of?(Class)
20
+ raise ArgumentError, "You must supply a Class that's a descendant of ActiveRecord::Base, not: #{active_record_class.inspect}" unless superclasses(active_record_class).include?(::ActiveRecord::Base)
21
+
22
+ @active_record_class = active_record_class
23
+ @oid_columns = { }
24
+
25
+ # We use a DynamicMethodsModule to add our magic to the target ActiveRecord class, rather than just defining
26
+ # methods directly on the class, for a number of very good reasons -- see the class comment on
27
+ # DynamicMethodsModule for more information.
28
+ @dynamic_methods_module = ObjectidColumns::DynamicMethodsModule.new(active_record_class, :ObjectidColumnsDynamicMethods)
29
+ end
30
+
31
+ # Declares that this class is using an ObjectId as its primary key. Ordinarily, this requires no arguments;
32
+ # however, if your primary key is not named +id+ and you have not yet told ActiveRecord this (using
33
+ # <tt>self.primary_key = :foo</tt>), then you must pass the name of the primary-key column.
34
+ #
35
+ # Note that, unlike normal database-generated primary keys, this will cause us to auto-generate an ObjectId
36
+ # primary key value for a new record just before saving it to the database (ActiveRecord's +before_create hook).
37
+ # ObjectIds are safe to generate client-side, and very difficult to properly generate server-side in a relational
38
+ # database. However, we will respect (and not overwrite) any primary key already assigned to the record before it's
39
+ # saved, so if you want to assign your own ObjectId primary keys, you can.
40
+ def has_objectid_primary_key(primary_key_name = nil)
41
+ # The Symbol-vs.-String distinction is critical when dealing with old versions of ActiveRecord; for example, if
42
+ # you say <tt>self.primary_key = :foo</tt> (instead of <tt>self.primary_key = 'foo'</tt>) to older versions of
43
+ # ActiveRecord, you can end up with some seriously weird errors later (like models trying to save themselves with
44
+ # _both_ a +:foo+ and a +'foo'+ attribute -- ick!). Boo, AR.
45
+ primary_key_name = primary_key_name.to_s if primary_key_name
46
+ pk = active_record_class.primary_key
47
+
48
+ # Make sure we know what the primary key is!
49
+ if (! pk) && (! primary_key_name)
50
+ raise ArgumentError, "Class #{active_record_class.name} has no primary key set, and you haven't supplied one to .has_objectid_primary_key. Either set one before this call (using self.primary_key = :foo), or supply one to this call (has_objectid_primary_key :foo) and we'll set it for you."
51
+ end
52
+
53
+ pk = pk.to_s if pk
54
+
55
+ # Initially, this was a simple +||=+ statement. However, older versions of ActiveRecord will return the string or
56
+ # symbol +id+ for the primary key if you haven't set an explicit primary key, even if there is no such column on
57
+ # the underlying table. Again, ick.
58
+ if (! pk) || (primary_key_name && pk.to_s != primary_key_name.to_s)
59
+ active_record_class.primary_key = pk = primary_key_name
60
+ end
61
+
62
+ # In case someone is using composite_primary_keys (http://compositekeys.rubyforge.org/).
63
+ raise "You can't have an ObjectId primary key that's not a String or Symbol: #{pk.inspect}" unless pk.kind_of?(String) || pk.kind_of?(Symbol)
64
+
65
+ # Declare our primary-key column as an ObjectId column.
66
+ has_objectid_column pk
67
+
68
+ # If it's not called just "id", we need to explicitly define an "id" method that correctly reads from and writes
69
+ # to the table.
70
+ unless pk.to_s == 'id'
71
+ p = pk
72
+ dynamic_methods_module.define_method("id") { read_objectid_column(p) }
73
+ dynamic_methods_module.define_method("id=") { |new_value| write_objectid_column(p, new_value) }
74
+ end
75
+
76
+ # Allow us to autogenerate the primary key, if needed, on save.
77
+ active_record_class.send(:before_create, :assign_objectid_primary_key)
78
+
79
+ # Override a couple of methods that, if you're using an ObjectId column as your primary key, need overriding. ;)
80
+ [ :find, :find_by_id ].each do |class_method_name|
81
+ @dynamic_methods_module.define_class_method(class_method_name) do |*args, &block|
82
+ if args.length == 1 && args[0].kind_of?(String) || ObjectidColumns.is_valid_bson_object?(args[0]) || args[0].kind_of?(Array)
83
+ args[0] = if args[0].kind_of?(Array)
84
+ args[0].map { |x| objectid_columns_manager.to_valid_value_for_column(primary_key, x) if x }
85
+ else
86
+ objectid_columns_manager.to_valid_value_for_column(primary_key, args[0]) if args[0]
87
+ end
88
+
89
+ super(args[0], &block)
90
+ else
91
+ super(*args, &block)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ # Declares one or more columns as containing ObjectId values. After this call, they can be written using a String
98
+ # in hex or binary formats, or an ObjectId object; they will return ObjectId objects for values, and can be queried
99
+ # using any of the above (as long as you use the <tt>where(:foo_oid => ...)</tt> Hash-style syntax).
100
+ #
101
+ # If you don't pass in any column names, this will look for columns that end in +_oid+ and assume those are
102
+ # ObjectId columns.
103
+ def has_objectid_columns(*columns)
104
+ return unless active_record_class.table_exists?
105
+
106
+ # Autodetect columns ending in +_oid+ if needed
107
+ columns = autodetect_columns if columns.length == 0
108
+
109
+ columns = columns.map { |c| c.to_s.strip.downcase.to_sym }
110
+ columns.each do |column_name|
111
+ # Go fetch the column object from the ActiveRecord class, and make sure it's present and of the right type.
112
+ column_object = active_record_class.columns.detect { |c| c.name.to_s == column_name.to_s }
113
+
114
+ unless column_object
115
+ raise ArgumentError, "#{active_record_class.name} doesn't seem to have a column named #{column_name.inspect} that we could make an ObjectId column; did you misspell it? It has columns: #{active_record_class.columns.map(&:name).inspect}"
116
+ end
117
+
118
+ unless [ :string, :binary ].include?(column_object.type)
119
+ raise ArgumentError, "#{active_record_class.name} has a column named #{column_name.inspect}, but it is of type #{column_object.type.inspect}; we can only make ObjectId columns out of :string or :binary columns"
120
+ end
121
+
122
+ # Is the column long enough to contain the data we'll need to put in it?
123
+ required_length = self.class.const_get("#{column_object.type.to_s.upcase}_OBJECTID_LENGTH")
124
+ # The ||= is in case there's no limit on the column at all -- for example, PostgreSQL +bytea+ columns
125
+ # behave this way.
126
+ unless (column_object.limit || required_length + 1) >= required_length
127
+ raise ArgumentError, "#{active_record_class.name} has a column named #{column_name.inspect} of type #{column_object.type.inspect}, but it is of length #{column_object.limit}, which is too short to contain an ObjectId of this format; it must be of length at least #{required_length}"
128
+ end
129
+
130
+ # Define reader and writer methods that just call through to ObjectidColumns::HasObjectidColumns (which, in
131
+ # turn, just delegates the call back to this object -- the #read_objectid_column method below; the one on
132
+ # HasObjectidColumns just passes through the model object itself).
133
+ cn = column_name
134
+ dynamic_methods_module.define_method(column_name) do
135
+ read_objectid_column(cn)
136
+ end
137
+
138
+ dynamic_methods_module.define_method("#{column_name}=") do |x|
139
+ write_objectid_column(cn, x)
140
+ end
141
+
142
+ # Store away the fact that we've done this.
143
+ @oid_columns[column_name] = column_object.type
144
+ end
145
+ end
146
+
147
+ # Called from ObjectidColumns::HasObjectidColumns#read_objectid_column -- given a model and a column name (which
148
+ # must be an ObjectId column), returns the data in it, as an ObjectId.
149
+ def read_objectid_column(model, column_name)
150
+ column_name = column_name.to_s
151
+ value = model[column_name]
152
+ return value unless value # in case it's nil
153
+
154
+ # If it's not nil, the database should always be giving us back a String...
155
+ unless value.kind_of?(String)
156
+ raise "When trying to read the ObjectId column #{column_name.inspect} on #{inspect}, we got the following data from the database; we expected a String: #{value.inspect}"
157
+ end
158
+
159
+ # ugh...ActiveRecord 3.1.x can return this in certain circumstances
160
+ return nil if value.length == 0
161
+
162
+ # In many databases, if you have a column that is, _e.g._, BINARY(16), and you only store twelve bytes in it,
163
+ # you get back all 16 anyway, with 0x00 bytes at the end. Converting this to an ObjectId will fail, so we make
164
+ # sure we chop those bytes off. (Note that while String#strip will, in fact, remove these bytes too, it is not
165
+ # safe: if the ObjectId itself ends in one or more 0x00 bytes, then these will get incorrectly removed.)
166
+ case objectid_column_type(column_name)
167
+ when :binary then value = value[0..(BINARY_OBJECTID_LENGTH - 1)]
168
+ when :string then value = value[0..(STRING_OBJECTID_LENGTH - 1)]
169
+ else unknown_type(type)
170
+ end
171
+
172
+ # +lib/objectid_columns/extensions.rb+ adds this method to String.
173
+ value.to_bson_id
174
+ end
175
+
176
+ # Called from ObjectidColumns::HasObjectidColumns#write_objectid_column -- given a model, a column name (which must
177
+ # be an ObjectId column) and a new value, stores that value in the column.
178
+ def write_objectid_column(model, column_name, new_value)
179
+ column_name = column_name.to_s
180
+ if (! new_value)
181
+ model[column_name] = new_value
182
+ elsif new_value.respond_to?(:to_bson_id)
183
+ model[column_name] = to_valid_value_for_column(column_name, new_value)
184
+ else
185
+ raise ArgumentError, "When trying to write the ObjectId column #{column_name.inspect} on #{inspect}, we were passed the following value, which doesn't seem to be a valid BSON ID in any format: #{new_value.inspect}"
186
+ end
187
+ end
188
+
189
+ alias_method :has_objectid_column, :has_objectid_columns
190
+
191
+ # Given a value for an ObjectId column -- could be a String in either hex or binary formats, or an
192
+ # ObjectId object -- returns a String of the correct type for the given column (_i.e._, either the binary or hex
193
+ # String representation of an ObjectId, depending on the type of the underlying column).
194
+ def to_valid_value_for_column(column_name, value)
195
+ out = value.to_bson_id
196
+ unless ObjectidColumns.is_valid_bson_object?(out)
197
+ raise "We called #to_bson_id on #{value.inspect}, but it returned this, which is not a BSON ID object: #{out.inspect}"
198
+ end
199
+
200
+ case objectid_column_type(column_name)
201
+ when :binary then out = out.to_binary
202
+ when :string then out = out.to_s
203
+ else unknown_type(type)
204
+ end
205
+
206
+ out
207
+ end
208
+
209
+ # Given a key in a Hash supplied to +where+ for the given ActiveRecord class, returns a two-element Array
210
+ # consisting of the key and the proper value we should actually use to query on that column. If the key does not
211
+ # represent an ObjectID column, then this will just be exactly the data passed in; however, if it does represent
212
+ # an ObjectId column, then the value will be translated to whichever String format (binary or hex) that column is
213
+ # using.
214
+ #
215
+ # We use this in ObjectidColumns:;ActiveRecord::Relation#where to make the following work properly:
216
+ #
217
+ # MyModel.where(:foo_oid => BSON::ObjectId('52ec126d78161f56d8000001'))
218
+ #
219
+ # This method is used to translate this to:
220
+ #
221
+ # MyModel.where(:foo_oid => "52ec126d78161f56d8000001")
222
+ def translate_objectid_query_pair(query_key, query_value)
223
+ if (type = oid_columns[query_key.to_sym])
224
+
225
+ # Handle nil, false
226
+ if (! query_value)
227
+ [ query_key, query_value ]
228
+
229
+ # +lib/objectid_columns/extensions.rb+ adds String#to_bson_id
230
+ elsif query_value.respond_to?(:to_bson_id)
231
+ v = query_value.to_bson_id
232
+ v = case type
233
+ when :binary then v.to_binary
234
+ when :string then v.to_s
235
+ else unknown_type(type)
236
+ end
237
+ [ query_key, v ]
238
+
239
+ # Handle arrays of values
240
+ elsif query_value.kind_of?(Array)
241
+ array = query_value.map do |v|
242
+ translate_objectid_query_pair(query_key, v)[1]
243
+ end
244
+ [ query_key, array ]
245
+
246
+ # Um...what did you pass?
247
+ else
248
+ raise ArgumentError, "You're trying to constrain #{active_record_class.name} on column #{query_key.inspect}, which is an ObjectId column, but the value you passed, #{query_value.inspect}, is not a valid format for an ObjectId."
249
+ end
250
+ else
251
+ [ query_key, query_value ]
252
+ end
253
+ end
254
+
255
+ private
256
+ attr_reader :active_record_class, :dynamic_methods_module, :oid_columns
257
+
258
+ # Given the name of a column -- which must be an ObjectId column -- returns its type, either +:binary+ or
259
+ # +:string+.
260
+ def objectid_column_type(column_name)
261
+ out = oid_columns[column_name.to_sym]
262
+ raise "Something is horribly wrong; #{column_name.inspect} is not an ObjectId column -- we have: #{oid_columns.keys.inspect}" unless out
263
+ out
264
+ end
265
+
266
+ # Raises an exception -- used for +case+ statements where we switch on the type of the column. Useful so that if,
267
+ # in the future, we support a new column type, we won't forget to add a case for it in various places.
268
+ def unknown_type(type)
269
+ raise "Bug in ObjectidColumns in this method -- type #{type.inspect} does not have a case here."
270
+ end
271
+
272
+ # What's the entire superclass chain of the given class? Used in the constructor to make sure something is
273
+ # actually a descendant of ActiveRecord::Base.
274
+ def superclasses(klass)
275
+ out = [ ]
276
+ while (sc = klass.superclass)
277
+ out << sc
278
+ klass = sc
279
+ end
280
+ out
281
+ end
282
+
283
+ # If someone called +has_objectid_columns+ but didn't pass an argument, this method detects which columns we should
284
+ # automatically turn into ObjectId columns -- which means any columns ending in +_oid+, except for the primary key.
285
+ def autodetect_columns
286
+ out = active_record_class.columns.select { |c| c.name =~ /_oid$/i }.map(&:name).map(&:to_s)
287
+
288
+ # Make sure we never, ever automatically make the primary-key column an ObjectId column.
289
+ out -= [ active_record_class.primary_key ].compact.map(&:to_s)
290
+
291
+ unless out.length > 0
292
+ raise ArgumentError, "You didn't pass in the names of any ObjectId columns, and we couldn't find any columns ending in _oid to pick up automatically (primary key is always excluded). Either name some columns explicitly, or remove the has_objectid_columns call."
293
+ end
294
+
295
+ out
296
+ end
297
+ end
298
+ end
@@ -0,0 +1,4 @@
1
+ # What's the current version of this gem?
2
+ module ObjectidColumns
3
+ VERSION = "1.0.0"
4
+ end
@@ -0,0 +1,121 @@
1
+ require "objectid_columns/version"
2
+ require "objectid_columns/active_record/base"
3
+ require "objectid_columns/active_record/relation"
4
+ require "active_record"
5
+
6
+ # This is the root module for ObjectidColumns. It contains largely just configuration and integration information;
7
+ # all the real work is done in ObjectidColumns::ObjectidColumnsManager. ObjectidColumns gets its start through the
8
+ # module ObjectidColumns::ActiveRecord::Base, which gets mixed into ::ActiveRecord::Base (below) and is where methods
9
+ # like +has_objectid_columns+ are declared.
10
+ module ObjectidColumns
11
+ class << self
12
+ # This is the set of classes we support for representing ObjectIds, as objects, themselves. BSON::ObjectId comes
13
+ # from the +bson+ gem, and Moped::BSON::ObjectId comes from the +moped+ gem.
14
+ #
15
+ # Note that this gem does not declare a dependency on either one of these gems, because we want you to be able to
16
+ # use either, and there's currently no way of expressing that explicitly. Instead,
17
+ # .available_objectid_columns_bson_classes, below, takes care of figuring out which ones are availble and loading
18
+ # them.
19
+ #
20
+ # Order is important here: when creating new ObjectId objects (such as when reading from an ObjectId column), we
21
+ # will prefer the earliest one of these classes that is actually defined. You can change this using
22
+ # .preferred_bson_class=, below.
23
+ #
24
+ #
25
+ # Any class added here has to obey the following constraints:
26
+ #
27
+ # * You can create a new instance from a hex string using .from_string(hex_string)
28
+ # * Calling #to_s on it returns a hexadecimal String of exactly 24 characters
29
+ #
30
+ # Both these objects currently do. If they change, or you want to introduce a new ObjectId representation class,
31
+ # a small amount of refactoring will be necessary.
32
+ SUPPORTED_OBJECTID_BSON_CLASS_NAMES = %w{BSON::ObjectId Moped::BSON::ObjectId}
33
+
34
+ # When we create a new ObjectId object (such as when reading from a column), what class should it be? If you have
35
+ # multiple classes loaded and you don't like this one, you can call .preferred_bson_class=, below.
36
+ def preferred_bson_class
37
+ @preferred_bson_class ||= available_objectid_columns_bson_classes.first
38
+ end
39
+
40
+ # Sets the preferred BSON class to the given class.
41
+ def preferred_bson_class=(bson_class)
42
+ unless SUPPORTED_OBJECTID_BSON_CLASS_NAMES.include?(bson_class.name)
43
+ raise ArgumentError, "ObjectidColumns does not support BSON class #{bson_class.name}; it supports: #{SUPPORTED_OBJECTID_BSON_CLASS_NAMES.inspect}"
44
+ end
45
+
46
+ @preferred_bson_class = bson_class
47
+ end
48
+
49
+ # Returns an array of Class objects -- of length at least 1, but potentially more than 1 -- of the various
50
+ # ObjectId classes we have available to use. Again, because we don't explicitly depend on the BSON gems
51
+ # (see above), this needs to take care of trying to load and require the gems in question, and fail gracefully
52
+ # if they're not present.
53
+ def available_objectid_columns_bson_classes
54
+ @available_objectid_columns_bson_classes ||= begin
55
+ # Try to load both gems, but don't fail if there are errors
56
+ %w{moped bson}.each do |require_name|
57
+ begin
58
+ gem require_name
59
+ rescue Gem::LoadError => le
60
+ end
61
+
62
+ begin
63
+ require require_name
64
+ rescue LoadError => le
65
+ end
66
+ end
67
+
68
+ # See which classes we have managed to load
69
+ defined_classes = SUPPORTED_OBJECTID_BSON_CLASS_NAMES.map do |name|
70
+ eval("if defined?(#{name}) then #{name} end")
71
+ end.compact
72
+
73
+ # Raise an error if we haven't loaded either
74
+ if defined_classes.length == 0
75
+ raise %{ObjectidColumns requires a library that implements an ObjectId class to be loaded; we support
76
+ the following ObjectId classes: #{SUPPORTED_OBJECTID_BSON_CLASS_NAMES.join(", ")}.
77
+ (These are from the 'bson' or 'moped' gems.) You seem to have neither one installed.
78
+
79
+ Please add one of these gems to your project and try again. Usually, this just means
80
+ adding this to your Gemfile:
81
+
82
+ gem 'bson'
83
+
84
+ (ObjectidColumns does not explicitly depend on either of these, because we want you
85
+ to be able to choose whichever one you prefer.)}
86
+ end
87
+
88
+ defined_classes
89
+ end
90
+ end
91
+
92
+ # Is the given object a valid BSON ObjectId? This doesn't count Strings in any format -- only objects.
93
+ def is_valid_bson_object?(x)
94
+ available_objectid_columns_bson_classes.detect { |k| x.kind_of?(k) }
95
+ end
96
+
97
+ # Creates a new BSON ObjectId from the given String, which must be in the hexadecimal format.
98
+ def construct_objectid(hex_string)
99
+ preferred_bson_class.send(:from_string, hex_string)
100
+ end
101
+
102
+ # Creates a new BSON ObjectId, from scratch; this must return an ObjectId with a value, suitable for assigning
103
+ # to a newly-created row. We use this only if you've declared that your primary key column is an ObjectId, and
104
+ # then only if you're about to save a new row and it has no ID yet.
105
+ def new_objectid
106
+ preferred_bson_class.new
107
+ end
108
+ end
109
+ end
110
+
111
+ # Include the modules that add the initial methods to ActiveRecord::Base, like +has_objectid_columns+.
112
+ ::ActiveRecord::Base.class_eval do
113
+ include ::ObjectidColumns::ActiveRecord::Base
114
+ end
115
+
116
+ # This adds our patch to +#where+, so that queries will work properly (assuming you use Hash-style syntax).
117
+ ::ActiveRecord::Relation.class_eval do
118
+ include ::ObjectidColumns::ActiveRecord::Relation
119
+ end
120
+
121
+ require "objectid_columns/extensions"