When the `required` and `optional` qualifiers on `belong_to` fail, the
message can be a little confusing. There are actually two ways to
satisfy both qualifiers. One way, of course, is to match the option on
the association with the qualifier itself, so if the association is
defined with `required: true`, that will match
`belong_to(...).required`, and if it's `required: false`, that will
match `belong_to(...).required(false)` (and similar for `optional`). The
second way, though, is to manually add a presence validation to the
association (this will obviously only match `required(true)`). We need
to update the failure message to reflect these cases.
In Rails 5, belongs_to associations default to `required: true`. This is
configurable by setting
`ActiveRecord::Base.belongs_to_required_by_default` to true. In new
Rails 5 apps, this is set to true, because the Rails generator will add
an initializer with the following line in it:
config.active_record.belongs_to_required_by_default = true
However, for Rails apps that have been upgraded from 4 to 5, this
initializer may not be present, and in that case, that setting will not
be set, and `belong_to` associations will not default to `required:
true`.
This means that under Rails 5, our `belong_to` matcher cannot always
default to applying the `required` qualifier; it must abide by the
`belongs_to_required_by_default` setting in doing so.
Rails 5 made two changes to `belongs_to` associations:
* `required` and `optional` were added as options (which add and remove
a presence validation on the association, respectively)
* `required` was made the default
In addition, a `required` option was also added to `has_one`.
These new qualifiers allow us to test these options appropriately.
Credit: Shia <rise.shia@gmail.com>
Rails supports declaring a `has_and_belongs_to_many` relationship with a
custom `:join_table` option and some developers want to assert that the
correct value is being used. Add `AssocationMatcher#join_table` to allow
developers to test the following:
1) That the :join_table option is being used for the relationship
2) That the *correct value* is being used
3) That the custom join table exists in the database
Fix `class_name` qualifier for association matchers so that if the
model being referenced is namespaced, the matcher will correctly resolve
the class before checking it against the association's `class_name`.
Take these models for instance:
module Models
class Friend < ActiveRecord::Base
end
class User < ActiveRecord::Base
has_many :friends, class_name: 'Friend'
end
end
Here, the `has_many` is referring to Models::Friend, not just Friend.
Previously in order to test the association, you had to write:
describe Models::User do
it { should have_many(:friends).class_name('Models::Friend') }
end
Now, `have_many` will attempt to resolve the string given to
`class_name` within the context of the namespace first before treating
it as a reference to a global constant. This means you can now write
this:
describe Models::User do
it { should have_many(:friends).class_name('Friend') }
end
If the habtm association in question specifies a custom foreign_key or
association_foreign_key, when the habtm matcher checks that the join
table has the correct columns, it should use the specified column names
instead of the defaults.
The `have_and_belong_to_many` matcher could give a false positive if the
join table is present but does not contain the correct columns. Check to
see if the columns exist in the join table and provide a meaningful
failure message if one or more of the columns are not present.
For a polymorphic belongs_to association, the association doesn't
actually point to a class, it points to a concept, so we can't check
that the model it points to exists.
The way that we figure out whether the value which is passed to `order`
is the same as the `order` that the association was defined with is by
creating an ActiveRecord::Relation object from the given `order` and
comparing it with the Relation stored under the association.
Specifically, every time you call `order` on a Relation, it appends the
value you've given to an internal `order_values` array, so we actually
access this array and compare the two between the two Relation objects.
Currently we assume that this `order_values` array is an array of
strings, so it's very easy to compare. That was valid pre-Rails 4.0.1,
but it looks like as per [these][1] [commits][2], it's now possible for
`order_values` to be an array of Arel nodes. So, to make a proper
comparison, we have to convert any Arel nodes to strings by calling
#to_sql on them.
[1]: https://github.com/rails/rails/commit/d345ed4
[2]: f83c9b10b4
Under Rails 3 when using an association matcher in conjunction with a
submatcher such as #order, we have to take the options on the
association (:conditions, :order, etc.) and convert them to an
ActiveRecord::Relation object. This happens in ModelReflector.
Unfortunately, converting an :includes option was broken. This is in
part due to ModelReflector not having proper tests, but also, the method
that does the conversion to Relation is rather complex and could be
broken up so it can be tested better. In fact I noticed that there's a
lot of stuff in ModelReflector that does stuff around a Reflection
object, so it would be better to define a ModelReflection class that
simply decorates a Reflection. That's what I've done and also added
proper tests.
In Rails 4, the following construct:
has_many :children, conditions: { adopted: true }
changes to:
has_many :children, lambda { where(adopted: true) }
As a result, the way we check the conditions attached to a has_many
changes too: instead of accessing `reflection.options`, we have to use
`reflection.scope` -- this which refers to the lambda above, so we have
to evaluate it and then grab the `where` from the Relation that the
lambda returns.
When using an association matcher you may have qualifiers on that
matcher which let you make assertions on options passed to the
association method that you are testing. For instance, has_many has a
:dependent option and so in order to test this you say something like
it { should have_many(:people).dependent(:destroy) }
In order to test such an option we have to compare the option you passed
with what the actual value of that option is. This is usually obtained
by looking at the reflection object of the association in question,
although it can be obtained by other means too.
Anyway, the code that does this comparison isn't terrible, but there are
two problems with it. First, it involves typecasting both expected and
actual values. For instance, this:
has_many :people, dependent: :destroy
it { should have_many(:people).dependent(:destroy) }
should be equivalent to:
has_many :people, dependent: :destroy
it { should have_many(:people).dependent('destroy') }
should be equivalent to:
has_many :people, dependent: 'destroy'
it { should have_many(:people).dependent(:destroy) }
Second, we are a little screwed if the method of obtaining the actual
value of the option changes depending on which Rails version you're
using.
So, OptionVerifier attempts to address both of these issues. It's a
little crazy, but it works.
I also moved some methods from AssociationMatcher to ModelReflector
where they really belong.
Refactored AssociationMatcher so that `#order`, `#through`, and `#dependent`
would be their own submatchers. This reduces some of the clutter in the main class,
especially as we continue expanding it. In addition, a few related tests were
modified so that they would check failure messages also.