og 0.16.0 → 0.17.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.
Files changed (69) hide show
  1. data/CHANGELOG +485 -0
  2. data/README +35 -12
  3. data/Rakefile +4 -7
  4. data/benchmark/bench.rb +1 -1
  5. data/doc/AUTHORS +3 -3
  6. data/doc/RELEASES +153 -2
  7. data/doc/config.txt +0 -7
  8. data/doc/tutorial.txt +7 -0
  9. data/examples/README +5 -0
  10. data/examples/mysql_to_psql.rb +25 -50
  11. data/examples/run.rb +62 -77
  12. data/install.rb +1 -1
  13. data/lib/og.rb +45 -106
  14. data/lib/og/collection.rb +156 -0
  15. data/lib/og/entity.rb +131 -0
  16. data/lib/og/errors.rb +10 -15
  17. data/lib/og/manager.rb +115 -0
  18. data/lib/og/{mixins → mixin}/hierarchical.rb +43 -37
  19. data/lib/og/{mixins → mixin}/orderable.rb +35 -35
  20. data/lib/og/{mixins → mixin}/timestamped.rb +0 -6
  21. data/lib/og/{mixins → mixin}/tree.rb +0 -4
  22. data/lib/og/relation.rb +178 -0
  23. data/lib/og/relation/belongs_to.rb +14 -0
  24. data/lib/og/relation/has_many.rb +62 -0
  25. data/lib/og/relation/has_one.rb +17 -0
  26. data/lib/og/relation/joins_many.rb +69 -0
  27. data/lib/og/relation/many_to_many.rb +17 -0
  28. data/lib/og/relation/refers_to.rb +31 -0
  29. data/lib/og/store.rb +223 -0
  30. data/lib/og/store/filesys.rb +113 -0
  31. data/lib/og/store/madeleine.rb +4 -0
  32. data/lib/og/store/memory.rb +291 -0
  33. data/lib/og/store/mysql.rb +283 -0
  34. data/lib/og/store/psql.rb +238 -0
  35. data/lib/og/store/sql.rb +599 -0
  36. data/lib/og/store/sqlite.rb +190 -0
  37. data/lib/og/store/sqlserver.rb +262 -0
  38. data/lib/og/types.rb +19 -0
  39. data/lib/og/validation.rb +0 -4
  40. data/test/og/{mixins → mixin}/tc_hierarchical.rb +21 -23
  41. data/test/og/{mixins → mixin}/tc_orderable.rb +15 -14
  42. data/test/og/mixin/tc_timestamped.rb +38 -0
  43. data/test/og/store/tc_filesys.rb +71 -0
  44. data/test/og/tc_relation.rb +36 -0
  45. data/test/og/tc_store.rb +290 -0
  46. data/test/og/tc_types.rb +21 -0
  47. metadata +54 -40
  48. data/examples/mock_example.rb +0 -50
  49. data/lib/og/adapters/base.rb +0 -706
  50. data/lib/og/adapters/filesys.rb +0 -117
  51. data/lib/og/adapters/mysql.rb +0 -350
  52. data/lib/og/adapters/oracle.rb +0 -368
  53. data/lib/og/adapters/psql.rb +0 -272
  54. data/lib/og/adapters/sqlite.rb +0 -265
  55. data/lib/og/adapters/sqlserver.rb +0 -356
  56. data/lib/og/database.rb +0 -290
  57. data/lib/og/enchant.rb +0 -149
  58. data/lib/og/meta.rb +0 -407
  59. data/lib/og/testing/mock.rb +0 -165
  60. data/lib/og/typemacros.rb +0 -24
  61. data/test/og/adapters/tc_filesys.rb +0 -83
  62. data/test/og/adapters/tc_sqlite.rb +0 -86
  63. data/test/og/adapters/tc_sqlserver.rb +0 -96
  64. data/test/og/tc_automanage.rb +0 -46
  65. data/test/og/tc_lifecycle.rb +0 -105
  66. data/test/og/tc_many_to_many.rb +0 -61
  67. data/test/og/tc_meta.rb +0 -55
  68. data/test/og/tc_validation.rb +0 -89
  69. data/test/tc_og.rb +0 -364
