splash 0.0.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/Rakefile +19 -0
  2. data/lib/splash/acts_as_collection.rb +55 -0
  3. data/lib/splash/acts_as_scope.rb +118 -0
  4. data/lib/splash/acts_as_scope_root.rb +10 -0
  5. data/lib/splash/annotated.rb +34 -0
  6. data/lib/splash/application.rb +36 -0
  7. data/lib/splash/attribute.rb +45 -0
  8. data/lib/splash/attributed_struct.rb +8 -0
  9. data/lib/splash/collection.rb +52 -0
  10. data/lib/splash/connection.rb +26 -0
  11. data/lib/splash/constraint/all.rb +24 -0
  12. data/lib/splash/constraint/any.rb +13 -0
  13. data/lib/splash/constraint/in.rb +13 -0
  14. data/lib/splash/constraint/not_nil.rb +9 -0
  15. data/lib/splash/constraint.rb +62 -0
  16. data/lib/splash/document.rb +38 -0
  17. data/lib/splash/embed.rb +37 -0
  18. data/lib/splash/has_attributes.rb +146 -0
  19. data/lib/splash/has_constraint.rb +9 -0
  20. data/lib/splash/namespace.rb +67 -0
  21. data/lib/splash/password.rb +31 -0
  22. data/lib/splash/persister.rb +66 -0
  23. data/lib/splash/query_interface.rb +84 -0
  24. data/lib/splash/saveable.rb +141 -0
  25. data/lib/splash/scope/options.rb +58 -0
  26. data/lib/splash/scope.rb +46 -0
  27. data/lib/splash/scope_delegator.rb +14 -0
  28. data/lib/splash/scope_options.rb +31 -0
  29. data/lib/splash/standart_extensions/hash.rb +45 -0
  30. data/lib/splash/standart_extensions/module.rb +73 -0
  31. data/lib/splash/standart_extensions/object.rb +11 -0
  32. data/lib/splash/standart_extensions/string.rb +5 -0
  33. data/lib/splash/validates.rb +75 -0
  34. data/lib/splash/validator.rb +24 -0
  35. data/lib/splash.rb +35 -0
  36. data/spec/helper.rb +15 -0
  37. data/spec/lib/annotated_spec.rb +87 -0
  38. data/spec/lib/collection_spec.rb +64 -0
  39. data/spec/lib/has_attributes_spec.rb +43 -0
  40. data/spec/lib/has_constraints_spec.rb +53 -0
  41. data/spec/lib/inheritance_spec.rb +69 -0
  42. metadata +139 -0
