Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use mongo driver directly instead of Origin gem #22

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/rom/mongo.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require 'mongo'
require 'rom/core'

require 'rom/mongo/version'
Expand Down
59 changes: 59 additions & 0 deletions lib/rom/mongo/builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
module ROM
module Mongo

# An auxillary module for converting arguments to objects for the MongoDB
# Ruby driver's API.
#
# @since 0.3.0
module Builder

extend self

# A map from symbol sort directions to their corresponding values in MongoDB's API.
#
# @since 0.3.0
SORT_DIRECTION_MAP = {
asc: ::Mongo::Index::ASCENDING,
desc: ::Mongo::Index::DESCENDING,
}.freeze

# Convert a list of fields into a projection document.
#
# @example Create a projection document from a list of fields.
# Builder.to_projection_doc([:name])
#
# @param [ Array<String, Symbol> ] fields The list of fields.
#
# @return [ Hash ] The projection document.
#
# @since 0.3.0
def to_projection_doc(fields)
fields.inject({}) do |doc, f|
doc.merge!(f => 1)
end
end

# Convert a sort document to comply with MongoDB's API.
#
# @example Convert a sort document.
# Builder.to_sort_doc({ name: :asc })
#
# @ [ Hash ] doc The sort document.
#
# @return [ Hash ] The converted sort document.
#
# @since 0.3.0
def to_sort_doc(doc)
doc.inject({}) do |sort_doc, (key, value)|
if SORT_DIRECTION_MAP.values.include?(value)
sort_doc.merge!(key => value)
elsif direction = SORT_DIRECTION_MAP[value]
sort_doc.merge!(key => direction)
else
sort_doc
end
end
end
end
end
end
151 changes: 125 additions & 26 deletions lib/rom/mongo/dataset.rb
Original file line number Diff line number Diff line change
@@ -1,73 +1,172 @@
require 'origin'
require 'rom/mongo/builder'

module ROM
module Mongo
class Dataset
class Criteria
include Origin::Queryable
end

def initialize(collection, criteria = Criteria.new)
@collection = collection
@criteria = criteria
end
# This is a class interfacing with the MongoDB Ruby driver.
# It provides a DSL for constructing queries and writes.
class Dataset

# The collection object.
#
# @return [ Mongo::Collection ] The collection object.
attr_reader :collection

# The collection view object.
#
# @return [ Mongo::Collection::View ] The collection view object.
attr_reader :criteria

# Initialize the Dataset object.
#
# @example Create a Dataset object.
# Dataset.new(collection, { name: 'Emily' })
#
# @param [ Mongo::Collection ] collection The collection to run the operation on.
# @param [ Mongo::Collection::View ] criteria The collection view object.
#
def initialize(collection, criteria = nil)
@collection = collection
@criteria = criteria || collection.find
end

# Construct a Dataset object with query criteria.
#
# @example Create a Dataset object
# dataset.find({ name: 'Emily' })
#
# @param [ Hash ] criteria The query selector.
#
# @return [ Dataset ] The new database object with the specified query selector.
def find(criteria = {})
Dataset.new(collection, Criteria.new.where(criteria))
Dataset.new(collection, collection.find(criteria))
end

# Perform the query and return the results.
#
# @example Perform the query.
# dataset.to_a
#
# @return [ Array<Hash> ] An array of the result set.
def to_a
view.to_a
end

# @api private
def each
view.each { |doc| yield(doc) }
end

# @api private
def map(&block)
to_a.map(&block)
end

# Insert a single document into the collection.
#
# @example Insert a document
# dataset.insert( { name: 'Emily' })
#
# @param [ Hash ] data The document to insert.
#
# @return [ Mongo::Result ] The result of the insert operation.
def insert(data)
collection.insert_one(data)
end

def update_all(attributes)
view.update_many(attributes)
# Update multiple documents in the collection.
#
# @example Update many documents in the collection.
# dataset.update_all({ :name => "Emily" }, { "$set" => { :name => "Em" } })
#
# @param [ Hash ] update The update document.
#
# @return [ Mongo::Result ] The result of the update operation.
def update_all(update)
view.update_many(update)
end

