og 0.14.0 → 0.15.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/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
|