og 0.14.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +68 -0
- data/README +16 -7
- data/doc/AUTHORS +3 -6
- data/doc/RELEASES +44 -1
- data/examples/run.rb +1 -1
- data/examples/test.db +0 -0
- data/lib/og.rb +2 -2
- data/lib/og/adapters/oracle.rb +1 -7
- data/lib/og/adapters/sqlserver.rb +360 -0
- data/lib/og/meta.rb +1 -4
- data/lib/og/mixins/hierarchical.rb +134 -0
- data/lib/og/mixins/{list.rb → orderable.rb} +25 -23
- data/lib/og/typemacros.rb +4 -3
- data/test/og/mixins/tc_hierarchical.rb +79 -0
- data/test/og/mixins/{tc_list.rb → tc_orderable.rb} +5 -2
- data/test/og/tc_sqlserver.rb +93 -0
- metadata +10 -7
- data/lib/og/backend.rb +0 -297
- data/test/og/mixins/tc_tree.rb +0 -59
data/lib/og/meta.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# * George Moschovitis <gm@navel.gr>
|
2
2
|
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
-
# $Id: meta.rb
|
3
|
+
# $Id: meta.rb 337 2005-03-31 16:20:40Z gmosx $
|
4
4
|
#--
|
5
5
|
# TODO:
|
6
6
|
# - precreate the meta sql statements as much as possible to
|
@@ -11,8 +11,6 @@ require 'glue/inflector'
|
|
11
11
|
require 'og/adapter'
|
12
12
|
require 'og/typemacros'
|
13
13
|
|
14
|
-
require 'og/mixins/list'
|
15
|
-
|
16
14
|
module Og
|
17
15
|
|
18
16
|
class Relation < N::Property
|
@@ -395,7 +393,6 @@ end
|
|
395
393
|
if Og.include_meta_language
|
396
394
|
class Module # :nodoc: all
|
397
395
|
include Og::MetaLanguage
|
398
|
-
include Og::List
|
399
396
|
=begin
|
400
397
|
A hack to avoid forward references. Does not work
|
401
398
|
with namespave modules though. Any idea?
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
+
# $Id: hierarchical.rb 341 2005-04-04 08:28:54Z gmosx $
|
4
|
+
|
5
|
+
require 'glue/dynamic_include'
|
6
|
+
|
7
|
+
module Og
|
8
|
+
|
9
|
+
# Implements the Nested Sets pattern for hierarchical
|
10
|
+
# SQL queries.
|
11
|
+
|
12
|
+
module NestedSets
|
13
|
+
|
14
|
+
def self.append_dynamic_features(base, options)
|
15
|
+
c = {
|
16
|
+
:left => 'lft',
|
17
|
+
:right => 'rgt',
|
18
|
+
:type => Fixnum,
|
19
|
+
:scope => '"1 = 1"',
|
20
|
+
:parent => N::Inflector.name(base),
|
21
|
+
:children => N::Inflector.plural_name(base)
|
22
|
+
}
|
23
|
+
c.update(options) if options
|
24
|
+
|
25
|
+
parent = "#{c[:parent]}_oid"
|
26
|
+
left = c[:left]
|
27
|
+
right = c[:right]
|
28
|
+
children = c[:children]
|
29
|
+
child = N::Inflector.singularize(children)
|
30
|
+
|
31
|
+
if c[:scope].is_a?(Symbol) && c[:scope].to_s !~ /_oid$/
|
32
|
+
c[:scope] = "#{c[:scope]}_oid".intern
|
33
|
+
end
|
34
|
+
scope = c[:scope]
|
35
|
+
if scope.is_a?(Symbol)
|
36
|
+
scope = %{(#{scope} ? "#{scope} = \#{@#{scope}}" : "#{scope} IS NULL")}
|
37
|
+
end
|
38
|
+
|
39
|
+
base.module_eval <<-EOE, __FILE__, __LINE__
|
40
|
+
property :#{parent}, Fixnum, :sql_index => true
|
41
|
+
property :#{left}, :#{right}, #{c[:type]}
|
42
|
+
|
43
|
+
def root?
|
44
|
+
(@#{parent}.nil? || @#{parent} == 0) && (@#{left} == 1) && (@#{right} > @#{left})
|
45
|
+
end
|
46
|
+
|
47
|
+
def child?
|
48
|
+
(@#{parent} && @#{parent} != 0) && (@#{left} > 1) && (@#{right} > @#{left})
|
49
|
+
end
|
50
|
+
|
51
|
+
def #{children}_count
|
52
|
+
return (@#{right} - @#{left} - 1)/2
|
53
|
+
end
|
54
|
+
|
55
|
+
def full_#{children}(extrasql = nil)
|
56
|
+
#{base}.all("WHERE " + #{scope} + " AND (#{left} BETWEEN \#\{@#{left}\} AND \#{@#{right}}) \#{extrasql}")
|
57
|
+
end
|
58
|
+
|
59
|
+
def #{children}(extrasql = nil)
|
60
|
+
#{base}.all("WHERE " + #{scope} + " AND (#{left} > \#\{@#{left}\}) AND (#{right} < \#{@#{right}}) \#{extrasql}")
|
61
|
+
end
|
62
|
+
|
63
|
+
def direct_#{children}(extrasql = nil)
|
64
|
+
#{base}.all("WHERE " + #{scope} + " AND #{parent} = \#{@oid} \#{extrasql}")
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_#{child}(child)
|
68
|
+
self.reload if @oid
|
69
|
+
child.reload if child.oid
|
70
|
+
|
71
|
+
if @#{left}.nil? || @#{left} == 0 || @#{right}.nil? || @#{right} == 0
|
72
|
+
@#{left} = 1
|
73
|
+
@#{right} = 2
|
74
|
+
end
|
75
|
+
|
76
|
+
child.#{parent} = @oid
|
77
|
+
child.#{left} = pivot = @#{right}
|
78
|
+
child.#{right} = pivot + 1
|
79
|
+
@#{right} = pivot + 2
|
80
|
+
|
81
|
+
self.class.transaction do
|
82
|
+
self.class.update("#{left} = (#{left} + 2)", "WHERE " + #{scope} + " AND #{left} >= \#{pivot}")
|
83
|
+
self.class.update("#{right} = (#{right} + 2)", "WHERE " + #{scope} + " AND #{right} >= \#{pivot}")
|
84
|
+
end
|
85
|
+
|
86
|
+
self.save
|
87
|
+
child.save
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.og_pre_delete(conn, obj)
|
91
|
+
return unless (obj.#{left} and obj.#{right})
|
92
|
+
|
93
|
+
span = obj.#{right} - obj.#{left} + 1
|
94
|
+
|
95
|
+
(klass = obj.class).transaction do
|
96
|
+
klass.delete(#{scope} + " AND #{left} > \#{obj.#{left}} AND (#{right} < \#{obj.#{right}})")
|
97
|
+
klass.update("#{left} = (#{left} - \#{span})", "WHERE " + #{scope} + " AND #{left} >= \#{obj.#{right}}")
|
98
|
+
klass.update("#{right} = (#{right} - \#{span})", "WHERE " + #{scope} + " AND #{right} >= \#{obj.#{right}}")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
EOE
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
# Transform the base class to a hierarchical node.
|
107
|
+
# A selection of different implementation strategies
|
108
|
+
# are provided.
|
109
|
+
#
|
110
|
+
# === Example
|
111
|
+
#
|
112
|
+
# class Comment
|
113
|
+
# include Hierarchical, :method => :nested_sets
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# [+:method+]
|
117
|
+
# :simple
|
118
|
+
# :nested_sets
|
119
|
+
# :nested_intervals
|
120
|
+
|
121
|
+
module Hierarchical
|
122
|
+
|
123
|
+
def self.append_dynamic_features(base, options)
|
124
|
+
c = {
|
125
|
+
:method => :nested_sets,
|
126
|
+
}
|
127
|
+
c.update(options) if options
|
128
|
+
|
129
|
+
base.include(NestedSets)
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
@@ -1,24 +1,26 @@
|
|
1
1
|
# * George Moschovitis <gm@navel.gr>
|
2
2
|
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
-
# $Id:
|
3
|
+
# $Id: orderable.rb 340 2005-04-04 08:26:58Z gmosx $
|
4
|
+
|
5
|
+
require 'glue/dynamic_include'
|
4
6
|
|
5
7
|
module Og
|
6
8
|
|
7
9
|
# Attach list/ordering methods to the enchanted class.
|
8
|
-
#--
|
9
|
-
# TODO:
|
10
|
-
# Convert to new Og filter system.
|
11
|
-
# Implement as super-mixin.
|
12
|
-
#++
|
13
10
|
|
14
|
-
module
|
11
|
+
module Orderable
|
15
12
|
|
16
|
-
|
13
|
+
def self.append_dynamic_features(base, options)
|
14
|
+
c = {
|
15
|
+
:position => 'position',
|
16
|
+
:type => Fixnum,
|
17
|
+
:scope => '"1 = 1"'
|
18
|
+
}
|
19
|
+
c.update(options) if options
|
17
20
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
c[:scope] = "#{c[:scope]}_oid".intern if c[:scope].is_a?(Symbol) && c[:scope].to_s !~ /_oid$/
|
21
|
+
if c[:scope].is_a?(Symbol) && c[:scope].to_s !~ /_oid$/
|
22
|
+
c[:scope] = "#{c[:scope]}_oid".intern
|
23
|
+
end
|
22
24
|
|
23
25
|
position = c[:position]
|
24
26
|
scope = c[:scope]
|
@@ -27,7 +29,7 @@ module List
|
|
27
29
|
scope = %{(#{scope} ? "#{scope} = \#{@#{scope}}" : "#{scope} IS NULL")}
|
28
30
|
end
|
29
31
|
|
30
|
-
module_eval <<-EOE, __FILE__, __LINE__
|
32
|
+
base.module_eval <<-EOE, __FILE__, __LINE__
|
31
33
|
property :#{position}, #{c[:type]}
|
32
34
|
|
33
35
|
def og_pre_insert(conn)
|
@@ -40,7 +42,7 @@ module List
|
|
40
42
|
|
41
43
|
def move_higher
|
42
44
|
if higher = higher_item
|
43
|
-
#{
|
45
|
+
#{base}.transaction do
|
44
46
|
higher.increment_position
|
45
47
|
decrement_position
|
46
48
|
end
|
@@ -49,7 +51,7 @@ module List
|
|
49
51
|
|
50
52
|
def move_lower
|
51
53
|
if lower = lower_item
|
52
|
-
#{
|
54
|
+
#{base}.transaction do
|
53
55
|
lower.decrement_position
|
54
56
|
increment_position
|
55
57
|
end
|
@@ -57,14 +59,14 @@ module List
|
|
57
59
|
end
|
58
60
|
|
59
61
|
def move_to_top
|
60
|
-
#{
|
62
|
+
#{base}.transaction do
|
61
63
|
increment_position_of_higher_items
|
62
64
|
set_top_position
|
63
65
|
end
|
64
66
|
end
|
65
67
|
|
66
68
|
def move_to_bottom
|
67
|
-
#{
|
69
|
+
#{base}.transaction do
|
68
70
|
decrement_position_of_lower_items
|
69
71
|
set_bottom_position
|
70
72
|
end
|
@@ -85,12 +87,12 @@ module List
|
|
85
87
|
end
|
86
88
|
|
87
89
|
def higher_item
|
88
|
-
#{
|
90
|
+
#{base}.one(#{scope} + " AND #{position}=\#\{@#{position} - 1\}")
|
89
91
|
end
|
90
92
|
alias_method :previous_item, :higher_item
|
91
93
|
|
92
94
|
def lower_item
|
93
|
-
#{
|
95
|
+
#{base}.one(#{scope} + " AND #{position}=\#\{@#{position} + 1\}")
|
94
96
|
end
|
95
97
|
alias_method :next_item, :lower_item
|
96
98
|
|
@@ -99,7 +101,7 @@ module List
|
|
99
101
|
alias_method :first_item, :top_item
|
100
102
|
|
101
103
|
def bottom_item
|
102
|
-
#{
|
104
|
+
#{base}.one(#{scope} + " ORDER BY #{position} DESC")
|
103
105
|
end
|
104
106
|
alias_method :last_item, :last_item
|
105
107
|
|
@@ -139,15 +141,15 @@ module List
|
|
139
141
|
end
|
140
142
|
|
141
143
|
def increment_position_of_higher_items
|
142
|
-
#{
|
144
|
+
#{base}.update("#{position}=(#{position} + 1)", "WHERE " + #{scope} + " AND #{position} < \#\{@#{position}\}")
|
143
145
|
end
|
144
146
|
|
145
147
|
def increment_position_of_all_items
|
146
|
-
#{
|
148
|
+
#{base}.update("#{position}=(#{position} + 1)", "WHERE " + #{scope})
|
147
149
|
end
|
148
150
|
|
149
151
|
def decrement_position_of_lower_items
|
150
|
-
#{
|
152
|
+
#{base}.update("#{position}=(#{position} - 1)", "WHERE " + #{scope} + " AND #{position} > \#\{@#{position}\}")
|
151
153
|
end
|
152
154
|
EOE
|
153
155
|
end
|
data/lib/og/typemacros.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# * Michael Neumann <mneumann@ntecs.de>
|
2
3
|
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
-
# $Id: typemacros.rb
|
4
|
+
# $Id: typemacros.rb 340 2005-04-04 08:26:58Z gmosx $
|
4
5
|
|
5
6
|
module Og
|
6
7
|
|
@@ -16,8 +17,8 @@ def VarChar(size)
|
|
16
17
|
return String, :sql => "VARCHAR(#{size})"
|
17
18
|
end
|
18
19
|
|
19
|
-
NotNull = {:sql =>
|
20
|
+
NotNull = { :sql => 'NOT NULL' }.freeze
|
20
21
|
|
21
|
-
Null = {:sql =>
|
22
|
+
Null = { :sql => 'NULL' }.freeze
|
22
23
|
|
23
24
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib')
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
require 'og/mixins/hierarchical'
|
7
|
+
|
8
|
+
require 'og'
|
9
|
+
|
10
|
+
$og = Og::Database.new(
|
11
|
+
:adapter => 'psql',
|
12
|
+
:database => 'test',
|
13
|
+
:user => 'postgres',
|
14
|
+
:password => 'navelrulez',
|
15
|
+
:drop => true
|
16
|
+
)
|
17
|
+
|
18
|
+
class TestCaseOgHierarchical < Test::Unit::TestCase # :nodoc: all
|
19
|
+
include N
|
20
|
+
|
21
|
+
class Comment
|
22
|
+
property :body, String
|
23
|
+
property :create_time, Time
|
24
|
+
|
25
|
+
include Og::NestedSets
|
26
|
+
|
27
|
+
def initialize(body = nil)
|
28
|
+
@body = body
|
29
|
+
@create_time = Time.now
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
sprintf("%3d %3d %s", @lft, @rgt, @body)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_all
|
38
|
+
$og.auto_manage_classes
|
39
|
+
|
40
|
+
root = Comment.create('root')
|
41
|
+
c1 = Comment.new('1')
|
42
|
+
root.add_comment(c1)
|
43
|
+
c2 = Comment.new('1.1')
|
44
|
+
c1.add_comment(c2)
|
45
|
+
c3 = Comment.new('1.2')
|
46
|
+
c1.add_comment(c3)
|
47
|
+
c4 = Comment.new('1.1.1')
|
48
|
+
c2.add_comment(c4)
|
49
|
+
c5 = Comment.new('1.2.1')
|
50
|
+
c3.add_comment(c5)
|
51
|
+
c6 = Comment.new('1.1.1.1')
|
52
|
+
c4.add_comment(c6)
|
53
|
+
c7 = Comment.new('2')
|
54
|
+
root.add_comment(c7)
|
55
|
+
c8 = Comment.new('3')
|
56
|
+
root.add_comment(c8)
|
57
|
+
c9 = Comment.new('2.1')
|
58
|
+
c7.add_comment(c9)
|
59
|
+
|
60
|
+
c1.reload
|
61
|
+
=begin
|
62
|
+
Comment.all("ORDER BY lft, rgt").each { |c|
|
63
|
+
puts sprintf("%3d %3d %s", c.lft, c.rgt, c.body)
|
64
|
+
# p c
|
65
|
+
}
|
66
|
+
puts '--1'
|
67
|
+
c1.comments("ORDER BY lft, rgt").each { |c| puts c.body }
|
68
|
+
puts '--2'
|
69
|
+
c1.full_comments("ORDER BY lft, rgt").each { |c| puts c.body }
|
70
|
+
puts '--3'
|
71
|
+
c1.direct_comments("ORDER BY lft, rgt").each { |c| puts c.body }
|
72
|
+
=end
|
73
|
+
|
74
|
+
assert_equal 6, c1.full_comments.size
|
75
|
+
assert_equal 5, c1.comments.size
|
76
|
+
assert_equal 2, c1.direct_comments.size
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -3,6 +3,8 @@ $:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib')
|
|
3
3
|
require 'test/unit'
|
4
4
|
require 'ostruct'
|
5
5
|
|
6
|
+
require 'og/mixins/orderable'
|
7
|
+
|
6
8
|
require 'og'
|
7
9
|
|
8
10
|
$og = Og::Database.new(
|
@@ -13,7 +15,7 @@ $og = Og::Database.new(
|
|
13
15
|
:drop => true
|
14
16
|
)
|
15
17
|
|
16
|
-
class
|
18
|
+
class TestCaseOgOrderable < Test::Unit::TestCase # :nodoc: all
|
17
19
|
include N
|
18
20
|
|
19
21
|
class Comment; end
|
@@ -30,8 +32,9 @@ class TestCaseOgList < Test::Unit::TestCase # :nodoc: all
|
|
30
32
|
class Comment
|
31
33
|
property :body, String
|
32
34
|
belongs_to :article, Article
|
33
|
-
acts_as_list :scope => :article
|
34
35
|
|
36
|
+
include Og::Orderable, :scope => :article
|
37
|
+
|
35
38
|
def initialize(body = nil)
|
36
39
|
@body = body
|
37
40
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib')
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
require 'og'
|
7
|
+
#require 'og/adapters/sqlserver'
|
8
|
+
|
9
|
+
class TC_OgSqlserver < Test::Unit::TestCase # :nodoc: all
|
10
|
+
include N
|
11
|
+
|
12
|
+
# Forward declaration.
|
13
|
+
|
14
|
+
class Comment; end
|
15
|
+
|
16
|
+
class Article
|
17
|
+
prop_accessor :name, String
|
18
|
+
prop_accessor :age, Fixnum
|
19
|
+
has_many :comments, Comment
|
20
|
+
|
21
|
+
def initialize (name = nil, age = nil)
|
22
|
+
@name, @age = name, age
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Comment
|
27
|
+
prop_accessor :text, String
|
28
|
+
belongs_to :article, Article
|
29
|
+
|
30
|
+
def initialize(text = nil)
|
31
|
+
@text = text
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def setup
|
36
|
+
config = {
|
37
|
+
:adapter => 'sqlserver',
|
38
|
+
:address => 'localhost',
|
39
|
+
:database => 'test',
|
40
|
+
:user => 'sa',
|
41
|
+
:password => 'sa',
|
42
|
+
:connection_count => 2
|
43
|
+
}
|
44
|
+
|
45
|
+
$DBG = true
|
46
|
+
|
47
|
+
# Og::Database.drop_db!(config)
|
48
|
+
@og = Og::Database.new(config)
|
49
|
+
end
|
50
|
+
|
51
|
+
def teardown
|
52
|
+
@og.shutdown
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_all
|
56
|
+
|
57
|
+
puts Article.all.size
|
58
|
+
Article.each {|a| a.delete! }
|
59
|
+
puts Article.all.size
|
60
|
+
|
61
|
+
a = Article.new('gmosx', 30)
|
62
|
+
a.save!
|
63
|
+
|
64
|
+
baseoid = a.oid
|
65
|
+
|
66
|
+
a1 = Article[baseoid]
|
67
|
+
|
68
|
+
assert_equal 'gmosx', a1.name
|
69
|
+
assert_equal 30, a1.age
|
70
|
+
assert_equal baseoid, a1.oid
|
71
|
+
|
72
|
+
Article.create('drak', 12)
|
73
|
+
Article.create('ekarak', 34)
|
74
|
+
Article.create('mario', 53)
|
75
|
+
Article.create('elathan', 34)
|
76
|
+
|
77
|
+
articles = Article.all
|
78
|
+
|
79
|
+
assert_equal 5, articles.size
|
80
|
+
|
81
|
+
a3 = Article['ekarak']
|
82
|
+
|
83
|
+
assert_equal 'ekarak', a3.name
|
84
|
+
|
85
|
+
c1 = Comment.new('a comment')
|
86
|
+
c1.save!
|
87
|
+
a3.add_comment(c1)
|
88
|
+
|
89
|
+
a5 = Article['ekarak']
|
90
|
+
assert_equal 1, a5.comments.size
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|