lib/moped/session.rb
# encoding: utf-8
require "moped/read_preference"
require "moped/readable"
require "moped/write_concern"
require "moped/collection"
require "moped/cluster"
require "moped/database"
module Moped
# A session in moped is root for all interactions with a MongoDB server or
# replica set.
#
# It can talk to a single default database, or dynamically speak to multiple
# databases.
#
# @example Single database (console-style)
# session = Moped::Session.new(["127.0.0.1:27017"])
# session.use(:moped)
# session[:users].find.one
#
# @example Multiple databases
# session = Moped::Session.new(["127.0.0.1:27017"])
# session.with(database: :admin) do |admin|
# admin.command(ismaster: 1)
# end
#
# @example Authentication
# session = Moped::Session.new %w[127.0.0.1:27017],
# session.with(database: "admin").login("admin", "s3cr3t")
#
# @since 1.0.0
class Session
include Optionable
# @!attribute cluster
# @return [ Cluster ] The cluster of nodes.
# @!attribute options
# @return [ Hash ] The configuration options.
attr_reader :cluster, :options
# Return +collection+ from the current database.
#
# @param (see Moped::Database#[])
#
# @return (see Moped::Database#[])
#
# @since 1.0.0
def [](name)
current_database[name]
end
# Return non system collection name from the current database.
#
# @param (see Moped::Database#collection_names)
#
# @return (see Moped::Database#collection_names)
#
# @since 1.0.0
def collection_names
current_database.collection_names
end
# Return non system collection name from the current database.
#
# @param (see Moped::Database#collections)
#
# @return (see Moped::Database#collections)
#
# @since 1.0.0
def collections
current_database.collections
end
# Run +command+ on the current database.
#
# @param (see Moped::Database#command)
#
# @return (see Moped::Database#command)
#
# @since 1.0.0
def command(op)
current_database.command(op)
end
# Get a list of all the database names for the session.
#
# @example Get all the database names.
# session.database_names
#
# @note This requires admin access on your server.
#
# @return [ Array<String>] All the database names.
#
# @since 1.2.0
def database_names
databases["databases"].map { |database| database["name"] }
end
# Get information on all databases for the session. This includes the name,
# size on disk, and if it is empty or not.
#
# @example Get all the database information.
# session.databases
#
# @note This requires admin access on your server.
#
# @return [ Hash ] The hash of database information, under the "databases"
# key.
#
# @since 1.2.0
def databases
with(database: :admin).command(listDatabases: 1)
end
# Disconnects all nodes in the session's cluster. This should only be used
# in cases # where you know you're not going to use the cluster on the
# thread anymore and need to force the connections to close.
#
# @return [ true ] True if the disconnect succeeded.
#
# @since 1.2.0
def disconnect
cluster.disconnect
end
# Drop the current database.
#
# @param (see Moped::Database#drop)
#
# @return (see Moped::Database#drop)
#
# @since 1.0.0
def drop
current_database.drop
end
# Provide a string inspection for the session.
#
# @example Inspect the session.
# session.inspect
#
# @return [ String ] The string inspection.
#
# @since 1.4.0
def inspect
"<#{self.class.name} seeds=#{cluster.seeds} database=#{current_database_name}>"
end
# Log in with +username+ and +password+ on the current database.
#
# @param (see Moped::Database#login)
#
# @raise (see Moped::Database#login)
#
# @since 1.0.0
def login(username, password)
current_database.login(username, password)
end
# Log out from the current database.
#
# @param (see Moped::Database#logout)
#
# @raise (see Moped::Database#login)
#
# @since 1.0.0
def logout
current_database.logout
end
# Setup validation of allowed write concern options.
#
# @since 2.0.0
option(:write).allow({ w: Optionable.any(Integer) }, { "w" => Optionable.any(Integer) })
option(:write).allow({ w: Optionable.any(String) }, { "w" => Optionable.any(String) })
option(:write).allow({ j: true }, { "j" => true })
option(:write).allow({ j: false }, { "j" => false })
option(:write).allow({ fsync: true }, { "fsync" => true })
option(:write).allow({ fsync: false }, { "fsync" => false })
# Setup validation of allowed read preference options.
#
# @since 2.0.0
option(:read).allow(
:nearest,
:primary,
:primary_preferred,
:secondary,
:secondary_preferred,
"nearest",
"primary",
"primary_preferred",
"secondary",
"secondary_preferred"
)
# Setup validation of allowed database options. (Any string or symbol)
#
# @since 2.0.0
option(:database).allow(Optionable.any(String), Optionable.any(Symbol))
# Setup validation of allowed max retry options. (Any integer)
#
# @since 2.0.0
option(:max_retries).allow(Optionable.any(Integer))
# Setup validation of allowed pool size options. (Any integer)
#
# @since 2.0.0
option(:pool_size).allow(Optionable.any(Integer))
# Setup validation of allowed pool timeout options. (Any numeric)
#
# @since 2.0.0
option(:pool_timeout).allow(Optionable.any(Numeric))
# Setup validation of allowed retry interval options. (Any numeric)
#
# @since 2.0.0
option(:retry_interval).allow(Optionable.any(Numeric))
# Setup validation of allowed refresh interval options. (Any numeric)
#
# @since 2.0.0
option(:refresh_interval).allow(Optionable.any(Numeric))
# Setup validation of allowed down interval options. (Any numeric)
#
# @since 2.0.0
option(:down_interval).allow(Optionable.any(Numeric))
# Setup validation of allowed ssl options. (Any boolean)
#
# @since 2.0.0
option(:ssl).allow(true, false)
# Setup validation of allowed timeout options. (Any numeric)
#
# @since 2.0.0
option(:timeout).allow(Optionable.any(Numeric))
# Pass an object that responds to instrument as an instrumenter.
#
# @since 2.0.0
option(:instrumenter).allow(Optionable.any(Object))
# Setup validation of allowed auto_discover preference options.
#
# @since 1.5.0
option(:auto_discover).allow(true, false)
# Initialize a new database session.
#
# @example Initialize a new session.
# Session.new([ "localhost:27017" ])
#
# @param [ Array ] seeds An array of host:port pairs.
# @param [ Hash ] options The options for the session.
#
# @see Above options validations for allowed values in the options hash.
#
# @since 1.0.0
def initialize(seeds, options = {})
validate_strict(options)
@options = options
@cluster = Cluster.new(seeds, options)
end
# Create a new session with +options+ and use new socket connections.
#
# @example Change safe mode
# session.with(write: { w: 2 })[:people].insert(name: "Joe")
#
# @example Change safe mode with block
# session.with(write: { w: 2 }) do |session|
# session[:people].insert(name: "Joe")
# end
#
# @example Temporarily change database
# session.with(database: "admin") do |admin|
# admin.command ismaster: 1
# end
#
# @example Copy between databases
# session.use "moped"
# session.with(database: "backup") do |backup|
# session[:people].each do |person|
# backup[:people].insert person
# end
# end
#
# @param [ Hash ] options The options.
#
# @return [ Session ] The new session.
#
# @see #with
#
# @since 1.0.0
#
# @yieldparam [ Session ] session The new session.
def new(options = {})
session = with(options)
session.instance_variable_set(:@cluster, cluster.dup)
if block_given?
yield(session)
else
session
end
end
# Get the read preference for the session. Will default to primary if none
# was provided.
#
# @example Get the session's read preference.
# session.read_preference
#
# @return [ Object ] The read preference.
#
# @since 2.0.0
def read_preference
@read_preference ||= ReadPreference.get(options[:read] || :primary)
end
# Switch the session's current database.
#
# @example Switch the current database.
# session.use :moped
# session[:people].find.one # => { :name => "John" }
#
# @param [ String, Symbol ] database The database to use.
#
# @since 1.0.0
def use(database)
options[:database] = database
set_current_database(database)
end
# Create a new session with +options+ reusing existing connections.
#
# @example Change safe mode
# session.with(write: { w: 2 })[:people].insert(name: "Joe")
#
# @example Change safe mode with block
# session.with(write: { w: 2 }) do |session|
# session[:people].insert(name: "Joe")
# end
#
# @example Temporarily change database
# session.with(database: "admin") do |admin|
# admin.command ismaster: 1
# end
#
# @example Copy between databases
# session.use "moped"
# session.with(database: "backup") do |backup|
# session[:people].each do |person|
# backup[:people].insert person
# end
# end
#
# @param [ Hash ] options The session options.
#
# @return [ Session, Object ] The new session, or the value returned
# by the block if provided.
#
# @since 1.0.0
#
# @yieldparam [ Session ] session The new session.
def with(options = {})
session = dup
session.options.update(options)
if block_given?
yield(session)
else
session
end
end
# Get the write concern for the session. Will default to propagate if none
# was provided.
#
# @example Get the session's write concern.
# session.write_concern
#
# @return [ Object ] The write concern.
#
# @since 2.0.0
def write_concern
@write_concern ||= WriteConcern.get(options[:write] || { w: 1 })
end
class << self
# Create a new session from a URI.
#
# @example Initialize a new session.
# Session.connect("mongodb://localhost:27017/my_db")
#
# @param [ String ] MongoDB URI formatted string.
#
# @return [ Session ] The new session.
#
# @since 3.0.0
def connect(uri)
uri = Uri.new(uri)
session = new(*uri.moped_arguments)
session.login(uri.username, uri.password) if uri.auth_provided?
session
end
end
private
# Get the database that the session is currently using.
#
# @api private
#
# @example Get the current database.
# session.current_database
#
# @return [ Database ] The current database or nil.
#
# @since 2.0.0
def current_database
return @current_database if @current_database
if database = options[:database]
set_current_database(database)
else
raise "No database set for session. Call #use or #with before accessing the database"
end
end
def current_database_name
@current_database ? current_database.name : :none
end
def initialize_copy(_)
@options = @options.dup
@read_preference = nil
@write_concern = nil
@current_database = nil
end
def set_current_database(database)
@current_database = Database.new(self, database)
end
end
end