Skip to content

Commit c5191d5

Browse files
authored
MONGOID-5547 create index needs to load models (#5526)
* make the load_models task actually load models required decoupling the model loading code from Rails, which required some creative rearchitecting of some brittle tests. * fix other tests that relied on juggling the Rails constant * tidy up the FeatureSandbox * fix typo * parenthesize arguments to `defined?` * extract array to constant for better documentation * update docs * don't try to parse uri string if it hasn't been set
1 parent a692400 commit c5191d5

File tree

22 files changed

+409
-287
lines changed

22 files changed

+409
-287
lines changed

docs/reference/indexes.txt

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,42 @@ in Rails console:
143143
# Remove indexes for Model
144144
Model.remove_indexes
145145

146+
Telling Mongoid Where to Look For Models
147+
----------------------------------------
148+
149+
For non-Rails applications, Mongoid's rake tasks will look for models in
150+
``./app/models`` and ``./lib/models``. For Rails, Mongoid will look in
151+
``./app/models`` (or wherever you've configured Rails to look for models). If
152+
your models are in another location, you will need to tell Mongoid where to
153+
look for them with ``Mongoid.model_paths=``. You can do this by setting it
154+
in your application's Rakefile:
155+
156+
.. code-block:: ruby
157+
158+
# Rakefile
159+
160+
# keep the defaults, but add more paths to look for models
161+
Mongoid.model_paths += [ "./src/models", "./lib/documents" ]
162+
163+
# or, override the defaults entirely
164+
Mongoid.model_paths = [ "./src/models", "./lib/documents" ]
165+
166+
Make sure that these paths are in your application's load path, as well. For
167+
example:
168+
169+
.. code-block:: ruby
170+
171+
# Rakefile
172+
173+
$LOAD_PATHS.concat [ "./src/models", "./lib/documents" ]
174+
175+
146176
Using Rake Tasks With Non-Rails Applications
147177
--------------------------------------------
148178

149179
Mongoid's Rake tasks are automatically loaded in Rails applications using
150180
Mongoid. When using Mongoid with a non-Rails application, these tasks must
151-
be loaded manually. This can be achieved by loading them in the Rakefile and
152-
providing an ``:environment`` task to load your application's models:
181+
be loaded manually:
153182

154183
.. code-block:: ruby
155184

@@ -158,10 +187,6 @@ providing an ``:environment`` task to load your application's models:
158187
require 'mongoid'
159188
load 'mongoid/tasks/database.rake'
160189

161-
task :environment do
162-
# Require/load your application's models here
163-
end
164-
165190
If your application uses Bundler, you can require ``bundler/setup`` instead of
166191
explicitly requiring ``mongoid``:
167192

@@ -171,7 +196,3 @@ explicitly requiring ``mongoid``:
171196

172197
require 'bundler/setup'
173198
load 'mongoid/tasks/database.rake'
174-
175-
task :environment do
176-
# Require/load your application's models here
177-
end

lib/mongoid.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
require "mongoid/deprecable"
2222
require "mongoid/config"
2323
require "mongoid/persistence_context"
24+
require "mongoid/loadable"
2425
require "mongoid/loggable"
2526
require "mongoid/clients"
2627
require "mongoid/document"
@@ -41,6 +42,7 @@
4142
module Mongoid
4243
extend Forwardable
4344
extend Loggable
45+
extend Loadable
4446
extend self
4547
extend Clients::Sessions::ClassMethods
4648

lib/mongoid/config/defaults.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,4 @@ def load_defaults(version)
5959
end
6060
end
6161
end
62-
end
62+
end

lib/mongoid/loadable.rb

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# frozen_string_literal: true
2+
3+
module Mongoid
4+
5+
# Defines how Mongoid can autoload all defined models.
6+
module Loadable
7+
# The default list of paths where model classes should be looked for. If
8+
# Rails is present, the "app/models" paths will be used instead.
9+
# (See #model_paths.)
10+
DEFAULT_MODEL_PATHS = %w( ./app/models ./lib/models ).freeze
11+
12+
# Search a list of model paths to get every model and require it, so
13+
# that indexing and inheritance work in both development and production
14+
# with the same results.
15+
#
16+
# @example Load all the application models from default model paths.
17+
# Mongoid.load_models
18+
#
19+
# @example Load all application models from a non-standard set of paths.
20+
# Mongoid.load_models(%w( ./models ./admin/models ))
21+
#
22+
# @param [ Array ] paths The list of paths that should be looked in
23+
# for model files. These must either be absolute paths, or relative to
24+
# the current working directory.
25+
def load_models(paths = model_paths)
26+
paths.each do |path|
27+
if preload_models.resizable?
28+
files = preload_models.map { |model| "#{path}/#{model.underscore}.rb" }
29+
else
30+
files = Dir.glob("#{path}/**/*.rb")
31+
end
32+
33+
files.sort.each do |file|
34+
load_model(file.gsub(/^#{path}\// , "").gsub(/\.rb$/, ""))
35+
end
36+
end
37+
end
38+
39+
# A convenience method for loading a model's file. If Rails'
40+
# `require_dependency` method exists, it will be used; otherwise
41+
# `require` will be used.
42+
#
43+
# @example Load the model.
44+
# Mongoid.load_model("/mongoid/behavior")
45+
#
46+
# @param [ String ] file The base filename.
47+
#
48+
# @api private
49+
def load_model(file)
50+
if defined?(require_dependency)
51+
require_dependency(file)
52+
else
53+
require(file)
54+
end
55+
end
56+
57+
# Returns the array of paths where the application's model definitions
58+
# are located. If Rails is loaded, this defaults to the configured
59+
# "app/models" paths (e.g. `config.paths["app/models"]`); otherwise, it
60+
# defaults to `%w(./app/models ./lib/models)`.
61+
#
62+
# Note that these paths are the *roots* of the directory hierarchies where
63+
# the models are located; it is not necessary to indicate every subdirectory,
64+
# as long as these root paths are located in `$LOAD_PATH`.
65+
#
66+
# @return [ Array<String> ] the array of model paths
67+
def model_paths
68+
@model_paths ||= defined?(Rails) ?
69+
Rails.application.config.paths["app/models"].expanded :
70+
DEFAULT_MODEL_PATHS
71+
end
72+
73+
# Sets the model paths to the given array of paths. These are the paths
74+
# where the application's model definitions are located.
75+
#
76+
# @param [ Array<String> ] paths The list of model paths
77+
def model_paths=(paths)
78+
@model_paths = paths
79+
end
80+
end
81+
82+
end

lib/mongoid/tasks/database.rake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace :db do
44
namespace :mongoid do
55
task :load_models do
6+
::Mongoid.load_models
67
end
78

89
desc "Create collections for Mongoid models"

lib/rails/mongoid.rb

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,7 @@ module Mongoid
1313
#
1414
# @param [ Application ] app The rails application.
1515
def load_models(app)
16-
app.config.paths["app/models"].expanded.each do |path|
17-
preload = ::Mongoid.preload_models
18-
if preload.resizable?
19-
files = preload.map { |model| "#{path}/#{model.underscore}.rb" }
20-
else
21-
files = Dir.glob("#{path}/**/*.rb")
22-
end
23-
24-
files.sort.each do |file|
25-
load_model(file.gsub("#{path}/" , "").gsub(".rb", ""))
26-
end
27-
end
16+
::Mongoid.load_models(app.config.paths["app/models"].expanded)
2817
end
2918

3019
# Conditionally calls `Rails::Mongoid.load_models(app)` if the
@@ -34,22 +23,5 @@ def load_models(app)
3423
def preload_models(app)
3524
load_models(app) if ::Mongoid.preload_models
3625
end
37-
38-
private
39-
40-
# I don't want to mock out kernel for unit testing purposes, so added this
41-
# method as a convenience.
42-
#
43-
# @example Load the model.
44-
# Mongoid.load_model("/mongoid/behavior")
45-
#
46-
# @param [ String ] file The base filename.
47-
def load_model(file)
48-
begin
49-
require_dependency(file)
50-
rescue Exception => e
51-
Logger.new(STDERR).warn(e.message)
52-
end
53-
end
5426
end
5527
end

spec/integration/app_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ def build_mongodb_uri(parts)
247247
def write_mongoid_yml
248248
# HACK: the driver does not provide a MongoDB URI parser and assembler,
249249
# and the Ruby standard library URI module doesn't handle multiple hosts.
250-
parts = parse_mongodb_uri(SpecConfig.instance.uri_str)
250+
parts = parse_mongodb_uri(SpecConfig.instance.safe_uri)
251251
parts[:database] = 'mongoid_test'
252252
uri = build_mongodb_uri(parts)
253253
p uri

spec/mongoid/config/environment_spec.rb

Lines changed: 12 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
11
# frozen_string_literal: true
22

33
require "spec_helper"
4+
require "support/feature_sandbox"
45

56
describe Mongoid::Config::Environment do
67

78
around do |example|
8-
if defined?(Rails)
9-
SavedRails = Rails
9+
FeatureSandbox.quarantine do
1010
example.run
11-
Object.send(:remove_const, :Rails) if defined?(Rails)
12-
Rails = SavedRails
13-
Object.send(:remove_const, :SavedRails)
14-
else
15-
example.run
16-
if defined?(Rails)
17-
Object.send(:remove_const, :Rails)
18-
end
1911
end
2012
end
2113

@@ -26,11 +18,8 @@
2618
context "when an environment exists" do
2719

2820
before do
29-
module Rails
30-
class << self
31-
def env; "production"; end
32-
end
33-
end
21+
require "support/rails_mock"
22+
Rails.env = "production"
3423
end
3524

3625
it "returns the rails environment" do
@@ -41,20 +30,7 @@ def env; "production"; end
4130

4231
context "when using sinatra" do
4332

44-
before do
45-
Object.send(:remove_const, :Rails) if defined?(Rails)
46-
47-
module Sinatra
48-
module Base
49-
extend self
50-
def environment; :staging; end
51-
end
52-
end
53-
end
54-
55-
after do
56-
Object.send(:remove_const, :Sinatra)
57-
end
33+
before { require "support/sinatra_mock" }
5834

5935
it "returns the sinatra environment" do
6036
expect(described_class.env_name).to eq("staging")
@@ -63,14 +39,9 @@ def environment; :staging; end
6339

6440
context "when the rack env variable is defined" do
6541

66-
before do
67-
Object.send(:remove_const, :Rails) if defined?(Rails)
68-
ENV["RACK_ENV"] = "acceptance"
69-
end
42+
before { ENV["RACK_ENV"] = "acceptance" }
7043

71-
after do
72-
ENV["RACK_ENV"] = nil
73-
end
44+
after { ENV["RACK_ENV"] = nil }
7445

7546
it "returns the rack environment" do
7647
expect(described_class.env_name).to eq("acceptance")
@@ -79,10 +50,6 @@ def environment; :staging; end
7950

8051
context "when no environment information is found" do
8152

82-
before do
83-
Object.send(:remove_const, :Rails) if defined?(Rails)
84-
end
85-
8653
it "raises an error" do
8754
expect { described_class.env_name }.to raise_error(
8855
Mongoid::Errors::NoEnvironment
@@ -94,7 +61,11 @@ def environment; :staging; end
9461
describe "#load_yaml" do
9562
let(:path) { 'mongoid.yml' }
9663
let(:environment) {}
97-
before { allow(Rails).to receive('env').and_return('test') }
64+
65+
before do
66+
require "support/rails_mock"
67+
Rails.env = "test"
68+
end
9869

9970
subject { described_class.load_yaml(path, environment) }
10071

0 commit comments

Comments
 (0)