@@ -0,0 +1,156 @@
1
+ module Og
2
+
3
+ # An 'active' collection that reflects a relation.
4
+ # A collection stores entitities that participate in
5
+ # a relation.
6
+
7
+ class Collection
8
+
9
+ # The owner of this collection.
10
+
11
+ attr_accessor :owner
12
+
13
+ # The members of this collection. Keeps the objects
14
+ # tha belong to this collection.
15
+
16
+ attr_accessor :members
17
+
18
+ # A method used to add insert objects in the collection.
19
+
20
+ attr_accessor :insert_proc
21
+
22
+ # A method used to find the objects that belong to the
23
+ # collection.
24
+
25
+ attr_accessor :find_proc
26
+
27
+ # The default find options.
28
+
29
+ attr_accessor :find_options
30
+
31
+ # Is the collection in build mode?
32
+
33
+ attr_accessor :building
34
+
35
+ # Is the collection loaded?
36
+
37
+ attr_accessor :loaded
38
+
39
+ # Initialize the collection.
40
+
41
+ def initialize(owner = nil, insert_proc = nil, find_proc = nil, find_options = {})
42
+ @owner = owner
43
+ @insert_proc = insert_proc
44
+ @find_proc = find_proc
45
+ @find_options = find_options
46
+ @members = []
47
+ @loaded = false
48
+ @building = false
49
+ end
50
+
51
+ # Load the members of the collection.
52
+
53
+ def load_members
54
+ unless @loaded
55
+ @members = @owner.send(@find_proc, @find_options)
56
+ @loaded = true
57
+ end
58
+ @members
59
+ end
60
+
61
+ # Reload the collection.
62
+
63
+ def reload(options)
64
+ @find_options = options
65
+ @members = @owner.send(@find_proc, @find_options)
66
+ end
67
+
68
+ # Convert the collection to an array.
69
+
70
+ def to_ary
71
+ load_members
72
+ @members
73
+ end
74
+
75
+ # Defined to avoid the method missing overhead.
76
+
77
+ def each(&block)
78
+ load_members
79
+ @members.each(&block)
80
+ end
81
+
82
+ # Defined to avoid the method missing overhead.
83
+
84
+ def [](idx)
85
+ load_members
86
+ @members[idx]
87
+ end
88
+
89
+ # Add a new member to the collection.
90
+
91
+ def push(obj)
92
+ @members.push(obj)
93
+ unless @building or owner.unsaved?
94
+ @owner.send(@insert_proc, obj)
95
+ end
96
+ end
97
+ alias_method :<<, :push
98
+ alias_method :add, :push
99
+
100
+ # Delete a member from the collection.
101
+
102
+ def delete(*objects)
103
+ objects = objects.flatten
104
+
105
+ objects.reject! { |obj| @members.delete(obj) if obj.unsaved? }
106
+ return if objects.empty?
107
+
108
+ @owner.transaction do
109
+ objects.each do |obj|
110
+ obj.delete
111
+ @members.delete(obj)
112
+ end
113
+ end
114
+ end
115
+
116
+ # Delete all members of the collection.
117
+
118
+ def clear
119
+ @owner.transaction do
120
+ @members.each { |obj| obj.delete }
121
+ end
122
+ @members.clear
123
+ end
124
+
125
+ # Redirect all other methods to the members array.
126
+
127
+ def method_missing(symbol, *args, &block)
128
+ load_members
129
+ @members.send(symbol, *args, &block)
130
+ end
131
+
132
+ private
133
+
134
+ def check_type(obj)
135
+ end
136
+
137
+ end
138
+
139
+ end
140
+
141
+ __END__
142
+
143
+ === Examples
144
+
145
+ article.comments.to_ary
146
+
147
+ for article in article.comments
148
+ end
149
+
150
+ blog = Blog.new
151
+ blog.categories << Category.new
152
+ blog.categories << Category.new
153
+ blog.enties << Entry.new
154
+ blog.save
155
+
156
+ blog.categories.unlink(1)
data/lib/og/entity.rb ADDED
@@ -0,0 +1,131 @@
1
+ require 'og/relation'
2
+
3
+ module Og
4
+
5
+ # Include this module to classes to make them managable by Og.
6
+
7
+ module EntityMixin
8
+
9
+ def self.append_features(base)
10
+ super
11
+
12
+ base.extend(ClassMethods)
13
+
14
+ base.module_eval <<-end_eval, __FILE__, __LINE__
15
+ def save
16
+ self.class.ogmanager.store.save(self)
17
+ return self
18
+ end
19
+ alias_method :save!, :save
20
+
21
+ def insert
22
+ self.class.ogmanager.store.insert(self)
23
+ return self
24
+ end
25
+
26
+ def update(*properties)
27
+ properties = nil if properties.empty?
28
+ self.class.ogmanager.store.update(self, properties)
29
+ end
30
+
31
+ def update_properties(set)
32
+ self.class.ogmanager.store.update_properties(self, set)
33
+ end
34
+
35
+ def reload
36
+ self.class.ogmanager.store.reload(self, self.pk)
37
+ end
38
+ alias_method :reload!, :reload
39
+
40
+ def delete
41
+ self.class.ogmanager.store.delete(self)
42
+ end
43
+ alias_method :delete!, :delete
44
+
45
+ def transaction(&block)
46
+ self.class.ogmanager.store.transaction(&block)
47
+ end
48
+ end_eval
49
+
50
+ base.send(:include, RelationMacros)
51
+ end
52
+
53
+ module ClassMethods
54
+ def create(*args)
55
+ obj = self.new(*args)
56
+ yield(obj) if block_given?
57
+ ogmanager.store.save(obj)
58
+ return obj
59
+ end
60
+
61
+ def load(pk)
62
+ ogmanager.store.load(pk, self)
63
+ end
64
+ alias_method :[], :load
65
+
66
+ def update_properties(set, options = nil)
67
+ ogmanager.store.update_properties(self, set, options)
68
+ end
69
+ alias_method :pupdate, :update_properties
70
+ alias_method :update_property, :update_properties
71
+ alias_method :batch_update, :update_properties
72
+
73
+ def find(options = {})
74
+ options[:class] = self
75
+ ogmanager.store.find(options)
76
+ end
77
+ alias_method :all, :find
78
+
79
+ def find_one(options = {})
80
+ options[:class] = self
81
+ ogmanager.store.find_one(options)
82
+ end
83
+ alias_method :one, :find_one
84
+ alias_method :first, :find_one
85
+
86
+ def count(options = {})
87
+ options[:class] = self
88
+ ogmanager.store.count(options)
89
+ end
90
+
91
+ def delete(obj_or_pk)
92
+ ogmanager.store.delete(obj_or_pk, self)
93
+ end
94
+ alias_method :delete!, :delete
95
+
96
+ def transaction(&block)
97
+ ogmanager.store.transaction(&block)
98
+ end
99
+
100
+ def primary_key
101
+ unless @__meta and @__meta[:primary_key]
102
+ self.meta :primary_key, Entity.resolve_primary_key(self)
103
+ end
104
+ @__meta[:primary_key].flatten
105
+ end
106
+
107
+ def const_missing(sym)
108
+ return sym
109
+ end
110
+ end
111
+
112
+ end
113
+
114
+ # A helper class.
115
+
116
+ class Entity
117
+ class << self
118
+ def resolve_primary_key(klass)
119
+ for p in klass.properties
120
+ if p.meta[:primary_key]
121
+ return p.symbol, p.klass
122
+ end
123
+ end
124
+ return :oid, Fixnum
125
+ end
126
+ end
127
+
128
+ include EntityMixin
129
+ end
130
+
131
+ end
data/lib/og/errors.rb CHANGED
@@ -1,21 +1,16 @@
1
- # * George Moschovitis <gm@navel.gr>
2
- # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: errors.rb 1 2005-04-11 11:04:30Z gmosx $
1
+ # $Id$
4
2
 
