Skip to content

Commit

Permalink
Add with_primary_key to AssociationMatchers
Browse files Browse the repository at this point in the history
  • Loading branch information
petedmarsh committed Nov 1, 2014
1 parent a009c83 commit 0f34231
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 0 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Expand Up @@ -27,11 +27,14 @@
* Tweak `allow_value` failure message so that it reads a bit nicer when listing
existing errors.

* Add ability to test `:primary_key` option on associations. ([#597])

[#591]: https://github.com/thoughtbot/shoulda-matchers/pull/591
[#592]: https://github.com/thoughtbot/shoulda-matchers/pull/592
[#588]: https://github.com/thoughtbot/shoulda-matchers/pull/588
[#584]: https://github.com/thoughtbot/shoulda-matchers/pull/584
[#593]: https://github.com/thoughtbot/shoulda-matchers/pull/593
[#597]: https://github.com/thoughtbot/shoulda-matchers/pull/597

# 2.7.0

Expand Down
86 changes: 86 additions & 0 deletions lib/shoulda/matchers/active_record/association_matcher.rb
Expand Up @@ -100,6 +100,28 @@ module ActiveRecord
# should belong_to(:ancient_city).class_name('City')
# end
#
# ##### with_primary_key
#
# Use `with_primary_key` to test usage of the `:primary_key` option.
#
# class Person < ActiveRecord::Base
# belongs_to :great_country, primary_key: 'country_id'
# end
#
# # RSpec
# describe Person do
# it do
# should belong_to(:great_country).
# with_primary_key('country_id')
# end
# end
#
# # Test::Unit
# class PersonTest < ActiveSupport::TestCase
# should belong_to(:great_country).
# with_primary_key('country_id')
# end
#
# ##### with_foreign_key
#
# Use `with_foreign_key` to test usage of the `:foreign_key` option.
Expand Down Expand Up @@ -295,6 +317,24 @@ def belong_to(name)
# should have_many(:hopes).class_name('Dream')
# end
#
# ##### with_primary_key
#
# Use `with_primary_key` to test usage of the `:primary_key` option.
#
# class Person < ActiveRecord::Base
# has_many :worries, primary_key: 'worrier_id'
# end
#
# # RSpec
# describe Person do
# it { should have_many(:worries).with_primaryu_key('worrier_id') }

This comment has been minimized.

Copy link
@jtadeulopes

jtadeulopes Nov 4, 2014

s/with_primaryu_key/with_primary_key

This comment has been minimized.

Copy link
@mcmire

mcmire Nov 4, 2014

Collaborator

Good catch! Fixed in b9795b3

# end
#
# # Test::Unit
# class PersonTest < ActiveSupport::TestCase
# should have_many(:worries).with_primary_key('worrier_id')
# end
#
# ##### with_foreign_key
#
# Use `with_foreign_key` to test usage of the `:foreign_key` option.
Expand Down Expand Up @@ -511,6 +551,24 @@ def have_many(name)
# should have_one(:contract).dependent(:nullify)
# end
#
# ##### with_primary_key
#
# Use `with_primary_key` to test usage of the `:primary_key` option.
#
# class Person < ActiveRecord::Base
# has_one :job, primary_key: 'worker_id'
# end
#
# # RSpec
# describe Person do
# it { should have_one(:job).with_primary_key('worker_id') }
# end
#
# # Test::Unit
# class PersonTest < ActiveSupport::TestCase
# should have_one(:job).with_primary_key('worker_id')
# end
#
# ##### with_foreign_key
#
# Use `with_foreign_key` to test usage of the `:foreign_key` option.
Expand Down Expand Up @@ -814,6 +872,11 @@ def with_foreign_key(foreign_key)
self
end

def with_primary_key(primary_key)
@options[:primary_key] = primary_key
self
end

def validate(validate = true)
@options[:validate] = validate
self
Expand Down Expand Up @@ -846,6 +909,7 @@ def matches?(subject)
macro_correct? &&
(polymorphic? || class_exists?) &&
foreign_key_exists? &&
primary_key_exists? &&
class_name_correct? &&
join_table_correct? &&
autosave_correct? &&
Expand Down Expand Up @@ -928,10 +992,19 @@ def macro_correct?
end
end

def macro_supports_primary_key?
macro == :belongs_to ||
([:has_many, :has_one].include?(macro) && !through?)
end

def foreign_key_exists?
!(belongs_foreign_key_missing? || has_foreign_key_missing?)
end

def primary_key_exists?
!macro_supports_primary_key? || primary_key_correct?(model_class)
end

def belongs_foreign_key_missing?
macro == :belongs_to && !class_has_foreign_key?(model_class)
end
Expand Down Expand Up @@ -1034,6 +1107,19 @@ def class_has_foreign_key?(klass)
end
end

def primary_key_correct?(klass)
if options.key?(:primary_key)
if option_verifier.correct_for_string?(:primary_key, options[:primary_key])
true
else
@missing = "#{klass} does not have a #{options[:primary_key]} primary key"
false
end
else
true
end
end

def foreign_key
if foreign_key_reflection
if foreign_key_reflection.respond_to?(:foreign_key)
Expand Down
48 changes: 48 additions & 0 deletions spec/shoulda/matchers/active_record/association_matcher_spec.rb
Expand Up @@ -29,6 +29,22 @@
expect(Child.new).to belong_to(:parent)
end

it 'accepts an association using an existing custom primary key' do
define_model :parent
define_model :child, parent_id: :integer, custom_primary_key: :integer do
belongs_to :parent, primary_key: :custom_primary_key
end
expect(Child.new).to belong_to(:parent).with_primary_key(:custom_primary_key)
end

it 'rejects an association with a bad :primary_key option' do
matcher = belong_to(:parent).with_primary_key(:custom_primary_key)

expect(belonging_to_parent).not_to matcher

expect(matcher.failure_message).to match(/Child does not have a custom_primary_key primary key/)
end

it 'accepts a polymorphic association' do
define_model :child, parent_type: :string, parent_id: :integer do
belongs_to :parent, polymorphic: true
Expand Down Expand Up @@ -288,6 +304,22 @@ def belonging_to_non_existent_class(model_name, assoc_name, options = {})
expect(Parent.new).not_to have_many(:children)
end

it 'accepts an association using an existing custom primary key' do
define_model :child, parent_id: :integer
define_model :parent, custom_primary_key: :integer do
has_many :children, primary_key: :custom_primary_key
end
expect(Parent.new).to have_many(:children).with_primary_key(:custom_primary_key)
end

it 'rejects an association with a bad :primary_key option' do
matcher = have_many(:children).with_primary_key(:custom_primary_key)

expect(having_many_children).not_to matcher

expect(matcher.failure_message).to match(/Parent does not have a custom_primary_key primary key/)
end

it 'rejects an association with a bad :as option' do
define_model :child, caretaker_type: :string,
caretaker_id: :integer
Expand Down Expand Up @@ -537,6 +569,22 @@ def having_many_non_existent_class(model_name, assoc_name, options = {})
expect(Person.new).to have_one(:detail).with_foreign_key(:detailed_person_id)
end

it 'accepts an association using an existing custom primary key' do
define_model :detail, person_id: :integer
define_model :person, custom_primary_key: :integer do
has_one :detail, primary_key: :custom_primary_key
end
expect(Person.new).to have_one(:detail).with_primary_key(:custom_primary_key)
end

it 'rejects an association with a bad :primary_key option' do
matcher = have_one(:detail).with_primary_key(:custom_primary_key)

expect(having_one_detail).not_to matcher

expect(matcher.failure_message).to match(/Person does not have a custom_primary_key primary key/)
end

it 'rejects an association with a bad :as option' do
define_model :detail, detailable_id: :integer,
detailable_type: :string
Expand Down

0 comments on commit 0f34231

Please sign in to comment.