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.
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