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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3c5fc774da8b9ad81904fc662c1845ce3a54569183d019ac896aa67a3359a60e
4
+ data.tar.gz: f0e5879e0fea0e817ccc269d74050e6bbef2f60cfe75c7d58d3c6767d6af35c3
5
+ SHA512:
6
+ metadata.gz: ac54e057125932ef589776d1e18f08cd221725c5660ce75207f8250c76bcf3f18f7e8b88ceec5483c60b2e7479ae4b5e069e8a01f5edbe36b5bc3af143793e3f
7
+ data.tar.gz: c68a7050639334ea019bd952784af540d03e57aa9600095b4b4273f2d6eff072250cb03e62fcef6d64f201739ea7eaaea6494f7fa2638fbebd88929fd9ea1dc4
checksums.yaml.gz.sig ADDED
Binary file
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2023-Present MongoDB Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # Railsmdb for Mongoid
2
+
3
+ Railsmdb is a command-line utility for creating, updating, managing,
4
+ and maintaining Rails applications that use Mongoid and MongoDB for data storage. It is an extension of (and supports all other functionality of) the `rails` command from Ruby on Rails.
5
+
6
+
7
+ ## Installation
8
+
9
+ To install Railsmdb:
10
+
11
+ ```
12
+ $ gem install railsmdb
13
+ ```
14
+
15
+ This will install a new command, `railsmdb`.
16
+
17
+
18
+ ## Usage
19
+
20
+ The `railsmdb` command may be invoked exactly as you would invoke the `rails` command. For example, to create a new Rails app:
21
+
22
+ ```
23
+ $ railsmdb new my_new_rails_app
24
+ ```
25
+
26
+ This will create a new folder under the current directory called `my_new_rails_app`, and will populate it with all the scaffolding necessary to begin building your app.
27
+
28
+ Unlike the `rails` command, however, it will set up the necessary gems and configuration for you to begin your Rails app using the MongoDB database, with Mongoid as the Object-Document Mapper (ODM).
29
+
30
+ Also, in your new application, there will be a new script in the `bin` folder: `bin/railsmdb`. You'll see `bin/rails` in there as well, but it now links to `bin/railsmdb`.
31
+
32
+ By default, `railsmdb` will not include ActiveRecord in your new application. If you wish to use both Mongoid and ActiveRecord (to connect to MongoDB and a separate, relational database in the same application), you can pass `--no-skip-active-record`:
33
+
34
+ ```
35
+ $ railsmdb new my_new_rails_app --no-skip-active-record
36
+ ```
37
+
38
+ This will set up your application to use both Mongoid, and sqlite3 (by default). To start with a different relational database instead, you can pass the `--database` option:
39
+
40
+ ```
41
+ $ railsmdb new my_new_rails_app --no-skip-active-record --database=mysql
42
+ ```
43
+
44
+ To see a list of all available commands, simply type `railsmdb` without any arguments.
45
+
46
+ ```
47
+ $ railsmdb
48
+
49
+ # alternatively:
50
+ $ railsmdb -h
51
+ ```
52
+
53
+
54
+ ### Setting up railsmdb and Mongoid in an established Rails app
55
+
56
+ If you want to add `railsmdb` to an existing (non-Mongoid) Rails app, and add Mongoid configuration as well, you can use `railsmdb setup`:
57
+
58
+ ```
59
+ $ railsmdb setup
60
+ ```
61
+
62
+ This must be run from the root directory of a Rails project. It will replace `bin/rails` with `bin/railsmdb`, add the `mongoid.yml` configuration file and the `mongoid.rb` initializer, and add the necessary gem entries to the `Gemfile`.
63
+
64
+ **Note:** it is recommended to run this command in a branch, so that you can easily experiment with the changes and roll them back if necessary.
65
+
66
+
67
+ ### Generating Mongoid models
68
+
69
+ You can use `railsmdb` to generate stubs for new Mongoid models. From within a project:
70
+
71
+ ```
72
+ $ bin/railsmdb generate model person
73
+ ```
74
+
75
+ This will create a new model at `app/models/person.rb`:
76
+
77
+ ```ruby
78
+ class Person
79
+ include Mongoid::Document
80
+ include Mongoid::Timestamp
81
+ end
82
+ ```
83
+
84
+ You can specify the fields of the model as well:
85
+
86
+ ```ruby
87
+ # bin/railsmdb generate model person name:string birth:date
88
+
89
+ class Person
90
+ include Mongoid::Document
91
+ include Mongoid::Timestamp
92
+ field :name, type: String
93
+ field :birth, type: Date
94
+ end
95
+ ```
96
+
97
+ You can instruct the generator to make the new model a subclass of another, by passing the `--parent` option:
98
+
99
+ ```ruby
100
+ # bin/railsmdb generate model student --parent=person
101
+
102
+ class Student < Person
103
+ include Mongoid::Timestamp
104
+ end
105
+ ```
106
+
107
+ And if you need to store your models in a different collection than can be inferred from the model name, you can specify `--collection`:
108
+
109
+ ```ruby
110
+ # bin/railsmdb generate model course --collection=classes
111
+
112
+ class Course
113
+ include Mongoid::Document
114
+ include Mongoid::Timestamp
115
+ store_in collection: 'classes'
116
+ end
117
+ ```
data/Rakefile ADDED
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
4
+
5
+ require 'bundler'
6
+ require 'bundler/gem_tasks'
7
+ require 'rubygems/package'
8
+ require 'rubygems/security'
9
+ require 'rspec/core/rake_task'
10
+
11
+ require_relative './lib/railsmdb/version'
12
+
13
+ def signed_gem?(path_to_gem)
14
+ Gem::Package.new(path_to_gem, Gem::Security::HighSecurity).verify
15
+ true
16
+ rescue Gem::Security::Exception
17
+ false
18
+ end
19
+
20
+ RSpec::Core::RakeTask.new(:spec) do |t|
21
+ t.rspec_opts = %w[ -I lib -I spec/support --format documentation ]
22
+ end
23
+
24
+ task default: %i[ spec ]
25
+
26
+ Rake::Task['release'].clear
27
+
28
+ desc 'Release railsmdb gem'
29
+ task release: %w[ release:require_private_key clobber build release:verify release:tag release:publish ]
30
+
31
+ namespace :release do
32
+ desc 'Requires the private key to be present'
33
+ task :require_private_key do
34
+ raise 'No private key present, cannot release' unless File.exist?('gem-private_key.pem')
35
+ end
36
+
37
+ desc 'Verifies that all built gems in pkg/ are valid'
38
+ task :verify do
39
+ gems = Dir['pkg/*.gem']
40
+ if gems.empty?
41
+ puts 'There are no gems in pkg/ to verify'
42
+ else
43
+ gems.each do |gem|
44
+ if signed_gem?(gem)
45
+ puts "#{gem} is signed"
46
+ else
47
+ abort "#{gem} is not signed"
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ desc 'Creates a new tag for the current version'
54
+ task :tag do
55
+ system "git tag -a v#{Railsmdb::Version::STRING} -m 'Tagging release: #{Railsmdb::Version::STRING}'"
56
+ system "git push upstream v#{Railsmdb::Version::STRING}"
57
+ end
58
+
59
+ desc 'Publishes the most recently built gem'
60
+ task :publish do
61
+ system "gem push pkg/railsmdb-#{Railsmdb::Version::STRING}.gem"
62
+ end
63
+ end
64
+
65
+ # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/app_loader'
4
+ require 'railsmdb/ext/rails/command'
5
+ require 'railsmdb/ext/rails/generators/rails/app/app_generator'
6
+
7
+ # the EXECUTABLES constant might eventually be frozen, so we should do
8
+ # this the long, difficult way...
9
+ Rails::AppLoader.send :remove_const, :EXECUTABLES
10
+ Rails::AppLoader::EXECUTABLES = %w[ bin/railsmdb ].freeze
11
+
12
+ if ARGV.first == 'setup'
13
+ Rails::Command.invoke :setup, ARGV
14
+ else
15
+ require 'rails/cli'
16
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongoid'
4
+ require 'rails/command/base'
5
+ require 'rails/command/environment_argument'
6
+
7
+ module Mongoid
8
+ module Command
9
+ # The implementation of the `dbconsole` command for Railsmdb.
10
+ class DbconsoleCommand < Rails::Command::Base
11
+ include Rails::Command::EnvironmentArgument
12
+
13
+ desc 'dbconsole', 'Start a console for MongoDB using the info in config/mongoid.yml'
14
+ def perform
15
+ require_application_and_environment!
16
+ exec_mongosh_with(Rails.env || 'default')
17
+ end
18
+
19
+ private
20
+
21
+ # Invokes mongosh using the config/mongoid.yml configuration for the
22
+ # current Rails environment. If no such configuration exists, it tries
23
+ # to fall back to the `default` configuration.
24
+ #
25
+ # @param [ String ] environment the named configuration to use.
26
+ def exec_mongosh_with(environment)
27
+ puts "Launching mongosh with #{environment} configuration."
28
+ config = find_configuration_for(environment)
29
+
30
+ command = ENV['MONGOSH_CMD'] || 'mongosh'
31
+
32
+ uri = config[:uri] || "mongodb://#{config[:hosts].first}/#{config[:database]}"
33
+
34
+ exec(command, uri)
35
+ rescue Errno::ENOENT
36
+ abort "mongosh is not installed, or is not in your PATH. Please see\n" \
37
+ "https://www.mongodb.com/docs/mongodb-shell for instructions on\n" \
38
+ 'downloading and installing mongosh.'
39
+ end
40
+
41
+ # Looks for a Mongoid client configuration with the given name. If no
42
+ # such configuration exists, tries to load the `default` configuration.
43
+ # If that can't be found, it will abort executation.
44
+ #
45
+ # @param [ String ] environment the name of the configuration to load.
46
+ #
47
+ # @return [ Hash ] the named configuration
48
+ def find_configuration_for(environment)
49
+ config = Mongoid.clients[environment]
50
+ return config if config
51
+
52
+ warn "There is no #{environment} configuration defined in config/mongoid.yml."
53
+
54
+ config = Mongoid.clients[:default]
55
+ unless config
56
+ abort "There is no default configuration to fall back to.\n" \
57
+ 'Please define a client in config/mongoid.yml.'
58
+ end
59
+
60
+ warn 'Using default configuration instead.'
61
+ config
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/command/base'
4
+ require 'railsmdb/generators/setup/setup_generator'
5
+
6
+ module Mongoid
7
+ module Command
8
+ # The implementation of the `setup` command for Railsmdb.
9
+ class SetupCommand < Rails::Command::Base
10
+ desc 'setup', 'Install railsmdb into an existing Rails app'
11
+ def perform(*args)
12
+ # remove the first argument, which will be `setup`, and pass
13
+ # the rest through to the generator
14
+ args.shift
15
+
16
+ Railsmdb::Generators::SetupGenerator.start(args)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'railsmdb/ext/rails/command'
4
+ require 'railsmdb/ext/rails/generators'
5
+ require 'rails/commands'
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'railsmdb/crypt_shared/listing'
4
+ require 'os'
5
+
6
+ module Railsmdb
7
+ module CryptShared
8
+ # A utility method for querying a catalog listing.
9
+ #
10
+ # @api private
11
+ class Catalog
12
+ class << self
13
+ # Return a Catalog instance representing the current listing.
14
+ #
15
+ # @return [ Catalog ] the current listing.
16
+ def current
17
+ new(Listing.fetch)
18
+ end
19
+ end
20
+
21
+ # @return [ Hash ] the current listing
22
+ attr_reader :listing
23
+
24
+ # Create a new Catalog instance from the giving listing.
25
+ #
26
+ # @param [ Hash ] listing the data to query
27
+ def initialize(listing)
28
+ @listing = listing
29
+ end
30
+
31
+ # Queries the listing data using the given criteria, yielding the
32
+ # corresponding download data.
33
+ #
34
+ # @example Finding all production releases for M1 macs
35
+ # catalog.downloads(
36
+ # production_release: true,
37
+ # downloads: { arch: 'arm64', target: 'macos' }
38
+ # ) do |download|
39
+ # puts download['crypt_shared']['url']
40
+ # end
41
+ #
42
+ # @param [ Hash ] criteria the criteria hash
43
+ #
44
+ # @yield [ Hash ] each download record matching the criteria
45
+ def downloads(criteria = {})
46
+ download_criteria = criteria.delete(:downloads) || {}
47
+
48
+ listing['versions'].each do |version|
49
+ next unless hash_matches?(version, criteria)
50
+
51
+ version['downloads'].each do |download|
52
+ next unless hash_matches?(download, download_criteria)
53
+
54
+ yield download
55
+ end
56
+ end
57
+
58
+ self
59
+ end
60
+
61
+ # Queries the listing for all downloads that match the platform
62
+ # criteria for the current host.
63
+ #
64
+ # @param [ String ] which the download entry to return
65
+ #
66
+ # @return [ Array<String, String> ] a tuple of (url, sha256) for the
67
+ # requested download.
68
+ def optimal_download_url_for_this_host(which = 'crypt_shared')
69
+ downloads(production_release: true, downloads: download_criteria) do |dl|
70
+ return [ dl[which]['url'], dl[which]['sha256'] ]
71
+ end
72
+
73
+ nil
74
+ end
75
+
76
+ # Returns the download criteria (arch/target/edition) for the current
77
+ # host.
78
+ #
79
+ # @return [ Hash ] the download criteria
80
+ def download_criteria
81
+ {
82
+ arch: platform_arch,
83
+ target: platform_target,
84
+ edition: 'enterprise'
85
+ }
86
+ end
87
+
88
+ private
89
+
90
+ # @return [ String ] the host CPU specification.
91
+ def platform_arch
92
+ OS.host_cpu
93
+ end
94
+
95
+ # @return [ String ] the normalized host operating system
96
+ def platform_target
97
+ if OS.windows? || OS::Underlying.windows?
98
+ 'windows'
99
+ elsif OS.mac?
100
+ 'macos'
101
+ elsif OS.linux?
102
+ # this will almost certainly need tweaking
103
+ release = OS.parse_os_release
104
+ id = release[:ID]
105
+ version = release[:VERSION_ID].gsub(/[^\d]/, '')
106
+ "#{id}#{version}"
107
+ else
108
+ warn 'cannot install the crypt_shared library for this platform'
109
+ end
110
+ end
111
+
112
+ # Asks if the given hash satisfies all the given criteria.
113
+ #
114
+ # @param [ Hash ] hash the hash to query
115
+ # @param [ Hash ] criteria the criteria to use for the query
116
+ #
117
+ # @return [ true | false ] whether the hash meets the criteria or not.
118
+ def hash_matches?(hash, criteria)
119
+ criteria.all? { |key, value| hash[key.to_s] == value }
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'open-uri'
5
+ require 'tmpdir'
6
+
7
+ module Railsmdb
8
+ module CryptShared
9
+ # A utility class for fetching the JSON list of current MongoDB
10
+ # database offerings.
11
+ #
12
+ # @api private
13
+ class Listing
14
+ # Convenience method for fetching and returning the current listing.
15
+ #
16
+ # @return [ Hash ] the parsed JSON contents of the listing
17
+ def self.fetch
18
+ new.listing
19
+ end
20
+
21
+ # Downloads and parses the listing, returning the result.
22
+ #
23
+ # @return [ Hash ] the parsed JSON contents of the listing
24
+ def listing
25
+ @listing ||= fetch_listing_json
26
+ end
27
+
28
+ private
29
+
30
+ # the URI of the current.json catalog
31
+ CURRENT_URI = 'https://downloads.mongodb.org/current.json'
32
+
33
+ # where the JSON file should be cached
34
+ CURRENT_CACHE = File.join(Dir.tmpdir, '.current.json')
35
+
36
+ # how old the cache may be before it must be fetched again
37
+ CACHE_CUTOFF = 24 * 60 * 60 # seconds in 24 hours
38
+
39
+ # Fetches and parses the current catalog file, first checking the
40
+ # cache, and then if necessary fetching from the remote server.
41
+ #
42
+ # @return [ Hash ] the parsed JSON catalog file
43
+ def fetch_listing_json
44
+ fetch_listing_json_from_cache ||
45
+ fetch_listing_json_from_uri
46
+ end
47
+
48
+ # Looks at the cache for the requested catalog file. If it doesn't
49
+ # exist, or if it is too old, this returns nil.
50
+ #
51
+ # @return [ Hash | nil ] the parsed JSON catalog file, or nil if
52
+ # it needs to be fetched from the server
53
+ def fetch_listing_json_from_cache
54
+ return nil unless File.exist?(CURRENT_CACHE)
55
+ return nil unless File.mtime(CURRENT_CACHE) >= Time.now - CACHE_CUTOFF
56
+
57
+ JSON.load_file(CURRENT_CACHE)
58
+ end
59
+
60
+ # Fetches the requested catalog file from the server. This will
61
+ # save the fetched file to the cache.
62
+ #
63
+ # @return [ Hash ] the parsed JSON catalog file
64
+ def fetch_listing_json_from_uri
65
+ uri = URI.parse(CURRENT_URI)
66
+ File.write(CURRENT_CACHE, uri.read)
67
+ fetch_listing_json_from_cache
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+
5
+ module Railsmdb
6
+ # A utility class for downloading a file from a given URL.
7
+ class Downloader
8
+ # @return [ String ] the url to download from
9
+ attr_reader :url
10
+
11
+ # @return [ String ] where the file should be saved to.
12
+ attr_reader :destination
13
+
14
+ # A helper method for fetching the file in a single call.
15
+ #
16
+ # @param [ String ] url the url to fetch from
17
+ # @param [ String ] destination the location to write to
18
+ # @param [ Proc ] callback a callback block that is invoked with the
19
+ # current total number of bytes read, as each chunk is read from the
20
+ # stream.
21
+ def self.fetch(url, destination, &callback)
22
+ new(url, destination).fetch(&callback)
23
+ end
24
+
25
+ # Create a new Downloader object.
26
+ #
27
+ # @param [ String ] url the url to fetch from
28
+ # @param [ String ] destination the location to write to
29
+ def initialize(url, destination)
30
+ @url = url
31
+ @destination = destination
32
+ end
33
+
34
+ # Perform the fetch, pulling from the url and writing to the destination.
35
+ def fetch
36
+ File.open(destination, 'w:BINARY') do |io|
37
+ connection.get(url) do |req|
38
+ req.options.on_data = lambda do |chunk, total, _env|
39
+ yield total if block_given?
40
+ io << chunk
41
+ end
42
+ end
43
+ end
44
+
45
+ true
46
+ end
47
+
48
+ private
49
+
50
+ # The underlying HTTP connection used to query the file.
51
+ def connection
52
+ @connection ||= Faraday.new(url: url, ssl: { verify: false, verify_hostname: false })
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/command/behavior'
4
+
5
+ module Railsmdb
6
+ # Extensions to the Rails::Command::Behavior module
7
+ module RailsCommandBehaviorExtension
8
+ module ClassMethods # :nodoc:
9
+ def self.included(mod)
10
+ mod.alias_method :rails_lookup, :lookup
11
+ mod.alias_method :lookup, :railsmdb_lookup
12
+ end
13
+
14
+ private
15
+
16
+ # Railsmdb's version of `Command#lookup`, which makes
17
+ # sure any `rails:` namespace is preempted by a corresponding
18
+ # `railsmdb:` namespace.
19
+ #
20
+ # @param [ Array<String> ] namespaces the list of namespaces
21
+ # to look at
22
+ def railsmdb_lookup(namespaces)
23
+ rails_lookup(preempt_rails_namespace(namespaces))
24
+ end
25
+
26
+ # If a "rails:" namespace exists in the list,
27
+ # insert a new namespace before it with "rails:"
28
+ # replaced with "mongoid:"
29
+ #
30
+ # @param [ Array<String> ] namespaces the list of namespaces
31
+ # to consider
32
+ #
33
+ # @return [ Array<String> ] the (possibly modified) list of
34
+ # namespaces.
35
+ def preempt_rails_namespace(namespaces)
36
+ new_namespaces = []
37
+
38
+ namespaces.each do |ns|
39
+ if ns.match?(/\brails:/)
40
+ new_ns = ns.sub(/\brails:/, 'mongoid:')
41
+ new_namespaces.push(new_ns)
42
+ end
43
+
44
+ new_namespaces.push(ns)
45
+ end
46
+
47
+ # we need to replace the namespaces list in-place, so that the caller
48
+ # gets the updated namespaces.
49
+ namespaces.replace(new_namespaces)
50
+ end
51
+ end
52
+
53
+ ::Rails::Command::Behavior::ClassMethods.include ClassMethods
54
+ end
55
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/command'
4
+ require 'railsmdb/ext/rails/command/behavior'
5
+
6
+ module Rails
7
+ module Command # :nodoc:
8
+ class << self
9
+ private
10
+
11
+ # Prepends the railsmdb commands lookup path to the existing rails
12
+ # lookup paths.
13
+ def railsmdb_lookup_paths
14
+ @railsmdb_lookup_paths ||= [ 'railsmdb/commands', *rails_lookup_paths ]
15
+ end
16
+
17
+ alias rails_lookup_paths lookup_paths
18
+ alias lookup_paths railsmdb_lookup_paths
19
+ end
20
+ end
21
+ end