5
3
  module Og
4
+
5
+ # This exception is thrown when a low level error happens
6
+ # in the store.
6
7
 
7
- # This exception is thrown when a low level error
8
- # happens in the store implementation.
9
- #--
10
- # TODO: rename adapter_exception to store_exception.
11
- #++
12
-
13
- class SqlException < Exception
14
- attr_accessor :adapter_exception, :sql
8
+ class StoreException < Exception
9
+ attr_accessor :original_exception, :info
15
10
 
16
- def initialize(adapter_exception, sql = nil)
17
- @adapter_exception, @sql = adapter_exception, sql
11
+ def initialize(original_exception, info = nil)
12
+ @original_exception, @info = original_exception, info
18
13
  end
19
14
  end
20
-
21
- end
15
+
16
+ end
data/lib/og/manager.rb ADDED
@@ -0,0 +1,115 @@
1
+ require 'og/entity'
2
+ require 'og/store'
3
+
4
+ module Og
5
+
6
+ # A Manager defines the relations between entities, ie Objects
7
+ # managed by Og.
8
+
9
+ class Manager
10
+
11
+ # Information about an Entity class
12
+
13
+ class EntityInfo
14
+ attr_accessor :klass
15
+ attr_accessor :enchanted
16
+ attr_accessor :options
17
+
18
+
19
+ def initialize(klass = nil)
20
+ @klass = klass
21
+ @enchanted = false
22
+ @options = {}
23
+ end
24
+ end
25
+
26
+ # The configuration options.
27
+
28
+ attr_accessor :options
29
+
30
+ # The store used for persistence.
31
+
32
+ attr_accessor :store
33
+
34
+ # The collection of Entities managed by this manager.
35
+
36
+ attr_accessor :entities
37
+
38
+ def initialize(options)
39
+ @options = options
40
+ @entities = {}
41
+ end
42
+
43
+ # Manage a class. Converts the class to an Entity.
44
+
45
+ def manage(klass)
46
+ return if managed?(klass) or klass.ancestors.include?(Unmanageable)
47
+
48
+ info = EntityInfo.new(klass)
49
+
50
+ klass.module_eval %{
51
+ def ==(other)
52
+ @#{klass.primary_key.first} == other.#{klass.primary_key.first}
53
+ end
54
+ }
55
+
56
+ klass.instance_variable_set '@ogmanager', self
57
+ klass.class.send(:attr_accessor, :ogmanager)
58
+
59
+ Relation.enchant(klass)
60
+ @store.enchant(klass, self)
61
+
62
+ @entities[klass] = info
63
+ end
64
+
65
+ # Is the class managed by Og?
66
+
67
+ def managed?(klass)
68
+ @entities.include?(klass)
69
+ end
70
+ alias_method :entity?, :managed?
71
+
72
+ # Use Ruby's advanced reflection features to find
73
+ # all manageable classes. Managable are all classes that
74
+ # define Properties.
75
+
76
+ def manageable_classes
77
+ classes = []
78
+
79
+ ObjectSpace.each_object(Class) do |c|
80
+ if c.respond_to?(:__props) and (!c.__props.empty?)
81
+ classes << c
82
+ end
83
+ end
84
+
85
+ Logger.debug "Og manageable classes: #{classes.inspect}" if $DBG
86
+
87
+ return classes
88
+ end
89
+
90
+ # Manage a collection of classes.
91
+
92
+ def manage_classes(*classes)
93
+ classes = manageable_classes if classes.empty?
94
+ classes.flatten.each { |c| manage(c) }
95
+ end
96
+
97
+ end
98
+
99
+ class << self
100
+
101
+ # Helper method, useful to initialize Og.
102
+
103
+ def setup(options = {})
104
+ m = Manager.new(options)
105
+ store = Store.for_name(options[:store])
106
+ store.destroy(options) if options[:destroy]
107
+ m.store = store.new(options)
108
+ m.manage_classes
109
+ return m
110
+ end
111
+ alias_method :connect, :setup
112
+
113
+ end
114
+
115
+ end
@@ -1,6 +1,7 @@
1
- # * George Moschovitis <gm@navel.gr>
2
- # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: hierarchical.rb 1 2005-04-11 11:04:30Z gmosx $
1
+ require 'facet/string/plural'
2
+ require 'facet/string/singular'
3
+ require 'facet/string/demodulize'
4
+ require 'facet/string/underscore'
4
5
 
