splash 0.0.1.alpha → 0.0.1.beta
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/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
|