rom-mongodb 0.1.0

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.
data/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # MongoDB adapter for Ruby Object Mapper
2
+
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/5b38ebba392bd37f166b/maintainability)](https://codeclimate.com/github/bestwebua/rom-mongo/maintainability)
4
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/5b38ebba392bd37f166b/test_coverage)](https://codeclimate.com/github/bestwebua/rom-mongo/test_coverage)
5
+ [![CircleCI](https://circleci.com/gh/bestwebua/rom-mongo/tree/master.svg?style=svg)](https://circleci.com/gh/bestwebua/rom-mongo/tree/master)
6
+ [![Gem Version](https://badge.fury.io/rb/rom-mongodb.svg)](https://badge.fury.io/rb/rom-mongodb)
7
+ [![Downloads](https://img.shields.io/gem/dt/rom-mongodb.svg?colorA=004d99&colorB=0073e6)](https://rubygems.org/gems/rom-mongodb)
8
+ [![GitHub](https://img.shields.io/github/license/bestwebua/rom-mongo)](LICENSE.txt)
9
+ [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v1.4%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md)
10
+
11
+ `rom-mongodb` - MongoDB adapter for [ROM](https://rom-rb.org). What is ROM? It's a fast ruby persistence library with the goal of providing powerful object mapping capabilities without limiting the full power of the underlying datastore.
12
+
13
+ ## Table of Contents
14
+
15
+ - [Requirements](#requirements)
16
+ - [Installation](#installation)
17
+ - [Usage](#usage)
18
+ - [Contributing](#contributing)
19
+ - [License](#license)
20
+ - [Code of Conduct](#code-of-conduct)
21
+ - [Credits](#credits)
22
+ - [Versioning](#versioning)
23
+ - [Changelog](CHANGELOG.md)
24
+
25
+ ## Requirements
26
+
27
+ Ruby MRI 2.5.0+
28
+
29
+ ## Installation
30
+
31
+ Add this line to your application's `Gemfile`:
32
+
33
+ ```ruby
34
+ gem 'rom-mongodb'
35
+ ```
36
+
37
+ And then execute:
38
+
39
+ $ bundle
40
+
41
+ Or install it yourself as:
42
+
43
+ $ gem install rom-mongodb
44
+
45
+ ## Usage
46
+
47
+ ```ruby
48
+ # Define your container with mongo adapter
49
+
50
+ require 'mongo'
51
+ require 'rom'
52
+ require 'rom/mongodb'
53
+
54
+ connection = Mongo::Client.new('mongodb://127.0.0.1:27017/your_db_name')
55
+ container = ROM.container(:mongo, connection) do |config|
56
+ config.relation(:users) do
57
+ schema(:users) do
58
+ attribute :_id, ROM::Types.Nominal(BSON::ObjectId)
59
+ attribute :email, ROM::Types::String
60
+ attribute :rating, ROM::Types::Integer
61
+ attribute :status, ROM::Types::Bool
62
+ end
63
+ end
64
+ end
65
+
66
+ # Define your repository
67
+
68
+ require 'rom/repository'
69
+
70
+ User = ::Class.new(ROM::Repository[:users]) do
71
+ commands(:create, :delete, update: :by_pk)
72
+
73
+ def all
74
+ users.to_a
75
+ end
76
+
77
+ def find(**options)
78
+ users.find(options)
79
+ end
80
+ end
81
+
82
+ user_repository = User.new(container)
83
+
84
+ # Now you can do some manipulations with your repository
85
+
86
+ user_repository.create({ email: 'olo@domain.com', rating: 42, status: true })
87
+ user_repository.all
88
+ user_repository.find(email: 'olo@domain.com')
89
+ ```
90
+
91
+ ## Contributing
92
+
93
+ Bug reports and pull requests are welcome on GitHub at https://github.com/bestwebua/rom-mongo. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. Please check the [open tickets](https://github.com/bestwebua/rom-mongo/issues). Be sure to follow Contributor Code of Conduct below and our [Contributing Guidelines](CONTRIBUTING.md).
94
+
95
+ ## License
96
+
97
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
98
+
99
+ ## Code of Conduct
100
+
101
+ Everyone interacting in the rom-mongodb project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
102
+
103
+ ## Credits
104
+
105
+ - [The Contributors](https://github.com/bestwebua/rom-mongo/graphs/contributors) for code and awesome suggestions
106
+ - [The Stargazers](https://github.com/bestwebua/rom-mongo/stargazers) for showing their support
107
+
108
+ ## Versioning
109
+
110
+ rom-mongodb uses [Semantic Versioning 2.0.0](https://semver.org)
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'rom/mongo'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Mongo
5
+ module Commands
6
+ # Create command
7
+ #
8
+ # Inserts a new tuple into a relation
9
+ #
10
+ # @abstract
11
+ class Create < ROM::Commands::Create
12
+ include ROM::Mongo::Commands::Helper
13
+
14
+ adapter :mongo
15
+
16
+ # TODO: always returns :one, even more then one documents have created
17
+ # Research how to display :many needed
18
+
19
+ # Passes tuple to relation for insertion
20
+ #
21
+ # @param attributes [Hash]
22
+ #
23
+ # @return [Array<Hash>]
24
+ #
25
+ # @api public
26
+ def execute(*attributes)
27
+ ids = dataset.insert(*attributes)
28
+ return [] if ids.empty?
29
+
30
+ attributes.each_with_object([]).with_index do |(attribute, arr), index|
31
+ arr << { _id: ids[index], **attribute }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Mongo
5
+ module Commands
6
+ # Delete command
7
+ #
8
+ # Removes tuple from its target relation
9
+ #
10
+ # @abstract
11
+ class Delete < ROM::Commands::Delete
12
+ include ROM::Mongo::Commands::Helper
13
+
14
+ adapter :mongo
15
+
16
+ # Passes tuple to relation for deletion
17
+ #
18
+ # @param attributes [Hash]
19
+ #
20
+ # @return [Array<Hash>]
21
+ #
22
+ # @api public
23
+ def execute(attributes)
24
+ filtered_documents = dataset.find(pk.merge(attributes), projection)
25
+ document_snapshot = process_with_schema(filtered_documents.first)
26
+ return [document_snapshot] if filtered_documents.delete_one.deleted_count.eql?(1)
27
+
28
+ []
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Mongo
5
+ module Commands
6
+ module Helper
7
+ private
8
+
9
+ # @api private
10
+ def dataset
11
+ relation.dataset
12
+ end
13
+
14
+ # @api private
15
+ def pk
16
+ return {} unless dataset.respond_to?(:filter)
17
+
18
+ dataset.filter.transform_keys(&:to_sym)
19
+ end
20
+
21
+ # @api private
22
+ def process_with_schema(attributes)
23
+ schema.to_output_hash.call(attributes)
24
+ end
25
+
26
+ # @api private
27
+ def schema
28
+ relation.schema
29
+ end
30
+
31
+ # @api private
32
+ def schema_attributes
33
+ schema
34
+ .attributes
35
+ .map(&:name)
36
+ .each_with_object({}) { |item, hash| hash[item] = true }
37
+ end
38
+
39
+ # @api private
40
+ def projection
41
+ { projection: schema_attributes }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Mongo
5
+ module Commands
6
+ # Update command
7
+ #
8
+ # Updates tuple in its relation with new attributes
9
+ #
10
+ # @abstract
11
+ class Update < ROM::Commands::Update
12
+ include ROM::Mongo::Commands::Helper
13
+
14
+ adapter :mongo
15
+
16
+ # Passes tuple to relation for updation
17
+ #
18
+ # @param attributes [Hash]
19
+ #
20
+ # @return [Array<Hash>]
21
+ #
22
+ # @api public
23
+ def execute(attributes)
24
+ document = process_with_schema(pk.merge(attributes))
25
+ return [document] if dataset.collection.update_one(pk, attributes).modified_count.eql?(1)
26
+
27
+ []
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/core'
4
+
5
+ module ROM
6
+ module Mongo
7
+ module Commands
8
+ require_relative '../mongo/commands/helper'
9
+ require_relative '../mongo/commands/create'
10
+ require_relative '../mongo/commands/update'
11
+ require_relative '../mongo/commands/delete'
12
+ end
13
+
14
+ require_relative '../mongo/version'
15
+ require_relative '../mongo/dataset'
16
+ require_relative '../mongo/gateway'
17
+ require_relative '../mongo/schema'
18
+ require_relative '../mongo/relation'
19
+ end
20
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Mongo
5
+ # Class interfacing with the MongoDB Ruby driver
6
+ # Provides a DSL for constructing queries
7
+ class Dataset
8
+ # The collection object
9
+ #
10
+ # @return [Mongo::Collection] The collection object
11
+ #
12
+ # @api public
13
+ attr_reader :collection
14
+
15
+ # Initializes the Dataset object
16
+ #
17
+ # @example Create a Dataset object.
18
+ # ROM::Mongo::Dataset.new(collection)
19
+ #
20
+ # @param [Mongo::Collection] collection The collection to run the operation on
21
+ #
22
+ # @api public
23
+ def initialize(collection)
24
+ @collection = collection
25
+ end
26
+
27
+ # Constructs a collection view with a given selector
28
+ #
29
+ # @example Define ascending order by the name
30
+ # dataset_instance.find(
31
+ # { company: 'Some Company' },
32
+ # { projection: { _id: true, name: true, email: true, company: true } }
33
+ # )
34
+ #
35
+ # @param [Hash] options The query criteria
36
+ # @param [Hash] projection The projection criteria
37
+ #
38
+ # @return [Mongo::Collection::View] The collection view object
39
+ #
40
+ # @api public
41
+ def find(options = {}, projection = {})
42
+ view(options, projection)
43
+ end
44
+
45
+ # Difines order for collection view
46
+ #
47
+ # @example Define ascending order by the name
48
+ # dataset_instance.sort(name: 1)
49
+ #
50
+ # @param [Hash] attributes The order criteria.
51
+ #
52
+ # @return [Mongo::Collection::View] The collection view object
53
+ #
54
+ # @api public
55
+ def sort(attributes)
56
+ view.sort(attributes)
57
+ end
58
+
59
+ # Difines limit for collection view
60
+ #
61
+ # @example Define ascending order by the name
62
+ # dataset_instance.limit(42)
63
+ #
64
+ # @param [Integer] count The limit criteria.
65
+ #
66
+ # @return [Mongo::Collection::View] The collection view object
67
+ #
68
+ # @api public
69
+ def limit(count)
70
+ view.limit(count)
71
+ end
72
+
73
+ # Difines skip for collection view
74
+ #
75
+ # @example Define ascending order by the name
76
+ # dataset_instance.skip(2)
77
+ #
78
+ # @param [Integer] count The limit criteria
79
+ #
80
+ # @return [Mongo::Collection::View] The collection view object
81
+ #
82
+ # @api public
83
+ def skip(count)
84
+ view.skip(count)
85
+ end
86
+
87
+ # Difines interface to insert one or more new documents
88
+ #
89
+ # @example Inserting one document
90
+ # dataset_instance.insert({ name: 'Some Name', email: 'olo@molo.com' })
91
+ #
92
+ # @example Inserting more than one document
93
+ # dataset_instance.insert(
94
+ # { name: 'One', email: 'olo@domain.com' },
95
+ # { name: 'Two', email: 'molo@domain.com' }
96
+ # )
97
+ #
98
+ # @param [Hash] attributes The attributes for new document
99
+ #
100
+ # @return [Array<String>] The array with inserted ids
101
+ #
102
+ # @api public
103
+ def insert(*attributes)
104
+ method_params =
105
+ attributes.one? ? [:insert_one, *attributes] : [:insert_many, attributes]
106
+ collection.public_send(*method_params).inserted_ids
107
+ end
108
+
109
+ # Difines count for collection view
110
+ #
111
+ # @example Define ascending order by the name
112
+ # dataset_instance.count
113
+ #
114
+ # @return [Integer] The total count of documents
115
+ #
116
+ # @api public
117
+ def count
118
+ view.count
119
+ end
120
+
121
+ # Difines distinct for collection view
122
+ #
123
+ # @example Define ascending order by the name
124
+ # dataset_instance.distinct(:some_attribute)
125
+ #
126
+ # @param [Symbol] attribute The document field name
127
+ #
128
+ # @return [Array<String>] The collection of uniq values by field name
129
+ #
130
+ # @api public
131
+ def distinct(attribute)
132
+ view.distinct(attribute)
133
+ end
134
+
135
+ # Difines view without options
136
+ #
137
+ # @return [Mongo::Collection::View] The collection view object
138
+ #
139
+ # @api public
140
+ def to_a
141
+ view
142
+ end
143
+
144
+ # @api private
145
+ def map(&block)
146
+ to_a.map(&block)
147
+ end
148
+
149
+ private
150
+
151
+ # Applies given options to the view
152
+ #
153
+ # @return [Mongo::Collection::View] The collection view object
154
+ #
155
+ # @api private
156
+ def view(options = {}, projection = {})
157
+ collection.find(options, projection)
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Mongo
5
+ # Wrapping the mongo collections to dataset abstractions
6
+ # and passing them to the relations
7
+ #
8
+ # @abstract
9
+ class Gateway < ROM::Gateway
10
+ adapter :mongo
11
+
12
+ # Builded datasets
13
+ #
14
+ # @return [Hash] The hash with datasets
15
+ #
16
+ # @api public
17
+ attr_reader :datasets
18
+
19
+ # Initializes the Gateway object
20
+ #
21
+ # @example Create a Gateway object
22
+ # ROM::Mongo::Gateway.new(client)
23
+ #
24
+ # @param [Mongo::Client] client The mongo client instance
25
+ #
26
+ # @api public
27
+ def initialize(client)
28
+ @client = client
29
+ @datasets = {}
30
+ end
31
+
32
+ # Buildes dataset with the given name
33
+ #
34
+ # @param [Symbol] name dataset name
35
+ #
36
+ # @param [Class] dataset_class dataset class
37
+ #
38
+ # @return [ROM::Mongo::Dataset]
39
+ #
40
+ # @api public
41
+ def dataset(name, dataset_class = ROM::Mongo::Dataset)
42
+ datasets[name] = dataset_class.new(client[name])
43
+ end
44
+
45
+ # Checkes if dataset exists
46
+ #
47
+ # @param [Symbol] name dataset name
48
+ #
49
+ # @return [true, false]
50
+ #
51
+ # @api public
52
+ def dataset?(name)
53
+ datasets.key?(name)
54
+ end
55
+
56
+ # Retrieves dataset with the given name
57
+ #
58
+ # @param [Symbol] name dataset name
59
+ #
60
+ # @return [ROM::Mongo::Dataset, nil]
61
+ #
62
+ # @api public
63
+ def [](name)
64
+ datasets[name]
65
+ end
66
+
67
+ private
68
+
69
+ # @api private
70
+ attr_reader :client
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Mongo
5
+ # Mongo specific relation extensions
6
+ class Relation < ROM::Relation
7
+ adapter :mongo
8
+ schema_class ROM::Mongo::Schema
9
+ option :output_schema, default: -> { schema.to_output_hash }
10
+ forward :find, :sort, :limit, :skip
11
+
12
+ # @api private
13
+ def self.view_methods
14
+ super + [:by_pk]
15
+ end
16
+
17
+ # Returns relation restricted by _id
18
+ #
19
+ # @param id [BSON::ObjectId] Document's PK value
20
+ #
21
+ # @return [ROM::Mongo::Relation]
22
+ #
23
+ # @api public
24
+ auto_curry def by_pk(id)
25
+ find(_id: id)
26
+ end
27
+
28
+ # Single purpose aggregation operations
29
+
30
+ # Difines count for dataset
31
+ #
32
+ # @return [Integer] The total count of documents
33
+ #
34
+ # @api public
35
+ def count
36
+ dataset.count
37
+ end
38
+
39
+ # Difines distinct for dataset
40
+ #
41
+ # @param [Symbol] attribute The document field name
42
+ #
43
+ # @return [Array<String>] The collection of uniq values by field name
44
+ #
45
+ # @api public
46
+ def distinct(attribute)
47
+ dataset.distinct(attribute)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Mongo
5
+ class Schema < ROM::Schema
6
+ # :nocov:
7
+ def to_output_hash
8
+ Types::Hash
9
+ .schema(to_h { |attr| [attr.key, attr.to_read_type] })
10
+ .with_key_transform(&:to_sym)
11
+ end
12
+ # :nocov:
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Mongo
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
data/lib/rom/mongo.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'mongo/core'
4
+
5
+ ROM.register_adapter(:mongo, ROM::Mongo)
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/rom/mongo/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'rom-mongodb'
7
+ spec.version = ROM::Mongo::VERSION
8
+ spec.authors = ['Vladislav Trotsenko']
9
+ spec.email = ['admin@bestweb.com.ua']
10
+
11
+ spec.summary = %(rom-mongodb)
12
+ spec.description = 'MongoDB adapter for Ruby Object Mapper'
13
+
14
+ spec.homepage = 'https://github.com/bestwebua/rom-mongo'
15
+ spec.license = 'MIT'
16
+
17
+ spec.metadata = {
18
+ 'homepage_uri' => 'https://rom-rb.org',
19
+ 'changelog_uri' => 'https://github.com/bestwebua/rom-mongo/blob/master/CHANGELOG.md',
20
+ 'source_code_uri' => 'https://github.com/bestwebua/rom-mongo',
21
+ 'documentation_uri' => 'https://github.com/bestwebua/rom-mongo/blob/master/README.md',
22
+ 'bug_tracker_uri' => 'https://github.com/bestwebua/rom-mongo/issues'
23
+ }
24
+
25
+ spec.required_ruby_version = '>= 2.5.0'
26
+
27
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
+ spec.bindir = 'exe'
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| ::File.basename(f) }
30
+ spec.require_paths = ['lib']
31
+
32
+ spec.add_runtime_dependency 'mongo', '~> 2.17', '>= 2.17.1'
33
+ spec.add_runtime_dependency 'rom-core', '~> 5.2', '>= 5.2.6'
34
+
35
+ spec.add_development_dependency 'ffaker', '~> 2.21'
36
+ spec.add_development_dependency 'rake', '~> 13.0', '>= 13.0.6'
37
+ spec.add_development_dependency 'rom-repository', '~> 5.2', '>= 5.2.2'
38
+ spec.add_development_dependency 'rspec', '~> 3.11'
39
+ end