og 0.23.0 → 0.24.0
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.
- 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>
|