objectid_columns 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +35 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +224 -0
- data/Rakefile +6 -0
- data/lib/objectid_columns/active_record/base.rb +33 -0
- data/lib/objectid_columns/active_record/relation.rb +40 -0
- data/lib/objectid_columns/dynamic_methods_module.rb +104 -0
- data/lib/objectid_columns/extensions.rb +40 -0
- data/lib/objectid_columns/has_objectid_columns.rb +47 -0
- data/lib/objectid_columns/objectid_columns_manager.rb +298 -0
- data/lib/objectid_columns/version.rb +4 -0
- data/lib/objectid_columns.rb +121 -0
- data/objectid_columns.gemspec +51 -0
- data/spec/objectid_columns/helpers/database_helper.rb +178 -0
- data/spec/objectid_columns/helpers/system_helpers.rb +90 -0
- data/spec/objectid_columns/system/basic_system_spec.rb +398 -0
- data/spec/objectid_columns/system/extensions_spec.rb +69 -0
- metadata +145 -0
@@ -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
|