mordor 0.3.0-java
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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.travis.yml +22 -0
- data/CHANGES.md +9 -0
- data/Gemfile +7 -0
- data/Guardfile +39 -0
- data/LICENSE +20 -0
- data/README.md +27 -0
- data/Rakefile +24 -0
- data/ext/mkrf_conf.rb +21 -0
- data/lib/mordor.rb +84 -0
- data/lib/mordor/collection.rb +50 -0
- data/lib/mordor/config.rb +91 -0
- data/lib/mordor/resource.rb +385 -0
- data/lib/mordor/version.rb +3 -0
- data/mordor.gemspec +38 -0
- data/spec/mordor/collection_spec.rb +159 -0
- data/spec/mordor/connection_spec.rb +143 -0
- data/spec/mordor/resource_spec.rb +519 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +41 -0
- data/tasks/github-gem.rake +376 -0
- metadata +158 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c0c2388e8d060d289571c40264cadf1d531c4efc
|
4
|
+
data.tar.gz: 2d660783cf567837d0936d10e6e66886ad47e14d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0f8ddbdd70ec496a7a02df49b61425e7ad2064b5e88d3bd986d50f072d87addcb328acc14cc34c097dfdcfb633eb24cc4838e0df0e3f156318757ccb12fa925a
|
7
|
+
data.tar.gz: f4c0113fbbc300bd213b568f4df16307f6bcea8e51831d060f067924066fbb098848a6117e7eed840f72d84f960c1ef2e119e75490a2b988bb23e0e4ce910b73
|
data/.travis.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
language: ruby
|
2
|
+
cache: bundler
|
3
|
+
bundler_args: --without optional
|
4
|
+
|
5
|
+
jdk:
|
6
|
+
- oraclejdk7
|
7
|
+
|
8
|
+
services:
|
9
|
+
- mongodb
|
10
|
+
|
11
|
+
rvm:
|
12
|
+
- 1.9.3
|
13
|
+
- 2.1.6
|
14
|
+
- 2.2.2
|
15
|
+
- jruby-19mode
|
16
|
+
- rbx-19mode
|
17
|
+
- rbx-2.5.3
|
18
|
+
|
19
|
+
matrix:
|
20
|
+
allow_failures:
|
21
|
+
- rvm: rbx-19mode
|
22
|
+
- rvm: rbx-2.5.3
|
data/CHANGES.md
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features)
|
6
|
+
|
7
|
+
## Uncomment to clear the screen before every task
|
8
|
+
# clearing :on
|
9
|
+
|
10
|
+
## Guard internally checks for changes in the Guardfile and exits.
|
11
|
+
## If you want Guard to automatically start up again, run guard in a
|
12
|
+
## shell loop, e.g.:
|
13
|
+
##
|
14
|
+
## $ while bundle exec guard; do echo "Restarting Guard..."; done
|
15
|
+
##
|
16
|
+
## Note: if you are using the `directories` clause above and you are not
|
17
|
+
## watching the project directory ('.'), then you will want to move
|
18
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
19
|
+
#
|
20
|
+
# $ mkdir config
|
21
|
+
# $ mv Guardfile config/
|
22
|
+
# $ ln -s config/Guardfile .
|
23
|
+
#
|
24
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
25
|
+
|
26
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
27
|
+
# rspec may be run, below are examples of the most common uses.
|
28
|
+
# * bundler: 'bundle exec rspec'
|
29
|
+
# * bundler binstubs: 'bin/rspec'
|
30
|
+
# * spring: 'bin/rsspec' (This will use spring if running and you have
|
31
|
+
# installed the spring binstubs per the docs)
|
32
|
+
# * zeus: 'zeus rspec' (requires the server to be started separetly)
|
33
|
+
# * 'just' rspec: 'rspec'
|
34
|
+
|
35
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
36
|
+
watch(%r{^spec/.+_spec\.rb$})
|
37
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
38
|
+
watch('spec/spec_helper.rb') { "spec" }
|
39
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 J.W. Koelewijn
|
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,27 @@
|
|
1
|
+
[](http://travis-ci.org/jwkoelewijn/mordor)
|
2
|
+
|
3
|
+
## Introduction
|
4
|
+
Small library to add DataMapper style resources for MongoDB.
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
class ExampleResource
|
8
|
+
include Mordor::Resource
|
9
|
+
|
10
|
+
attribute :first, :index => true
|
11
|
+
attribute :second
|
12
|
+
attribute :third, :finder_method => :find_by_third_attribute
|
13
|
+
attribute :at, :timestamp => true
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
This adds attr_accessors to the ExampleResource for each attribute, plus adds finder methods of the form
|
18
|
+
`find_by_{attribute}`. The naming convention can be overridden by using the optional `:finder_method` option,
|
19
|
+
as can be seen with the third attribute.
|
20
|
+
|
21
|
+
When the `:index => true` option is set, indices are ensured before each query on
|
22
|
+
the collection. Indices are descending by default, but this can be changed by also supplying a `:index_type => Mongo::ASCENDING` option.
|
23
|
+
|
24
|
+
At most one attribute per Resource can have the option `:timestamp => true` set. This means that the attribute will be saved as one of the two first
|
25
|
+
attributes (the other one being the `_id` attribute. When no value is given for the timestamped attribute, a timestamp with value 0 will be inserted,
|
26
|
+
which results in a timestamp being assigned to it by the MongoDB.
|
27
|
+
An exception is raised when more than one attribute is given the timestamp option
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler"
|
3
|
+
|
4
|
+
Bundler.setup
|
5
|
+
|
6
|
+
load('./tasks/github-gem.rake')
|
7
|
+
|
8
|
+
task :default => :spec
|
9
|
+
|
10
|
+
# Register the gem release tasks in the gem namespace
|
11
|
+
GithubGem::RakeTasks.new(:gem) do |config|
|
12
|
+
|
13
|
+
# Note that the following values are all default values and can
|
14
|
+
# therefore be omitted if they are not changed.
|
15
|
+
|
16
|
+
config.gemspec_file = GithubGem.detect_gemspec_file
|
17
|
+
config.main_include = GithubGem.detect_main_include
|
18
|
+
config.root_dir = Dir.pwd
|
19
|
+
config.test_pattern = 'test/**/*_test.rb'
|
20
|
+
config.spec_pattern = 'spec/**/*_spec.rb'
|
21
|
+
config.local_branch = 'master'
|
22
|
+
config.remote = 'origin'
|
23
|
+
config.remote_branch = 'master'
|
24
|
+
end
|
data/ext/mkrf_conf.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems/dependency_installer'
|
2
|
+
|
3
|
+
gdi = Gem::DependencyInstaller.new
|
4
|
+
|
5
|
+
begin
|
6
|
+
if RUBY_PLATFORM == 'java'
|
7
|
+
puts "Not installing bson_ext gem, because we're running on JRuby"
|
8
|
+
else
|
9
|
+
puts "Installing bson_ext"
|
10
|
+
gdi.install "bson_ext"
|
11
|
+
end
|
12
|
+
rescue => e
|
13
|
+
warn "#{$0}: #{e}"
|
14
|
+
|
15
|
+
exit!
|
16
|
+
end
|
17
|
+
|
18
|
+
# Write fake Rakefile for rake since Makefile isn't used
|
19
|
+
File.open(File.join(File.dirname(__FILE__), 'Rakefile'), 'w') do |f|
|
20
|
+
f.write("task :default" + $/)
|
21
|
+
end
|
data/lib/mordor.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rational'
|
3
|
+
require 'mongo'
|
4
|
+
require 'extlib'
|
5
|
+
require 'json'
|
6
|
+
require 'mordor/version'
|
7
|
+
require 'mordor/config'
|
8
|
+
require 'mordor/collection'
|
9
|
+
require 'mordor/resource'
|
10
|
+
|
11
|
+
unless Object.const_defined?('BigDecimal')
|
12
|
+
BigDecimal = Float
|
13
|
+
end
|
14
|
+
|
15
|
+
class Date
|
16
|
+
def to_time(form = :utc)
|
17
|
+
Time.send("#{form}", year, month, day)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_gm
|
21
|
+
Time.gm(year, month, day).to_datetime
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_local
|
25
|
+
Time.local(year, month, day).to_datetime
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_datetime
|
29
|
+
DateTime.civil(self.year, self.mon, self.day)
|
30
|
+
end
|
31
|
+
|
32
|
+
def show(format = nil)
|
33
|
+
case format
|
34
|
+
when :human
|
35
|
+
strftime("%d-%m-%Y")
|
36
|
+
else
|
37
|
+
to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def full_string
|
42
|
+
strftime("%A %d %B %Y")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class DateTime
|
47
|
+
def to_datetime
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_date
|
52
|
+
Date.civil(self.year, self.mon, self.day)
|
53
|
+
end
|
54
|
+
|
55
|
+
def show(format = nil)
|
56
|
+
case format
|
57
|
+
when :human
|
58
|
+
strftime("%d-%m-%Y %H:%M")
|
59
|
+
when :full
|
60
|
+
strftime("%d-%m-%Y %H:%M:%S")
|
61
|
+
else
|
62
|
+
to_s
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Time
|
68
|
+
|
69
|
+
def to_date
|
70
|
+
Date.civil(year, month, day)
|
71
|
+
end if RUBY_VERSION < '1.8.6'
|
72
|
+
|
73
|
+
def show(format = nil)
|
74
|
+
return self.to_date.show(format) if [hour,min] == [0,0]
|
75
|
+
case format
|
76
|
+
when :human
|
77
|
+
strftime("%d-%m-%Y %H:%m")
|
78
|
+
when :full
|
79
|
+
strftime("%d-%m-%Y %H:%M:%S")
|
80
|
+
else
|
81
|
+
to_s
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Mordor
|
2
|
+
class Collection
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(klass, cursor)
|
6
|
+
@klass = klass
|
7
|
+
@cursor = cursor
|
8
|
+
end
|
9
|
+
|
10
|
+
def each
|
11
|
+
@cursor.each do |element|
|
12
|
+
if element.is_a? @klass
|
13
|
+
yield element
|
14
|
+
else
|
15
|
+
yield @klass.new(element)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
@cursor.rewind! unless @cursor.is_a? Array
|
19
|
+
end
|
20
|
+
|
21
|
+
def size(regard_limits_and_offsets = true)
|
22
|
+
@cursor.is_a?(Array) ? @cursor.count : @cursor.count(regard_limits_and_offsets)
|
23
|
+
end
|
24
|
+
alias :count :size
|
25
|
+
|
26
|
+
def method_missing(method, *args, &block)
|
27
|
+
if @cursor.respond_to?(method)
|
28
|
+
self.class.new(@klass, @cursor.__send__(method, *args, &block))
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_json(*args)
|
35
|
+
to_a.to_json(*args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def merge(other_collection)
|
39
|
+
Collection.new(@klass, (self.to_a + other_collection.to_a))
|
40
|
+
end
|
41
|
+
alias_method :+, :merge
|
42
|
+
|
43
|
+
def merge!(other_collection)
|
44
|
+
unless @cursor.is_a? Array
|
45
|
+
@cursor = @cursor.to_a
|
46
|
+
end
|
47
|
+
@cursor += other_collection.to_a
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Mordor
|
2
|
+
class Config
|
3
|
+
class << self
|
4
|
+
|
5
|
+
# Yields the configuration.
|
6
|
+
#
|
7
|
+
# ==== Block parameters
|
8
|
+
# c<Hash>:: The configuration parameters.
|
9
|
+
#
|
10
|
+
# ==== Examples
|
11
|
+
# Merb::Config.use do |config|
|
12
|
+
# config[:exception_details] = false
|
13
|
+
# config[:log_stream] = STDOUT
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# ==== Returns
|
17
|
+
# nil
|
18
|
+
#
|
19
|
+
# :api: publicdef use
|
20
|
+
def use
|
21
|
+
@configuration ||= defaults
|
22
|
+
yield @configuration
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# Resets the configuration to its default state
|
27
|
+
#
|
28
|
+
def reset
|
29
|
+
setup
|
30
|
+
end
|
31
|
+
|
32
|
+
# Retrieve the value of a config entry.
|
33
|
+
#
|
34
|
+
# ==== Parameters
|
35
|
+
# key<Object>:: The key to retrieve the parameter for.
|
36
|
+
#
|
37
|
+
# ==== Returns
|
38
|
+
# Object:: The value of the configuration parameter.
|
39
|
+
#
|
40
|
+
# :api: public
|
41
|
+
def [](key)
|
42
|
+
(@configuration ||= setup)[key]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Set the value of a config entry.
|
46
|
+
#
|
47
|
+
# ==== Parameters
|
48
|
+
# key<Object>:: The key to set the parameter for.
|
49
|
+
# val<Object>:: The value of the parameter.
|
50
|
+
#
|
51
|
+
# :api: public
|
52
|
+
def []=(key, val)
|
53
|
+
(@configuration ||= setup)[key] = val
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Returns the hash of default config values for Merb.
|
59
|
+
#
|
60
|
+
# ==== Returns
|
61
|
+
# Hash:: The defaults for the config.
|
62
|
+
#
|
63
|
+
# :api: private
|
64
|
+
def defaults
|
65
|
+
@defaults ||= {
|
66
|
+
:hostname => 'localhost',
|
67
|
+
:port => 27017,
|
68
|
+
:database => 'development',
|
69
|
+
:pool_size => nil,
|
70
|
+
:pool_timeout => nil,
|
71
|
+
:rs_refresh_mode => :sync,
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
# Sets up the configuration by storing the given settings.
|
76
|
+
#
|
77
|
+
# ==== Parameters
|
78
|
+
# settings<Hash>::
|
79
|
+
# Configuration settings to use. These are merged with the defaults.
|
80
|
+
#
|
81
|
+
# ==== Returns
|
82
|
+
# The configuration as a hash.
|
83
|
+
#
|
84
|
+
# :api: private
|
85
|
+
def setup(settings = {})
|
86
|
+
@configuration = defaults.merge(settings)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,385 @@
|
|
1
|
+
module Mordor
|
2
|
+
module Resource
|
3
|
+
attr_accessor :_id
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(attributes = {})
|
10
|
+
attributes.each do |k,v|
|
11
|
+
self.send("#{k}=", v)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def replace_params(params = {})
|
16
|
+
result = {}
|
17
|
+
return result if params.nil? or params.empty?
|
18
|
+
params.each do |key, value|
|
19
|
+
value = replace_type(value)
|
20
|
+
key = key.to_s.gsub(/\W|\./, "_")
|
21
|
+
result[key] = value
|
22
|
+
end
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
def replace_type(value)
|
27
|
+
case value
|
28
|
+
when Hash
|
29
|
+
value = replace_params(value)
|
30
|
+
when Date, DateTime
|
31
|
+
value = value.to_time.getlocal
|
32
|
+
when Time
|
33
|
+
value = value.getlocal
|
34
|
+
when BigDecimal
|
35
|
+
value = value.to_f
|
36
|
+
when Array
|
37
|
+
value = value.map do |val|
|
38
|
+
replace_type(val)
|
39
|
+
end
|
40
|
+
when BSON::Timestamp
|
41
|
+
value = replace_params({:seconds => value ? value.seconds : 0, :increment => value ? value.increment : 0})
|
42
|
+
when Integer
|
43
|
+
else
|
44
|
+
value = value.to_s
|
45
|
+
end
|
46
|
+
value
|
47
|
+
end
|
48
|
+
|
49
|
+
def new?
|
50
|
+
return self._id == nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def saved?
|
54
|
+
return !new?
|
55
|
+
end
|
56
|
+
|
57
|
+
def reload
|
58
|
+
return unless _id
|
59
|
+
res = self.class.get(_id).to_hash.each do |k, v|
|
60
|
+
self.send("#{k}=".to_sym, v)
|
61
|
+
end
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def save
|
66
|
+
unless self._id
|
67
|
+
self_hash = self.to_hash
|
68
|
+
if timestamp_attribute = self.class.timestamped_attribute
|
69
|
+
timestamp_value = self_hash.delete(timestamp_attribute)
|
70
|
+
if timestamp_value.is_a?(Hash)
|
71
|
+
timestamp_value = BSON::Timestamp.new(timestamp_value["seconds"], timestamp_value["increment"])
|
72
|
+
end
|
73
|
+
ordered_self_hash = BSON::OrderedHash.new
|
74
|
+
if timestamp_value.nil? || (timestamp_value.is_a?(String) && timestamp_value.empty?)
|
75
|
+
ordered_self_hash[timestamp_attribute] = BSON::Timestamp.new(0, 0)
|
76
|
+
else
|
77
|
+
ordered_self_hash[timestamp_attribute] = timestamp_value
|
78
|
+
end
|
79
|
+
self_hash.each do |key, value|
|
80
|
+
ordered_self_hash[key] = value
|
81
|
+
end
|
82
|
+
self_hash = ordered_self_hash
|
83
|
+
end
|
84
|
+
with_collection do |collection|
|
85
|
+
insert_id = collection.insert(self_hash)
|
86
|
+
self._id = insert_id
|
87
|
+
end
|
88
|
+
else
|
89
|
+
insert_id = self.update
|
90
|
+
end
|
91
|
+
saved?
|
92
|
+
end
|
93
|
+
|
94
|
+
alias_method :save!, :save
|
95
|
+
|
96
|
+
def with_collection
|
97
|
+
self.class.with_collection do |collection|
|
98
|
+
yield collection
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def update
|
103
|
+
insert_id = nil
|
104
|
+
with_collection do |collection|
|
105
|
+
insert_id = collection.update({:_id => self._id}, self.to_hash)
|
106
|
+
end
|
107
|
+
insert_id
|
108
|
+
end
|
109
|
+
|
110
|
+
def collection
|
111
|
+
self.class.collection
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_hash
|
115
|
+
attributes = self.class.instance_variable_get(:@attributes)
|
116
|
+
result = {}
|
117
|
+
return result unless attributes
|
118
|
+
attributes.each do |attribute_name|
|
119
|
+
result[attribute_name] = replace_type(self.send(attribute_name))
|
120
|
+
end
|
121
|
+
result
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_json(*args)
|
125
|
+
to_hash.merge(:_id => _id).to_json(*args)
|
126
|
+
end
|
127
|
+
|
128
|
+
def destroyed?
|
129
|
+
@destroyed ||= false
|
130
|
+
end
|
131
|
+
|
132
|
+
def destroy
|
133
|
+
collection.remove({:_id => _id})
|
134
|
+
self.class.send(:ensure_indices)
|
135
|
+
@destroyed = true
|
136
|
+
end
|
137
|
+
|
138
|
+
module ClassMethods
|
139
|
+
def create(attributes = {})
|
140
|
+
resource = self.new(attributes)
|
141
|
+
resource.save
|
142
|
+
resource
|
143
|
+
end
|
144
|
+
|
145
|
+
def all(options = {})
|
146
|
+
Collection.new(self, perform_collection_find({}, options))
|
147
|
+
end
|
148
|
+
|
149
|
+
def collection
|
150
|
+
collection = nil
|
151
|
+
with_database do |database|
|
152
|
+
collection = database.collection(self.collection_name)
|
153
|
+
end
|
154
|
+
collection
|
155
|
+
end
|
156
|
+
|
157
|
+
def collection_name
|
158
|
+
klassname = self.to_s.downcase.gsub(/[\/|.|::]/, '_')
|
159
|
+
"#{klassname}s"
|
160
|
+
end
|
161
|
+
|
162
|
+
def get(id)
|
163
|
+
if id.is_a?(String)
|
164
|
+
id = BSON::ObjectId.from_string(id)
|
165
|
+
end
|
166
|
+
if attributes = perform_collection_find_one(:_id => id)
|
167
|
+
new(attributes)
|
168
|
+
else
|
169
|
+
nil
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def database
|
174
|
+
unless @db
|
175
|
+
if connecting_to_replica_set?
|
176
|
+
client = new_replica_set_client
|
177
|
+
else
|
178
|
+
client = new_mongo_connection
|
179
|
+
end
|
180
|
+
@db = database_connection(client)
|
181
|
+
end
|
182
|
+
|
183
|
+
@db
|
184
|
+
end
|
185
|
+
|
186
|
+
def with_database(max_retries=60)
|
187
|
+
retries = 0
|
188
|
+
begin
|
189
|
+
yield self.database
|
190
|
+
rescue Mongo::ConnectionFailure => ex
|
191
|
+
retries += 1
|
192
|
+
raise ex if retries > max_retries
|
193
|
+
sleep(0.5)
|
194
|
+
retry
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def with_collection
|
199
|
+
with_database do |database|
|
200
|
+
yield database.collection(self.collection_name)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def find_by_id(id)
|
205
|
+
get(id)
|
206
|
+
end
|
207
|
+
|
208
|
+
def find(query, options = {})
|
209
|
+
Collection.new(self, perform_collection_find(query, options))
|
210
|
+
end
|
211
|
+
|
212
|
+
def find_by_day(value, options = {})
|
213
|
+
if value.is_a?(Hash)
|
214
|
+
raise ArgumentError.new(":value missing from complex query hash") unless value.keys.include?(:value)
|
215
|
+
day = value.delete(:value)
|
216
|
+
query = value.merge(day_to_query(day))
|
217
|
+
else
|
218
|
+
query = day_to_query(value)
|
219
|
+
end
|
220
|
+
|
221
|
+
cursor = perform_collection_find(query, options)
|
222
|
+
Collection.new(self, cursor)
|
223
|
+
end
|
224
|
+
|
225
|
+
def timestamped_attribute
|
226
|
+
@timestamped_attribute
|
227
|
+
end
|
228
|
+
|
229
|
+
def attribute(name, options = {})
|
230
|
+
@attributes ||= []
|
231
|
+
@indices ||= []
|
232
|
+
@index_types ||= {}
|
233
|
+
|
234
|
+
@attributes << name unless @attributes.include?(name)
|
235
|
+
if options[:index]
|
236
|
+
@indices << name unless @indices.include?(name)
|
237
|
+
@index_types[name] = options[:index_type] ? options[:index_type] : Mongo::DESCENDING
|
238
|
+
ensure_index(name)
|
239
|
+
end
|
240
|
+
|
241
|
+
if options[:timestamp]
|
242
|
+
raise ArgumentError.new("Only one timestamped attribute is allowed, '#{@timestamped_attribute}' is already timestamped") unless @timestamped_attribute.nil?
|
243
|
+
@timestamped_attribute = name
|
244
|
+
end
|
245
|
+
|
246
|
+
method_name = options.key?(:finder_method) ? options[:finder_method] : "find_by_#{name}"
|
247
|
+
|
248
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
249
|
+
attr_accessor name
|
250
|
+
|
251
|
+
def self.#{method_name}(value, options = {})
|
252
|
+
if value.is_a?(Hash)
|
253
|
+
raise ArgumentError.new(":value missing from complex query hash") unless value.keys.include?(:value)
|
254
|
+
query = {:#{name} => value.delete(:value)}
|
255
|
+
query = query.merge(value)
|
256
|
+
else
|
257
|
+
query = {:#{name} => value}
|
258
|
+
end
|
259
|
+
col = perform_collection_find(query, options)
|
260
|
+
Collection.new(self, col)
|
261
|
+
end
|
262
|
+
EOS
|
263
|
+
end
|
264
|
+
|
265
|
+
private
|
266
|
+
|
267
|
+
def perform_collection_find(query, options = {})
|
268
|
+
ensure_indices
|
269
|
+
collection.find(query, options)
|
270
|
+
end
|
271
|
+
|
272
|
+
def perform_collection_find_one(query, options = {})
|
273
|
+
ensure_indices
|
274
|
+
collection.find_one(query, options)
|
275
|
+
end
|
276
|
+
|
277
|
+
def ensure_indices
|
278
|
+
indices.each do |index|
|
279
|
+
ensure_index(index)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def ensure_index(attribute)
|
284
|
+
with_collection do |collection|
|
285
|
+
collection.ensure_index( [ [attribute.to_s, index_types[attribute] ]] )
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def indices
|
290
|
+
@indices ||= []
|
291
|
+
end
|
292
|
+
|
293
|
+
def index_types
|
294
|
+
@index_types ||= {}
|
295
|
+
end
|
296
|
+
|
297
|
+
def day_to_range(day)
|
298
|
+
case day
|
299
|
+
when DateTime
|
300
|
+
start = day.to_date.to_time
|
301
|
+
end_of_day = (day.to_date + 1).to_time
|
302
|
+
when Date
|
303
|
+
start = day.to_time
|
304
|
+
end_of_day = (day + 1).to_time
|
305
|
+
when Time
|
306
|
+
start = day.to_datetime.to_date.to_time
|
307
|
+
end_of_day = (day.to_datetime + 1).to_date.to_time
|
308
|
+
end
|
309
|
+
[start, end_of_day]
|
310
|
+
end
|
311
|
+
|
312
|
+
def date_range_to_query(range)
|
313
|
+
{:at => {:$gte => range.first, :$lt => range.last}}
|
314
|
+
end
|
315
|
+
|
316
|
+
def day_to_query(day)
|
317
|
+
date_range_to_query( day_to_range(day) )
|
318
|
+
end
|
319
|
+
|
320
|
+
# Connection setup
|
321
|
+
|
322
|
+
def new_mongo_connection
|
323
|
+
Mongo::Connection.new(*mongo_connection_args)
|
324
|
+
end
|
325
|
+
|
326
|
+
def new_replica_set_client
|
327
|
+
Mongo::MongoReplicaSetClient.new(*replica_set_client_args)
|
328
|
+
end
|
329
|
+
|
330
|
+
def database_connection(client)
|
331
|
+
client.db(Mordor::Config[:database]).tap do |db|
|
332
|
+
db.authenticate(*authentication_args) if authentication_args.any?
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# Connection arguments
|
337
|
+
|
338
|
+
def mongo_connection_args
|
339
|
+
args = [ Mordor::Config[:hostname], Mordor::Config[:port] ]
|
340
|
+
args << connection_pool_options if connection_pool_options.any?
|
341
|
+
args
|
342
|
+
end
|
343
|
+
|
344
|
+
def replica_set_client_args
|
345
|
+
[ replica_set_host_list, replica_set_options ]
|
346
|
+
end
|
347
|
+
|
348
|
+
def authentication_args
|
349
|
+
args = []
|
350
|
+
if user_name = Mordor::Config[:username]
|
351
|
+
args << user_name
|
352
|
+
args << Mordor::Config[:password] if Mordor::Config[:password]
|
353
|
+
end
|
354
|
+
args
|
355
|
+
end
|
356
|
+
|
357
|
+
# Connection options
|
358
|
+
|
359
|
+
def connection_pool_options
|
360
|
+
@connection_pool_options ||= Hash.new.tap do |hash|
|
361
|
+
hash[:pool_size] = Mordor::Config[:pool_size] if Mordor::Config[:pool_size]
|
362
|
+
hash[:pool_timeout] = Mordor::Config[:pool_timeout] if Mordor::Config[:pool_timeout]
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def replica_set_options
|
367
|
+
@replica_set_options ||= connection_pool_options.tap do |hash|
|
368
|
+
hash[:refresh_mode] = Mordor::Config[:rs_refresh_mode]
|
369
|
+
hash[:rs_name] = Mordor::Config[:replica_set] if Mordor::Config[:replica_set]
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
# Replica set helpers
|
374
|
+
|
375
|
+
def replica_set_host_list
|
376
|
+
@replica_set_hosts ||= Mordor::Config[:hostname].split(',').map(&:strip).compact
|
377
|
+
end
|
378
|
+
|
379
|
+
def connecting_to_replica_set?
|
380
|
+
replica_set_host_list.size > 1
|
381
|
+
end
|
382
|
+
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|