5
6
  require 'glue/dynamic_include'
6
7
 
@@ -8,6 +9,9 @@ module Og
8
9
 
9
10
  # Implements the Nested Sets pattern for hierarchical
10
11
  # SQL queries.
12
+ #--
13
+ # TODO: use actice collections.
14
+ #++
11
15
 
12
16
  module NestedSets
13
17
 
@@ -16,9 +20,8 @@ module NestedSets
16
20
  :left => 'lft',
17
21
  :right => 'rgt',
18
22
  :type => Fixnum,
19
- :scope => '"1 = 1"',
20
- :parent => Inflector.name(base),
21
- :children => Inflector.plural_name(base)
23
+ :parent => base.to_s.demodulize.underscore.downcase,
24
+ :children => base.to_s.demodulize.underscore.downcase.plural
22
25
  }
23
26
  c.update(options) if options
24
27
 
@@ -26,16 +29,26 @@ module NestedSets
26
29
  left = c[:left]
27
30
  right = c[:right]
28
31
  children = c[:children]
29
- child = Inflector.singularize(children)
32
+ child = children.singular
30
33
 
31
34
  if c[:scope].is_a?(Symbol) && c[:scope].to_s !~ /_oid$/
32
35
  c[:scope] = "#{c[:scope]}_oid".intern
