railsmdb 1.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,545 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'railsmdb/prioritizable'
4
+ require 'active_support/encrypted_configuration'
5
+ require 'os'
6
+ require 'railsmdb/version'
7
+ require 'railsmdb/crypt_shared/catalog'
8
+ require 'railsmdb/downloader'
9
+ require 'railsmdb/extractor'
10
+ require 'rails/generators/rails/app/app_generator'
11
+ require 'digest'
12
+ require 'mongoid'
13
+
14
+ module Railsmdb
15
+ module Generators
16
+ module Setup
17
+ module Concerns
18
+ # Tasks used for configuring a Rails app to use Mongoid, including
19
+ # adding support for encryption. This concern is shared between
20
+ # the app generator (e.g. `railsmdb new`) and the setup generator
21
+ # (e.g. `railsmdb setup`).
22
+ #
23
+ # @api private
24
+ module Setuppable
25
+ extend ActiveSupport::Concern
26
+
27
+ include Railsmdb::Prioritizable
28
+
29
+ GemfileEntry = Rails::Generators::AppBase::GemfileEntry
30
+
31
+ KEY_VAULT_CONFIG = <<~CONFIG
32
+ # This client is used to obtain the encryption keys from the key vault.
33
+ # For security reasons, this should be a different database instance than
34
+ # your primary application database.
35
+ key_vault:
36
+ uri: mongodb://localhost:27017
37
+
38
+ CONFIG
39
+
40
+ AUTO_ENCRYPTION_CONFIG = <<~CONFIG.freeze
41
+ # You can read about the auto encryption options here:
42
+ # https://www.mongodb.com/docs/ruby-driver/v#{Mongo::VERSION.split('.').first(2).join('.')}/reference/in-use-encryption/client-side-encryption/#auto-encryption-options
43
+ auto_encryption_options:
44
+ key_vault_client: 'key_vault'
45
+ key_vault_namespace: 'encryption.__keyVault'
46
+ kms_providers:
47
+ # Using a local master key is insecure and is not recommended if you plan
48
+ # to use client-side encryption in production.
49
+ #
50
+ # To learn how to set up a remote Key Management Service, see the tutorials
51
+ # at https://www.mongodb.com/docs/manual/core/csfle/tutorials/.
52
+ local:
53
+ key: '<%= Rails.application.credentials.mongodb_master_key %>'
54
+ extra_options:
55
+ crypt_shared_lib_path: %crypt-shared-path%
56
+
57
+ CONFIG
58
+
59
+ PRELOAD_MODELS_OPTION = <<~CONFIG
60
+ #
61
+ # Setting it to true is recommended for auto encryption to work
62
+ # properly in development.
63
+ preload_models: true
64
+ CONFIG
65
+
66
+ included do
67
+ # add the setup generator templates folder to the source path
68
+ source_paths.unshift File.join(__dir__, '..', 'templates')
69
+
70
+ # An option for enabling MongoDB encryption features in the new app.
71
+ class_option :encryption, type: :boolean,
72
+ aliases: '-E',
73
+ default: false,
74
+ desc: 'Add gems and configuration to enable MongoDB encryption features'
75
+
76
+ # Add an option for accepting the customer agreement related to
77
+ # MongoDB enterprise, allowing the acceptance prompt to be skipped.
78
+ class_option :accept_customer_agreement, type: :boolean,
79
+ default: false,
80
+ desc: 'Accept the MongoDB Customer Agreement'
81
+ end
82
+
83
+ # Save the current directory; this way, we can see
84
+ # if it is being run from the railsmdb project directory, in
85
+ # development, and set up the railsmdb gem dependency appropriately.
86
+ def save_initial_path
87
+ @initial_path = Dir.pwd
88
+ end
89
+
90
+ # Checks to see if the user agrees to the encryption terms and conditions
91
+ def confirm_legal_shenanigans
92
+ return unless options[:encryption]
93
+
94
+ @okay_to_support_encryption =
95
+ options[:accept_customer_agreement] ||
96
+ okay_with_legal_shenanigans?
97
+ end
98
+
99
+ # Fetches the MongoDB crypt_shared library and stores it in
100
+ # vendor/crypt_shared.
101
+ def fetch_crypt_shared
102
+ return unless @okay_to_support_encryption
103
+
104
+ log :fetch, 'current MongoDB catalog'
105
+ catalog = Railsmdb::CryptShared::Catalog.current
106
+ url, sha = catalog.optimal_download_url_for_this_host
107
+
108
+ if url
109
+ fetch_and_extract_crypt_shared_from_url(url, sha)
110
+ else
111
+ say_error 'Cannot find download URL for crypt_shared, for this host'
112
+ end
113
+ end
114
+
115
+ # Appends the mongoid gem entries to the Gemfile.
116
+ def add_mongoid_gem_entries
117
+ mongoid_gem_entries.each do |group, list|
118
+ append_to_file 'Gemfile' do
119
+ prefix = group ? "\ngroup :#{group} do\n" : "\n"
120
+ suffix = group ? "\nend\n" : "\n"
121
+ indent_size = group ? 2 : 0
122
+
123
+ prefix +
124
+ list.map { |entry| indent_entry(entry, indent_size) }
125
+ .join("\n\n") +
126
+ suffix
127
+ end
128
+ end
129
+ end
130
+
131
+ # Emit the mongoid.yml file to the new application folder. The
132
+ # mongoid.yml file is taken directly from the installed mongoid
133
+ # gem.
134
+ def mongoid_yml
135
+ file = Gem.find_files('rails/generators/mongoid/config/templates/mongoid.yml').first
136
+ database_name = app_name
137
+ template file, 'config/mongoid.yml', context: binding
138
+ end
139
+
140
+ # Appends a new local master key to the credentials file
141
+ def add_mongodb_local_master_key_to_credentials
142
+ return unless @okay_to_support_encryption
143
+
144
+ say_status :append, CREDENTIALS_FILE_PATH
145
+
146
+ credentials_file.change do |tmp_path|
147
+ File.open(tmp_path, 'a') do |io|
148
+ io.puts
149
+ io.puts '# Master key for MongoDB auto encryption'
150
+ # passing `96 / 2` because we need a 96-byte key, but
151
+ # SecureRandom.hex returns a hex-encoded string, which will
152
+ # be two bytes for requested byte.
153
+ io.puts "mongodb_master_key: '#{SecureRandom.hex(96 / 2)}'"
154
+ end
155
+ end
156
+ end
157
+
158
+ # If encryption is enabled, update the mongoid.yml with the necessary
159
+ # options for encryption.
160
+ def add_encryption_options_to_mongoid_yml
161
+ return unless @okay_to_support_encryption
162
+
163
+ mongoid_yml = File.join(Dir.pwd, 'config/mongoid.yml')
164
+ contents = File.read(mongoid_yml)
165
+
166
+ contents = insert_key_vault_config(contents)
167
+ contents = insert_auto_encryption_options(contents)
168
+ contents = insert_preload_models_option(contents)
169
+
170
+ say_status :update, 'config/mongoid.yml'
171
+ File.write(mongoid_yml, contents)
172
+ end
173
+
174
+ # Emit the mongoid.rb initializer. Unlike mongoid.yml, this is not
175
+ # taken from the mongoid gem, because mongoid versions prior to 9
176
+ # did not include an initializer template.
177
+ def mongoid_initializer
178
+ template '_config/initializers/mongoid.rb', 'config/initializers/mongoid.rb'
179
+ end
180
+
181
+ # Emit the bin/railsmdb script to the new app's bin folder. The
182
+ # existing bin/rails script is removed, and replaced by a link to
183
+ # bin/railsmdb.
184
+ def railsmdb
185
+ template '_bin/railsmdb', 'bin/railsmdb' do |content|
186
+ "#{shebang}\n" + content
187
+ end
188
+
189
+ chmod 'bin/railsmdb', 0o755, verbose: false
190
+
191
+ remove_file 'bin/rails', verbose: false
192
+ create_link 'bin/rails', File.expand_path('bin/railsmdb', destination_root), verbose: false
193
+ end
194
+
195
+ private
196
+
197
+ # Returns the Railsmdb project directory if run from inside a
198
+ # checkout of the railsmdb repository. Otherwise, returns nil.
199
+ #
200
+ # @return [ String | nil ] the railsmdb project directory, or nil
201
+ # if not run within a railsmdb checkout.
202
+ def railsmdb_project_directory
203
+ return @railsmdb_project_directory if defined?(@railsmdb_project_directory)
204
+
205
+ @railsmdb_project_directory ||= begin
206
+ path = @initial_path
207
+
208
+ while path != '/'
209
+ break if File.exist?(File.join(path, 'railsmdb.gemspec'))
210
+
211
+ path = File.dirname(path)
212
+ end
213
+
214
+ (path == '/') ? nil : path
215
+ end
216
+ end
217
+
218
+ # Adds indentation to the string representation of the given
219
+ # gem entry.
220
+ #
221
+ # @param [ Rails::Generators::AppBase::GemfileEntry ] entry The
222
+ # GemfileEntry instance to format.
223
+ # @param [ Integer ] indent_size the number of spaces to prepend
224
+ # to each line of the entry's string representation.
225
+ #
226
+ # @return [ String ] the string representation of the given entry
227
+ # with each line indented by the given number of spaces.
228
+ def indent_entry(entry, indent_size)
229
+ if indent_size < 1
230
+ entry.to_s
231
+ else
232
+ indent = ' ' * indent_size
233
+ entry.to_s.gsub(/^/, indent)
234
+ end
235
+ end
236
+
237
+ # The gem entries to be appended to the Gemfile, sorted by gem
238
+ # group.
239
+ #
240
+ # @return [ Hash ] a hash where the keys are the gem groups, and
241
+ # the values are lists of gem entries corresponding to those
242
+ # groups.
243
+ def mongoid_gem_entries
244
+ {
245
+ nil => [
246
+ mongoid_gem_entry,
247
+ railsmdb_gem_entry
248
+ ].tap { |list| maybe_add_encryption_gems(list) }
249
+ }
250
+ end
251
+
252
+ # The gem entry for the Mongoid gem. The version is set to whichever
253
+ # version is installed and active.
254
+ #
255
+ # @return [ Rails::Generators::AppBase::GemfileEntry ] the gem
256
+ # entry for Mongoid.
257
+ def mongoid_gem_entry
258
+ if @okay_to_support_encryption && ::Mongoid::VERSION < '9.0'
259
+ # FIXME: once Mongoid 9.0 is released, update this so that it
260
+ # uses that released version.
261
+ GemfileEntry.github \
262
+ 'mongoid',
263
+ 'mongodb/mongoid',
264
+ 'master',
265
+ 'Encryption requires an unreleased version of Mongoid'
266
+ else
267
+ GemfileEntry.version \
268
+ 'mongoid',
269
+ ::Mongoid::VERSION,
270
+ 'Use MongoDB for the database, with Mongoid as the ODM'
271
+ end
272
+ end
273
+
274
+ # The gem entry for the Railsmdb gem. If run from a railsmdb
275
+ # checkout, the gem will reference the path to that checkout.
276
+ # Otherwise, the version is set to whichever
277
+ # version is installed and active.
278
+ #
279
+ # @return [ Rails::Generators::AppBase::GemfileEntry ] the gem
280
+ # entry for Railsmdb.
281
+ def railsmdb_gem_entry
282
+ if railsmdb_project_directory.present?
283
+ GemfileEntry.path \
284
+ 'railsmdb',
285
+ railsmdb_project_directory,
286
+ 'The development version of railsmdb'
287
+ else
288
+ GemfileEntry.version \
289
+ 'railsmdb',
290
+ Railsmdb::Version::STRING,
291
+ 'The Rails CLI tool for MongoDB'
292
+ end
293
+ end
294
+
295
+ # Build the gem entry for the libmongocrypt-helper gem.
296
+ #
297
+ # @return [ Rails::Generators::AppBase::GemfileEntry ] the gem
298
+ # entry for libmongocrypt-helper gem.
299
+ def libmongocrypt_helper_gem_entry
300
+ GemfileEntry.version \
301
+ 'libmongocrypt-helper', '~> 1.8',
302
+ 'Encryption helper for MongoDB-based applications'
303
+ end
304
+
305
+ # Build the gem entry for the ffi gem.
306
+ #
307
+ # @return [ Rails::Generators::AppBase::GemfileEntry ] the gem
308
+ # entry for ffi gem.
309
+ def ffi_gem_entry
310
+ GemfileEntry.version \
311
+ 'ffi', nil,
312
+ 'Mongoid needs the ffi gem when encryption is enabled'
313
+ end
314
+
315
+ # If encryption is enabled, adds the necessary gems to the given list
316
+ # of gem entries, to prepare them to be added to the gemfile.
317
+ #
318
+ # @param [ Array<GemfileEntry> ] list The list of gemfile entries.
319
+ def maybe_add_encryption_gems(list)
320
+ return unless @okay_to_support_encryption
321
+
322
+ list.push libmongocrypt_helper_gem_entry
323
+ list.push ffi_gem_entry
324
+ end
325
+
326
+ # The location of the directory where the crypt_shared library will
327
+ # be saved to, relative to the app root.
328
+ CRYPT_SHARED_DIR = 'vendor/crypt_shared'
329
+
330
+ # Download and extract the crypt_shared library from the given url,
331
+ # and install it in vendor/crypt_shared.
332
+ #
333
+ # @param [ String ] url the url to the crypt_shared library archive
334
+ # @param [ String ] sha the sha hash for the file
335
+ def fetch_and_extract_crypt_shared_from_url(url, sha)
336
+ archive = fetch_crypt_shared_from_cache(url, sha) ||
337
+ fetch_crypt_shared_from_url(url, sha)
338
+
339
+ return unless archive
340
+
341
+ log :directory, CRYPT_SHARED_DIR
342
+ FileUtils.mkdir_p CRYPT_SHARED_DIR
343
+
344
+ extracted = extract_crypt_shared_from_file(archive)
345
+
346
+ log :error, 'No crypt_shared library could be found in the downloaded archive' unless extracted
347
+ end
348
+
349
+ # Computes the path to the cache for the file at the given URL.
350
+ #
351
+ # @param [ String ] url the url to consider
352
+ #
353
+ # @return [ String ] the path to the file's location on disk.
354
+ def cached_file_for(url)
355
+ uri = URI.parse(url)
356
+ File.join(Dir.tmpdir, File.basename(uri.path))
357
+ end
358
+
359
+ # Look in the cache location for a file downloaded from the given
360
+ # url. If it exists, make sure the SHA hash matches.
361
+ #
362
+ # @param [ String ] url the url to fetch the file from
363
+ # @param [ String ] sha the sha256 hash for the file
364
+ #
365
+ # @return [ String | nil ] the path to the file, if it exists, or nil
366
+ def fetch_crypt_shared_from_cache(url, sha)
367
+ path = cached_file_for(url)
368
+
369
+ return path if File.exist?(path) && Digest::SHA256.file(path).to_s == sha
370
+
371
+ nil
372
+ end
373
+
374
+ # Download the crypt_shared library archive from the given url and
375
+ # store it in the current directory.
376
+ #
377
+ # @param [ String ] url the url to fetch the file from
378
+ # @param [ String ] sha the sha256 hash for the file
379
+ #
380
+ # @return [ String ] the filename that the archive was saved to
381
+ def fetch_crypt_shared_from_url(url, sha)
382
+ log :fetch, url
383
+
384
+ cached_file_for(url).tap do |archive|
385
+ Railsmdb::Downloader.fetch(url, archive) { print '.' }
386
+ puts
387
+
388
+ unless File.exist?(archive) && Digest::SHA256.file(archive).to_s == sha
389
+ log :error, 'an uncorrupted crypt-shared library could not be downloaded'
390
+ return nil
391
+ end
392
+ end
393
+ end
394
+
395
+ # Extracts the crypt_shared library from the given archive file, and
396
+ # writes it to the CRYPT_SHARED_DIR.
397
+ #
398
+ # @param [ String ] archive the path to the archive file
399
+ #
400
+ # @return [ String | nil ] the name of the extracted file if successful,
401
+ # or nil if no file could be extracted.
402
+ def extract_crypt_shared_from_file(archive)
403
+ extractor = Railsmdb::Extractor.for(archive)
404
+ extractor.extract(%r{/mongo_crypt_v1\.(so|dylib|dll)}) do |name, data|
405
+ file = File.join(CRYPT_SHARED_DIR, File.basename(name))
406
+
407
+ log :create, file
408
+ File.open(file, 'w:BINARY') { |io| io.write(data) }
409
+ end
410
+ end
411
+
412
+ # Ask the user if they agree to the MongoDB Customer Agreement, which is
413
+ # required in order to download the crypt_shared library.
414
+ #
415
+ # @return [ true | false ] whether the user agrees or not
416
+ def okay_with_legal_shenanigans?
417
+ # primarily so we can interact with this programmatically in tests...
418
+ $stdout.sync = true
419
+
420
+ say "You've requested to begin a new Rails app with MongoDB encryption."
421
+ say
422
+
423
+ say "Using MongoDB's encryption features requires MongoDB Enterprise Edition,"
424
+ say 'which is for MongoDB customers only. Are you a MongoDB Atlas customer, or'
425
+ say 'are you currently a MongoDB Enterprise Advanced subscriber?'
426
+ say
427
+
428
+ case ask('"[yes], I am a MongoDB customer", or "[no], I am not" =>', limited_to: %w[ yes no ])
429
+ when 'yes'
430
+ say
431
+ say '* Use of these features constitutes acceptance of the Customer Agreement.'
432
+ say
433
+
434
+ true
435
+ when 'no'
436
+ say
437
+ say '* Encryption will not be enabled for your application.'
438
+ say
439
+
440
+ false
441
+ end
442
+ end
443
+
444
+ # Attempts to insert the key-vault configuration into the given
445
+ # string, which must be the contents of the generated mongoid.yml file.
446
+ #
447
+ # @param [String] contents the contents of mongoid.yml
448
+ #
449
+ # @return [ String ] the updated contents
450
+ def insert_key_vault_config(contents)
451
+ position = (contents =~ /^\s*# Defines the default client/)
452
+ unless position
453
+ say_error 'Default mongoid.yml format has changed; cannot update it with key-vault settings'
454
+ return contents
455
+ end
456
+
457
+ indent_size = contents[position..][/^\s*/].length
458
+ contents[position, 0] = KEY_VAULT_CONFIG.indent(indent_size).gsub(/%app%/, app_name)
459
+
460
+ contents
461
+ end
462
+
463
+ # Returns the path to the downloaded crypt-shared library.
464
+ #
465
+ # @return [ String ] path to the crypt_shared library.
466
+ def crypt_shared_path
467
+ ext = if OS.windows? || OS::Underlying.windows?
468
+ 'dll'
469
+ elsif OS.mac?
470
+ 'dylib'
471
+ else
472
+ 'so'
473
+ end
474
+
475
+ # excuse the final '# >'' at the end of the next line...this string
476
+ # confuses vscode's syntax highlighter, and that final comment is
477
+ # to shake it back to its senses...
478
+ %{"<%= Rails.root.join('vendor', 'crypt_shared', 'mongo_crypt_v1.#{ext}') %>"} # >
479
+ end
480
+
481
+ # Attempts to insert the auto-encryption configuration into the given
482
+ # string, which must be the contents of the generated mongoid.yml file.
483
+ #
484
+ # @param [String] contents the contents of mongoid.yml
485
+ #
486
+ # @return [ String ] the updated contents
487
+ def insert_auto_encryption_options(contents)
488
+ position = (contents =~ /\sdefault:.*?\s+options:\n/m)
489
+
490
+ unless position
491
+ say_error 'Default mongoid.yml format has changed; cannot update it with auto-encryption settings'
492
+ return contents
493
+ end
494
+
495
+ position += Regexp.last_match(0).length
496
+
497
+ indent_size = contents[position..][/^\s*/].length
498
+ contents[position, 0] = AUTO_ENCRYPTION_CONFIG
499
+ .indent(indent_size)
500
+ .gsub(/%crypt-shared-path%/, crypt_shared_path)
501
+
502
+ contents
503
+ end
504
+
505
+ # Attempts to enable the preload_models option in the given
506
+ # string, which must be the contents of the generated mongoid.yml file.
507
+ #
508
+ # @param [String] contents the contents of mongoid.yml
509
+ #
510
+ # @return [ String ] the updated contents
511
+ def insert_preload_models_option(contents)
512
+ position = (contents =~ /^\s+# preload_models: .*?\n/)
513
+
514
+ unless position
515
+ say_error 'Default mongoid.yml format has changed; cannot enable preload_models'
516
+ return contents
517
+ end
518
+
519
+ length = Regexp.last_match(0).length
520
+
521
+ indent_size = contents[position..][/^\s*/].length
522
+ contents[position, length] = PRELOAD_MODELS_OPTION.indent(indent_size)
523
+
524
+ contents
525
+ end
526
+
527
+ CREDENTIALS_FILE_PATH = 'config/credentials.yml.enc'
528
+
529
+ # Return the encrypted credentials file.
530
+ #
531
+ # @return [ ActiveSupport::EncryptedConfiguration ] the encrypted
532
+ # credentials file.
533
+ def credentials_file
534
+ ActiveSupport::EncryptedConfiguration.new(
535
+ config_path: CREDENTIALS_FILE_PATH,
536
+ key_path: 'config/master.key',
537
+ env_key: 'RAILS_MASTER_KEY',
538
+ raise_if_missing_key: true
539
+ )
540
+ end
541
+ end
542
+ end
543
+ end
544
+ end
545
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/base'
4
+ require 'railsmdb/generators/setup/concerns/setuppable'
5
+
6
+ module Railsmdb
7
+ module Generators
8
+ # The implementation of the setup generator for Railsmdb, for
9
+ # configuring railsmdb to work in an existing Rails application.
10
+ class SetupGenerator < Rails::Generators::Base
11
+ include Railsmdb::Generators::Setup::Concerns::Setuppable
12
+
13
+ NEED_RAILS_APP_WARNING = <<~WARNING
14
+ The `railsmdb setup` command must be run from the root of an
15
+ existing Rails application. It will add railsmdb to the project,
16
+ prepare the application to use Mongoid for data access, and
17
+ replace the `bin/rails` script with `bin/railsmdb`.
18
+
19
+ Please try this command again from the root of an existing Rails
20
+ application.
21
+ WARNING
22
+
23
+ ALREADY_HAS_RAILSMDB = <<~WARNING
24
+ This Rails application is already configured to use railsmdb.
25
+ WARNING
26
+
27
+ WARN_ABOUT_UNDO = <<~WARNING
28
+ It is strongly recommended to invoke this in a separate branch,
29
+ where you can safely test the changes made by the script and
30
+ roll them back if they cause problems.
31
+ WARNING
32
+
33
+ add_shebang_option!
34
+
35
+ # Make sure the current directory is an appropriate place to
36
+ # run this generator. It must:
37
+ # - be the root directory of a Rails project
38
+ # - not already have been set up with railsmdb
39
+ #
40
+ # Additionally, this will encourage the user to run this generator
41
+ # in a branch, in order to safely see what it does to their app.
42
+ def ensure_proper_invocation
43
+ ensure_rails_app!
44
+ ensure_railsmdb_not_already_present!
45
+ warn_about_undo!
46
+ end
47
+
48
+ public_task :save_initial_path
49
+ public_task :confirm_legal_shenanigans
50
+ public_task :fetch_crypt_shared
51
+ public_task :add_mongoid_gem_entries
52
+ public_task :mongoid_yml
53
+ public_task :add_mongodb_local_master_key_to_credentials
54
+ public_task :add_encryption_options_to_mongoid_yml
55
+ public_task :mongoid_initializer
56
+ public_task :railsmdb
57
+
58
+ # Make sure the newly required gems are installed automatically.
59
+ def run_bundle_install
60
+ say_status :run, 'bundle install'
61
+ system 'bundle install'
62
+ end
63
+
64
+ private
65
+
66
+ # Infers the name of the app from the application's config/application.rb
67
+ # file.
68
+ #
69
+ # @return [ String ] the name of the application
70
+ def app_name
71
+ @app_name ||= File.read('config/application.rb').match(/module (\w+)/)[1].underscore
72
+ end
73
+
74
+ # Warns and exits if the current directory is not the root of a
75
+ # Rails project.
76
+ def ensure_rails_app!
77
+ return if rails_app?
78
+
79
+ warn NEED_RAILS_APP_WARNING
80
+ exit 1
81
+ end
82
+
83
+ # Warns and exits if railsmdb is already present in the current
84
+ # Rails project.
85
+ def ensure_railsmdb_not_already_present!
86
+ return unless railsmdb?
87
+
88
+ warn ALREADY_HAS_RAILSMDB
89
+ exit 1
90
+ end
91
+
92
+ # Encourages the user to run this in a branch so that the changes
93
+ # may be easily rolled back.
94
+ #
95
+ # If the user chooses not to proceed, this method will exit the
96
+ # program.
97
+ def warn_about_undo!
98
+ warn WARN_ABOUT_UNDO
99
+ say
100
+
101
+ exit 0 if ask('Do you wish to proceed in the current branch?', limited_to: %w[ yes no ]) == 'no'
102
+ end
103
+
104
+ # Returns true if the current directory appears to be a Rails app.
105
+ #
106
+ # @return [ true | false ] if the current directory is a Rails app
107
+ # or not.
108
+ def rails_app?
109
+ File.exist?('bin/rails') &&
110
+ File.exist?('app') &&
111
+ File.exist?('config')
112
+ end
113
+
114
+ # Returns true if railsmdb appears to already be present in the
115
+ # current Rails app.
116
+ #
117
+ # @return [ true | false ] if railsmdb is already present.
118
+ def railsmdb?
119
+ File.exist?('bin/railsmdb')
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,3 @@
1
+ # Welcome
2
+
3
+ Hey, you're running Rails with Mongoid!
@@ -0,0 +1,3 @@
1
+ APP_PATH = File.expand_path("../config/application", __dir__)
2
+ require_relative "../config/boot"
3
+ require "railsmdb/commands"