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