og 0.23.0 → 0.24.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ProjectInfo +58 -0
- data/README +5 -4
- data/Rakefile +2 -2
- data/doc/AUTHORS +10 -7
- data/doc/RELEASES +108 -0
- data/lib/og.rb +1 -3
- data/lib/og/collection.rb +4 -4
- data/lib/og/entity.rb +96 -27
- data/lib/og/evolution.rb +78 -0
- data/lib/og/manager.rb +29 -32
- data/lib/og/mixin/hierarchical.rb +1 -1
- data/lib/og/mixin/optimistic_locking.rb +5 -8
- data/lib/og/mixin/orderable.rb +15 -2
- data/lib/og/mixin/schema_inheritance_base.rb +12 -0
- data/lib/og/mixin/taggable.rb +29 -25
- data/lib/og/mixin/timestamped.rb +4 -2
- data/lib/og/mixin/tree.rb +0 -1
- data/lib/og/relation.rb +161 -116
- data/lib/og/relation/all.rb +6 -0
- data/lib/og/relation/belongs_to.rb +4 -1
- data/lib/og/relation/has_many.rb +6 -5
- data/lib/og/relation/joins_many.rb +13 -12
- data/lib/og/relation/refers_to.rb +3 -3
- data/lib/og/store.rb +9 -9
- data/lib/og/store/{filesys.rb → alpha/filesys.rb} +0 -0
- data/lib/og/store/alpha/kirby.rb +284 -0
- data/lib/og/store/{memory.rb → alpha/memory.rb} +2 -0
- data/lib/og/store/{sqlserver.rb → alpha/sqlserver.rb} +6 -6
- data/lib/og/store/kirby.rb +145 -162
- data/lib/og/store/mysql.rb +58 -27
- data/lib/og/store/psql.rb +15 -13
- data/lib/og/store/sql.rb +136 -135
- data/lib/og/store/sqlite.rb +13 -12
- data/lib/og/validation.rb +2 -2
- data/lib/vendor/kbserver.rb +20 -0
- data/lib/vendor/kirbybase.rb +2790 -1601
- data/test/og/CONFIG.rb +79 -0
- data/test/og/mixin/tc_hierarchical.rb +1 -1
- data/test/og/mixin/tc_optimistic_locking.rb +1 -3
- data/test/og/mixin/tc_orderable.rb +42 -1
- data/test/og/mixin/tc_taggable.rb +1 -1
- data/test/og/mixin/tc_timestamped.rb +1 -1
- data/test/og/store/tc_filesys.rb +1 -2
- data/test/og/tc_delete_all.rb +45 -0
- data/test/og/tc_inheritance.rb +10 -38
- data/test/og/tc_join.rb +2 -11
- data/test/og/tc_multiple.rb +3 -16
- data/test/og/tc_override.rb +3 -3
- data/test/og/tc_polymorphic.rb +3 -13
- data/test/og/tc_relation.rb +8 -6
- data/test/og/tc_reverse.rb +2 -11
- data/test/og/tc_select.rb +2 -15
- data/test/og/tc_store.rb +4 -63
- data/test/og/tc_types.rb +1 -2
- metadata +80 -77
data/lib/og/evolution.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
require 'nano/kernel/constant'
|
4
|
+
require 'nano/kernel/assign_with'
|
5
|
+
|
6
|
+
# $DBG = true
|
7
|
+
|
8
|
+
module Og
|
9
|
+
|
10
|
+
class Manager
|
11
|
+
|
12
|
+
# Dump Og managed objects to the filesystem.
|
13
|
+
|
14
|
+
def dump(options = {})
|
15
|
+
classes = options[:classes] || options[:class] || manageable_classes
|
16
|
+
basedir = options[:basedir] || 'ogdump'
|
17
|
+
|
18
|
+
FileUtils.makedirs(basedir)
|
19
|
+
|
20
|
+
for c in [ classes ].flatten
|
21
|
+
Logger.info "Dumping class '#{c}'"
|
22
|
+
all = c.all.map { |obj| obj.properties_to_hash }
|
23
|
+
File.open("#{basedir}/#{c}.yml", 'w') { |f| f << all.to_yaml }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
alias_method :export, :dump
|
27
|
+
|
28
|
+
# Load Og managed objects from the filesystem. This method can apply
|
29
|
+
# optional transformation rules in order to evolve a schema.
|
30
|
+
|
31
|
+
def load(options = {})
|
32
|
+
classes = options[:classes] || manageable_classes
|
33
|
+
basedir = options[:basedir] || 'ogdump'
|
34
|
+
rules = options[:rules] || rules[:evolution] || {}
|
35
|
+
|
36
|
+
classes.each { |c| c.destroy if managed?(c) }
|
37
|
+
unmanage_classes(classes)
|
38
|
+
manage_classes
|
39
|
+
|
40
|
+
for f in Dir["#{basedir}/*.yml"]
|
41
|
+
all = YAML.load(File.read(f))
|
42
|
+
|
43
|
+
unless all.empty?
|
44
|
+
klass = f.split(/\/|\./)[1]
|
45
|
+
|
46
|
+
Logger.info "Loading class '#{klass}'"
|
47
|
+
|
48
|
+
if krules = rules[klass.to_sym]
|
49
|
+
if krules[:self]
|
50
|
+
# Class name changed.
|
51
|
+
Logger.info "Renaming class '#{klass}' to '#{krules[:self]}'"
|
52
|
+
klass = krules[:self]
|
53
|
+
end
|
54
|
+
|
55
|
+
Logger.info "Evolution transformation will be applied!"
|
56
|
+
end
|
57
|
+
|
58
|
+
klass = constant(klass)
|
59
|
+
|
60
|
+
for h in all
|
61
|
+
obj = klass.allocate
|
62
|
+
obj.assign_with(h)
|
63
|
+
if krules
|
64
|
+
krules.each do |old, new|
|
65
|
+
obj.instance_variable_set "@#{new}", h[old]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
obj.insert
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
alias_method :import, :load
|
74
|
+
alias_method :evolve, :load
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
data/lib/og/manager.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'mega/pool'
|
2
|
+
require 'nano/class/descendents'
|
2
3
|
|
3
4
|
require 'og/entity'
|
4
5
|
require 'og/store'
|
@@ -86,17 +87,6 @@ class Manager
|
|
86
87
|
end
|
87
88
|
end
|
88
89
|
|
89
|
-
# Resolves the inheritance for a class.
|
90
|
-
|
91
|
-
def resolve_inheritance(klass)
|
92
|
-
if has_super?(klass)
|
93
|
-
sclass = klass.superclass
|
94
|
-
klass.meta :superclass, sclass
|
95
|
-
klass.meta :schema_inheritance
|
96
|
-
sclass.meta :subclasses, klass
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
90
|
# Resolve polymorphic relations.
|
101
91
|
|
102
92
|
def resolve_polymorphic(klass)
|
@@ -107,25 +97,25 @@ class Manager
|
|
107
97
|
|
108
98
|
def manage(klass)
|
109
99
|
return if managed?(klass) or klass.ancestors.include?(Unmanageable)
|
110
|
-
|
100
|
+
|
111
101
|
info = EntityInfo.new(klass)
|
112
102
|
|
113
103
|
klass.module_eval %{
|
114
104
|
def ==(other)
|
115
|
-
other ? @#{klass.primary_key.
|
105
|
+
other ? @#{klass.primary_key.symbol} == other.#{klass.primary_key.symbol} : false
|
116
106
|
end
|
117
107
|
}
|
118
108
|
|
119
|
-
klass.instance_variable_set '@ogmanager', self
|
120
109
|
klass.class.send(:attr_accessor, :ogmanager)
|
121
|
-
|
110
|
+
klass.instance_variable_set '@ogmanager', self
|
111
|
+
|
122
112
|
Relation.enchant(klass)
|
123
|
-
|
113
|
+
|
124
114
|
# ensure that the superclass is managed before the
|
125
115
|
# subclass.
|
126
116
|
|
127
|
-
manage(klass.superclass) if
|
128
|
-
|
117
|
+
manage(klass.superclass) if manageable?(klass.superclass)
|
118
|
+
|
129
119
|
# FIXME: uggly!
|
130
120
|
store.enchant(klass, self); put_store
|
131
121
|
|
@@ -139,7 +129,7 @@ class Manager
|
|
139
129
|
# Is this class manageable by Og?
|
140
130
|
|
141
131
|
def manageable?(klass)
|
142
|
-
klass.respond_to?(:
|
132
|
+
klass.respond_to?(:properties) and (!klass.properties.empty?) # and klass.ann.this.polymorphic.nil?
|
143
133
|
end
|
144
134
|
|
145
135
|
# Is the class managed by Og?
|
@@ -149,13 +139,6 @@ class Manager
|
|
149
139
|
end
|
150
140
|
alias_method :entity?, :managed?
|
151
141
|
|
152
|
-
# Has this class a superclass?
|
153
|
-
|
154
|
-
def has_super?(klass)
|
155
|
-
manageable?(sclass = klass.superclass) and
|
156
|
-
(klass.metadata.schema_inheritance || sclass.metadata.schema_inheritance)
|
157
|
-
end
|
158
|
-
|
159
142
|
# Use Ruby's advanced reflection features to find
|
160
143
|
# all manageable classes. Managable are all classes that
|
161
144
|
# define Properties.
|
@@ -168,25 +151,39 @@ class Manager
|
|
168
151
|
classes << c
|
169
152
|
end
|
170
153
|
end
|
171
|
-
|
154
|
+
|
172
155
|
return classes
|
173
156
|
end
|
174
157
|
|
175
158
|
# Manage a collection of classes.
|
176
159
|
|
177
160
|
def manage_classes(*classes)
|
178
|
-
classes = manageable_classes.flatten if classes.empty?
|
161
|
+
classes = manageable_classes.flatten # if classes.empty? FIXME!
|
162
|
+
|
163
|
+
classes.each { |c| Relation.resolve_targets(c) }
|
164
|
+
classes.each { |c| Relation.resolve_polymorphic_markers(c) }
|
165
|
+
classes.each { |c| Relation.resolve_polymorphic_relations(c) }
|
166
|
+
|
167
|
+
# The polymorpic resolution step creates more manageable classes.
|
168
|
+
|
169
|
+
classes = manageable_classes.flatten # if classes.empty? FIXME!
|
179
170
|
|
180
171
|
Logger.debug "Og manageable classes: #{classes.inspect}" if $DBG
|
181
172
|
|
182
|
-
classes.each { |c|
|
183
|
-
classes.each { |c| Relation.
|
184
|
-
classes.each { |c| Relation.resolve(c, :resolve_polymorphic) }
|
185
|
-
classes.each { |c| Relation.resolve(c, :resolve_options) }
|
173
|
+
classes.each { |c| Relation.resolve_targets(c) }
|
174
|
+
classes.each { |c| Relation.resolve_names(c) }
|
186
175
|
classes.each { |c| manage(c) }
|
187
176
|
end
|
188
177
|
alias_method :manage_class, :manage_classes
|
189
178
|
|
179
|
+
def unmanage_classes(*classes)
|
180
|
+
classes = manageable_classes.flatten if classes.empty?
|
181
|
+
|
182
|
+
for c in classes
|
183
|
+
@entities.delete_if { |k, v| v.klass == c }
|
184
|
+
end
|
185
|
+
end
|
186
|
+
alias_method :unmanage_class, :unmanage_classes
|
190
187
|
end
|
191
188
|
|
192
189
|
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
require 'mega/
|
1
|
+
require 'mega/dynamod'
|
2
|
+
require 'glue/on_included'
|
2
3
|
|
3
4
|
module Og
|
4
5
|
|
@@ -18,12 +19,8 @@ end
|
|
18
19
|
module Locking
|
19
20
|
property :lock_version, Fixnum, :default => 0
|
20
21
|
pre "@lock_version = 0", :on => :og_insert
|
21
|
-
|
22
|
-
|
23
|
-
PropertyUtils.copy_features(self, base)
|
24
|
-
|
25
|
-
super
|
26
|
-
|
22
|
+
|
23
|
+
on_included %{
|
27
24
|
base.module_eval do
|
28
25
|
def self.enchant
|
29
26
|
self.send :alias_method, :update_without_lock, :update
|
@@ -32,7 +29,7 @@ module Locking
|
|
32
29
|
self.send :alias_method, :save, :save_with_lock
|
33
30
|
end
|
34
31
|
end
|
35
|
-
|
32
|
+
}
|
36
33
|
|
37
34
|
def update_with_lock
|
38
35
|
lock = @lock_version
|
data/lib/og/mixin/orderable.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'mega/
|
1
|
+
require 'mega/dynamod'
|
2
2
|
|
3
3
|
module Og
|
4
4
|
|
@@ -70,7 +70,20 @@ module Orderable
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
def move_to
|
73
|
+
def move_to(dest_position)
|
74
|
+
return if @#{position} == dest_position
|
75
|
+
|
76
|
+
#{base}.transaction do
|
77
|
+
if @#{position} < dest_position
|
78
|
+
#{base}.update("#{position}=(#{position} - 1)", #{cond_and}"#{position} > \#\{@#{position}\} AND #{position} <= \#\{dest_position\}")
|
79
|
+
else
|
80
|
+
#{base}.update("#{position}=(#{position} + 1)", #{cond_and}"#{position} >= \#\{dest_position\} AND #{position} < \#\{@#{position}\} ")
|
81
|
+
end
|
82
|
+
@#{position} = dest_position
|
83
|
+
update_property(:#{position})
|
84
|
+
end
|
85
|
+
|
86
|
+
self
|
74
87
|
end
|
75
88
|
|
76
89
|
def add_to_top
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Og
|
2
|
+
|
3
|
+
# This is a marker module that denotes that the
|
4
|
+
# base class follows the SingleTableInheritance
|
5
|
+
# pattern. Ie, all the subclasses of the base
|
6
|
+
# class are stored in the same schema (table).
|
7
|
+
|
8
|
+
module SchemaInheritanceBase; end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
# * George Moschovitis <gm@navel.gr>
|
data/lib/og/mixin/taggable.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
require 'mega/
|
2
|
-
require 'nano/
|
1
|
+
require 'mega/dynamod'
|
2
|
+
require 'nano/kernel/assign_with'
|
3
|
+
|
4
|
+
require 'og/relation'
|
3
5
|
|
4
6
|
#--
|
5
7
|
# gmosx: make polymorphic work with many_to_many.
|
@@ -45,15 +47,15 @@ module Taggable
|
|
45
47
|
# Helper.
|
46
48
|
|
47
49
|
def self.tags_to_names(the_tags, separator = ' ')
|
48
|
-
if the_tags.is_a? Array
|
50
|
+
if the_tags.is_a? Array
|
49
51
|
names = the_tags
|
50
|
-
elsif the_tags.is_a? String
|
51
|
-
names = the_tags.split(separator)
|
52
|
+
elsif the_tags.is_a? String
|
53
|
+
names = the_tags.split(separator)
|
52
54
|
end
|
53
|
-
|
55
|
+
|
54
56
|
names = names.flatten.uniq.compact
|
55
57
|
|
56
|
-
return names
|
58
|
+
return names
|
57
59
|
end
|
58
60
|
|
59
61
|
def self.append_dynamic_features(base, options = nil)
|
@@ -63,8 +65,10 @@ module Taggable
|
|
63
65
|
}
|
64
66
|
o.update(options) if options
|
65
67
|
|
68
|
+
base.send :include, Og::RelationDSL
|
69
|
+
|
66
70
|
code = ''
|
67
|
-
|
71
|
+
|
68
72
|
unless o[:tag_class]
|
69
73
|
o[:tag_class] = 'Tag'
|
70
74
|
code << %{
|
@@ -96,28 +100,28 @@ module Taggable
|
|
96
100
|
options = {
|
97
101
|
:clear => true
|
98
102
|
}.merge(options)
|
99
|
-
|
103
|
+
|
100
104
|
names = Taggable.tags_to_names(the_tags, #{separator})
|
101
105
|
|
102
106
|
tags.load_members
|
103
107
|
|
104
108
|
# clear the collection if needed.
|
105
|
-
|
106
|
-
self.tags.clear if options[:clear]
|
107
|
-
|
109
|
+
|
110
|
+
self.tags.clear if options[:clear]
|
111
|
+
|
108
112
|
# append the tag names to the collection
|
109
|
-
|
110
|
-
names.each do |name|
|
113
|
+
|
114
|
+
names.each do |name|
|
111
115
|
name = name.strip
|
112
|
-
unless tagged_with?(name)
|
116
|
+
unless tagged_with?(name)
|
113
117
|
unless tag_obj = #{tag_class}.find_by_name(name)
|
114
118
|
tag_obj = #{tag_class}.create(name)
|
115
119
|
end
|
116
120
|
tags << tag_obj
|
117
|
-
end
|
121
|
+
end
|
118
122
|
end
|
119
123
|
|
120
|
-
self.save
|
124
|
+
self.save
|
121
125
|
end
|
122
126
|
}
|
123
127
|
|
@@ -130,18 +134,18 @@ module Taggable
|
|
130
134
|
# Returns an array of strings containing the tags applied to
|
131
135
|
# this object.
|
132
136
|
|
133
|
-
code << %{
|
134
|
-
def tag_names
|
135
|
-
tags.map { |t| t.name }
|
136
|
-
end
|
137
|
-
}
|
137
|
+
code << %{
|
138
|
+
def tag_names
|
139
|
+
tags.map { |t| t.name }
|
140
|
+
end
|
141
|
+
}
|
138
142
|
|
139
143
|
# Checks to see if this object has been tagged
|
140
144
|
# with +tag_name+.
|
141
145
|
|
142
|
-
code << %{
|
143
|
-
def tagged_with?(tag_name)
|
144
|
-
tag_names.include?(tag_name)
|
146
|
+
code << %{
|
147
|
+
def tagged_with?(tag_name)
|
148
|
+
tag_names.include?(tag_name)
|
145
149
|
end
|
146
150
|
alias_method :tagged_by?, :tagged_with?
|
147
151
|
}
|
data/lib/og/mixin/timestamped.rb
CHANGED
@@ -5,9 +5,11 @@ module Og
|
|
5
5
|
# Adds timestamping functionality.
|
6
6
|
|
7
7
|
module Timestamped
|
8
|
+
include Glue::Aspects
|
9
|
+
|
8
10
|
property :create_time, Time
|
9
|
-
property :update_time, Time
|
10
|
-
property :access_time, Time
|
11
|
+
property :update_time, Time, :editor => :none
|
12
|
+
property :access_time, Time, :editor => :none
|
11
13
|
|
12
14
|
before "@create_time = @update_time = Time.now", :on => :og_insert
|
13
15
|
before "@update_time = Time.now", :on => :og_update
|
data/lib/og/mixin/tree.rb
CHANGED
data/lib/og/relation.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
|
-
require 'nano/
|
2
|
-
require 'nano/string/capitalized
|
3
|
-
|
1
|
+
require 'nano/kernel/constant'
|
2
|
+
require 'nano/string/capitalized'
|
4
3
|
require 'mega/orm_support'
|
4
|
+
require 'mega/inheritor'
|
5
|
+
require 'mega/annotation'
|
5
6
|
|
6
7
|
module Og
|
7
8
|
|
8
9
|
# A relation between Entities.
|
10
|
+
#--
|
11
|
+
# Relations are resolved in multiple passes. First
|
12
|
+
# the relations are logged in :relations...
|
13
|
+
#++
|
9
14
|
|
10
15
|
class Relation
|
11
16
|
|
@@ -13,10 +18,6 @@ class Relation
|
|
13
18
|
|
14
19
|
attr_accessor :options
|
15
20
|
|
16
|
-
# Is this a polymorphic relation?
|
17
|
-
|
18
|
-
attr_accessor :is_polymorphic
|
19
|
-
|
20
21
|
# A generalized initialize method for all relations.
|
21
22
|
# Contains common setup code.
|
22
23
|
|
@@ -53,74 +54,59 @@ class Relation
|
|
53
54
|
|
54
55
|
@options[:target_class] ||= @options[target_name].to_s.singular.camelize.intern
|
55
56
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
def setup
|
60
|
-
if target_class == Object
|
61
|
-
# If the target class is just an Object mark that class
|
62
|
-
# as a polymorphic parent class.
|
63
|
-
# This class acts as template
|
64
|
-
# to generate customized versions of this class.
|
65
|
-
owner_class.meta(:polymorphic, owner_class)
|
66
|
-
elsif target_class.respond_to?(:metadata) and target_class.metadata.polymorphic
|
67
|
-
# If the target class is polymorphic, create a specialized
|
68
|
-
# version of the class enclosed in the owner namespace.
|
69
|
-
|
70
|
-
target_dm = target_class.to_s.demodulize
|
71
|
-
owner_class.module_eval %{
|
72
|
-
class #{owner_class}::#{target_dm} < #{target_class}
|
73
|
-
end
|
74
|
-
}
|
75
|
-
eval %{
|
76
|
-
@options[:target_class] = #{owner_class}::#{target_dm}
|
77
|
-
}
|
78
|
-
end
|
79
|
-
|
80
|
-
target_name = if collection
|
81
|
-
:target_plural_name
|
82
|
-
else
|
83
|
-
:target_singular_name
|
84
|
-
end
|
85
|
-
|
86
|
-
# Inflect the relation name.
|
87
|
-
|
88
|
-
unless @options[target_name]
|
89
|
-
@options[target_name] = if collection
|
90
|
-
target_class.to_s.demodulize.underscore.downcase.plural.intern
|
91
|
-
else
|
92
|
-
target_class.to_s.demodulize.underscore.downcase.intern
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
@options[:name] = options[target_name]
|
57
|
+
# FIXME: this is a hack!
|
58
|
+
# setup() rescue nil
|
97
59
|
end
|
98
60
|
|
61
|
+
# Get an option.
|
62
|
+
|
99
63
|
def [](key)
|
100
64
|
@options[key]
|
101
65
|
end
|
102
66
|
|
67
|
+
# Set an option.
|
68
|
+
|
103
69
|
def []=(key, val)
|
104
70
|
@options[key] = val
|
105
71
|
end
|
106
72
|
|
107
|
-
# Is
|
73
|
+
# Is this a polymorphic marker?
|
108
74
|
|
109
|
-
def
|
75
|
+
def polymorphic_marker?
|
110
76
|
target_class == Object
|
111
77
|
end
|
112
|
-
|
113
|
-
#--
|
114
|
-
# gmosx, TODO: remove, this is not really needed.
|
115
|
-
#++
|
116
78
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
79
|
+
# Is this a polymorphic relation ?
|
80
|
+
|
81
|
+
def polymorphic?
|
82
|
+
target_class.ann.this[:polymorphic]
|
83
|
+
end
|
84
|
+
|
85
|
+
# Resolve a polymorphic target class.
|
86
|
+
# Overrided in subclasses.
|
87
|
+
|
88
|
+
def resolve_polymorphic
|
89
|
+
end
|
90
|
+
|
91
|
+
# This method is implemented in subclasses.
|
92
|
+
|
93
|
+
def enchant
|
122
94
|
end
|
123
95
|
|
96
|
+
# Access the hash values as methods.
|
97
|
+
|
98
|
+
def method_missing(sym, *args)
|
99
|
+
return @options[sym]
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
# A collection of helper methods and resolvers
|
105
|
+
# for relations.
|
106
|
+
|
107
|
+
class Relation
|
108
|
+
class << self
|
109
|
+
|
124
110
|
# To avoid forward declarations, references to undefined
|
125
111
|
# (at the time of the creation of the relation) classes are
|
126
112
|
# stored as symbols. These symbols are resolved by this
|
@@ -129,7 +115,7 @@ class Relation
|
|
129
115
|
# FIXME: do something more elegant here.
|
130
116
|
#++
|
131
117
|
|
132
|
-
def
|
118
|
+
def symbol_to_class(sym, owner_class)
|
133
119
|
c = owner_class.name.dup
|
134
120
|
c = "::" + c unless c =~ /::/
|
135
121
|
c.gsub!(/::.*$/, '::')
|
@@ -143,69 +129,131 @@ class Relation
|
|
143
129
|
end
|
144
130
|
end
|
145
131
|
end
|
132
|
+
alias_method :resolve_symbol, :symbol_to_class
|
146
133
|
|
147
|
-
def
|
148
|
-
|
149
|
-
|
150
|
-
|
134
|
+
def resolve_targets(klass)
|
135
|
+
for r in klass.relations
|
136
|
+
if r.target_class.is_a? Symbol
|
137
|
+
klass = symbol_to_class(r.target_class, r.owner_class)
|
138
|
+
r.options[:target_class] = klass
|
139
|
+
end
|
151
140
|
end
|
152
141
|
end
|
153
|
-
|
154
|
-
#
|
155
|
-
#
|
142
|
+
|
143
|
+
# If the target class is just an Object mark this class
|
144
|
+
# (self) as a polymorphic parent class. This class acts
|
145
|
+
# as template to generate customized versions of this class.
|
146
|
+
#
|
147
|
+
# For example:
|
148
|
+
#
|
149
|
+
# class Comment
|
150
|
+
# belongs_to :parent, Object # <= polymorphic
|
151
|
+
# ...
|
152
|
+
# end
|
153
|
+
|
154
|
+
def resolve_polymorphic_markers(klass)
|
155
|
+
for r in klass.relations
|
156
|
+
if r.polymorphic_marker?
|
157
|
+
r.owner_class.ann :this, :polymorphic => r.owner_class
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Resolve polymorphic relations.
|
163
|
+
# If the target class is polymorphic, create a specialized
|
164
|
+
# version of that class (the target) enclosed in the
|
165
|
+
# owner namespace.
|
166
|
+
#
|
167
|
+
# For example:
|
168
|
+
#
|
169
|
+
# class Article
|
170
|
+
# has_many Comment
|
171
|
+
# ...
|
172
|
+
# end
|
173
|
+
|
174
|
+
def resolve_polymorphic_relations(klass)
|
175
|
+
for r in klass.relations
|
176
|
+
if r.polymorphic?
|
177
|
+
|
178
|
+
target_dm = r.target_class.to_s.demodulize
|
179
|
+
r.owner_class.module_eval %{
|
180
|
+
class #{r.owner_class}::#{target_dm} < #{r.target_class}
|
181
|
+
end
|
182
|
+
}
|
183
|
+
|
184
|
+
# Replace the target class.
|
185
|
+
|
186
|
+
r[:target_class] = eval("#{r.owner_class}::#{target_dm}")
|
187
|
+
end
|
156
188
|
|
157
|
-
|
189
|
+
r.resolve_polymorphic
|
190
|
+
end
|
158
191
|
end
|
159
192
|
|
160
|
-
#
|
193
|
+
# Resolve the names of the relations.
|
161
194
|
|
162
|
-
def
|
195
|
+
def resolve_names(klass)
|
196
|
+
for r in klass.relations
|
197
|
+
target_name = if r.collection
|
198
|
+
:target_plural_name
|
199
|
+
else
|
200
|
+
:target_singular_name
|
201
|
+
end
|
202
|
+
|
203
|
+
# Inflect the relation name.
|
204
|
+
|
205
|
+
unless r[target_name]
|
206
|
+
r[target_name] = if r.collection
|
207
|
+
r.target_class.to_s.demodulize.underscore.downcase.plural.intern
|
208
|
+
else
|
209
|
+
r.target_class.to_s.demodulize.underscore.downcase.intern
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
r[:name] = r[target_name]
|
214
|
+
end
|
163
215
|
end
|
164
|
-
|
165
|
-
#
|
166
|
-
|
167
|
-
def
|
168
|
-
|
216
|
+
|
217
|
+
# General resovle method.
|
218
|
+
|
219
|
+
def resolve(klass, action = :resolve_polymorphic)
|
220
|
+
for r in klass.relations
|
221
|
+
r.send(action)
|
222
|
+
end
|
169
223
|
end
|
170
224
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
end
|
225
|
+
# Perform relation enchanting on this class.
|
226
|
+
|
227
|
+
def enchant(klass)
|
228
|
+
# update inherited relations.
|
229
|
+
|
230
|
+
for r in klass.relations
|
231
|
+
r[:owner_class] = klass
|
179
232
|
end
|
180
233
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
relation[:owner_class] = klass
|
187
|
-
unless relation.target_class == Object or relation.target_class.metadata.polymorphic
|
188
|
-
relation.setup
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
# enchant.
|
193
|
-
for relation in relations
|
194
|
-
relation.enchant unless relation.target_class == Object
|
195
|
-
end
|
196
|
-
end
|
234
|
+
# enchant.
|
235
|
+
|
236
|
+
for r in klass.relations
|
237
|
+
# p "=== #{klass} : #{r.class} : #{r.name}"
|
238
|
+
r.enchant() unless r.polymorphic_marker?
|
197
239
|
end
|
198
|
-
|
240
|
+
|
241
|
+
end
|
242
|
+
|
199
243
|
end
|
200
|
-
|
201
244
|
end
|
202
245
|
|
203
|
-
#
|
204
|
-
#
|
246
|
+
# Relations domain specific language (DSL). This
|
247
|
+
# language defines macros that are used to define Entity
|
248
|
+
# relations. Additional macros allow for relation
|
249
|
+
# inspection.
|
205
250
|
|
206
|
-
module
|
251
|
+
module RelationDSL
|
207
252
|
def self.append_features(base)
|
208
253
|
super
|
254
|
+
base.module_eval do
|
255
|
+
inheritor(:relations, [], :+) unless @relations
|
256
|
+
end
|
209
257
|
base.extend(ClassMethods)
|
210
258
|
end
|
211
259
|
|
@@ -220,7 +268,7 @@ module RelationMacros
|
|
220
268
|
|
221
269
|
def belongs_to(*args)
|
222
270
|
require 'og/relation/belongs_to'
|
223
|
-
|
271
|
+
relations! << Og::BelongsTo.new(args, :owner_class => self)
|
224
272
|
end
|
225
273
|
|
226
274
|
# === Examples
|
@@ -230,7 +278,7 @@ module RelationMacros
|
|
230
278
|
|
231
279
|
def refers_to(*args)
|
232
280
|
require 'og/relation/refers_to'
|
233
|
-
|
281
|
+
relations! << Og::RefersTo.new(args, :owner_class => self)
|
234
282
|
end
|
235
283
|
|
236
284
|
# === Examples
|
@@ -239,7 +287,7 @@ module RelationMacros
|
|
239
287
|
|
240
288
|
def has_one(*args)
|
241
289
|
require 'og/relation/has_one'
|
242
|
-
|
290
|
+
relations! << Og::HasOne.new(args, :owner_class => self)
|
243
291
|
end
|
244
292
|
|
245
293
|
# === Examples
|
@@ -249,30 +297,25 @@ module RelationMacros
|
|
249
297
|
|
250
298
|
def has_many(*args)
|
251
299
|
require 'og/relation/has_many'
|
252
|
-
|
300
|
+
relations! << Og::HasMany.new(args, :owner_class => self, :collection => true)
|
253
301
|
end
|
254
302
|
|
255
303
|
# ..
|
256
304
|
|
257
305
|
def joins_many(*args)
|
258
306
|
require 'og/relation/joins_many'
|
259
|
-
|
307
|
+
relations! << Og::JoinsMany.new(args, :owner_class => self, :collection => true)
|
260
308
|
end
|
261
309
|
|
262
310
|
# ..
|
263
311
|
|
264
312
|
def many_to_many(*args)
|
265
313
|
require 'og/relation/many_to_many'
|
266
|
-
|
314
|
+
relations! << Og::ManyToMany.new(args, :owner_class => self, :collection => true)
|
267
315
|
end
|
268
316
|
|
269
|
-
def inspect_relations
|
270
|
-
__meta[:relations]
|
271
|
-
end
|
272
|
-
alias_method :relations, :inspect_relations
|
273
|
-
|
274
317
|
def inspect_relation(name)
|
275
|
-
|
318
|
+
relations.find { |r| r[:name] == name }
|
276
319
|
end
|
277
320
|
alias_method :relation, :inspect_relation
|
278
321
|
|
@@ -280,3 +323,5 @@ module RelationMacros
|
|
280
323
|
end
|
281
324
|
|
282
325
|
end
|
326
|
+
|
327
|
+
# * George Moschovitis <gm@navel.gr>
|