# Remove all documents in the collection.
#
# @example Remove all documents.
# dataset.remove_all
#
# @return [ Mongo::Result ] The result of the delete operation.
def remove_all
view.delete_many
end

# Construct a Collection View with a given selector.
#
# @example Construct a View with query criteria.
# dataset.where({ name: 'Emily' })
#
# @param [ Hash ] doc The query criteria.
#
# @return [ Dataset ] A dataset object with the given query criteria.
def where(doc)
dataset(criteria.where(doc))
dataset(criteria.find(doc))
end

# Specify that only certain fields should be returned from the query.
#
# @example Define the fields to return from the query.
# dataset.only(:name)
#
# @param [ Hash ] fields The fields to project.
#
# @return [ Dataset ] The dataset object with the project set.
def only(fields)
dataset(criteria.only(fields))
dataset(criteria.projection(Builder.to_projection_doc(fields)))
end

# Define fields to exclude from the result documents.
#
# @example Define the fields to exclude from the result documents.
# dataset.without([:name, :address])
#
# @param [ Array<Symbol> ] fields The fields to exclude.
#
# @return [ Dataset ] The dataset object with certain fields specified to be excluded.
def without(fields)
dataset(criteria.without(fields))
end

# Define a limit on the query.
#
# @example Define a limit.
# dataset.limit(10)
#
# @param [ Integer ] limit The limit value.
#
# @return [ Dataset ] The dataset object with limit set.
def limit(limit)
dataset(criteria.limit(limit))
end

# Define a skip on the query.
#
# @example Define a limit.
# dataset.skip(5)
#
# @param [ Integer ] value The skip value.
#
# @return [ Dataset ] The dataset object with skip set.
def skip(value)
dataset(criteria.skip(value))
end

def order(value)
dataset(criteria.order(value))
# Define a sort on the query.
#
# @example Define a srt.
# dataset.sort(name: 1)
#
# @param [ Hash ] value The sort document.
#
# @return [ Dataset ] The dataset object with the sort set.
def sort(value)
dataset(criteria.sort(Builder.to_sort_doc(value)))
end
alias :order :sort

# @api private
def each
view.each { |doc| yield(doc) }
end

# @api private
def map(&block)
to_a.map(&block)
end

private
Expand Down
1 change: 0 additions & 1 deletion lib/rom/mongo/gateway.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
require 'mongo'
require 'uri'

require 'rom/gateway'
Expand Down
1 change: 0 additions & 1 deletion rom-mongo.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ Gem::Specification.new do |spec|

spec.add_runtime_dependency "rom-core", "~> 4.0"
spec.add_runtime_dependency "mongo", "~> 2.2"
spec.add_runtime_dependency "origin"

spec.add_development_dependency "bundler"
spec.add_development_dependency "rake"
Expand Down
55 changes: 55 additions & 0 deletions spec/unit/builder_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
require 'spec_helper'

RSpec.describe ROM::Mongo::Builder do

describe '#to_projection_doc' do

let(:result) do
described_class.to_projection_doc([:name, :address])
end

it 'converts the fields into a document with 1 values' do
expect(result).to eq({ name: 1, address: 1})
end
end

describe '#to_sort_doc' do

let(:result) do
described_class.to_sort_doc(arg)
end

context 'when symbols are used as the direction' do

let(:arg) do
{name: :asc, address: :desc}
end

it 'converts the fields into a document with 1 values' do
expect(result).to eq({ name: 1, address: -1 })
end
end

context 'when integers are used as the direction' do

let(:arg) do
{name: 1, address: -1}
end

it 'converts the fields into a document with 1 values' do
expect(result).to eq({ name: 1, address: -1 })
end
end

context 'when the direction is invalid' do

let(:arg) do
{name: 1, address: :invalid}
end

it 'ignores the invalid direction' do
expect(result).to eq({ name: 1 })
end
end
end
end