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,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