Rubernate 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +30 -0
- data/db/mysql.sql +35 -0
- data/db/oracle.sql +29 -0
- data/lib/rubernate/callbacks.rb +70 -0
- data/lib/rubernate/entity.rb +85 -0
- data/lib/rubernate/impl/dbigeneric.rb +286 -0
- data/lib/rubernate/impl/dbimysql.rb +28 -0
- data/lib/rubernate/impl/dbioracle.rb +29 -0
- data/lib/rubernate/impl/memory.rb +147 -0
- data/lib/rubernate/mixins.rb +91 -0
- data/lib/rubernate/peer.rb +70 -0
- data/lib/rubernate/queries.rb +444 -0
- data/lib/rubernate/runtime.rb +215 -0
- data/lib/rubernate.rb +127 -0
- data/tests/README +16 -0
- data/tests/all_tests.rb +12 -0
- data/tests/config.rb +34 -0
- data/tests/rubernate/callbacks_test.rb +120 -0
- data/tests/rubernate/fixtures.rb +27 -0
- data/tests/rubernate/impl/dbigeneric_stub.rb +635 -0
- data/tests/rubernate/impl/dbimysql_test.rb +19 -0
- data/tests/rubernate/impl/dbioracle_test.rb +19 -0
- data/tests/rubernate/impl/memory_test.rb +188 -0
- data/tests/rubernate/queries_test.rb +176 -0
- data/tests/rubernate/rubernate_test.rb +326 -0
- data/tests/rubernate/utils_test.rb +42 -0
- metadata +74 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'rubernate/queries'
|
2
|
+
|
3
|
+
module Rubernate
|
4
|
+
module Memory
|
5
|
+
module Queries
|
6
|
+
class Query < Rubernate::Queries::Generic::Expr
|
7
|
+
def params v
|
8
|
+
case v
|
9
|
+
when Array: v
|
10
|
+
when Hash: v.values
|
11
|
+
else [v]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
class Factory < Rubernate::Queries::Factory
|
16
|
+
def initialize
|
17
|
+
super
|
18
|
+
@query = Query
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
class Reference
|
23
|
+
attr_reader :primary_key, :object_class
|
24
|
+
def initialize pk, klass
|
25
|
+
@primary_key, @object_class = pk, klass
|
26
|
+
end
|
27
|
+
|
28
|
+
def == other
|
29
|
+
@primary_key == other.primary_key and
|
30
|
+
@object_class == other.object_class
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class RuntimeFactory
|
35
|
+
def initialize
|
36
|
+
@pk, @database = 1001, {}
|
37
|
+
end
|
38
|
+
def pk_next
|
39
|
+
@pk+= 1
|
40
|
+
end
|
41
|
+
def create
|
42
|
+
Runtime.new @database, self
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Runtime < Rubernate::Runtime
|
47
|
+
attr :pk, true
|
48
|
+
|
49
|
+
# Database is maps {pk => [class, {key => value}]}
|
50
|
+
def initialize database, pk_seq
|
51
|
+
super()
|
52
|
+
@pk_seq, @database, @factory = pk_seq, database, Queries::Factory.new
|
53
|
+
end
|
54
|
+
|
55
|
+
# Loads object by primary_key
|
56
|
+
def load_by_pk pk
|
57
|
+
klass, data = @database[pk]
|
58
|
+
return nil unless data
|
59
|
+
object = instantiate pk, klass, load_peer(data)
|
60
|
+
post_load object
|
61
|
+
object
|
62
|
+
end
|
63
|
+
|
64
|
+
# Loads objects by query
|
65
|
+
def load_by_query query, params=[]
|
66
|
+
result = []
|
67
|
+
if query =~ /\s*all\s*/
|
68
|
+
result = @database.keys
|
69
|
+
else
|
70
|
+
result = query.split.collect! {|pk_str| pk_str.to_i} * @database.keys
|
71
|
+
end
|
72
|
+
result.collect! {|pk| load_by_pk pk}
|
73
|
+
end
|
74
|
+
|
75
|
+
def load_peer data
|
76
|
+
peer = Rubernate::Peer.new
|
77
|
+
data.each_pair {|p_name, p_data| peer[p_name] = load_param p_data}
|
78
|
+
peer
|
79
|
+
end
|
80
|
+
|
81
|
+
def load_param p_data
|
82
|
+
case p_data
|
83
|
+
when Reference:
|
84
|
+
instantiate p_data.primary_key, p_data.object_class
|
85
|
+
when Array:
|
86
|
+
p_data.collect{|ref| instantiate(ref.primary_key, ref.object_class)}
|
87
|
+
when Hash:
|
88
|
+
result = {}
|
89
|
+
p_data.each_pair {|key, ref|
|
90
|
+
result[key] = instantiate(ref.primary_key, ref.object_class)
|
91
|
+
}
|
92
|
+
result
|
93
|
+
when Fixnum
|
94
|
+
p_data
|
95
|
+
else p_data.dup
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Saves objects and clears dirty flag
|
100
|
+
def save objects
|
101
|
+
objects = [objects] unless objects.kind_of? Array
|
102
|
+
for object in objects
|
103
|
+
@database[object.primary_key] = [object.class, save_peer(object.peer)]
|
104
|
+
object.peer.dirty = false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def save_peer peer
|
109
|
+
result = {}
|
110
|
+
peer.each {|key, value| result[key] = save_param value }
|
111
|
+
result
|
112
|
+
end
|
113
|
+
|
114
|
+
def save_param value
|
115
|
+
case value
|
116
|
+
when Entity:
|
117
|
+
Reference.new value.primary_key, value.class
|
118
|
+
when Array:
|
119
|
+
value.collect{ |item| Reference.new item.primary_key, item.class }
|
120
|
+
when Hash:
|
121
|
+
result = {}
|
122
|
+
value.each {|key, value|
|
123
|
+
result[key] = Reference.new value.primary_key, value.class
|
124
|
+
}
|
125
|
+
result
|
126
|
+
when Fixnum:
|
127
|
+
value
|
128
|
+
else
|
129
|
+
value.dup
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Creates object. Object must have peer.
|
134
|
+
def create object
|
135
|
+
object.primary_key = @pk_seq.pk_next
|
136
|
+
save object
|
137
|
+
object.primary_key
|
138
|
+
end
|
139
|
+
|
140
|
+
# Deletes object
|
141
|
+
def delete object
|
142
|
+
@database.delete object.primary_key
|
143
|
+
object.primary_key = nil
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
class Module
|
4
|
+
# Finds class defined in current module.
|
5
|
+
# :call-seq:
|
6
|
+
# Module.find_class 'ClassName' # finds class 'ClassName' in global space
|
7
|
+
# SomeModule.find_class 'ClassName' # finds class 'ClassName' in module SomeModule
|
8
|
+
#
|
9
|
+
def find_class name, scaned = Set.new
|
10
|
+
scaned << self
|
11
|
+
for unit in module_units - scaned
|
12
|
+
return unit if unit.name == name
|
13
|
+
result = unit.find_class name, scaned
|
14
|
+
return result if result
|
15
|
+
end
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# Iterates over all classes.
|
20
|
+
def find_all scaned = Set.new, &filter # :yeild: class
|
21
|
+
scaned << self
|
22
|
+
result = []
|
23
|
+
for unit in module_units - scaned
|
24
|
+
if unit.is_a? Class
|
25
|
+
result << unit if yield unit
|
26
|
+
end
|
27
|
+
result.concat unit.find_all(scaned, &filter)
|
28
|
+
end
|
29
|
+
return result
|
30
|
+
end
|
31
|
+
private
|
32
|
+
# Returns all modules and classes defined inside this module
|
33
|
+
def module_units
|
34
|
+
constants.collect { |name| const_get name }.
|
35
|
+
find_all { |const|
|
36
|
+
const.class == Class or
|
37
|
+
const.class == Module
|
38
|
+
}.to_set
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Class
|
43
|
+
# Creates persistent property in a class
|
44
|
+
# The class MUST have an empty constructor.
|
45
|
+
def persistent *props
|
46
|
+
include Rubernate::Entity
|
47
|
+
for prop in props
|
48
|
+
define_accessor prop
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns subclasses names (self is not included)
|
53
|
+
attr :subclasses
|
54
|
+
|
55
|
+
# Register +klass+ as descendant in all superclasses
|
56
|
+
def update_superclass klass
|
57
|
+
@subclasses ||= []
|
58
|
+
@subclasses << klass.name if klass != self
|
59
|
+
superclass.update_superclass klass if superclass
|
60
|
+
end
|
61
|
+
|
62
|
+
# Defines accessor for persistent property. Should not be used by users
|
63
|
+
def define_accessor name
|
64
|
+
module_eval %{
|
65
|
+
def #{name}
|
66
|
+
ensure_loaded
|
67
|
+
peer[:#{name}]
|
68
|
+
end
|
69
|
+
|
70
|
+
def #{name}= value
|
71
|
+
ensure_loaded
|
72
|
+
peer.set :#{name}, value, &on_change_callback
|
73
|
+
end
|
74
|
+
}
|
75
|
+
end
|
76
|
+
private :persistent, :define_accessor
|
77
|
+
|
78
|
+
alias old_inherited inherited
|
79
|
+
# Collect subclasses
|
80
|
+
def inherited klass
|
81
|
+
old_inherited klass
|
82
|
+
update_superclass klass
|
83
|
+
end
|
84
|
+
|
85
|
+
# Update classes already loaded
|
86
|
+
Module.find_all do |klass|
|
87
|
+
klass.update_superclass klass
|
88
|
+
end
|
89
|
+
# Hide update_superclass
|
90
|
+
protected :update_superclass
|
91
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Rubernate
|
2
|
+
# Represent objects peer - properties holder.
|
3
|
+
class Peer < Hash
|
4
|
+
# Sets peers dirty flag to specified value.
|
5
|
+
# And resets dirty flag for all properites which stored in this peer.
|
6
|
+
def dirty= f_dirty
|
7
|
+
@dirty = f_dirty
|
8
|
+
for value in values
|
9
|
+
value.memorize! if value.is_a? Container
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# If peers dirty flag set to true returns it immediately
|
14
|
+
# else iterate over all properties and returns true if dirty
|
15
|
+
# property apperas. +on_modify+ callback invoked if dirty is +true+.
|
16
|
+
def dirty? &on_modify
|
17
|
+
return true if @dirty
|
18
|
+
f_dirty, name, value, old_value = false
|
19
|
+
each do |name, value|
|
20
|
+
if value.is_a? Container
|
21
|
+
old_value = value.recall
|
22
|
+
if old_value
|
23
|
+
f_dirty = dirty = true
|
24
|
+
break
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
yield name, old_value, value if block_given? and f_dirty
|
29
|
+
f_dirty
|
30
|
+
end
|
31
|
+
|
32
|
+
alias old_set []=
|
33
|
+
|
34
|
+
def []= key, value
|
35
|
+
set key, value
|
36
|
+
end
|
37
|
+
|
38
|
+
def set key, value, &on_change
|
39
|
+
old_value = self[key]
|
40
|
+
return if old_value == value
|
41
|
+
@dirty = true
|
42
|
+
if value.is_a? Array or value.is_a? Hash
|
43
|
+
value.extend Container
|
44
|
+
end
|
45
|
+
if value
|
46
|
+
old_set key, value
|
47
|
+
else
|
48
|
+
delete key
|
49
|
+
end
|
50
|
+
yield key, old_value, value if block_given?
|
51
|
+
end
|
52
|
+
|
53
|
+
# This module is for including to collections of Persistent
|
54
|
+
# objects. It adds fied that contains copy of collection
|
55
|
+
# and controls dirty status of collection.
|
56
|
+
module Container
|
57
|
+
attr_writer :copy
|
58
|
+
|
59
|
+
# Remember state of collection
|
60
|
+
def memorize!
|
61
|
+
@copy = dup
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns remembered state of collection if it different now
|
65
|
+
def recall
|
66
|
+
return self != @copy ? @copy : nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|