splash 0.0.1.alpha

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