Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add with_primary_key to AssociationMatchers #597

Merged
merged 1 commit into from
Nov 2, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a newline here? (Well technically a newline but still within the comment section... if you know what I mean)

#
# ##### 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') }
# 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
Original file line number Diff line number Diff line change
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