Skip to content

Commit

Permalink
simpler example for the non-missing constants gotcha [ci skip]
Browse files Browse the repository at this point in the history
The previous example was a little convoluted and the exposition
claifying the parts that were correct albeit not totally obvious
were interferring in my view.

This example has less things going on and gets to the key problem
with less balls in the air.
  • Loading branch information
fxn committed Dec 17, 2014
1 parent fe46f00 commit 2e0429a
Showing 1 changed file with 39 additions and 42 deletions.
81 changes: 39 additions & 42 deletions guides/source/constant_autoloading_and_reloading.md
Expand Up @@ -1062,68 +1062,65 @@ spots.

### When Constants aren't Missed

Let's consider an `Image` model, superclass of `Hotel::Image`:
Let's consider a flight simulator. The application has a default flight model

```ruby
# app/models/image.rb
class Image
end

# app/models/hotel/image.rb
module Hotel
class Image < Image
end
# app/models/flight_model.rb
class FlightModel
end
```

No matter which file is interpreted first, `app/models/hotel/image.rb` is
well-defined.

Now consider a third file with this apparently harmless code:
that can be overriden by each airplane, for instance

```ruby
# app/models/hotel/poster.rb
module Hotel
class Poster < Image
# app/models/bell_x1/flight_model.rb
module BellX1
class FlightModel < FlightModel
end
end
```

The intention is to subclass `Hotel::Image`, but which is actually the
superclass of `Hotel::Poster`? Well, it depends on the order of execution:

1. If neither `app/models/image.rb` nor `app/models/hotel/image.rb` have been
loaded at that point, the superclass is `Hotel::Image` because Rails is told
`Hotel` is missing a constant called "Image" and loads
`app/models/hotel/image.rb`. Good.

2. If `app/models/hotel/image.rb` has been loaded at that point, the superclass
is `Hotel::Image` because Ruby is able to resolve the constant. Good.
# app/models/bell_x1/aircraft.rb
module BellX1
class Aircraft
def initialize
@flight_model = FlightModel.new
end
end
end
```

3. Lastly, if only `app/models/image.rb` has been loaded so far, the superclass
is `Image`. Gotcha!
The initializer wants to create a `BellX1::FlightModel` and nesting has
`BellX1`, that looks good. But if the default flight model is loaded and the
one for the Bell-X1 is not, the interpreter is able to resolve the top-level
`FlightModel` and autoloading is thus not triggered for `BellX1::FlightModel`.

The last scenario (3) may be surprising. Why isn't `Hotel::Image` autoloaded?
Because Ruby is able to resolve `Image` as a top-level constant, so
autoloading does not even get a chance.
That code depends on the execution path.

Most of the time, these kind of ambiguities can be resolved using qualified
constants. In this case we would write
These kind of ambiguities can often be resolved using qualified constants:

```ruby
module Hotel
class Poster < Hotel::Image
module BellX1
class Plane
def flight_model
@flight_model ||= BellX1::FlightModel.new
end
end
end
```

That class definition now is robust.
Also, `require_dependency` is a solution:

```ruby
require_dependency 'bell_x1/flight_model'

It is interesting to note here that the fix works because `Hotel` is a module, and
`Hotel::Image` won’t look for `Image` in `Object` as it would if `Hotel` was a
class with `Object` in its ancestors. If `Hotel` was a class we would resort to
loading `Hotel::Image` with `require_dependency`. Furthermore, with that
solution the qualified name would no longer be necessary.
module BellX1
class Plane
def flight_model
@flight_model ||= FlightModel.new
end
end
end
```

### Autoloading within Singleton Classes

Expand Down

0 comments on commit 2e0429a

Please sign in to comment.