objectid_columns 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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"