mongery 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +42 -0
- data/Rakefile +2 -0
- data/lib/mongery.rb +177 -0
- data/lib/mongery/version.rb +3 -0
- data/mongery.gemspec +28 -0
- data/spec/mongery/builder_spec.rb +73 -0
- data/spec/spec_helper.rb +13 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8360d8979a97ea0236f28a3aa49bcc35cd9f3861
|
4
|
+
data.tar.gz: 6d63c13b93d91147b1e18956dc7e53ebd9555368
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 394339097c78801844b502505add3cb95881871b51eca88edda565d56333034893d7e092a28e31a286e1f1c5f59b66cea80db55a51fe850df3aa28464e1ffa99
|
7
|
+
data.tar.gz: f9a1ee7321cf2e71f0bc308f0caaa5755016a68ad4be1409b789c5712289331cf36df4df2c097978872def591d91782f4a2b3bfd41e0b8c9e355bed281981e99
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Tatsuhiko Miyagawa
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# Mongery
|
2
|
+
|
3
|
+
Mongery helps you to migrate off of MongoDB object storage to PostgreSQL with JSON column by translating Mongo query into Arel object, ready to use with ActiveRecord connection.
|
4
|
+
|
5
|
+
## Limitation
|
6
|
+
|
7
|
+
MOngery currently supports the limited set of queries to use with PostgreSQL 9.3. Most query on JSON data will end up with the full table scan. Index using hstore columns and/or 9.4 `jsonb` types are planned but not implemented.
|
8
|
+
|
9
|
+
See the spec file in `spec` directory for the supported conversion.
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Create a table with the following structure. The name of the table could be anything.
|
14
|
+
|
15
|
+
```
|
16
|
+
CREATE TABLE objects (
|
17
|
+
id varchar(32) not null primary key,
|
18
|
+
data json not null,
|
19
|
+
updated_at timestamp DEFAULT current_timestamp
|
20
|
+
)
|
21
|
+
```
|
22
|
+
|
23
|
+
Currently Mongery assumes the `id` values are stored as `_id` key duplicated in the json data as well. This can be customized in the future updates.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
builder = Mongery::Builder.new(:objects)
|
27
|
+
|
28
|
+
builder.find({ _id: 'abcd' }).limit(1).to_sql
|
29
|
+
# => SELECT data FROM objects WHERE id = 'abcd' LIMIT 1
|
30
|
+
|
31
|
+
builder.find({ age: {"$gte" => 21 } }).sort({ name: -1 }).to_sql
|
32
|
+
# => SELECT data FROM objects WHERE (data->>'age')::integer >= 21 ORDER BY data->>'name' DESC
|
33
|
+
```
|
34
|
+
|
35
|
+
## License
|
36
|
+
|
37
|
+
MIT
|
38
|
+
|
39
|
+
## Copyright
|
40
|
+
|
41
|
+
Tatsuhiko Miyagawa
|
42
|
+
|
data/Rakefile
ADDED
data/lib/mongery.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
require "mongery/version"
|
2
|
+
require "arel"
|
3
|
+
|
4
|
+
module Mongery
|
5
|
+
# Translate Mongo query to Arel AST
|
6
|
+
|
7
|
+
class Builder
|
8
|
+
attr_reader :model, :table
|
9
|
+
|
10
|
+
def initialize(model, engine = ActiveRecord::Base)
|
11
|
+
@model = model
|
12
|
+
@table = Arel::Table.new(model, engine)
|
13
|
+
end
|
14
|
+
|
15
|
+
def find(args)
|
16
|
+
Query.new(table).where(args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Query
|
21
|
+
attr_reader :table
|
22
|
+
|
23
|
+
def initialize(table)
|
24
|
+
@table = table
|
25
|
+
end
|
26
|
+
|
27
|
+
def where(args)
|
28
|
+
condition = translate(args)
|
29
|
+
arel.where(condition) if condition
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def arel
|
34
|
+
@arel ||= table.project(table[:data])
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_arel
|
38
|
+
arel
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_sql
|
42
|
+
to_arel.to_sql
|
43
|
+
end
|
44
|
+
|
45
|
+
def limit(number)
|
46
|
+
arel.take(number)
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def skip(number)
|
51
|
+
arel.skip(number)
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def sort(params)
|
56
|
+
params.each do |col, val|
|
57
|
+
order = val > 0 ? :asc : :desc
|
58
|
+
case col.to_s
|
59
|
+
when "_id"
|
60
|
+
arel.order(table[:id].send(order))
|
61
|
+
else
|
62
|
+
arel.order(sql_json_path(col).send(order))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def translate(query)
|
69
|
+
chain(:and, query.map { |col, value| translate_cv(col, value) })
|
70
|
+
end
|
71
|
+
|
72
|
+
def translate_cv(col, value)
|
73
|
+
case col.to_s
|
74
|
+
when "_id"
|
75
|
+
translate_value(table[:id], value)
|
76
|
+
when "$or"
|
77
|
+
chain(:or, value.map {|q| translate(q) })
|
78
|
+
when "$and"
|
79
|
+
chain(:and, value.map { |q| translate(q) })
|
80
|
+
when /^\$/
|
81
|
+
raise UnsupportedQuery, "Unsupported operator #{col}"
|
82
|
+
else
|
83
|
+
translate_value_json(sql_json_path(col), value)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def translate_value(col, value, json = false)
|
88
|
+
case value
|
89
|
+
when Hash
|
90
|
+
ops = value.keys
|
91
|
+
if ops.size > 1
|
92
|
+
raise UnsupportedQuery, "Multiple operator supported: #{ops.join(", ")}"
|
93
|
+
end
|
94
|
+
|
95
|
+
val = value[ops.first]
|
96
|
+
case ops.first
|
97
|
+
when "$in"
|
98
|
+
col.in(val)
|
99
|
+
when "$gt", "$gte", "$lt", "$lte"
|
100
|
+
col.send(COMPARE_MAPS[ops.first], val)
|
101
|
+
when "$eq"
|
102
|
+
col.eq(val)
|
103
|
+
when "$ne"
|
104
|
+
col.not_eq(val)
|
105
|
+
when /^\$/
|
106
|
+
raise UnsupportedQuery, "Unknown operator #{ops.first}"
|
107
|
+
end
|
108
|
+
else
|
109
|
+
col.eq(value)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
COMPARE_MAPS = { "$gt" => :gt, "$gte" => :gteq, "$lt" => :lt, "$lte" => :lteq }
|
114
|
+
|
115
|
+
def translate_value_json(col, value, json = false)
|
116
|
+
case value
|
117
|
+
when String, Numeric, TrueClass, FalseClass
|
118
|
+
# in Postgres 9.3, you can't compare numeric
|
119
|
+
col.eq(value.to_s)
|
120
|
+
when NilClass
|
121
|
+
# You can't use IS NULL
|
122
|
+
col.eq('')
|
123
|
+
when Hash
|
124
|
+
ops = value.keys
|
125
|
+
if ops.size > 1
|
126
|
+
raise UnsupportedQuery, "Multiple operator supported: #{ops.join(", ")}"
|
127
|
+
end
|
128
|
+
|
129
|
+
val = value[ops.first]
|
130
|
+
case ops.first
|
131
|
+
when "$in"
|
132
|
+
chain(:or, val.map { |val| col.matches(%Q[%"#{val}"%]) })
|
133
|
+
when "$gt", "$gte", "$lt", "$lte"
|
134
|
+
wrap_numeric(col, val).send(COMPARE_MAPS[ops.first], val)
|
135
|
+
when "$eq"
|
136
|
+
wrap_numeric(col, val).eq(val)
|
137
|
+
when "$ne"
|
138
|
+
wrap_numeric(col, val).not_eq(val)
|
139
|
+
when /^\$/
|
140
|
+
raise UnsupportedQuery, "Unknown operator #{ops.first}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def wrap_numeric(col, val)
|
146
|
+
case val
|
147
|
+
when Float
|
148
|
+
Arel.sql("(#{col})::float")
|
149
|
+
when Integer
|
150
|
+
Arel.sql("(#{col})::integer")
|
151
|
+
else
|
152
|
+
col
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def sql_json_path(col)
|
157
|
+
path = "data"
|
158
|
+
parts = col.to_s.split('.')
|
159
|
+
parts.each_with_index do |part, index|
|
160
|
+
sep = index == parts.size - 1 ? "->>" : "->"
|
161
|
+
path += "#{sep}'#{part}'"
|
162
|
+
end
|
163
|
+
Arel.sql(path)
|
164
|
+
end
|
165
|
+
|
166
|
+
def chain(op, conditions)
|
167
|
+
result = nil
|
168
|
+
conditions.each do |cond|
|
169
|
+
result = result ? result.send(op, cond) : cond
|
170
|
+
end
|
171
|
+
result
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class UnsupportedQuery < StandardError
|
176
|
+
end
|
177
|
+
end
|
data/mongery.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mongery/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "mongery"
|
8
|
+
spec.version = Mongery::VERSION
|
9
|
+
spec.authors = ["Tatsuhiko Miyagawa"]
|
10
|
+
spec.email = ["miyagawa@bulknews.net"]
|
11
|
+
spec.summary = %q{Convert MongoDB query to Arel for PostgreSQL + JSON}
|
12
|
+
spec.description = %q{}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "arel", ">= 4.0.2"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
spec.add_development_dependency "activerecord"
|
27
|
+
spec.add_development_dependency "pg"
|
28
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mongery::Builder do
|
4
|
+
tests = [
|
5
|
+
[ { }, { },
|
6
|
+
/^SELECT "test"\."data" FROM "test"$/ ],
|
7
|
+
[ { _id: "foo" }, { },
|
8
|
+
/WHERE "test"\."id" = 'foo'$/ ],
|
9
|
+
[ { _id: { "$in" => ["foo", "bar"] } }, { },
|
10
|
+
/WHERE "test"\."id" IN \('foo', 'bar'\)$/ ],
|
11
|
+
[ { _id: "foo" }, { limit: 1 },
|
12
|
+
/WHERE "test"\."id" = 'foo' LIMIT 1$/ ],
|
13
|
+
[ { _id: "foo" }, { limit: 1, skip: 10 },
|
14
|
+
/WHERE "test"\."id" = 'foo' LIMIT 1 OFFSET 10$/ ],
|
15
|
+
[ { _id: "foo" }, { skip: 10, sort: { _id: 1 } },
|
16
|
+
/WHERE "test"\."id" = 'foo' ORDER BY "test"\."id" ASC OFFSET 10$/ ],
|
17
|
+
[ { _id: "foo" }, { sort: { name: -1, email: 1 } },
|
18
|
+
/WHERE "test"\."id" = 'foo' ORDER BY data->>'name' DESC, data->>'email' ASC$/ ],
|
19
|
+
[ { "_id" => "foo" }, { },
|
20
|
+
/WHERE "test"\."id" = 'foo'$/ ],
|
21
|
+
[ { name: "foo" }, { },
|
22
|
+
/WHERE data->>'name' = 'foo'$/ ],
|
23
|
+
[ { name: nil }, { },
|
24
|
+
/WHERE data->>'name' = ''$/ ],
|
25
|
+
[ { name: "foo", other: "bar" }, { },
|
26
|
+
/WHERE data->>'name' = 'foo' AND data->>'other' = 'bar'/ ],
|
27
|
+
[ { weight: 66 }, { },
|
28
|
+
/WHERE data->>'weight' = '66'$/ ],
|
29
|
+
[ { weight: { "$gt" => 66 } }, { },
|
30
|
+
/WHERE \(data->>'weight'\)::integer > 66$/ ],
|
31
|
+
[ { weight: { "$gt" => 66.0 } }, { },
|
32
|
+
/WHERE \(data->>'weight'\)::float > 66\.0$/ ],
|
33
|
+
[ { weight: { "$lte" => 66 } }, { },
|
34
|
+
/WHERE \(data->>'weight'\)::integer <= 66$/ ],
|
35
|
+
[ { age: { '$eq' => 10 }}, { },
|
36
|
+
/WHERE \(data->>'age'\)::integer = 10/ ],
|
37
|
+
[ { age: { '$ne' => 10 }}, { },
|
38
|
+
/WHERE \(data->>'age'\)::integer != 10/ ],
|
39
|
+
[ { bool: true }, { },
|
40
|
+
/WHERE data->>'bool' = 'true'$/ ],
|
41
|
+
[ { 'email.address' => 'john@example.com' }, { },
|
42
|
+
/WHERE data->'email'->>'address' = 'john@example.com'$/ ],
|
43
|
+
[ { type: "food", "$or" => [{name: "miso"}, {name: "tofu"}]}, { },
|
44
|
+
/WHERE data->>'type' = 'food' AND \(data->>'name' = 'miso' OR data->>'name' = 'tofu'\)$/ ],
|
45
|
+
[ { "$or" => [{ _id: "foo" }, { _id: "bar" }] }, { },
|
46
|
+
/WHERE \("test"\."id" = 'foo' OR "test"\."id" = 'bar'\)$/ ],
|
47
|
+
[ { "$or" => [{ name: "John" }, { weight: 120 }] }, { },
|
48
|
+
/WHERE \(data->>'name' = 'John' OR data->>'weight' = '120'\)$/ ],
|
49
|
+
[ { "$and" => [{ _id: "foo" }, { name: "bar" }] }, { },
|
50
|
+
/WHERE "test"\."id" = 'foo' AND data->>'name' = 'bar'$/ ],
|
51
|
+
[ { "$and" => [{ name: "John" }, { weight: 120 }] }, { },
|
52
|
+
/WHERE data->>'name' = 'John' AND data->>'weight' = '120'$/ ],
|
53
|
+
[ { "$and" => [{ "$or" => [{name: "John"}, {email: "john"}] }, {_id: "Bob"}] }, { },
|
54
|
+
/WHERE \(data->>'name' = 'John' OR data->>'email' = 'john'\) AND "test"\."id" = 'Bob'$/ ],
|
55
|
+
[ { ids: {"$in" => [ "foo" ]} }, { },
|
56
|
+
/WHERE data->>'ids' ILIKE '%"foo"%'$/ ],
|
57
|
+
[ { ids: {"$in" => [ "foo", "bar" ]} }, { },
|
58
|
+
/WHERE \(data->>'ids' ILIKE '%"foo"%' OR data->>'ids' ILIKE '%"bar"%'\)$/ ],
|
59
|
+
]
|
60
|
+
|
61
|
+
tests.each do |query, condition, sql|
|
62
|
+
context "with query #{query}" do
|
63
|
+
subject do
|
64
|
+
Mongery::Builder.new(:test).find(query).tap { |q|
|
65
|
+
condition.each do |method, value|
|
66
|
+
q.send(method, value)
|
67
|
+
end
|
68
|
+
}.to_sql
|
69
|
+
end
|
70
|
+
it { should match sql }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'mongery'
|
2
|
+
require 'rspec'
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
ActiveRecord::Base.establish_connection(
|
7
|
+
adapter: 'postgresql',
|
8
|
+
encoding: 'unicode',
|
9
|
+
database: ENV['PG_DATABASE'],
|
10
|
+
username: ENV['USER'],
|
11
|
+
password: nil,
|
12
|
+
)
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongery
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tatsuhiko Miyagawa
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: arel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.0.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activerecord
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pg
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: ''
|
98
|
+
email:
|
99
|
+
- miyagawa@bulknews.net
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- Gemfile
|
106
|
+
- LICENSE.txt
|
107
|
+
- README.md
|
108
|
+
- Rakefile
|
109
|
+
- lib/mongery.rb
|
110
|
+
- lib/mongery/version.rb
|
111
|
+
- mongery.gemspec
|
112
|
+
- spec/mongery/builder_spec.rb
|
113
|
+
- spec/spec_helper.rb
|
114
|
+
homepage: ''
|
115
|
+
licenses:
|
116
|
+
- MIT
|
117
|
+
metadata: {}
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
require_paths:
|
121
|
+
- lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
requirements: []
|
133
|
+
rubyforge_project:
|
134
|
+
rubygems_version: 2.2.2
|
135
|
+
signing_key:
|
136
|
+
specification_version: 4
|
137
|
+
summary: Convert MongoDB query to Arel for PostgreSQL + JSON
|
138
|
+
test_files:
|
139
|
+
- spec/mongery/builder_spec.rb
|
140
|
+
- spec/spec_helper.rb
|
141
|
+
has_rdoc:
|