The original patch that added this concept can be found
[here](https://web.archive.org/web/20090601022739/http://dev.rubyonrails.org/ticket/9767).
The current default behavior is to cache everything except serialized
columns, unless the user specified otherwise. If anyone were to specify
otherwise, many types would actually be completely broken. Still, the
method is left in place with a deprecation warning in case anyone is
actually still calling this method.
add_constraints is complicated and difficult to read. This is the
beginning of a long process of refactoring this code. First step:
moved the join keys out of AssociationScope and into reflection.
We then don't need to call `reflection` because now reflection is
`self`.
`foreign_key` must be named something else because reflection already has a
`foreign_key` method and when passed into `JoinKeys` it was getting the
wrong assignment. `reflection_foreign_key` seemed to be an appropriate name.
I also named `key` `reflection_key` to match `reflection_foreign_key`.
We never want result types to override column types, and
`decorate_columns` can only affect column types. No need to go through
the decoration multiple times, we can just exclude the column types from
the result types instead.
Reflection has an available method that is used to check if the
reflection is a collection. Any :has_many macro is considered a
collection and `collection?` should be used instead of
`macro == :has_many`.
By having the `:has_and_belongs_to_many` macro in the `@collection`
we are punishing `:has_many` associations because it has to allocate
the array and check the macro.
@collection is returned to `macro == :has_many` and a new reflection
class `HABTMReflection` is created to handle this case instead.
Instead of checking for `macro == :has_one` throughout the
codebase we can create a `has_one?` method to match the `belongs_to?`,
`polymorphic?` and other methods.
In some cases there is a difference between the two, we should always
be doing one or the other. For convenience, `type_cast` is still a
private method on type, so new types that do not need different behavior
don't need to implement two methods, but it has been moved to private so
it cannot be used accidentally.
- The following is now true for all types, all the time
- `model.attribute_before_type_cast == given_value`
- `model.attribute == model.save_and_reload.attribute`
- `model.attribute == model.dup.attribute`
- `model.attribute == YAML.load(YAML.dump(model)).attribute`
- Removes the remaining types implementing `type_cast_for_write`
- Simplifies the implementation of time zone aware attributes
- Brings tz aware attributes closer to being implemented as an attribute
decorator
- Adds additional point of control for custom types
The definition of `write_attribute` in dirty checking ultimately leads
to the columns calling `type_cast` on the value to perform the
comparison. However, this is a potentially expensive computation that we
cache when it occurs in `read_attribute`. The only case that we need the
non-type-cast form is for numeric, so we pass that through as well
(something I'm looking to remove in the future).
This also reduces the number of places that manually access various
stages in an attribute's type casting lifecycle, which will aid in one
of the larger refactorings that I'm working on.
Reduces the number of places that care about the internals of how we
store and type cast attributes. We do not need to go through the
dup/freeze dance, as you couldn't have saved a frozen new record anyway,
and that is the only time we would end up modifying the frozen hash.
There is no way to have an instance of an Active Record model where
`has_attribute?(self.class.primary_key)` returns false. The record is
always initialized in such a way that `@raw_attributes` will have an id
key with nil for the value.
We need to decorate the types lazily. This is extracted to a separate
API, as there are other refactorings that will be able to make use of
it, and to allow unit testing the finer points more granularly.