@@ -0,0 +1,146 @@
1
+ module Splash
2
+ module HasAttributes
3
+
4
+ class Attributes < Hash
5
+ def load(raw)
6
+ raw.each do |key,value|
7
+ key=key.to_s
8
+
9
+
10
+ if value.kind_of? type(key).persisted_class
11
+ self[key]=value
12
+ elsif Splash::Persister.raw? value
13
+ @raw[key]=value
14
+ else
15
+
16
+ puts value.class
17
+ puts type(key).persisted_class
18
+ raise "Don't know what to do with #{key}= #{value.inspect}"
19
+ end
20
+ end
21
+ end
22
+
23
+ def load_raw(raw)
24
+ @raw.update(raw)
25
+ end
26
+
27
+ def [](key)
28
+ return super if( self.key? key )
29
+ t=type(key)
30
+ if( @raw.key? key )
31
+ value = t.read(@raw[key])
32
+ else
33
+ value = t.default
34
+ end
35
+ return self[key]=value
36
+ end
37
+
38
+ def initialize(klass)
39
+ super()
40
+ @class = klass
41
+ @raw = {}
42
+ end
43
+
44
+ def raw
45
+ write_back!
46
+ return @raw
47
+ end
48
+
49
+ def type(key)
50
+ return @class.attribute(key)
51
+ end
52
+
53
+ protected
54
+ def write_back!
55
+ self.each do |key,value|
56
+ @raw[key]=type(key).write(value)
57
+ end
58
+ end
59
+ end
60
+
61
+ class << self
62
+ def included(base)
63
+ base.extend(ClassMethods)
64
+ base.instance_eval do
65
+ merged_inheritable_attr :attributes,{}
66
+ end
67
+ end
68
+
69
+ def get_persister(*args,&block)
70
+ klass = Object
71
+ if args.length > 0
72
+ if args.first.respond_to? :persister
73
+ klass = args.shift
74
+ end
75
+ end
76
+ type = (klass).persister
77
+ result=type.new(*args,&block)
78
+ result.persist_class(klass)
79
+ return result
80
+ end
81
+ end
82
+
83
+ def attributes
84
+ @attributes ||= Attributes.new(self.class)
85
+ end
86
+
87
+ def attribute(name)
88
+ self.class.attribute(name)
89
+ end
90
+
91
+ def respond_to?(meth)
92
+ return true if meth =~/^([a-zA-Z_]+)\?$/
93
+ return true if meth =~ /^([a-zA-Z_]+)=$/
94
+ super(meth)
95
+ end
96
+
97
+ def inspect
98
+ "#{self.class.to_s}{#{attributes.inspect}}"
99
+ end
100
+
101
+ def to_saveable
102
+ attributes.raw
103
+ end
104
+
105
+ def method_missing(meth,*args,&block)
106
+ if( meth.to_s =~ /^([a-zA-Z_]+)=$/ && args.size == 1 )
107
+ # setter
108
+ return attributes[$1]=args.first
109
+ elsif( meth.to_s =~ /^([a-zA-Z_]+)$/ && args.size == 0 )
110
+ return attributes[$1]
111
+ elsif( meth.to_s =~ /^([a-zA-Z_]+)\?$/ && args.size == 0 )
112
+ return attributes.key?(meth.to_s)
113
+ end
114
+ super
115
+ end
116
+
117
+ module ClassMethods
118
+
119
+ def attribute_accessor(name)
120
+ self.class_eval <<-CODE, __FILE__, __LINE__
121
+ def #{name.to_s}() return attributes[#{name.to_s.inspect}] end
122
+ def #{name.to_s}=(value) return attributes[#{name.to_s.inspect}] = value end
123
+ CODE
124
+ end
125
+
126
+ def def_attribute(name,*args,&block)
127
+ name = name.to_s
128
+ attr=attributes[name]=Splash::Attribute.new(*args, &block)
129
+ attribute_accessor(name)
130
+ return attr
131
+ end
132
+
133
+ def attribute(name,create = true)
134
+ name = name.to_s
135
+ each_attributes do |attr|
136
+ return attr[name] if attr.key? name
137
+ end
138
+ if create
139
+ return def_attribute(name)
140
+ end
141
+ return nil
142
+ end
143
+
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,9 @@
1
+ require "set"
2
+
3
+ module Splash::HasConstraint
4
+
5
+ def constraint
6
+ @constraint ||= Splash::Constraint::All.new
7
+ end
8
+
9
+ end
@@ -0,0 +1,67 @@
1
+ require "logger"
2
+ module Splash
3
+ class NameSpace
4
+
5
+ URI_MATCHER = /^mongodb:\/\/([^\/:@]+)(:\d+|)\/(.*)/
6
+
7
+ LOGGER = ::Logger.new(STDOUT)
8
+ #LOGGER.level = Logger::WARN
9
+
10
+ attr_reader :db
11
+
12
+ def self.default
13
+ @default ||= self.new
14
+ end
15
+
16
+ def self.default=(value)
17
+ @default = value
18
+ end
19
+
20
+ def initialize(uri='mongodb://localhost/splash')
21
+ match = URI_MATCHER.match(uri)
22
+ if match.nil?
23
+ @db = Mongo::Connection.from_uri(uri,:logger=>LOGGER)
24
+ else
25
+ @db = Mongo::Connection.new(match[1],match[2].length==0 ? nil : match[2].to_i,:logger=>LOGGER).db(match[3])
26
+ end
27
+ end
28
+
29
+ def clear!
30
+ @db.collections.each do |collection|
31
+ begin
32
+ collection.drop
33
+ rescue Mongo::OperationFailure
34
+ end
35
+ end
36
+ return true
37
+ end
38
+
39
+ def collection_for(klass)
40
+ @class_collection_map ||= {}
41
+ @top_classes ||={}
42
+ return @class_collection_map[klass] if( @class_collection_map.key? klass)
43
+
44
+
45
+ classes=Saveable.get_class_hierachie(klass)
46
+
47
+ collection=@db.collection(classes.last.to_s.gsub(/(<?[a-z])([A-Z])/){ |c| c[0,1]+"_"+c[1,2].downcase }.gsub("::",".").downcase)
48
+
49
+ @top_classes[collection.name] = classes.last
50
+
51
+ classes.each do |klass|
52
+ @class_collection_map[klass]=collection
53
+ end
54
+
55
+ return collection
56
+ end
57
+
58
+ def class_for(collection_name)
59
+ @top_classes[collection_name]
60
+ end
61
+
62
+ def dereference(dbref)
63
+ self.class_for(dbref.namespace)[dbref.object_id]
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,31 @@
1
+ require 'digest/sha1'
2
+ require 'securerandom'
3
+
4
+ class Splash::Password
5
+
6
+ include Splash::Embed
7
+
8
+ DIGESTER = Digest::SHA1
9
+
10
+ def_attribute( 'salt' )
11
+
12
+ def_attribute( 'hash' )
13
+
14
+ def matches?(string)
15
+ DIGESTER.hexdigest("#{string}#{self.salt}") == self.hash
16
+ end
17
+
18
+ def initialize(str_or_args={})
19
+ if str_or_args.kind_of? String
20
+ slt = SecureRandom.hex
21
+ hash = DIGESTER.hexdigest("#{str_or_args}#{slt}")
22
+ super({'salt'=>slt,'hash'=>hash})
23
+ elsif str_or_args.kind_of? Hash
24
+ super(str_or_args)
25
+ else
26
+ super({})
27
+ end
28
+ end
29
+
30
+
31
+ end
@@ -0,0 +1,66 @@
1
+ class Splash::Persister
2
+
3
+ RAW_TYPES=[String,NilClass,Numeric,FalseClass,TrueClass,BSON::ObjectID,Time]
4
+
5
+ attr_reader :persisted_class
6
+
7
+ def read(value)
8
+ return value
9
+ end
10
+
11
+ def write(value)
12
+ return value
13
+ end
14
+
15
+ def self.raw?(value)
16
+ return true if RAW_TYPES.any? do |type| value.class == type end
17
+ if value.kind_of?(Hash) || value.kind_of?(Array)
18
+ value.each do |key,val|
19
+ return false unless self.raw?(key)
20
+ return false unless self.raw?(val)
21
+ end
22
+ return true
23
+ elsif value.kind_of?(Array)
24
+ value.each do |sub|
25
+ return false unless self.raw?(sub)
26
+ end
27
+ return true
28
+ end
29
+ return false
30
+ end
31
+
32
+ def initialize(config={},&block)
33
+ self.config= default_config.merge(config)
34
+ if block_given?
35
+ instance_eval &block
36
+ end
37
+ end
38
+
39
+ def persist_class(klass)
40
+ @persisted_class=klass
41
+ end
42
+
43
+ def persisted_class()
44
+ @persisted_class
45
+ end
46
+
47
+ def bind_to(klass)
48
+
49
+ end
50
+
51
+ def default(object)
52
+ @config[:default]
53
+ end
54
+
55
+ def missing(object)
56
+ nil
57
+ end
58
+
59
+ protected
60
+ def default_config
61
+ {:default=>nil,:read=>nil,:write=>nil}
62
+ end
63
+ def config=(conf)
64
+ @config=conf
65
+ end
66
+ end
@@ -0,0 +1,84 @@
1
+ module Splash
2
+ module QueryInterface
3
+
4
+ %w(preload nopreload limit conditions fieldmode with_id extend_scoped sort).each do |fct|
5
+ class_eval <<-CODE, __FILE__,__LINE__
6
+ def #{fct}(*args,&block)
7
+ query(query_#{fct}(*args,&block))
8
+ end
9
+ def #{fct}!(*args,&block)
10
+ query!(query_#{fct}(*args,&block))
11
+ end
12
+ protected :#{fct}!
13
+ CODE
14
+ end
15
+
16
+ protected
17
+
18
+ def query_preload(*field)
19
+ fields=field.flatten.inject({}){|hsh,key|
20
+ hsh[key.to_s]=1
21
+ hsh
22
+ }
23
+ return {:fields=>fields}
24
+ end
25
+
26
+ def query_nopreload(*field)
27
+ fields=field.flatten.inject({}){|hsh,key|
28
+ hsh[key.to_s]=0
29
+ hsh
30
+ }
31
+ return {:fields=>fields}
32
+ end
33
+
34
+ def query_limit(limit)
35
+ return {:limit=>limit}
36
+ end
37
+
38
+ def query_conditions(conditions)
39
+ return {:selector=>conditions}
40
+ end
41
+
42
+ def query_default_attributes(values)
43
+
44
+ end
45
+
46
+ def query_with_id(*args)
47
+ ids=args.flatten.map &:to_bson
48
+ if ids.size == 1
49
+ return {:selector=>{"_id"=>ids.first}}
50
+ else
51
+ return {:selector=>{"_id"=>{"$in"=>ids}}}
52
+ end
53
+ end
54
+ def query_fieldmode(type)
55
+ return {:fieldmode=>type}
56
+ end
57
+
58
+ def query_sort(*args)
59
+ result=[]
60
+ args.each do |arg|
61
+ if arg.kind_of? String
62
+ result << [arg,'ascending']
63
+ elsif arg.kind_of? Hash
64
+ result += arg.to_a
65
+ end
66
+ end
67
+ return {:sort=>result}
68
+ end
69
+
70
+ def query_extend_scoped(*modules,&block)
71
+ if block_given?
72
+ m=Module.new
73
+ m.class_eval &block
74
+ modules << m
75
+ end
76
+
77
+ modules.each do |mod|
78
+ self.extend(mod)
79
+ end
80
+
81
+ return {:extend_scoped => modules}
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,141 @@
1
+ module Splash
2
+ module Saveable
3
+ class Persister < Splash::Persister
4
+ def write(value)
5
+ return nil if value.nil?
6
+ return value._id
7
+ end
8
+
9
+ def read(value)
10
+ return nil if value.nil?
11
+ return persisted_class.with_id(value).first
12
+ end
13
+ end
14
+
15
+ class MultiPersister < Splash::Persister
16
+ def write(value)
17
+ return nil if value.nil?
18
+ return BSON::DBRef.new(value.class.collection.name,value._id)
19
+ end
20
+
21
+ def read(value)
22
+ return nil if value.nil?
23
+ return @persisted_class.namespace.dereference(value)
24
+ end
25
+ end
26
+
27
+ class EmbedPersister < Splash::Persister
28
+ def write(value)
29
+ return nil if value.nil?
30
+ return Saveable.wrap(value)
31
+ end
32
+
33
+ def read(value)
34
+ return nil if value.nil?
35
+ return Saveable.load(value,persisted_class)
36
+ end
37
+ end
38
+
39
+ self.persister= MultiPersister
40
+
41
+
42
+ UPPERCASE=65..90
43
+
44
+ class << self
45
+ def included(base)
46
+ base.extend(ClassMethods)
47
+ base.persister=Splash::Saveable::Persister
48
+ end
49
+
50
+ def unwrap(keys)
51
+ keys.inject({}) do |hsh,(key,val)| hsh[key]=val unless UPPERCASE.include? key[0]; hsh end
52
+ end
53
+
54
+ def wrap(object)
55
+ object.to_saveable.merge("Type"=>Saveable.get_class_hierachie(object.class).map(&:to_s))
56
+ end
57
+
58
+ def load(keys,klass=Hash)
59
+ if keys.nil?
60
+ keys={}
61
+ end
62
+ if keys["Type"]
63
+ klass = Kernel.eval(keys["Type"].first)
64
+ end
65
+ k = klass.new()
66
+ k.attributes.load_raw(self.unwrap(keys))
67
+ return k
68
+ end
69
+
70
+ def to_saveable(obj)
71
+ if obj.respond_to? :to_saveable
72
+ return obj.to_saveable
73
+ end
74
+ return obj
75
+ end
76
+
77
+ def get_class_hierachie(klass)
78
+ base=[]
79
+ begin
80
+ if klass.named?
81
+ base << klass
82
+ end
83
+ #return base unless klass.instance_of? Class
84
+ klass = klass.superclass
85
+ end while ( klass < Splash::HasAttributes )
86
+ return base
87
+ end
88
+ end
89
+
90
+ def namespace
91
+ self.class.namespace
92
+ end
93
+
94
+ def storable_id
95
+ self.attributes["_id"]
96
+ end
97
+
98
+ def store!
99
+ self.attributes["_id"]=self.class.store!(self)
100
+ return self
101
+ end
102
+
103
+ def remove!
104
+ return self.class.collection.remove('_id'=>self._id)
105
+ end
106
+
107
+ def stored?
108
+ return !storable_id.nil?
109
+ end
110
+
111
+ def find_self
112
+ self.class.with_id(self.storable_id)
113
+ end
114
+
115
+ module ClassMethods
116
+
117
+ def store!(object)
118
+ return self.collection.save(
119
+ Saveable.wrap(object)
120
+ );
121
+ end
122
+
123
+ def namespace
124
+ @namespace ||= Splash::NameSpace.default
125
+ end
126
+
127
+ def collection
128
+ @collection ||= namespace.collection_for(self)
129
+ end
130
+
131
+ def [](*args)
132
+ a=args.flatten
133
+ if a.length == 1
134
+ return with_id(a).first
135
+ else
136
+ return with_id(a).finish!
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,58 @@
1
+ class Splash::Scope::Options
2
+
3
+ OPTION_KEYS=[:fields,:limit,:sort]
4
+ def self.cast(hsh)
5
+ if hsh.kind_of? self
6
+ return hsh
7
+ end
8
+ return self.new(hsh)
9
+ end
10
+
11
+ def initialize(hsh=nil)
12
+ @options={:selector=>{},:fieldmode=>:exclude,:extend_scoped=>[],:limit=>0,:sort=>[]}
13
+ @options.merge! hsh if hsh
14
+ @options.freeze
15
+ end
16
+
17
+ def merge(options)
18
+ self.class.new(Splash::Scope::Options.merge_options(@options,options))
19
+ end
20
+
21
+ def self.merge_options(a,b)
22
+ return {
23
+ :selector => a[:selector].deep_merge(b[:selector]),
24
+ :fieldmode => (b[:fieldmode] || a[:fieldmode]),
25
+ :extend_scoped => (a[:extend_scoped] + (b[:extend_scoped] || [])),
26
+ :limit => (b[:limit] || a[:limit]),
27
+ :sort => (a[:sort] + (b[:sort] || []))
28
+ }
29
+ end
30
+
31
+ def extensions
32
+ @options[:extend_scoped] || []
33
+ end
34
+
35
+ def to_h
36
+ @options
37
+ end
38
+
39
+ def selector
40
+ @options[:selector]
41
+ end
42
+
43
+ def options
44
+ opt=@options.reject{|key,value| !(OPTION_KEYS.include? key)}
45
+ if opt.key? :fields
46
+ if @options[:fieldmode]==:eager
47
+ opt.delete :fields
48
+ else
49
+ fieldmode = (@options[:fieldmode]==:include ? 0 : 1)
50
+ opt[:fields]=opt[:fields].reject{|key,value|
51
+ value == fieldmode
52
+ }
53
+ end
54
+
55
+ end
56
+ return opt
57
+ end
58
+ end
@@ -0,0 +1,46 @@
1
+ require "delegate"
2
+ module Splash
3
+ class Scope
4
+
5
+ autoload :Options,Splash::DIR+"/splash/scope/options"
6
+
7
+ include Splash::ActsAsScope
8
+
9
+ def initialize(parent,options)
10
+ @parent_scope=parent.scope_root
11
+ @scope_options=options
12
+ end
13
+
14
+ def scope_root?
15
+ false
16
+ end
17
+
18
+ def scope_root
19
+ @parent_scope
20
+ end
21
+
22
+ def respond_to?(meth)
23
+ load_scope_extensions!
24
+ super
25
+ end
26
+
27
+ private
28
+ def load_scope_extensions!
29
+ unless @scope_extesions_loaded
30
+ @scope_options.extensions.each do |mod|
31
+ self.extend(mod)
32
+ end
33
+ @scope_extesions_loaded = true
34
+ return true
35
+ end
36
+ return false
37
+ end
38
+ def method_missing(meth,*args,&block)
39
+ if load_scope_extensions!
40
+ return self.send(meth,*args,&block)
41
+ end
42
+ super
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,14 @@
1
+ class Splash::ScopeDelegator < Delegator
2
+ def initialize(obj)
3
+
4
+ end
5
+
6
+ def respond_to?(meth)
7
+
8
+ end
9
+
10
+ private
11
+ def method_missing(meth,*args,&block)
12
+
13
+ end
14
+ end
@@ -0,0 +1,31 @@
1
+ class ScopeOptions
2
+
3
+ def self.cast(hsh)
4
+ if hsh.kind_of? self
5
+ return hsh
6
+ end
7
+ return self.new(hsh)
8
+ end
9
+
10
+ def initialize(hsh=nil)
11
+ @options={:selector=>{},:limit=>-1}
12
+ @options.merge! hsh if hsh
13
+
14
+ @options.freeze
15
+ end
16
+
17
+ def merge(options)
18
+ self.class.new(@options.merge(options.to_h))
19
+ end
20
+
21
+ def to_h
22
+ @options
23
+ end
24
+
25
+ def selector
26
+ @options[:selector]
27
+ end
28
+ def options
29
+ @options.reject{|key,value| key == :selector}
30
+ end
31
+ end