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.
Files changed (55) hide show
  1. data/Gemfile +5 -0
  2. data/lib/splash.rb +22 -26
  3. data/lib/splash/acts_as_collection.rb +3 -2
  4. data/lib/splash/acts_as_scope.rb +97 -33
  5. data/lib/splash/acts_as_scope_root.rb +2 -1
  6. data/lib/splash/annotated.rb +2 -1
  7. data/lib/splash/application.rb +2 -1
  8. data/lib/splash/attribute.rb +39 -21
  9. data/lib/splash/attributed_struct.rb +2 -1
  10. data/lib/splash/callbacks.rb +37 -0
  11. data/lib/splash/collection.rb +14 -11
  12. data/lib/splash/connection.rb +2 -1
  13. data/lib/splash/constraint.rb +3 -5
  14. data/lib/splash/constraint/all.rb +2 -1
  15. data/lib/splash/constraint/any.rb +2 -1
  16. data/lib/splash/constraint/in.rb +2 -1
  17. data/lib/splash/constraint/not_nil.rb +2 -1
  18. data/lib/splash/created_at.rb +16 -0
  19. data/lib/splash/document.rb +43 -15
  20. data/lib/splash/embed.rb +29 -9
  21. data/lib/splash/has_attributes.rb +68 -37
  22. data/lib/splash/has_collection.rb +79 -0
  23. data/lib/splash/has_constraint.rb +2 -1
  24. data/lib/splash/map_reduce.rb +124 -0
  25. data/lib/splash/matcher.rb +160 -0
  26. data/lib/splash/namespace.rb +149 -15
  27. data/lib/splash/password.rb +2 -1
  28. data/lib/splash/persister.rb +22 -62
  29. data/lib/splash/query_interface.rb +28 -6
  30. data/lib/splash/saveable.rb +5 -104
  31. data/lib/splash/scope.rb +16 -3
  32. data/lib/splash/scope/map_reduce_interface.rb +33 -0
  33. data/lib/splash/scope/options.rb +16 -6
  34. data/lib/splash/updated_at.rb +15 -0
  35. data/lib/splash/validates.rb +3 -1
  36. data/lib/splash/validator.rb +2 -1
  37. data/lib/splash/writeback.rb +46 -0
  38. data/lib/standart_extensions/array.rb +9 -0
  39. data/lib/standart_extensions/bson.rb +122 -0
  40. data/lib/standart_extensions/class.rb +11 -0
  41. data/lib/{splash/standart_extensions → standart_extensions}/hash.rb +6 -1
  42. data/lib/{splash/standart_extensions → standart_extensions}/module.rb +17 -3
  43. data/lib/standart_extensions/object.rb +13 -0
  44. data/splash.gemspec +17 -0
  45. metadata +20 -16
  46. data/lib/splash/scope_delegator.rb +0 -14
  47. data/lib/splash/scope_options.rb +0 -31
  48. data/lib/splash/standart_extensions/object.rb +0 -11
  49. data/lib/splash/standart_extensions/string.rb +0 -5
  50. data/spec/helper.rb +0 -15
  51. data/spec/lib/annotated_spec.rb +0 -87
  52. data/spec/lib/collection_spec.rb +0 -64
  53. data/spec/lib/has_attributes_spec.rb +0 -43
  54. data/spec/lib/has_constraints_spec.rb +0 -53
  55. data/spec/lib/inheritance_spec.rb +0 -69
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require "set"
2
3
 
3
4
  module Splash::HasConstraint
@@ -6,4 +7,4 @@ module Splash::HasConstraint
6
7
  @constraint ||= Splash::Constraint::All.new
7
8
  end
8
9
 
9
- end
10
+ end
@@ -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
@@ -1,29 +1,93 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require "logger"
3
+ require "delegate"
2
4
  module Splash
3
- class NameSpace
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
- LOGGER = ::Logger.new(STDOUT)
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
- @default ||= self.new
50
+ NAMESPACES[:default]
14
51
  end
15
52
 
16
53
  def self.default=(value)
17
- @default = value
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=>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=>LOGGER).db(match[3])
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
- @class_collection_map ||= {}
41
- @top_classes ||={}
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=Saveable.get_class_hierachie(klass)
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
- collection=@db.collection(classes.last.to_s.gsub(/(<?[a-z])([A-Z])/){ |c| c[0,1]+"_"+c[1,2].downcase }.gsub("::",".").downcase)
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
- self.class_for(dbref.namespace)[dbref.object_id]
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