splash 0.0.1.alpha → 0.0.1.beta
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -0
- data/lib/splash.rb +22 -26
- data/lib/splash/acts_as_collection.rb +3 -2
- data/lib/splash/acts_as_scope.rb +97 -33
- data/lib/splash/acts_as_scope_root.rb +2 -1
- data/lib/splash/annotated.rb +2 -1
- data/lib/splash/application.rb +2 -1
- data/lib/splash/attribute.rb +39 -21
- data/lib/splash/attributed_struct.rb +2 -1
- data/lib/splash/callbacks.rb +37 -0
- data/lib/splash/collection.rb +14 -11
- data/lib/splash/connection.rb +2 -1
- data/lib/splash/constraint.rb +3 -5
- data/lib/splash/constraint/all.rb +2 -1
- data/lib/splash/constraint/any.rb +2 -1
- data/lib/splash/constraint/in.rb +2 -1
- data/lib/splash/constraint/not_nil.rb +2 -1
- data/lib/splash/created_at.rb +16 -0
- data/lib/splash/document.rb +43 -15
- data/lib/splash/embed.rb +29 -9
- data/lib/splash/has_attributes.rb +68 -37
- data/lib/splash/has_collection.rb +79 -0
- data/lib/splash/has_constraint.rb +2 -1
- data/lib/splash/map_reduce.rb +124 -0
- data/lib/splash/matcher.rb +160 -0
- data/lib/splash/namespace.rb +149 -15
- data/lib/splash/password.rb +2 -1
- data/lib/splash/persister.rb +22 -62
- data/lib/splash/query_interface.rb +28 -6
- data/lib/splash/saveable.rb +5 -104
- data/lib/splash/scope.rb +16 -3
- data/lib/splash/scope/map_reduce_interface.rb +33 -0
- data/lib/splash/scope/options.rb +16 -6
- data/lib/splash/updated_at.rb +15 -0
- data/lib/splash/validates.rb +3 -1
- data/lib/splash/validator.rb +2 -1
- data/lib/splash/writeback.rb +46 -0
- data/lib/standart_extensions/array.rb +9 -0
- data/lib/standart_extensions/bson.rb +122 -0
- data/lib/standart_extensions/class.rb +11 -0
- data/lib/{splash/standart_extensions → standart_extensions}/hash.rb +6 -1
- data/lib/{splash/standart_extensions → standart_extensions}/module.rb +17 -3
- data/lib/standart_extensions/object.rb +13 -0
- data/splash.gemspec +17 -0
- metadata +20 -16
- data/lib/splash/scope_delegator.rb +0 -14
- data/lib/splash/scope_options.rb +0 -31
- data/lib/splash/standart_extensions/object.rb +0 -11
- data/lib/splash/standart_extensions/string.rb +0 -5
- data/spec/helper.rb +0 -15
- data/spec/lib/annotated_spec.rb +0 -87
- data/spec/lib/collection_spec.rb +0 -64
- data/spec/lib/has_attributes_spec.rb +0 -43
- data/spec/lib/has_constraints_spec.rb +0 -53
- data/spec/lib/inheritance_spec.rb +0 -69
@@ -0,0 +1,124 @@
|
|
1
|
+
module Splash
|
2
|
+
|
3
|
+
module MapReduce
|
4
|
+
|
5
|
+
# hack!
|
6
|
+
# If we would set a constant to the class, it would be named to that constant.
|
7
|
+
# Yet, we don't want that.
|
8
|
+
BASE = [Class.new{
|
9
|
+
|
10
|
+
include Splash::Document
|
11
|
+
|
12
|
+
}].freeze
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def from_callback(callback,map,reduce,options,&block)
|
17
|
+
thiz = self
|
18
|
+
|
19
|
+
map = BSON::Code.new(map) if map.kind_of? String
|
20
|
+
reduce = BSON::Code.new(reduce) if reduce.kind_of? String
|
21
|
+
|
22
|
+
map.freeze
|
23
|
+
reduce.freeze
|
24
|
+
|
25
|
+
options.freeze
|
26
|
+
callback.freeze
|
27
|
+
|
28
|
+
c = Class.new(BASE.first) do
|
29
|
+
|
30
|
+
ex = class << self
|
31
|
+
self.method(:define_method)
|
32
|
+
end
|
33
|
+
|
34
|
+
if map
|
35
|
+
ex.call(:map) do
|
36
|
+
return map
|
37
|
+
end
|
38
|
+
else
|
39
|
+
ex.call(:map) do
|
40
|
+
raise NoMethod, "No map function given and 'map' not implemented."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if reduce
|
45
|
+
ex.call(:reduce) do
|
46
|
+
return reduce
|
47
|
+
end
|
48
|
+
else
|
49
|
+
ex.call(:reduce) do
|
50
|
+
raise NoMethod, "No reduce function given and 'reduce' not implemented."
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
ex.call(:map_reduce_callback) do
|
55
|
+
return callback
|
56
|
+
end
|
57
|
+
ex.call(:map_reduce_base_options) do
|
58
|
+
return options
|
59
|
+
end
|
60
|
+
|
61
|
+
class << self
|
62
|
+
private :map_reduce_callback
|
63
|
+
end
|
64
|
+
|
65
|
+
extend thiz
|
66
|
+
|
67
|
+
end
|
68
|
+
if block_given?
|
69
|
+
c.class_eval &block
|
70
|
+
end
|
71
|
+
return c
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.[](scope, map=nil, reduce=nil, options={},&block)
|
77
|
+
return scope.map_reduce(map, reduce, options.merge(:keeptemp => true),&block)
|
78
|
+
end
|
79
|
+
|
80
|
+
module Temporary
|
81
|
+
|
82
|
+
include Splash::MapReduce
|
83
|
+
extend Splash::MapReduce::ClassMethods
|
84
|
+
|
85
|
+
def collection
|
86
|
+
refresh!
|
87
|
+
super
|
88
|
+
end
|
89
|
+
|
90
|
+
def map_reduce_options
|
91
|
+
options = map_reduce_base_options.dup
|
92
|
+
options[:keeptemp] = false
|
93
|
+
options[:raw] = true
|
94
|
+
options[:out] = nil
|
95
|
+
return options
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
module Permanent
|
101
|
+
|
102
|
+
include Splash::MapReduce
|
103
|
+
extend Splash::MapReduce::ClassMethods
|
104
|
+
|
105
|
+
def map_reduce_options
|
106
|
+
options = map_reduce_base_options.dup
|
107
|
+
options[:keeptemp] = true
|
108
|
+
options[:raw] = true
|
109
|
+
options[:out] = self.collection.name
|
110
|
+
return options
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
def refresh!
|
116
|
+
result = map_reduce_callback.call(map,reduce,self.map_reduce_options)
|
117
|
+
self.collection = self.namespace.collection(result["result"])
|
118
|
+
return result.except(["result","ok"])
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Splash
|
3
|
+
|
4
|
+
class Matcher < Hash
|
5
|
+
|
6
|
+
WHERE = '$where'
|
7
|
+
|
8
|
+
OR = '$or'
|
9
|
+
|
10
|
+
|
11
|
+
ATOMS = Hash.new do |hash,key|
|
12
|
+
hash[key] = lambda{|value,matcher| false}
|
13
|
+
end
|
14
|
+
ATOMS['$neq'] = lambda{|value,matcher| value != matcher }
|
15
|
+
ATOMS['$in'] = lambda{|value,matcher| matcher.contains? value }
|
16
|
+
ATOMS['$nin'] = lambda{|value,matcher| !(matcher.contains? value) }
|
17
|
+
ATOMS['$lt'] = lambda{|value,matcher| matcher > value }
|
18
|
+
ATOMS['$leq'] = lambda{|value,matcher| matcher >= value }
|
19
|
+
ATOMS['$gt'] = lambda{|value,matcher| matcher < value }
|
20
|
+
ATOMS['$geq'] = lambda{|value,matcher| matcher <= value }
|
21
|
+
ATOMS['$elemMatch'] = lambda{|value,matcher| Matcher.cast(matcher).matches_any?(value) }
|
22
|
+
ATOMS['$not'] = lambda{|value,matcher|
|
23
|
+
if matcher.kind_of? Regexp
|
24
|
+
value !~ matcher
|
25
|
+
else
|
26
|
+
!Matcher.cast(matcher).matches?(value)
|
27
|
+
end
|
28
|
+
}
|
29
|
+
ATOMS['$all'] = lambda{|value,matcher|
|
30
|
+
matcher.all? do |sub|
|
31
|
+
Matcher.match_atomic(value,sub)
|
32
|
+
end
|
33
|
+
}
|
34
|
+
ATOMS['$type'] = lambda{|value,matcher|
|
35
|
+
if value.kind_of? Array
|
36
|
+
value.any? do |sub|
|
37
|
+
BSON.types_for_object(sub).include? matcher
|
38
|
+
end
|
39
|
+
else
|
40
|
+
BSON.types_for_object(value).include? matcher
|
41
|
+
end
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
def self.match_atomic(value,matcher)
|
46
|
+
if matcher.kind_of? Hash
|
47
|
+
matcher.each do |function,arg|
|
48
|
+
return false unless ATOMS[function].call(value,arg)
|
49
|
+
end
|
50
|
+
elsif matcher.kind_of? Regexp
|
51
|
+
return false unless value =~ matcher
|
52
|
+
elsif value.kind_of? Array
|
53
|
+
return false unless value.include? matcher
|
54
|
+
else
|
55
|
+
return false unless value == matcher
|
56
|
+
end
|
57
|
+
return true
|
58
|
+
end
|
59
|
+
|
60
|
+
def matches?(object)
|
61
|
+
self.each do |key,matcher|
|
62
|
+
if key == OR
|
63
|
+
return false unless matcher.any? do |sub|
|
64
|
+
Matcher.cast(sub).matches?(object)
|
65
|
+
end
|
66
|
+
next
|
67
|
+
elsif key == WHERE
|
68
|
+
#
|
69
|
+
# johnson anyone?
|
70
|
+
#
|
71
|
+
return true
|
72
|
+
end
|
73
|
+
value = get_value(object,key)
|
74
|
+
|
75
|
+
return false unless Matcher.match_atomic(value,matcher)
|
76
|
+
end
|
77
|
+
return true
|
78
|
+
end
|
79
|
+
|
80
|
+
def matches_any?(array)
|
81
|
+
array.any? do |obj|
|
82
|
+
self.matches? obj
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.cast(hsh)
|
87
|
+
return hsh if hsh.kind_of? Matcher
|
88
|
+
return self.new.update(hsh) if hsh.kind_of? Hash
|
89
|
+
raise "can't convert #{hsh.inspect} to matcher"
|
90
|
+
end
|
91
|
+
|
92
|
+
def and(other)
|
93
|
+
result = Matcher.new
|
94
|
+
other.each do |k,v|
|
95
|
+
if k == OR
|
96
|
+
result[OR] = (self[OR] || []) + v
|
97
|
+
elsif k == WHERE
|
98
|
+
if self.key?(k) # ierkssss!
|
99
|
+
warn "$where can't be merged!"
|
100
|
+
end
|
101
|
+
result[k]=v
|
102
|
+
elsif self.key?(k)
|
103
|
+
if self[k].kind_of? Hash and v.kind_of? Hash
|
104
|
+
result[k] = self[k].merge(v)
|
105
|
+
elsif self[k].kind_of? Hash or v.kind_of? Hash
|
106
|
+
hsh, simple = v , self[k]
|
107
|
+
if simple.kind_of? Hash
|
108
|
+
hsh, simple = simple, hsh
|
109
|
+
end
|
110
|
+
hsh = hsh.dup
|
111
|
+
hsh['$all'] ||= []
|
112
|
+
hsh['$all'] << simple
|
113
|
+
result[k] = hsh
|
114
|
+
else
|
115
|
+
result[k]={'$all'=>[self[k],v]}
|
116
|
+
end
|
117
|
+
else
|
118
|
+
result[k]=v
|
119
|
+
end
|
120
|
+
end
|
121
|
+
self.each do |k,v|
|
122
|
+
result[k]=v unless result.key?(k)
|
123
|
+
end
|
124
|
+
return result
|
125
|
+
end
|
126
|
+
|
127
|
+
def or(other)
|
128
|
+
Matcher.cast({
|
129
|
+
OR => (self.dnf + other.dnf)
|
130
|
+
})
|
131
|
+
end
|
132
|
+
|
133
|
+
def dnf
|
134
|
+
base = self.dup
|
135
|
+
self_or = base.delete(OR,[Matcher.new])
|
136
|
+
return self_or.map do |sub|
|
137
|
+
sub.and(base)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.and(*args)
|
142
|
+
args.compact!
|
143
|
+
return nil unless args.any?
|
144
|
+
result = Matcher.new
|
145
|
+
args.each do |arg|
|
146
|
+
result = result.and(Matcher.cast(arg))
|
147
|
+
end
|
148
|
+
return result
|
149
|
+
end
|
150
|
+
|
151
|
+
protected
|
152
|
+
def get_value(object,key)
|
153
|
+
key.split('.').each do |part|
|
154
|
+
object = object.send(part)
|
155
|
+
return nil if object.nil?
|
156
|
+
end
|
157
|
+
return object
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
data/lib/splash/namespace.rb
CHANGED
@@ -1,29 +1,93 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
1
2
|
require "logger"
|
3
|
+
require "delegate"
|
2
4
|
module Splash
|
3
|
-
class
|
5
|
+
class Namespace
|
6
|
+
|
7
|
+
NAMESPACES = Hash.new do |hash,key|
|
8
|
+
if( key == :default )
|
9
|
+
hash[:default] = Namespace.new
|
10
|
+
else
|
11
|
+
raise "unknow namespace #{key.inspect}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ClassNotFound < NameError
|
16
|
+
end
|
4
17
|
|
5
|
-
URI_MATCHER = /^mongodb:\/\/([^\/:@]+)(:\d+|)\/(.*)/
|
6
18
|
|
7
|
-
|
19
|
+
URI_MATCHER = /^mongodb:\/\/([^\/:@]+)(:\d+|)\/(.*)/.freeze
|
20
|
+
|
21
|
+
#LOGGER = ::Logger.new(STDOUT)
|
8
22
|
#LOGGER.level = Logger::WARN
|
9
23
|
|
10
24
|
attr_reader :db
|
11
25
|
|
26
|
+
class LoggerDelegator < Logger
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
super(nil)
|
30
|
+
end
|
31
|
+
|
32
|
+
def add(*args,&block)
|
33
|
+
Splash::Namespace.logger.add(*args,&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def <<(msg)
|
37
|
+
Splash::Namespace.logger << msg
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
class << self
|
43
|
+
attr_accessor :logger
|
44
|
+
end
|
45
|
+
|
46
|
+
self.logger = ::Logger.new(STDOUT)
|
47
|
+
self.logger.level = Logger::WARN
|
48
|
+
|
12
49
|
def self.default
|
13
|
-
|
50
|
+
NAMESPACES[:default]
|
14
51
|
end
|
15
52
|
|
16
53
|
def self.default=(value)
|
17
|
-
|
54
|
+
NAMESPACES[:default] = value
|
55
|
+
end
|
56
|
+
|
57
|
+
def class_to_collection_name(klass_name,recheck = true)
|
58
|
+
cn = klass_name.gsub(/(<?[a-z])([A-Z])/){ |c| c[0,1]+"_"+c[1,2].downcase }
|
59
|
+
cn.gsub!(/::([A-Z])/){|d| "." + (d[2,1].downcase) }
|
60
|
+
cn[0...1] = cn[0...1].downcase
|
61
|
+
if recheck
|
62
|
+
raise "#{klass_name} won't be findable as #{cn} ( got: #{self.collection_to_class_name(cn,false)} )" if self.collection_to_class_name(cn,false) != klass_name
|
63
|
+
end
|
64
|
+
return cn
|
65
|
+
end
|
66
|
+
|
67
|
+
def collection_to_class_name(collection_name, recheck = true)
|
68
|
+
kn = collection_name.gsub(/_([a-z])/){|c| c[1,2].upcase }
|
69
|
+
kn.gsub!(/\.[a-z]/){|c| '::'+c[1,1].upcase}
|
70
|
+
kn[0...1] = kn[0...1].upcase
|
71
|
+
if recheck
|
72
|
+
raise "#{collection} won't find a class #{kn} ( got: #{self.class_to_collection_name(kn,false)} )" if self.class_to_collection_name(kn,false) != collection_name
|
73
|
+
end
|
74
|
+
return kn
|
18
75
|
end
|
19
76
|
|
20
77
|
def initialize(uri='mongodb://localhost/splash')
|
21
78
|
match = URI_MATCHER.match(uri)
|
22
79
|
if match.nil?
|
23
|
-
@db = Mongo::Connection.from_uri(uri,:logger=>
|
80
|
+
@db = Mongo::Connection.from_uri(uri,:logger=>LoggerDelegator.new)
|
24
81
|
else
|
25
|
-
@db = Mongo::Connection.new(match[1],match[2].length==0 ? nil : match[2].to_i,:logger=>
|
82
|
+
@db = Mongo::Connection.new(match[1],match[2].length==0 ? nil : match[2].to_i,:logger=>LoggerDelegator.new).db(match[3])
|
26
83
|
end
|
84
|
+
@uri = uri
|
85
|
+
#@class_collection_map = {}
|
86
|
+
#@top_classes = {}
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_s
|
90
|
+
@uri
|
27
91
|
end
|
28
92
|
|
29
93
|
def clear!
|
@@ -33,18 +97,55 @@ module Splash
|
|
33
97
|
rescue Mongo::OperationFailure
|
34
98
|
end
|
35
99
|
end
|
100
|
+
#@top_classes.clear
|
101
|
+
#@class_collection_map.clear
|
36
102
|
return true
|
37
103
|
end
|
38
104
|
|
39
105
|
def collection_for(klass)
|
40
|
-
|
41
|
-
|
106
|
+
thiz = self
|
107
|
+
last_named = nil
|
108
|
+
klass.self_and_superclasses do |k|
|
109
|
+
unless k.respond_to?(:namespace)
|
110
|
+
break
|
111
|
+
end
|
112
|
+
unless k.namespace == thiz
|
113
|
+
raise "Namespace mismatch: #{k} ( namespace: #{k.namespace.to_s} ) is a Superclass of #{klass} ( namespace: #{thiz.to_s} )."
|
114
|
+
end
|
115
|
+
if k.named?
|
116
|
+
last_named = k
|
117
|
+
end
|
118
|
+
if last_named and k.instance_variable_defined?('@is_collection_base')
|
119
|
+
return collection(self.class_to_collection_name(last_named.to_s))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
raise "Couldn't find a collection for #{klass}!"
|
123
|
+
return nil
|
124
|
+
=begin
|
42
125
|
return @class_collection_map[klass] if( @class_collection_map.key? klass)
|
43
126
|
|
44
127
|
|
45
|
-
classes=
|
128
|
+
classes=[]
|
129
|
+
|
130
|
+
thiz = self
|
131
|
+
|
132
|
+
klass.self_and_superclasses do |k|
|
133
|
+
if @class_collection_map.key? k
|
134
|
+
collection = @class_collection_map[k]
|
135
|
+
classes.each do |sk|
|
136
|
+
@class_collection_map[sk]=collection
|
137
|
+
end
|
138
|
+
return collection
|
139
|
+
elsif k.named? and k.respond_to?(:namespace) && k.namespace == thiz
|
140
|
+
classes.push(k)
|
141
|
+
else
|
142
|
+
break
|
143
|
+
end
|
144
|
+
end
|
46
145
|
|
47
|
-
|
146
|
+
@class_collection_map.key? classes.last
|
147
|
+
|
148
|
+
collection=@db.collection(self.class_to_collection_name(classes.last.to_s))
|
48
149
|
|
49
150
|
@top_classes[collection.name] = classes.last
|
50
151
|
|
@@ -53,15 +154,48 @@ module Splash
|
|
53
154
|
end
|
54
155
|
|
55
156
|
return collection
|
157
|
+
=end
|
56
158
|
end
|
57
|
-
|
159
|
+
=begin
|
58
160
|
def class_for(collection_name)
|
59
|
-
@top_classes[collection_name]
|
161
|
+
return @top_classes[collection_name] if @top_classes[collection_name]
|
162
|
+
return @top_classes[collection_name] = Kernel.const_get(collection_to_class_name(collection_name))
|
163
|
+
end
|
164
|
+
=end
|
165
|
+
def class_for(collection_name)
|
166
|
+
begin
|
167
|
+
return collection_to_class_name(collection_name).split('::').inject(Kernel) do |memo,obj|
|
168
|
+
memo.const_get(obj)
|
169
|
+
end
|
170
|
+
catch NameError => e
|
171
|
+
raise ClassNotFound.new('No Class found for ' + collection_name +'. Error received: ' + e.message)
|
172
|
+
end
|
173
|
+
|
174
|
+
#return Kernel.const_get(collection_to_class_name(collection_name))
|
60
175
|
end
|
61
176
|
|
62
177
|
def dereference(dbref)
|
63
|
-
|
178
|
+
begin
|
179
|
+
klass = self.class_for(dbref.namespace)
|
180
|
+
return Saveable.load( @db.dereference(dbref), klass)
|
181
|
+
rescue ClassNotFound => e
|
182
|
+
warn e.message
|
183
|
+
end
|
184
|
+
return Saveable.load( @db.dereference(dbref) )
|
185
|
+
#self.class_for(dbref.namespace).conditions('_id'=>dbref.object_id).first
|
64
186
|
end
|
65
187
|
|
188
|
+
def collection(name)
|
189
|
+
@db.collection(name)
|
190
|
+
end
|
191
|
+
=begin
|
192
|
+
def register(klass,collection,top=true)
|
193
|
+
collection = self.collection(collection) if collection.kind_of? String
|
194
|
+
@class_collection_map[klass] = collection
|
195
|
+
if top
|
196
|
+
@top_classes[collection.name] = klass
|
197
|
+
end
|
198
|
+
end
|
199
|
+
=end
|
66
200
|
end
|
67
|
-
end
|
201
|
+
end
|