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,51 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'objectid_columns/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "objectid_columns"
8
+ spec.version = ObjectidColumns::VERSION
9
+ spec.authors = ["Andrew Geweke"]
10
+ spec.email = ["ageweke@swiftype.com"]
11
+ spec.summary = %q{Transparently store MongoDB ObjectId values in ActiveRecord.}
12
+ spec.homepage = "https://www.github.com/swiftype/objectid_columns"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+
21
+ ar_version = ENV['OBJECTID_COLUMNS_AR_TEST_VERSION']
22
+ ar_version = ar_version.strip if ar_version
23
+
24
+ version_spec = case ar_version
25
+ when nil then [ ">= 3.0", "<= 4.99.99" ]
26
+ when 'master' then nil
27
+ else [ "=#{ar_version}" ]
28
+ end
29
+
30
+ if version_spec
31
+ spec.add_dependency("activerecord", *version_spec)
32
+ spec.add_dependency("activesupport", *version_spec)
33
+ end
34
+
35
+ spec.add_development_dependency "bundler", "~> 1.5"
36
+ spec.add_development_dependency "rake"
37
+ spec.add_development_dependency "rspec", "~> 2.14"
38
+ spec.add_development_dependency "moped", "~> 1.5" unless RUBY_VERSION =~ /^1\.8\./
39
+ spec.add_development_dependency "bson", "~> 1.9"
40
+
41
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec', 'objectid_columns', 'helpers', 'database_helper'))
42
+ database_gem_name = ObjectidColumns::Helpers::DatabaseHelper.maybe_database_gem_name
43
+
44
+ # Ugh. Later versions of the 'mysql2' gem are incompatible with AR 3.0.x; so, here, we explicitly trap that case
45
+ # and use an earlier version of that Gem.
46
+ if database_gem_name && database_gem_name == 'mysql2' && ar_version && ar_version =~ /^3\.0\./
47
+ spec.add_development_dependency('mysql2', '~> 0.2.0')
48
+ else
49
+ spec.add_development_dependency(database_gem_name)
50
+ end
51
+ end
@@ -0,0 +1,178 @@
1
+ module ObjectidColumns
2
+ module Helpers
3
+ class DatabaseHelper
4
+ class InvalidDatabaseConfigurationError < StandardError; end
5
+
6
+ class << self
7
+ def maybe_database_gem_name
8
+ begin
9
+ dh = new
10
+ dh.database_gem_name
11
+ rescue InvalidDatabaseConfigurationError => idce
12
+ nil
13
+ end
14
+ end
15
+ end
16
+
17
+ def initialize
18
+ config # make sure we raise on instantiation if configuration is invalid
19
+ end
20
+
21
+ def database_type
22
+ case database_gem_name
23
+ when /mysql/i then :mysql
24
+ when /sqlite/i then :sqlite
25
+ when /pg/i, /postgres/i then :postgres
26
+ else raise "Unknown database type for Gem name: #{database_gem_name.inspect}"
27
+ end
28
+ end
29
+
30
+ def setup_activerecord!
31
+ require 'active_record'
32
+ require config[:require]
33
+ ::ActiveRecord::Base.establish_connection(config[:config])
34
+
35
+ require 'logger'
36
+ require 'stringio'
37
+ @logs = StringIO.new
38
+ ::ActiveRecord::Base.logger = Logger.new(@logs)
39
+
40
+ if config[:config][:adapter] == 'sqlite3'
41
+ sqlite_version = ::ActiveRecord::Base.connection.send(:sqlite_version).instance_variable_get("@version").inspect rescue "unknown"
42
+ end
43
+ end
44
+
45
+ def table_name(name)
46
+ "objectidcols_spec_#{name}"
47
+ end
48
+
49
+ def database_gem_name
50
+ config[:database_gem_name]
51
+ end
52
+
53
+ def adapter_name
54
+ config[:config][:adapter]
55
+ end
56
+
57
+ private
58
+ def config
59
+ config_from_config_file || travis_ci_config_from_environment || invalid_config_file!
60
+ end
61
+
62
+ def config_from_config_file
63
+ return nil unless File.exist?(config_file_path)
64
+ require config_file_path
65
+
66
+ return nil unless defined?(OBJECTID_COLUMNS_SPEC_DATABASE_CONFIG)
67
+ return nil unless OBJECTID_COLUMNS_SPEC_DATABASE_CONFIG.kind_of?(Hash)
68
+
69
+ return nil unless OBJECTID_COLUMNS_SPEC_DATABASE_CONFIG[:require]
70
+ return nil unless OBJECTID_COLUMNS_SPEC_DATABASE_CONFIG[:database_gem_name]
71
+
72
+ return nil unless OBJECTID_COLUMNS_SPEC_DATABASE_CONFIG
73
+ OBJECTID_COLUMNS_SPEC_DATABASE_CONFIG
74
+ end
75
+
76
+ def travis_ci_config_from_environment
77
+ dbtype = (ENV['OBJECTID_COLUMNS_TRAVIS_CI_DATABASE_TYPE'] || '').strip.downcase
78
+ is_jruby = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
79
+
80
+ if is_jruby
81
+ case dbtype
82
+ when 'mysql'
83
+ {
84
+ :require => 'activerecord-jdbcmysql-adapter',
85
+ :database_gem_name => 'activerecord-jdbcmysql-adapter',
86
+ :config => {
87
+ :adapter => 'jdbcmysql',
88
+ :database => 'myapp_test',
89
+ :username => 'travis',
90
+ :encoding => 'utf8'
91
+ }
92
+ }
93
+ when 'postgres', 'postgresql'
94
+ {
95
+ :require => 'activerecord-jdbcpostgresql-adapter',
96
+ :database_gem_name => 'activerecord-jdbcpostgresql-adapter',
97
+ :config => {
98
+ :adapter => 'jdbcpostgresql',
99
+ :database => 'myapp_test',
100
+ :username => 'postgres'
101
+ }
102
+ }
103
+ when 'sqlite'
104
+ {
105
+ :require => 'activerecord-jdbcsqlite3-adapter',
106
+ :database_gem_name => 'activerecord-jdbcsqlite3-adapter',
107
+ :config => {
108
+ :adapter => 'jdbcsqlite3',
109
+ :database => ':memory:'
110
+ }
111
+ }
112
+ when '', nil then nil
113
+ else
114
+ raise "Unknown Travis CI database type: #{dbtype.inspect}"
115
+ end
116
+ else
117
+ case dbtype
118
+ when 'postgres', 'postgresql'
119
+ {
120
+ :require => 'pg',
121
+ :database_gem_name => 'pg',
122
+ :config => {
123
+ :adapter => 'postgresql',
124
+ :database => 'myapp_test',
125
+ :username => 'postgres',
126
+ :min_messages => 'WARNING'
127
+ }
128
+ }
129
+ when 'mysql'
130
+ {
131
+ :require => 'mysql2',
132
+ :database_gem_name => 'mysql2',
133
+ :config => {
134
+ :adapter => 'mysql2',
135
+ :database => 'myapp_test',
136
+ :username => 'travis',
137
+ :encoding => 'utf8'
138
+ }
139
+ }
140
+ when 'sqlite'
141
+ {
142
+ :require => 'sqlite3',
143
+ :database_gem_name => 'sqlite3',
144
+ :config => {
145
+ :adapter => 'sqlite3',
146
+ :database => ':memory:',
147
+ :timeout => 500
148
+ }
149
+ }
150
+ when '', nil then nil
151
+ else
152
+ raise "Unknown Travis CI database type: #{dbtype.inspect}"
153
+ end
154
+ end
155
+ end
156
+
157
+ def config_file_path
158
+ @config_file_path ||= File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_database_config.rb'))
159
+ end
160
+
161
+ def invalid_config_file!
162
+ raise Errno::ENOENT, %{In order to run specs for ObjectIdColumns, you need to create a file at:
163
+
164
+ #{config_file_path}
165
+
166
+ ...that defines a top-level OBJECTID_COLUMNS_SPEC_DATABASE_CONFIG hash, with members:
167
+
168
+ :require => 'name_of_adapter_to_require',
169
+ :database_gem_name => 'name_of_gem_for_adapter',
170
+ :config => { ...whatever ActiveRecord::Base.establish_connection should be passed... }
171
+
172
+ Alternatively, if you're running under Travis CI, you can set the environment variable
173
+ OBJECTID_COLUMNS_TRAVIS_CI_DATABASE_TYPE to 'postgres', 'mysql', or 'sqlite', and it will
174
+ use the correct configuration for testing on Travis CI.}
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,90 @@
1
+ require 'active_record'
2
+ require 'active_record/migration'
3
+ require 'objectid_columns/helpers/database_helper'
4
+
5
+ module ObjectidColumns
6
+ module Helpers
7
+ module SystemHelpers
8
+ def migrate(&block)
9
+ migration_class = Class.new(::ActiveRecord::Migration)
10
+ metaclass = migration_class.class_eval { class << self; self; end }
11
+ metaclass.instance_eval { define_method(:up, &block) }
12
+
13
+ ::ActiveRecord::Migration.suppress_messages do
14
+ migration_class.migrate(:up)
15
+ end
16
+ end
17
+
18
+ def define_model_class(name, table_name, &block)
19
+ model_class = Class.new(::ActiveRecord::Base)
20
+ ::Object.send(:remove_const, name) if ::Object.const_defined?(name)
21
+ ::Object.const_set(name, model_class)
22
+ model_class.table_name = table_name
23
+ model_class.class_eval(&block)
24
+ end
25
+
26
+ def ensure_database_is_set_up!
27
+ ::ObjectidColumns::Helpers::SystemHelpers.database_helper
28
+ end
29
+
30
+ class << self
31
+ def database_helper
32
+ @database_helper ||= begin
33
+ out = ObjectidColumns::Helpers::DatabaseHelper.new
34
+ out.setup_activerecord!
35
+ out
36
+ end
37
+ end
38
+
39
+ def binary_column(length)
40
+ case ObjectidColumns::Helpers::SystemHelpers.database_helper.adapter_name.to_s
41
+ when /mysql/, /sqlite/ then "BINARY(#{length})"
42
+ when /postgres/ then "BYTEA"
43
+ else raise "Don't yet know how to define a binary column for database #{OBJECTID_COLUMNS_SPEC_DATABASE_CONFIG[:config][:adapter].inspect}"
44
+ end
45
+ end
46
+
47
+ def supports_length_limits_on_binary_columns?
48
+ case ObjectidColumns::Helpers::SystemHelpers.database_helper.adapter_name.to_s
49
+ when /mysql/, /sqlite/ then true
50
+ when /postgres/ then false
51
+ else raise "Don't yet know whether database #{OBJECTID_COLUMNS_SPEC_DATABASE_CONFIG[:config][:adapter].inspect} supports limits on binary columns"
52
+ end
53
+ end
54
+ end
55
+
56
+ def create_standard_system_spec_tables!
57
+ migrate do
58
+ drop_table :objectidcols_spec_table rescue nil
59
+ create_table :objectidcols_spec_table do |t|
60
+ t.column :perfect_b_oid, ObjectidColumns::Helpers::SystemHelpers.binary_column(12)
61
+ t.column :longer_b_oid, ObjectidColumns::Helpers::SystemHelpers.binary_column(15)
62
+
63
+ t.column :too_short_b, ObjectidColumns::Helpers::SystemHelpers.binary_column(11)
64
+ t.column :perfect_b, ObjectidColumns::Helpers::SystemHelpers.binary_column(12)
65
+ t.column :longer_b, ObjectidColumns::Helpers::SystemHelpers.binary_column(15)
66
+
67
+ t.column :perfect_s_oid, 'VARCHAR(24)'
68
+ t.column :longer_s_oid, 'VARCHAR(30)'
69
+
70
+ t.column :too_short_s, 'VARCHAR(23)'
71
+ t.column :perfect_s, 'VARCHAR(24)'
72
+ t.column :longer_s, 'VARCHAR(30)'
73
+
74
+ t.integer :some_int_column
75
+ end
76
+ end
77
+ end
78
+
79
+ def create_standard_system_spec_models!
80
+ define_model_class(:Spectable, 'objectidcols_spec_table') { }
81
+ end
82
+
83
+ def drop_standard_system_spec_tables!
84
+ migrate do
85
+ drop_table :objectidcols_spec_table rescue nil
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,398 @@
1
+ require 'objectid_columns'
2
+ require 'objectid_columns/helpers/system_helpers'
3
+
4
+ unless defined?(VALID_OBJECTID_CLASSES)
5
+ VALID_OBJECTID_CLASSES = [ BSON::ObjectId ]
6
+ VALID_OBJECTID_CLASSES << Moped::BSON::ObjectId if defined?(Moped::BSON::ObjectId)
7
+ end
8
+
9
+ RSpec::Matchers.define :be_an_objectid_object do
10
+ match do |actual|
11
+ VALID_OBJECTID_CLASSES.detect { |c| actual.kind_of?(c) }
12
+ end
13
+ failure_message_for_should do |actual|
14
+ "expected that #{actual} (#{actual.class}) would be an instance of BSON::ObjectId or Moped::BSON::ObjectId"
15
+ end
16
+ end
17
+
18
+ RSpec::Matchers.define :be_the_same_objectid_as do |expected|
19
+ match do |actual|
20
+ net_expected = expected ? expected.to_bson_id.to_s : expected
21
+ net_actual = actual ? actual.to_bson_id.to_s : actual
22
+ net_expected == net_actual
23
+ end
24
+ failure_message_for_should do |actual|
25
+ "expected that #{actual} (#{actual.class}) would be the same ObjectId as #{expected} (#{expected.class})"
26
+ end
27
+ end
28
+
29
+ RSpec::Matchers.define :be_an_objectid_object_matching do |expected|
30
+ match do |actual|
31
+ net_expected = expected ? expected.to_bson_id.to_s : expected
32
+ net_actual = actual ? actual.to_bson_id.to_s : actual
33
+ (net_expected == net_actual) && (VALID_OBJECTID_CLASSES.detect { |c| actual.kind_of?(c) })
34
+ end
35
+ failure_message_for_should do |actual|
36
+ "expected that #{actual} (#{actual.class}) would be an ObjectId object equal to #{expected} (#{expected.class})"
37
+ end
38
+ end
39
+
40
+ describe "ObjectidColumns basic operations" do
41
+ include ObjectidColumns::Helpers::SystemHelpers
42
+
43
+ before :each do
44
+ ensure_database_is_set_up!
45
+
46
+ create_standard_system_spec_tables!
47
+ create_standard_system_spec_models!
48
+ end
49
+
50
+ after :each do
51
+ drop_standard_system_spec_tables!
52
+ end
53
+
54
+ VALID_OBJECTID_CLASSES.each do |test_class|
55
+ context "using test class #{test_class}" do
56
+ before :each do
57
+ @tc = test_class
58
+ end
59
+
60
+ def new_oid
61
+ @tc.new
62
+ end
63
+
64
+ it "should not allow defining a column that's too short" do
65
+ if ObjectidColumns::Helpers::SystemHelpers.supports_length_limits_on_binary_columns?
66
+ expect { ::Spectable.class_eval { has_objectid_column :too_short_b } }.to raise_error(ArgumentError)
67
+ expect { ::Spectable.class_eval { has_objectid_column :too_short_s } }.to raise_error(ArgumentError)
68
+ end
69
+ end
70
+
71
+ it "should not allow defining a column that's the wrong type" do
72
+ expect { ::Spectable.class_eval { has_objectid_column :some_int_column } }.to raise_error(ArgumentError)
73
+ end
74
+
75
+ it "should not allow defining a column that doesn't exist" do
76
+ expect { ::Spectable.class_eval { has_objectid_column :unknown_column } }.to raise_error(ArgumentError)
77
+ end
78
+
79
+ it "should not fail if the table doesn't exist" do
80
+ define_model_class(:SpectableNonexistent, 'objectidcols_spec_table_nonexistent') { }
81
+ expect { ::SpectableNonexistent.class_eval { has_objectid_column :foo } }.to_not raise_error
82
+ end
83
+
84
+ describe "primary key column support" do
85
+ before :each do
86
+ migrate do
87
+ drop_table :objectidcols_spec_pk_bin rescue nil
88
+ create_table :objectidcols_spec_pk_bin, :id => false do |t|
89
+ t.binary :id, :null => false
90
+ t.string :name
91
+ end
92
+
93
+ drop_table :objectidcols_spec_pk_str rescue nil
94
+ create_table :objectidcols_spec_pk_str, :id => false do |t|
95
+ t.string :id, :null => false
96
+ t.string :name
97
+ end
98
+
99
+ drop_table :objectidcols_spec_pk_alt rescue nil
100
+ create_table :objectidcols_spec_pk_alt, :id => false do |t|
101
+ t.binary :some_name, :null => false
102
+ t.string :name
103
+ end
104
+
105
+ drop_table :objectidcols_spec_pk_implicit rescue nil
106
+ create_table :objectidcols_spec_pk_implicit, :id => false do |t|
107
+ t.binary :some_name, :null => false
108
+ t.string :name
109
+ end
110
+ end
111
+
112
+ define_model_class(:SpectablePkBin, :objectidcols_spec_pk_bin) { self.primary_key = 'id' }
113
+ define_model_class(:SpectablePkStr, :objectidcols_spec_pk_str) { self.primary_key = 'id' }
114
+ define_model_class(:SpectablePkAlt, :objectidcols_spec_pk_alt) { self.primary_key = 'some_name' }
115
+ define_model_class(:SpectablePkImplicit, :objectidcols_spec_pk_implicit) { }
116
+
117
+ ::SpectablePkBin.class_eval { has_objectid_primary_key }
118
+ ::SpectablePkStr.class_eval { has_objectid_primary_key }
119
+ ::SpectablePkAlt.class_eval { has_objectid_primary_key }
120
+ ::SpectablePkImplicit.class_eval { has_objectid_primary_key :some_name }
121
+ end
122
+
123
+ after :each do
124
+ drop_table :objectidcols_spec_pk_bin rescue nil
125
+ drop_table :objectidcols_spec_pk_str rescue nil
126
+ drop_table :objectidcols_spec_pk_table_alt rescue nil
127
+ drop_table :objectidcols_spec_pk_implicit rescue nil
128
+ end
129
+
130
+ [ :SpectablePkBin, :SpectablePkStr, :SpectablePkAlt, :SpectablePkImplicit ].each do |model_class|
131
+ context "on model #{model_class}" do
132
+ before :each do
133
+ @model_class = model_class.to_s.constantize
134
+ end
135
+
136
+ it "should fail autodetection, since there are no columns ending in _oid" do
137
+ expect { @model_class.has_objectid_columns }.to raise_error(ArgumentError)
138
+ end
139
+
140
+ it "should allow using a binary ObjectId column as a primary key" do
141
+ r1 = @model_class.new
142
+ r1.name = 'row 1'
143
+ expect(r1.id).to be_nil
144
+ r1.save!
145
+ expect(r1.id).to_not be_nil
146
+ expect(r1.id).to be_an_objectid_object
147
+ r1_id = r1.id
148
+
149
+ r2 = @model_class.new
150
+ r2.name = 'row 2'
151
+ expect(r2.id).to be_nil
152
+ r2.save!
153
+ expect(r2.id).to_not be_nil
154
+ expect(r2.id).to be_an_objectid_object
155
+ r2_id = r2.id
156
+
157
+ expect(r1.send(@model_class.primary_key)).to be_an_objectid_object_matching(r1.id)
158
+ expect(r2.send(@model_class.primary_key)).to be_an_objectid_object_matching(r2.id)
159
+
160
+ r1_again = @model_class.find(r1.id)
161
+ expect(r1_again.name).to eq('row 1')
162
+
163
+ r2_again = @model_class.find(r2.id)
164
+ expect(r2_again.name).to eq('row 2')
165
+
166
+ expect(@model_class.find([ r1.id, r2. id ]).map(&:id).sort_by(&:to_s)).to eq([ r1_id, r2_id ].sort_by(&:to_s))
167
+
168
+ expect(@model_class.where(:name => 'row 1').first.id).to eq(r1_id)
169
+ expect(@model_class.where(:name => 'row 2').first.id).to eq(r2_id)
170
+
171
+ find_by_id_method = "find_by_#{@model_class.primary_key}"
172
+ expect(@model_class.send(find_by_id_method, r1.id).id).to eq(r1_id)
173
+ expect(@model_class.send(find_by_id_method, r2.id).id).to eq(r2_id)
174
+ expect(@model_class.send(find_by_id_method, new_oid)).to be_nil
175
+ end
176
+
177
+ it "should not pick up primary-key columns automatically, even if they're named _oid" do
178
+ migrate do
179
+ drop_table :objectidcols_spec_pk_auto rescue nil
180
+ create_table :objectidcols_spec_pk_auto, :id => false do |t|
181
+ t.binary :foo_oid, :null => false
182
+ t.binary :bar_oid
183
+ t.string :name
184
+ end
185
+ end
186
+
187
+ define_model_class(:SpectablePkAuto, :objectidcols_spec_pk_auto) { self.primary_key = 'foo_oid' }
188
+
189
+ ::SpectablePkAuto.has_objectid_columns
190
+ r = ::SpectablePkAuto.new
191
+ r.foo_oid = 'foobar' # this will only work if we do NOT think it's an ObjectId
192
+ expect { r.bar_oid = 'foobar' }.to raise_error(ArgumentError)
193
+ r.bar_oid = the_bar_oid = new_oid.to_s
194
+
195
+ expect(r.bar_oid).to be_an_objectid_object_matching(the_bar_oid)
196
+
197
+ migrate do
198
+ drop_table :objectidcols_spec_pk_auto rescue nil
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ context "with a single, manually-defined column" do
206
+ before :each do
207
+ ::Spectable.class_eval { has_objectid_column :perfect_s_oid }
208
+ end
209
+
210
+ it "should allow writing and reading via an ObjectId object" do
211
+ the_oid = new_oid
212
+
213
+ r = ::Spectable.new
214
+ r.perfect_s_oid = the_oid
215
+ expect(r.perfect_s_oid).to be_the_same_objectid_as(the_oid)
216
+ expect(r.perfect_s_oid).to be_an_objectid_object
217
+ r.save!
218
+ expect(r.perfect_s_oid).to be_the_same_objectid_as(the_oid.to_s)
219
+ expect(r.perfect_s_oid).to be_an_objectid_object
220
+
221
+ r_again = ::Spectable.find(r.id)
222
+ expect(r_again.perfect_s_oid).to be_the_same_objectid_as(the_oid.to_s)
223
+ expect(r_again.perfect_s_oid).to be_an_objectid_object
224
+ end
225
+
226
+ it "should raise a good exception if you try to assign something that isn't a valid ObjectId" do
227
+ r = ::Spectable.new
228
+
229
+ expect { r.perfect_s_oid = 12345 }.to raise_error(ArgumentError, /12345/)
230
+ expect { r.perfect_s_oid = /foobar/ }.to raise_error(ArgumentError, /foobar/i)
231
+ end
232
+
233
+ if "".respond_to?(:encoding)
234
+ it "should not allow assigning binary strings unless their encoding is BINARY" do
235
+ r = ::Spectable.new
236
+
237
+ binary = new_oid.to_binary
238
+ binary = binary.force_encoding(Encoding::ISO_8859_1)
239
+ expect { r.perfect_s_oid = binary }.to raise_error(ArgumentError)
240
+ end
241
+ end
242
+
243
+ it "should not allow assigning strings that are the wrong format" do
244
+ r = ::Spectable.new
245
+
246
+ expect { r.perfect_s_oid = new_oid.to_binary[0..10] }.to raise_error(ArgumentError)
247
+ expect { r.perfect_s_oid = new_oid.to_binary + "\x00" }.to raise_error(ArgumentError)
248
+ end
249
+
250
+ it "should let you set columns to nil" do
251
+ r = ::Spectable.create!(:perfect_s_oid => (@oid = new_oid))
252
+
253
+ r_again = ::Spectable.find(r.id)
254
+ expect(r_again.perfect_s_oid).to be_an_objectid_object_matching(@oid)
255
+ r.perfect_s_oid = nil
256
+ r.save!
257
+
258
+ r_yet_again = ::Spectable.find(r.id)
259
+ expect(r_yet_again.perfect_s_oid).to be_nil
260
+ end
261
+
262
+ it "should accept ObjectIds for input in binary, String, or either object format" do
263
+ VALID_OBJECTID_CLASSES.each do |klass|
264
+ r = ::Spectable.create!(:perfect_s_oid => (@oid = klass.new))
265
+ expect(::Spectable.find(r.id).perfect_s_oid).to be_an_objectid_object_matching(@oid)
266
+ end
267
+
268
+ r = ::Spectable.create!(:perfect_s_oid => (@oid = new_oid.to_s))
269
+ expect(::Spectable.find(r.id).perfect_s_oid).to be_an_objectid_object_matching(@oid)
270
+
271
+ r = ::Spectable.create!(:perfect_s_oid => (@oid = new_oid.to_binary))
272
+ expect(::Spectable.find(r.id).perfect_s_oid).to be_an_objectid_object_matching(@oid)
273
+ end
274
+
275
+ it "should not do anything to the other columns" do
276
+ r = ::Spectable.new
277
+
278
+ r.perfect_b_oid = 'perfect_b_1'
279
+ r.longer_b_oid = 'longer_b_1'
280
+
281
+ r.too_short_b = 'short_b_2'
282
+ r.perfect_b = 'perfect_b_2'
283
+ r.longer_b = 'longer_b_2'
284
+
285
+ the_oid = new_oid
286
+ r.perfect_s_oid = the_oid
287
+ r.longer_s_oid = 'longer_s_1'
288
+
289
+ r.too_short_s = 'short_s_1'
290
+ r.perfect_s = 'perfect_s_2'
291
+ r.longer_s = 'longer_s'
292
+
293
+ r.save!
294
+
295
+ r_again = ::Spectable.find(r.id)
296
+
297
+ expect(r_again.perfect_b_oid.strip).to eq('perfect_b_1')
298
+ expect(r_again.longer_b_oid.strip).to eq('longer_b_1')
299
+
300
+ expect(r_again.too_short_b.strip).to eq('short_b_2')
301
+ expect(r_again.perfect_b.strip).to eq('perfect_b_2')
302
+ expect(r_again.longer_b.strip).to eq('longer_b_2')
303
+
304
+ expect(r_again.perfect_s_oid).to be_the_same_objectid_as(the_oid)
305
+ expect(r_again.perfect_s_oid).to be_an_objectid_object
306
+ expect(r_again.longer_s_oid).to eq('longer_s_1')
307
+
308
+ expect(r_again.too_short_s).to eq('short_s_1')
309
+ expect(r_again.perfect_s).to eq('perfect_s_2')
310
+ expect(r_again.longer_s).to eq('longer_s')
311
+ end
312
+
313
+ it "should allow querying on ObjectId columns via Hash, but not change other queries" do
314
+ r1 = ::Spectable.create!(:perfect_s_oid => (@oid1 = new_oid), :longer_s_oid => "foobar")
315
+ r2 = ::Spectable.create!(:perfect_s_oid => (@oid2 = new_oid), :longer_s_oid => "barfoo")
316
+
317
+ expect(::Spectable.where(:perfect_s_oid => @oid1).to_a.map(&:id)).to eq([ r1.id ])
318
+ expect(::Spectable.where(:perfect_s_oid => @oid2).to_a.map(&:id)).to eq([ r2.id ])
319
+ expect(::Spectable.where(:perfect_s_oid => [ @oid1, @oid2 ]).to_a.map(&:id).sort).to eq([ r1.id, r2.id ].sort)
320
+
321
+ expect(::Spectable.where(:perfect_s_oid => @oid1.to_s).to_a.map(&:id)).to eq([ r1.id ])
322
+ expect(::Spectable.where(:perfect_s_oid => @oid2.to_s).to_a.map(&:id)).to eq([ r2.id ])
323
+ expect(::Spectable.where(:perfect_s_oid => [ @oid1, @oid2 ].map(&:to_s)).to_a.map(&:id).sort).to eq([ r1.id, r2.id ].sort)
324
+
325
+ expect(::Spectable.where(:perfect_s_oid => @oid1.to_binary).to_a.map(&:id)).to eq([ r1.id ])
326
+ expect(::Spectable.where(:perfect_s_oid => @oid2.to_binary).to_a.map(&:id)).to eq([ r2.id ])
327
+ expect(::Spectable.where(:perfect_s_oid => [ @oid1, @oid2 ].map(&:to_binary)).to_a.map(&:id).sort).to eq([ r1.id, r2.id ].sort)
328
+ end
329
+ end
330
+
331
+ it "should allow using any column that's long enough, including binary or string columns" do
332
+ ::Spectable.class_eval do
333
+ has_objectid_columns :perfect_b_oid, :longer_b_oid
334
+ has_objectid_columns :perfect_s_oid, :longer_s_oid, :perfect_s, :longer_s
335
+ end
336
+
337
+ r = ::Spectable.new
338
+
339
+ r.perfect_b_oid = @perfect_b_oid = new_oid
340
+ r.longer_b_oid = @longer_b_oid = new_oid
341
+ r.perfect_s_oid = @perfect_s_oid = new_oid
342
+ r.longer_s_oid = @longer_s_oid = new_oid
343
+ r.perfect_s = @perfect_s = new_oid
344
+ r.longer_s = @longer_s = new_oid
345
+
346
+ r.save!
347
+
348
+ r_again = ::Spectable.find(r.id)
349
+ expect(r_again.perfect_b_oid).to be_an_objectid_object_matching(@perfect_b_oid)
350
+ expect(r_again.longer_b_oid).to be_an_objectid_object_matching(@longer_b_oid)
351
+ expect(r_again.perfect_s_oid).to be_an_objectid_object_matching(@perfect_s_oid)
352
+ expect(r_again.longer_s_oid).to be_an_objectid_object_matching(@longer_s_oid)
353
+ expect(r_again.perfect_s).to be_an_objectid_object_matching(@perfect_s)
354
+ expect(r_again.longer_s).to be_an_objectid_object_matching(@longer_s)
355
+ end
356
+
357
+ it "should automatically pick up any _oid columns" do
358
+ ::Spectable.class_eval do
359
+ has_objectid_columns
360
+ end
361
+
362
+ r = ::Spectable.new
363
+
364
+ r.perfect_b_oid = @perfect_b_oid = new_oid
365
+ r.longer_b_oid = @longer_b_oid = new_oid
366
+
367
+ r.too_short_b = 'short_b_2'
368
+ r.perfect_b = 'perfect_b_2'
369
+ r.longer_b = 'longer_b_2'
370
+
371
+ r.perfect_s_oid = @perfect_s_oid = new_oid
372
+ r.longer_s_oid = @longer_s_oid = new_oid
373
+
374
+ r.too_short_s = 'short_s_1'
375
+ r.perfect_s = 'perfect_s_2'
376
+ r.longer_s = 'longer_s'
377
+
378
+ r.save!
379
+
380
+ r_again = ::Spectable.find(r.id)
381
+
382
+ expect(r_again.perfect_b_oid).to be_an_objectid_object_matching(@perfect_b_oid)
383
+ expect(r_again.longer_b_oid).to be_an_objectid_object_matching(@longer_b_oid)
384
+
385
+ expect(r_again.too_short_b.strip).to eq('short_b_2')
386
+ expect(r_again.perfect_b.strip).to eq('perfect_b_2')
387
+ expect(r_again.longer_b.strip).to eq('longer_b_2')
388
+
389
+ r_again.perfect_s_oid.should be_an_objectid_object_matching(@perfect_s_oid)
390
+ r_again.longer_s_oid.should be_an_objectid_object_matching(@longer_s_oid)
391
+
392
+ expect(r_again.too_short_s).to eq('short_s_1')
393
+ expect(r_again.perfect_s).to eq('perfect_s_2')
394
+ expect(r_again.longer_s).to eq('longer_s')
395
+ end
396
+ end
397
+ end
398
+ end