33
36
  end
37
+
34
38
  scope = c[:scope]
35
- if scope.is_a?(Symbol)
36
- scope = %{(#{scope} ? "#{scope} = \#{@#{scope}}" : "#{scope} IS NULL")}
37
- end
38
39
 
40
+ if scope
41
+ if scope.is_a?(Symbol)
42
+ scope = %{(#{scope} ? "#{scope} = \#{@#{scope}}" : "#{scope} IS NULL")}
43
+ end
44
+
45
+ cond = 'condition => ' + scope
46
+ cond_and = ':condition => ' + scope + ' + " AND " +'
47
+ else
48
+ cond = ':condition => nil'
49
+ cond_and = ':condition => '
50
+ end
51
+
39
52
  base.module_eval <<-EOE, __FILE__, __LINE__
40
53
  property :#{parent}, Fixnum, :sql_index => true
41
54
  property :#{left}, :#{right}, #{c[:type]}
@@ -52,52 +65,43 @@ module NestedSets
52
65
  return (@#{right} - @#{left} - 1)/2
53
66
  end
54
67
 
55
- def full_#{children}(extrasql = nil)
56
- #{base}.all("WHERE " + #{scope} + " AND (#{left} BETWEEN \#\{@#{left}\} AND \#{@#{right}}) \#{extrasql}")
68
+ def full_#{children}(options = {})
69
+ options.update(#{cond_and}"(#{left} BETWEEN \#\{@#{left}\} AND \#{@#{right}})")
70
+ #{base}.all(options)
57
71
  end
58
72
 
59
- def #{children}(extrasql = nil)
60
- #{base}.all("WHERE " + #{scope} + " AND (#{left} > \#\{@#{left}\}) AND (#{right} < \#{@#{right}}) \#{extrasql}")
73
+ def #{children}(options = {})
74
+ options.update(#{cond_and}"(#{left} > \#\{@#{left}\}) AND (#{right} < \#{@#{right}})")
75
+ #{base}.all(options)
61
76
  end
62
77
 
63
- def direct_#{children}(extrasql = nil)
64
- #{base}.all("WHERE " + #{scope} + " AND #{parent} = \#{@oid} \#{extrasql}")
78
+ def direct_#{children}(options = {})
79
+ options.update(#{cond_and}"#{parent} = \#{pk}")
80
+ #{base}.all(options)
65
81
  end
66
82
 
67
83
  def add_#{child}(child)
68
- self.reload if @oid
69
- child.reload if child.oid
84
+ self.reload if pk
85
+ child.reload if child.pk
70
86
 
71
87
  if @#{left}.nil? || @#{left} == 0 || @#{right}.nil? || @#{right} == 0
72
88
  @#{left} = 1
73
89
  @#{right} = 2
74
90
  end
75
91
 
76
- child.#{parent} = @oid
92
+ child.#{parent} = pk
77
93
  child.#{left} = pivot = @#{right}
78
94
  child.#{right} = pivot + 1
79
95
  @#{right} = pivot + 2
80
96
 
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}")
97
+ #{base}.transaction do
98
+ #{base}.update_property("#{left} = (#{left} + 2)", #{cond_and}"#{left} >= \#{pivot}")
99
+ #{base}.update_property("#{right} = (#{right} + 2)", #{cond_and}"#{right} >= \#{pivot}")
84
100
  end
85
101
 
86
102
  self.save
87
103
  child.save
88
104
  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
105
  EOE
102
106
  end
103
107
 
@@ -121,14 +125,16 @@ end
121
125
  module Hierarchical
122
126
 
123
127
  def self.append_dynamic_features(base, options)
124
- c = {
128
+ o = {
125
129
  :method => :nested_sets,
126
130
  }
127
- c.update(options) if options
131
+ o.update(options) if options
128
132
 
129
- base.include(NestedSets)
133
+ base.include(NestedSets, o)
130
134
  end
131
135
 
132
136
  end
133
137
 
134
138
  end
139
+
140
+ __END__