railsmdb 1.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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