object_id_gem 1.0.6

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,4 @@
1
+ # What's the current version of this gem?
2
+ module ObjectidColumns
3
+ VERSION = "1.0.6"
4
+ end
@@ -0,0 +1,71 @@
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 = "object_id_gem"
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 [ ">= 5.0"]
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
+
52
+ # Double ugh. Basically, composite_primary_keys -- as useful as it is! -- is also incredibly incompatible with so
53
+ # much stuff:
54
+ #
55
+ # * Under Ruby 1.9+ with Postgres, it causes binary strings sent to or from the database to get truncated
56
+ # at the first null byte (!), which completely breaks binary-column support;
57
+ # * Under JRuby with ActiveRecord 3.0, it's completely broken;
58
+ # * Under JRuby with ActiveRecord 3.1 and PostgreSQL, it's also broken.
59
+ #
60
+ # In these cases, we simply don't load or test against composite_primary_keys; our code is good, but the interactions
61
+ # between CPK and the rest of the system make it impossible to run those tests. There is corresponding code in our
62
+ # +basic_system_spec+ to exclude those combinations.
63
+ cpk_allowed = true
64
+ cpk_allowed = false if database_gem_name =~ /(pg|postgres)/i && RUBY_VERSION =~ /^(1\.9)|(2\.)/ && ar_version && ar_version =~ /^4\.(0|1)\./
65
+ cpk_allowed = false if defined?(RUBY_ENGINE) && (RUBY_ENGINE == 'jruby') && ar_version && ar_version =~ /^3\.0\./
66
+ cpk_allowed = false if defined?(RUBY_ENGINE) && (RUBY_ENGINE == 'jruby') && ar_version && ar_version =~ /^3\.1\./ && database_gem_name =~ /(pg|postgres)/i
67
+
68
+ if cpk_allowed
69
+ spec.add_development_dependency "composite_primary_keys"
70
+ end
71
+ 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,92 @@
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, options = { }, &block)
19
+ superclass = options[:superclass] || ::ActiveRecord::Base
20
+ model_class = Class.new(superclass)
21
+ ::Object.send(:remove_const, name) if ::Object.const_defined?(name)
22
+ ::Object.const_set(name, model_class)
23
+ model_class.table_name = table_name if table_name
24
+ model_class.class_eval(&block) if block
25
+ model_class
26
+ end
27
+
28
+ def ensure_database_is_set_up!
29
+ ::ObjectidColumns::Helpers::SystemHelpers.database_helper
30
+ end
31
+
32
+ class << self
33
+ def database_helper
34
+ @database_helper ||= begin
35
+ out = ObjectidColumns::Helpers::DatabaseHelper.new
36
+ out.setup_activerecord!
37
+ out
38
+ end
39
+ end
40
+
41
+ def binary_column(length)
42
+ case ObjectidColumns::Helpers::SystemHelpers.database_helper.adapter_name.to_s
43
+ when /mysql/, /sqlite/ then "BINARY(#{length})"
44
+ when /postgres/ then "BYTEA"
45
+ else raise "Don't yet know how to define a binary column for database #{OBJECTID_COLUMNS_SPEC_DATABASE_CONFIG[:config][:adapter].inspect}"
46
+ end
47
+ end
48
+
49
+ def supports_length_limits_on_binary_columns?
50
+ case ObjectidColumns::Helpers::SystemHelpers.database_helper.adapter_name.to_s
51
+ when /mysql/, /sqlite/ then true
52
+ when /postgres/ then false
53
+ else raise "Don't yet know whether database #{OBJECTID_COLUMNS_SPEC_DATABASE_CONFIG[:config][:adapter].inspect} supports limits on binary columns"
54
+ end
55
+ end
56
+ end
57
+
58
+ def create_standard_system_spec_tables!
59
+ migrate do
60
+ drop_table :objectidcols_spec_table rescue nil
61
+ create_table :objectidcols_spec_table do |t|
62
+ t.column :perfect_b_oid, ObjectidColumns::Helpers::SystemHelpers.binary_column(12)
63
+ t.column :longer_b_oid, ObjectidColumns::Helpers::SystemHelpers.binary_column(15)
64
+
65
+ t.column :too_short_b, ObjectidColumns::Helpers::SystemHelpers.binary_column(11)
66
+ t.column :perfect_b, ObjectidColumns::Helpers::SystemHelpers.binary_column(12)
67
+ t.column :longer_b, ObjectidColumns::Helpers::SystemHelpers.binary_column(15)
68
+
69
+ t.column :perfect_s_oid, 'VARCHAR(24)'
70
+ t.column :longer_s_oid, 'VARCHAR(30)'
71
+
72
+ t.column :too_short_s, 'VARCHAR(23)'
73
+ t.column :perfect_s, 'VARCHAR(24)'
74
+ t.column :longer_s, 'VARCHAR(30)'
75
+
76
+ t.integer :some_int_column
77
+ end
78
+ end
79
+ end
80
+
81
+ def create_standard_system_spec_models!
82
+ define_model_class(:Spectable, 'objectidcols_spec_table') { }
83
+ end
84
+
85
+ def drop_standard_system_spec_tables!
86
+ migrate do
87
+ drop_table :objectidcols_spec_table rescue nil
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,600 @@
1
+ require 'objectid_columns'
2
+ require 'objectid_columns/helpers/system_helpers'
3
+
4
+ # See the gemspec for more details -- basically, we don't always load composite_primary_keys, because it's pretty
5
+ # broken and doesn't work with a fair number of combinations of Ruby versions, databases, and so on. So if it's not
6
+ # available, we skip those tests.
7
+ begin
8
+ require 'composite_primary_keys'
9
+ $composite_primary_keys_available = true
10
+ rescue LoadError => le
11
+ # nothing here
12
+ end
13
+
14
+ unless defined?(VALID_OBJECTID_CLASSES)
15
+ VALID_OBJECTID_CLASSES = [ BSON::ObjectId ]
16
+ VALID_OBJECTID_CLASSES << Moped::BSON::ObjectId if defined?(Moped::BSON::ObjectId)
17
+ end
18
+
19
+ RSpec::Matchers.define :be_an_objectid_object do
20
+ match do |actual|
21
+ VALID_OBJECTID_CLASSES.detect { |c| actual.kind_of?(c) }
22
+ end
23
+ failure_message_for_should do |actual|
24
+ "expected that #{actual} (#{actual.class}) would be an instance of BSON::ObjectId or Moped::BSON::ObjectId"
25
+ end
26
+ end
27
+
28
+ RSpec::Matchers.define :be_the_same_objectid_as do |expected|
29
+ match do |actual|
30
+ net_expected = expected ? expected.to_bson_id.to_s : expected
31
+ net_actual = actual ? actual.to_bson_id.to_s : actual
32
+ net_expected == net_actual
33
+ end
34
+ failure_message_for_should do |actual|
35
+ "expected that #{actual} (#{actual.class}) would be the same ObjectId as #{expected} (#{expected.class})"
36
+ end
37
+ end
38
+
39
+ RSpec::Matchers.define :be_an_objectid_object_matching do |expected|
40
+ match do |actual|
41
+ net_expected = expected ? expected.to_bson_id.to_s : expected
42
+ net_actual = actual ? actual.to_bson_id.to_s : actual
43
+ (net_expected == net_actual) && (VALID_OBJECTID_CLASSES.detect { |c| actual.kind_of?(c) })
44
+ end
45
+ failure_message_for_should do |actual|
46
+ "expected that #{actual} (#{actual.class}) would be an ObjectId object equal to #{expected} (#{expected.class})"
47
+ end
48
+ end
49
+
50
+ describe "ObjectidColumns basic operations" do
51
+ include ObjectidColumns::Helpers::SystemHelpers
52
+
53
+ before :each do
54
+ ensure_database_is_set_up!
55
+
56
+ create_standard_system_spec_tables!
57
+ create_standard_system_spec_models!
58
+ end
59
+
60
+ after :each do
61
+ drop_standard_system_spec_tables!
62
+ end
63
+
64
+ VALID_OBJECTID_CLASSES.each do |test_class|
65
+ context "using test class #{test_class}" do
66
+ before :each do
67
+ @tc = test_class
68
+ end
69
+
70
+ def new_oid
71
+ @tc.new
72
+ end
73
+
74
+ context "with an STI table and model" do
75
+ before :each do
76
+ migrate do
77
+ drop_table :objectidcols_spec_table_sti rescue nil
78
+ create_table :objectidcols_spec_table_sti do |t|
79
+ t.string :type
80
+ t.column :some_oid, 'VARCHAR(24)'
81
+ end
82
+ end
83
+ end
84
+
85
+ after :each do
86
+ migrate do
87
+ drop_table :objectidcols_spec_table_sti rescue nil
88
+ end
89
+ end
90
+
91
+ let(:parent_model_class) { define_model_class(:SpectableStiParent, 'objectidcols_spec_table_sti') { has_objectid_columns } }
92
+ let(:child_model_class) { define_model_class(:SpectableStiChild, 'objectidcols_spec_table_sti', :superclass => parent_model_class) { } }
93
+
94
+ it "should work from both the parent and child class" do
95
+ id_1 = new_oid
96
+ id_2 = new_oid
97
+
98
+ parent_instance = parent_model_class.new(:some_oid => id_1)
99
+ parent_instance.save!
100
+
101
+ child_instance = child_model_class.new(:some_oid => id_2)
102
+ child_instance.save!
103
+
104
+ all_models = parent_model_class.all.to_a
105
+ expect(all_models.length).to eq(2)
106
+ parent_instance_again = all_models.detect { |m| m.id == parent_instance.id }
107
+ child_instance_again = all_models.detect { |m| m.id == child_instance.id }
108
+
109
+ expect(parent_instance_again.some_oid).to be_an_objectid_object_matching(id_1)
110
+ expect(child_instance_again.some_oid).to be_an_objectid_object_matching(id_2)
111
+
112
+ child_models = child_model_class.all.to_a
113
+ expect(child_models.length).to eq(1)
114
+ expect(child_models[0].some_oid).to be_an_objectid_object_matching(id_2)
115
+ end
116
+ end
117
+
118
+ it "should not allow defining a column that's too short" do
119
+ if ObjectidColumns::Helpers::SystemHelpers.supports_length_limits_on_binary_columns?
120
+ expect { ::Spectable.class_eval { has_objectid_column :too_short_b } }.to raise_error(ArgumentError)
121
+ expect { ::Spectable.class_eval { has_objectid_column :too_short_s } }.to raise_error(ArgumentError)
122
+ end
123
+ end
124
+
125
+ it "should not allow defining a column that's the wrong type" do
126
+ expect { ::Spectable.class_eval { has_objectid_column :some_int_column } }.to raise_error(ArgumentError)
127
+ end
128
+
129
+ it "should not allow defining a column that doesn't exist" do
130
+ expect { ::Spectable.class_eval { has_objectid_column :unknown_column } }.to raise_error(ArgumentError)
131
+ end
132
+
133
+ it "should not fail if the table doesn't exist" do
134
+ define_model_class(:SpectableNonexistent, 'objectidcols_spec_table_nonexistent') { }
135
+ expect { ::SpectableNonexistent.class_eval { has_objectid_column :foo } }.to_not raise_error
136
+ end
137
+
138
+ it "should not fail if declared as a primary key and the table doesn't exist" do
139
+ define_model_class(:SpectableNonexistent, 'objectidcols_spec_table_nonexistent') { }
140
+ expect { ::SpectableNonexistent.class_eval { has_objectid_primary_key } }.to_not raise_error
141
+ expect { ::SpectableNonexistent.class_eval { has_objectid_primary_key :foo } }.to_not raise_error
142
+ end
143
+
144
+ if $composite_primary_keys_available
145
+ describe "composite primary key support" do
146
+ context "with an implicit PK" do
147
+ before :each do
148
+ migrate do
149
+ drop_table :objectidcols_spec_pk_cmp rescue nil
150
+ create_table :objectidcols_spec_pk_cmp, :id => false do |t|
151
+ t.binary :some_oid, :null => false
152
+ t.string :more_pk, :null => false
153
+ t.string :value
154
+ end
155
+ end
156
+
157
+ define_model_class(:SpectablePkCmp, :objectidcols_spec_pk_cmp) do
158
+ if respond_to?(:primary_keys=)
159
+ self.primary_keys = [ 'some_oid', 'more_pk' ]
160
+ else
161
+ self.set_primary_keys('some_oid', 'more_pk')
162
+ end
163
+ end
164
+ ::SpectablePkCmp.class_eval { has_objectid_primary_key }
165
+ @model_class = ::SpectablePkCmp
166
+ end
167
+
168
+ it "should allow using a composite primary key in individual parts" do
169
+ pending "disabled" unless $composite_primary_keys_available
170
+
171
+ instance = @model_class.new
172
+ instance.some_oid = new_oid
173
+ instance.more_pk = "foo"
174
+ instance.value = "foo value"
175
+ instance.save!
176
+
177
+ instance_again = @model_class.find([ instance.some_oid, instance.more_pk ])
178
+ expect(instance_again.value).to eq(instance.value)
179
+ expect(instance_again.some_oid).to eq(instance.some_oid)
180
+ expect(instance_again.more_pk).to eq(instance.more_pk)
181
+ end
182
+
183
+ it "should allow using a composite primary key as a whole" do
184
+ pending "disabled" unless $composite_primary_keys_available
185
+
186
+ oid = new_oid
187
+ instance = @model_class.new
188
+ instance.id = [ oid, "foo" ]
189
+ instance.value = "foo value"
190
+ instance.save!
191
+
192
+ expect(instance.some_oid).to be_an_objectid_object_matching(oid)
193
+ expect(instance.more_pk).to eq("foo")
194
+ expect(instance.value).to eq("foo value")
195
+
196
+ instance_again = @model_class.find(instance.id)
197
+ expect(instance_again.id).to eq(instance.id)
198
+ expect(instance_again.some_oid).to be_an_objectid_object_matching(oid)
199
+ expect(instance_again.more_pk).to eq("foo")
200
+ expect(instance_again.value).to eq("foo value")
201
+ expect(instance_again.id).to be_kind_of(Array)
202
+ expect(instance_again.id.length).to eq(2)
203
+ expect(instance_again.id[0]).to be_an_objectid_object_matching(oid)
204
+ expect(instance_again.id[1]).to eq("foo")
205
+ end
206
+ end
207
+
208
+ context "with an explicit PK" do
209
+ before :each do
210
+ migrate do
211
+ drop_table :objectidcols_spec_pk_cmp_2 rescue nil
212
+ create_table :objectidcols_spec_pk_cmp_2, :id => false do |t|
213
+ t.binary :one, :null => false
214
+ t.string :two, :null => false
215
+ t.string :three, :null => false
216
+ t.string :value
217
+ end
218
+ end
219
+
220
+ define_model_class(:SpectablePkCmp2, :objectidcols_spec_pk_cmp_2) do
221
+ if respond_to?(:primary_keys=)
222
+ self.primary_keys = [ 'one', 'two', 'three' ]
223
+ else
224
+ self.set_primary_keys('one', 'two', 'three')
225
+ end
226
+ end
227
+ ::SpectablePkCmp2.class_eval { has_objectid_primary_key :one, :three }
228
+ @model_class = ::SpectablePkCmp2
229
+ end
230
+
231
+ it "should allow using a composite primary key that's partially ObjectId and partially not" do
232
+ instance = @model_class.new
233
+ instance.two = "foo"
234
+ instance.value = "foo_value"
235
+ instance.save!
236
+
237
+ expect(instance.id).to be_kind_of(Array)
238
+ expect(instance.id[0]).to be_an_objectid_object
239
+ expect(instance.id[1]).to eq("foo")
240
+ expect(instance.id[2]).to be_an_objectid_object
241
+
242
+ id = instance.id
243
+ instance_again = @model_class.find(id)
244
+ expect(instance_again.id).to eq(id)
245
+ expect(instance_again.id[0]).to be_an_objectid_object_matching(id[0])
246
+ expect(instance_again.id[1]).to eq("foo")
247
+ expect(instance_again.id[2]).to be_an_objectid_object_matching(id[2])
248
+ end
249
+ end
250
+ end
251
+ end
252
+
253
+ describe "primary key column support" do
254
+ before :each do
255
+ migrate do
256
+ drop_table :objectidcols_spec_pk_bin rescue nil
257
+ create_table :objectidcols_spec_pk_bin, :id => false do |t|
258
+ t.binary :id, :null => false
259
+ t.string :name
260
+ end
261
+
262
+ drop_table :objectidcols_spec_pk_str rescue nil
263
+ create_table :objectidcols_spec_pk_str, :id => false do |t|
264
+ t.string :id, :null => false
265
+ t.string :name
266
+ end
267
+
268
+ drop_table :objectidcols_spec_pk_alt rescue nil
269
+ create_table :objectidcols_spec_pk_alt, :id => false do |t|
270
+ t.binary :some_name, :null => false
271
+ t.string :name
272
+ end
273
+
274
+ drop_table :objectidcols_spec_pk_implicit rescue nil
275
+ create_table :objectidcols_spec_pk_implicit, :id => false do |t|
276
+ t.binary :some_name, :null => false
277
+ t.string :name
278
+ end
279
+ end
280
+
281
+ define_model_class(:SpectablePkBin, :objectidcols_spec_pk_bin) { self.primary_key = 'id' }
282
+ define_model_class(:SpectablePkStr, :objectidcols_spec_pk_str) { self.primary_key = 'id' }
283
+ define_model_class(:SpectablePkAlt, :objectidcols_spec_pk_alt) { self.primary_key = 'some_name' }
284
+ define_model_class(:SpectablePkImplicit, :objectidcols_spec_pk_implicit) { }
285
+
286
+ ::SpectablePkBin.class_eval { has_objectid_primary_key }
287
+ ::SpectablePkStr.class_eval { has_objectid_primary_key }
288
+ ::SpectablePkAlt.class_eval { has_objectid_primary_key }
289
+ ::SpectablePkImplicit.class_eval { has_objectid_primary_key :some_name }
290
+ end
291
+
292
+ after :each do
293
+ drop_table :objectidcols_spec_pk_bin rescue nil
294
+ drop_table :objectidcols_spec_pk_str rescue nil
295
+ drop_table :objectidcols_spec_pk_table_alt rescue nil
296
+ drop_table :objectidcols_spec_pk_implicit rescue nil
297
+ end
298
+
299
+ [ :SpectablePkBin, :SpectablePkStr, :SpectablePkAlt, :SpectablePkImplicit ].each do |model_class|
300
+ context "on model #{model_class}" do
301
+ before :each do
302
+ @model_class = model_class.to_s.constantize
303
+ end
304
+
305
+ it "should fail autodetection, since there are no columns ending in _oid" do
306
+ expect { @model_class.has_objectid_columns }.to raise_error(ArgumentError)
307
+ end
308
+
309
+ it "should allow using a binary ObjectId column as a primary key" do
310
+ r1 = @model_class.new
311
+ r1.name = 'row 1'
312
+ expect(r1.id).to be_nil
313
+ r1.save!
314
+ expect(r1.id).to_not be_nil
315
+ expect(r1.id).to be_an_objectid_object
316
+ r1_id = r1.id
317
+
318
+ r2 = @model_class.new
319
+ r2.name = 'row 2'
320
+ expect(r2.id).to be_nil
321
+ r2.save!
322
+ expect(r2.id).to_not be_nil
323
+ expect(r2.id).to be_an_objectid_object
324
+ r2_id = r2.id
325
+
326
+ expect(r1.send(@model_class.primary_key)).to be_an_objectid_object_matching(r1.id)
327
+ expect(r2.send(@model_class.primary_key)).to be_an_objectid_object_matching(r2.id)
328
+
329
+ r1_again = @model_class.find(r1.id)
330
+ expect(r1_again.name).to eq('row 1')
331
+
332
+ r2_again = @model_class.find(r2.id)
333
+ expect(r2_again.name).to eq('row 2')
334
+
335
+ expect(@model_class.find([ r1.id, r2. id ]).map(&:id).sort_by(&:to_s)).to eq([ r1_id, r2_id ].sort_by(&:to_s))
336
+
337
+ expect(@model_class.where(:name => 'row 1').first.id).to eq(r1_id)
338
+ expect(@model_class.where(:name => 'row 2').first.id).to eq(r2_id)
339
+
340
+ find_by_id_method = "find_by_#{@model_class.primary_key}"
341
+ expect(@model_class.send(find_by_id_method, r1.id).id).to eq(r1_id)
342
+ expect(@model_class.send(find_by_id_method, r2.id).id).to eq(r2_id)
343
+ expect(@model_class.send(find_by_id_method, new_oid)).to be_nil
344
+ end
345
+
346
+ it "should let you load and save objects properly" do
347
+ r1 = @model_class.new
348
+ r1.name = 'row 1'
349
+ r1.id = new_oid
350
+ r1.save!
351
+
352
+ r1_again = @model_class.find(@tc.from_string(r1.id.to_s))
353
+ expect(r1_again.name).to eq('row 1')
354
+ r1_again.id = @tc.from_string(r1.id.to_s)
355
+ r1_again.name = 'row 1 again'
356
+ r1_again.save!
357
+
358
+ r1_yet_again = @model_class.find(r1_again.id)
359
+ expect(r1_yet_again.name).to eq('row 1 again')
360
+ end
361
+
362
+ it "should not pick up primary-key columns automatically, even if they're named _oid" do
363
+ migrate do
364
+ drop_table :objectidcols_spec_pk_auto rescue nil
365
+ create_table :objectidcols_spec_pk_auto, :id => false do |t|
366
+ t.binary :foo_oid, :null => false
367
+ t.binary :bar_oid
368
+ t.string :name
369
+ end
370
+ end
371
+
372
+ define_model_class(:SpectablePkAuto, :objectidcols_spec_pk_auto) { self.primary_key = 'foo_oid' }
373
+
374
+ ::SpectablePkAuto.has_objectid_columns
375
+ r = ::SpectablePkAuto.new
376
+ r.foo_oid = 'foobar' # this will only work if we do NOT think it's an ObjectId
377
+ expect { r.bar_oid = 'foobar' }.to raise_error(ArgumentError)
378
+ r.bar_oid = the_bar_oid = new_oid.to_s
379
+
380
+ expect(r.bar_oid).to be_an_objectid_object_matching(the_bar_oid)
381
+
382
+ migrate do
383
+ drop_table :objectidcols_spec_pk_auto rescue nil
384
+ end
385
+ end
386
+ end
387
+ end
388
+ end
389
+
390
+ context "with a single, manually-defined column" do
391
+ before :each do
392
+ ::Spectable.class_eval { has_objectid_column :perfect_s_oid }
393
+ end
394
+
395
+ it "should allow writing and reading via an ObjectId object" do
396
+ the_oid = new_oid
397
+
398
+ r = ::Spectable.new
399
+ r.perfect_s_oid = the_oid
400
+ expect(r.perfect_s_oid).to be_the_same_objectid_as(the_oid)
401
+ expect(r.perfect_s_oid).to be_an_objectid_object
402
+ r.save!
403
+ expect(r.perfect_s_oid).to be_the_same_objectid_as(the_oid.to_s)
404
+ expect(r.perfect_s_oid).to be_an_objectid_object
405
+
406
+ r_again = ::Spectable.find(r.id)
407
+ expect(r_again.perfect_s_oid).to be_the_same_objectid_as(the_oid.to_s)
408
+ expect(r_again.perfect_s_oid).to be_an_objectid_object
409
+ end
410
+
411
+ it "should raise a good exception if you try to assign something that isn't a valid ObjectId" do
412
+ r = ::Spectable.new
413
+
414
+ expect { r.perfect_s_oid = 12345 }.to raise_error(ArgumentError, /12345/)
415
+ expect { r.perfect_s_oid = /foobar/ }.to raise_error(ArgumentError, /foobar/i)
416
+ end
417
+
418
+ if "".respond_to?(:encoding)
419
+ it "should not allow assigning binary strings unless their encoding is BINARY" do
420
+ r = ::Spectable.new
421
+
422
+ binary = new_oid.to_binary
423
+ binary = binary.force_encoding(Encoding::ISO_8859_1)
424
+ expect { r.perfect_s_oid = binary }.to raise_error(ArgumentError)
425
+ end
426
+ end
427
+
428
+ it "should not allow assigning strings that are the wrong format" do
429
+ r = ::Spectable.new
430
+
431
+ expect { r.perfect_s_oid = new_oid.to_binary[0..10] }.to raise_error(ArgumentError)
432
+ expect { r.perfect_s_oid = new_oid.to_binary + "\x00" }.to raise_error(ArgumentError)
433
+ end
434
+
435
+ it "should let you set columns to nil" do
436
+ r = ::Spectable.create!(:perfect_s_oid => (@oid = new_oid))
437
+
438
+ r_again = ::Spectable.find(r.id)
439
+ expect(r_again.perfect_s_oid).to be_an_objectid_object_matching(@oid)
440
+ r.perfect_s_oid = nil
441
+ r.save!
442
+
443
+ r_yet_again = ::Spectable.find(r.id)
444
+ expect(r_yet_again.perfect_s_oid).to be_nil
445
+ end
446
+
447
+ it "should accept ObjectIds for input in binary, String, or either object format" do
448
+ VALID_OBJECTID_CLASSES.each do |klass|
449
+ r = ::Spectable.create!(:perfect_s_oid => (@oid = klass.new))
450
+ expect(::Spectable.find(r.id).perfect_s_oid).to be_an_objectid_object_matching(@oid)
451
+ end
452
+
453
+ r = ::Spectable.create!(:perfect_s_oid => (@oid = new_oid.to_s))
454
+ expect(::Spectable.find(r.id).perfect_s_oid).to be_an_objectid_object_matching(@oid)
455
+
456
+ r = ::Spectable.create!(:perfect_s_oid => (@oid = new_oid.to_binary))
457
+ expect(::Spectable.find(r.id).perfect_s_oid).to be_an_objectid_object_matching(@oid)
458
+ end
459
+
460
+ it "should not do anything to the other columns" do
461
+ r = ::Spectable.new
462
+
463
+ r.perfect_b_oid = 'perfect_b_1'
464
+ r.longer_b_oid = 'longer_b_1'
465
+
466
+ r.too_short_b = 'short_b_2'
467
+ r.perfect_b = 'perfect_b_2'
468
+ r.longer_b = 'longer_b_2'
469
+
470
+ the_oid = new_oid
471
+ r.perfect_s_oid = the_oid
472
+ r.longer_s_oid = 'longer_s_1'
473
+
474
+ r.too_short_s = 'short_s_1'
475
+ r.perfect_s = 'perfect_s_2'
476
+ r.longer_s = 'longer_s'
477
+
478
+ r.save!
479
+
480
+ r_again = ::Spectable.find(r.id)
481
+
482
+ expect(r_again.perfect_b_oid.strip).to eq('perfect_b_1')
483
+ expect(r_again.longer_b_oid.strip).to eq('longer_b_1')
484
+
485
+ expect(r_again.too_short_b.strip).to eq('short_b_2')
486
+ expect(r_again.perfect_b.strip).to eq('perfect_b_2')
487
+ expect(r_again.longer_b.strip).to eq('longer_b_2')
488
+
489
+ expect(r_again.perfect_s_oid).to be_the_same_objectid_as(the_oid)
490
+ expect(r_again.perfect_s_oid).to be_an_objectid_object
491
+ expect(r_again.longer_s_oid).to eq('longer_s_1')
492
+
493
+ expect(r_again.too_short_s).to eq('short_s_1')
494
+ expect(r_again.perfect_s).to eq('perfect_s_2')
495
+ expect(r_again.longer_s).to eq('longer_s')
496
+ end
497
+
498
+ it "should allow querying on ObjectId columns via Hash, but not change other queries" do
499
+ r1 = ::Spectable.create!(:perfect_s_oid => (@oid1 = new_oid), :longer_s_oid => "foobar")
500
+ r2 = ::Spectable.create!(:perfect_s_oid => (@oid2 = new_oid), :longer_s_oid => "barfoo")
501
+
502
+ expect(::Spectable.where(:perfect_s_oid => @oid1).to_a.map(&:id)).to eq([ r1.id ])
503
+ expect(::Spectable.where(:perfect_s_oid => @oid2).to_a.map(&:id)).to eq([ r2.id ])
504
+ expect(::Spectable.where(:perfect_s_oid => [ @oid1, @oid2 ]).to_a.map(&:id).sort).to eq([ r1.id, r2.id ].sort)
505
+
506
+ expect(::Spectable.where(:perfect_s_oid => @oid1.to_s).to_a.map(&:id)).to eq([ r1.id ])
507
+ expect(::Spectable.where(:perfect_s_oid => @oid2.to_s).to_a.map(&:id)).to eq([ r2.id ])
508
+ expect(::Spectable.where(:perfect_s_oid => [ @oid1, @oid2 ].map(&:to_s)).to_a.map(&:id).sort).to eq([ r1.id, r2.id ].sort)
509
+
510
+ expect(::Spectable.where(:perfect_s_oid => @oid1.to_binary).to_a.map(&:id)).to eq([ r1.id ])
511
+ expect(::Spectable.where(:perfect_s_oid => @oid2.to_binary).to_a.map(&:id)).to eq([ r2.id ])
512
+ expect(::Spectable.where(:perfect_s_oid => [ @oid1, @oid2 ].map(&:to_binary)).to_a.map(&:id).sort).to eq([ r1.id, r2.id ].sort)
513
+ end
514
+
515
+ it "should give a good exception if you try to pass an ObjectID for a column that isn't an official ObjectID column" do
516
+ oid = new_oid
517
+ expect { ::Spectable.where(:perfect_b_oid => oid).to_a }.to raise_error(/ObjectidColumns:.*perfect_b_oid/mi)
518
+ end
519
+
520
+ it "should not blow up (and instead let ActiveRecord blow up) if you try to pass an ObjectID for a column that isn't a column at all" do
521
+ oid = new_oid
522
+ expect { ::Spectable.where(:something => oid).to_a }.to raise_error(/something/mi)
523
+ end
524
+
525
+ it "should not blow up (and instead let ActiveRecord just find no records, or blow up) if you try to pass an ObjectID for a column on a table that isn't using this gem at all" do
526
+ define_model_class(:SpectableNondeclared, 'objectidcols_spec_table') { }
527
+ expect(::SpectableNondeclared.where(:perfect_s_oid => new_oid).to_a).to eq([ ])
528
+
529
+ expect { ::SpectableNondeclared.where(:something => new_oid).to_a }.to raise_error(/something/i)
530
+ end
531
+ end
532
+
533
+ it "should allow using any column that's long enough, including binary or string columns" do
534
+ ::Spectable.class_eval do
535
+ has_objectid_columns :perfect_b_oid, :longer_b_oid
536
+ has_objectid_columns :perfect_s_oid, :longer_s_oid, :perfect_s, :longer_s
537
+ end
538
+
539
+ r = ::Spectable.new
540
+
541
+ r.perfect_b_oid = @perfect_b_oid = new_oid
542
+ r.longer_b_oid = @longer_b_oid = new_oid
543
+ r.perfect_s_oid = @perfect_s_oid = new_oid
544
+ r.longer_s_oid = @longer_s_oid = new_oid
545
+ r.perfect_s = @perfect_s = new_oid
546
+ r.longer_s = @longer_s = new_oid
547
+
548
+ r.save!
549
+
550
+ r_again = ::Spectable.find(r.id)
551
+ expect(r_again.perfect_b_oid).to be_an_objectid_object_matching(@perfect_b_oid)
552
+ expect(r_again.longer_b_oid).to be_an_objectid_object_matching(@longer_b_oid)
553
+ expect(r_again.perfect_s_oid).to be_an_objectid_object_matching(@perfect_s_oid)
554
+ expect(r_again.longer_s_oid).to be_an_objectid_object_matching(@longer_s_oid)
555
+ expect(r_again.perfect_s).to be_an_objectid_object_matching(@perfect_s)
556
+ expect(r_again.longer_s).to be_an_objectid_object_matching(@longer_s)
557
+ end
558
+
559
+ it "should automatically pick up any _oid columns" do
560
+ ::Spectable.class_eval do
561
+ has_objectid_columns
562
+ end
563
+
564
+ r = ::Spectable.new
565
+
566
+ r.perfect_b_oid = @perfect_b_oid = new_oid
567
+ r.longer_b_oid = @longer_b_oid = new_oid
568
+
569
+ r.too_short_b = 'short_b_2'
570
+ r.perfect_b = 'perfect_b_2'
571
+ r.longer_b = 'longer_b_2'
572
+
573
+ r.perfect_s_oid = @perfect_s_oid = new_oid
574
+ r.longer_s_oid = @longer_s_oid = new_oid
575
+
576
+ r.too_short_s = 'short_s_1'
577
+ r.perfect_s = 'perfect_s_2'
578
+ r.longer_s = 'longer_s'
579
+
580
+ r.save!
581
+
582
+ r_again = ::Spectable.find(r.id)
583
+
584
+ expect(r_again.perfect_b_oid).to be_an_objectid_object_matching(@perfect_b_oid)
585
+ expect(r_again.longer_b_oid).to be_an_objectid_object_matching(@longer_b_oid)
586
+
587
+ expect(r_again.too_short_b.strip).to eq('short_b_2')
588
+ expect(r_again.perfect_b.strip).to eq('perfect_b_2')
589
+ expect(r_again.longer_b.strip).to eq('longer_b_2')
590
+
591
+ r_again.perfect_s_oid.should be_an_objectid_object_matching(@perfect_s_oid)
592
+ r_again.longer_s_oid.should be_an_objectid_object_matching(@longer_s_oid)
593
+
594
+ expect(r_again.too_short_s).to eq('short_s_1')
595
+ expect(r_again.perfect_s).to eq('perfect_s_2')
596
+ expect(r_again.longer_s).to eq('longer_s')
597
+ end
598
+ end
599
+ end
600
+ end