og 0.16.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
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__