og 0.31.0 → 0.40.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/doc/{AUTHORS → CONTRIBUTORS} +26 -10
  2. data/doc/LICENSE +2 -3
  3. data/doc/RELEASES +56 -7
  4. data/doc/tutorial.txt +15 -15
  5. data/lib/glue/cacheable.rb +2 -5
  6. data/lib/glue/hierarchical.rb +1 -4
  7. data/lib/glue/optimistic_locking.rb +0 -2
  8. data/lib/glue/orderable.rb +79 -75
  9. data/lib/glue/revisable.rb +19 -24
  10. data/lib/glue/searchable.rb +0 -2
  11. data/lib/glue/taggable.rb +31 -29
  12. data/lib/glue/timestamped.rb +4 -2
  13. data/lib/og.rb +50 -29
  14. data/lib/og/adapter.rb +19 -0
  15. data/lib/og/adapter/mysql.rb +212 -0
  16. data/lib/og/adapter/mysql/override.rb +34 -0
  17. data/lib/og/adapter/mysql/script.rb +15 -0
  18. data/lib/og/adapter/mysql/utils.rb +40 -0
  19. data/lib/og/adapter/postgresql.rb +231 -0
  20. data/lib/og/adapter/postgresql/override.rb +117 -0
  21. data/lib/og/adapter/postgresql/script.rb +15 -0
  22. data/lib/og/adapter/postgresql/utils.rb +35 -0
  23. data/lib/og/adapter/sqlite.rb +132 -0
  24. data/lib/og/adapter/sqlite/override.rb +33 -0
  25. data/lib/og/adapter/sqlite/script.rb +15 -0
  26. data/lib/og/collection.rb +35 -7
  27. data/lib/og/{evolution.rb → dump.rb} +4 -5
  28. data/lib/og/entity.rb +102 -173
  29. data/lib/og/entity/clone.rb +119 -0
  30. data/lib/og/errors.rb +0 -2
  31. data/lib/og/manager.rb +85 -37
  32. data/lib/og/relation.rb +52 -34
  33. data/lib/og/relation/belongs_to.rb +0 -2
  34. data/lib/og/relation/has_many.rb +27 -4
  35. data/lib/og/relation/joins_many.rb +41 -14
  36. data/lib/og/relation/many_to_many.rb +10 -0
  37. data/lib/og/relation/refers_to.rb +22 -5
  38. data/lib/og/store.rb +80 -86
  39. data/lib/og/store/sql.rb +710 -713
  40. data/lib/og/store/sql/evolution.rb +119 -0
  41. data/lib/og/store/sql/join.rb +155 -0
  42. data/lib/og/store/sql/utils.rb +149 -0
  43. data/lib/og/test/assertions.rb +1 -3
  44. data/lib/og/test/testcase.rb +0 -2
  45. data/lib/og/types.rb +2 -5
  46. data/lib/og/validation.rb +6 -9
  47. data/test/{og/mixin → glue}/tc_hierarchical.rb +3 -13
  48. data/test/glue/tc_og_paginate.rb +47 -0
  49. data/test/{og/mixin → glue}/tc_optimistic_locking.rb +2 -12
  50. data/test/{og/mixin → glue}/tc_orderable.rb +15 -23
  51. data/test/glue/tc_orderable2.rb +47 -0
  52. data/test/glue/tc_revisable.rb +3 -3
  53. data/test/{og/mixin → glue}/tc_taggable.rb +20 -10
  54. data/test/{og/mixin → glue}/tc_timestamped.rb +2 -12
  55. data/test/glue/tc_webfile.rb +36 -0
  56. data/test/og/CONFIG.rb +8 -11
  57. data/test/og/multi_validations_model.rb +14 -0
  58. data/test/og/store/tc_filesys.rb +3 -1
  59. data/test/og/store/tc_kirby.rb +16 -13
  60. data/test/og/store/tc_sti.rb +11 -11
  61. data/test/og/store/tc_sti2.rb +79 -0
  62. data/test/og/tc_build.rb +41 -0
  63. data/test/og/tc_cacheable.rb +3 -2
  64. data/test/og/tc_has_many.rb +96 -0
  65. data/test/og/tc_inheritance.rb +6 -4
  66. data/test/og/tc_joins_many.rb +93 -0
  67. data/test/og/tc_multi_validations.rb +5 -7
  68. data/test/og/tc_multiple.rb +7 -6
  69. data/test/og/tc_override.rb +13 -7
  70. data/test/og/tc_primary_key.rb +30 -0
  71. data/test/og/tc_relation.rb +8 -14
  72. data/test/og/tc_reldelete.rb +163 -0
  73. data/test/og/tc_reverse.rb +17 -14
  74. data/test/og/tc_scoped.rb +3 -11
  75. data/test/og/tc_setup.rb +13 -11
  76. data/test/og/tc_store.rb +21 -28
  77. data/test/og/tc_validation2.rb +2 -2
  78. data/test/og/tc_validation_loop.rb +17 -15
  79. metadata +109 -103
  80. data/INSTALL +0 -91
  81. data/ProjectInfo +0 -51
  82. data/README +0 -177
  83. data/doc/config.txt +0 -28
  84. data/examples/README +0 -23
  85. data/examples/mysql_to_psql.rb +0 -71
  86. data/examples/run.rb +0 -271
  87. data/lib/glue/tree.rb +0 -218
  88. data/lib/og/store/alpha/filesys.rb +0 -110
  89. data/lib/og/store/alpha/memory.rb +0 -295
  90. data/lib/og/store/alpha/sqlserver.rb +0 -256
  91. data/lib/og/store/kirby.rb +0 -490
  92. data/lib/og/store/mysql.rb +0 -415
  93. data/lib/og/store/psql.rb +0 -875
  94. data/lib/og/store/sqlite.rb +0 -348
  95. data/lib/og/store/sqlite2.rb +0 -241
  96. data/setup.rb +0 -1585
  97. data/test/og/tc_sti_find.rb +0 -35
@@ -15,5 +15,3 @@ class BelongsTo < RefersTo
15
15
  end
16
16
 
17
17
  end
18
-
19
- # * George Moschovitis <gm@navel.gr>
@@ -18,6 +18,8 @@ end
18
18
  # article.comments.size
19
19
 
20
20
  class HasMany < Relation
21
+ ann self, :collection => true # marker
22
+
21
23
  def resolve_polymorphic
22
24
  unless target_class.relations.empty?
23
25
  unless target_class.relations.find { |r| r.is_a?(BelongsTo) and r.target_class == owner_class }
@@ -27,7 +29,12 @@ class HasMany < Relation
27
29
  end
28
30
 
29
31
  def enchant
30
- self[:owner_singular_name] = owner_class.to_s.demodulize.underscore.downcase
32
+ self[:owner_singular_name] = if owner_class.schema_inheritance?
33
+ owner_class.schema_inheritance_root_class
34
+ else
35
+ owner_class
36
+ end.to_s.demodulize.underscore.downcase
37
+
31
38
  self[:target_singular_name] = target_plural_name.to_s.singular
32
39
  self[:foreign_key] = "#{foreign_name || owner_singular_name}_#{owner_class.primary_key}"
33
40
  # gmosx: DON'T set self[:foreign_field]
@@ -53,6 +60,25 @@ class HasMany < Relation
53
60
  @#{target_plural_name}.reload(options) if options and options[:reload]
54
61
  @#{target_plural_name}
55
62
  end
63
+
64
+ def #{target_plural_name}=(*args)
65
+ options = args.last.is_a?(Hash) ? args.pop : nil
66
+
67
+ if args.size == 1 && args.first.is_a?(HasManyCollection)
68
+ @#{target_plural_name} = args.first
69
+ @#{target_plural_name}.find_options = options if options
70
+
71
+ return @#{target_plural_name}
72
+ end
73
+
74
+ args.flatten!
75
+
76
+ args.each do |obj|
77
+ add_#{target_singular_name}(obj, options)
78
+ end
79
+
80
+ return #{target_plural_name}
81
+ end
56
82
 
57
83
  def add_#{target_singular_name}(obj, options = nil)
58
84
  return unless obj
@@ -89,6 +115,3 @@ class HasMany < Relation
89
115
  end
90
116
 
91
117
  end
92
-
93
- # * George Moschovitis <gm@navel.gr>
94
- # * Guillaunne Pierronnet <guillaunne.pierronnet@laposte.net>
@@ -14,10 +14,23 @@ end
14
14
  #
15
15
  # === Examples
16
16
  #
17
+ # joins_many :categories
17
18
  # joins_many Category
18
- # joins_many :categories, Category
19
+ # joins_many :categories, MyCategory
19
20
 
20
21
  class JoinsMany < Relation
22
+ ann self, :collection => true # marker
23
+
24
+ #--
25
+ # FIXME: better enchant polymorphic parents and then add
26
+ # an alias to :objects.
27
+ #++
28
+
29
+ def resolve_polymorphic
30
+ target_class.module_eval %{
31
+ joins_many :#{owner_class.to_s.demodulize.underscore.pluralize}
32
+ }
33
+ end
21
34
 
22
35
  def enchant
23
36
  self[:owner_singular_name] = owner_class.to_s.demodulize.underscore.downcase
@@ -32,22 +45,44 @@ class JoinsMany < Relation
32
45
  join_class = sclass
33
46
  end
34
47
  join_table_info = store.join_table_info(self)
35
-
48
+
36
49
  if through = self[:through]
37
50
  # A custom class is used for the join. Use the class
38
51
  # table and don't create a new table.
39
52
  through = Relation.symbol_to_class(through, owner_class) if through.is_a?(Symbol)
40
53
  join_table = self[:join_table] = store.table(through)
54
+
55
+ tmp_join_class = through
41
56
  else
42
- # Calculate the name of the join table
57
+ # Calculate the name of the join table.
43
58
  join_table = self[:join_table] = join_table_info[:table]
44
59
  # Create a join table.
45
- owner_class.ann :self, :join_tables => [] if owner_class.ann.self.join_tables.nil?
60
+ owner_class.ann :self, :join_tables => [] unless owner_class.ann.self.join_tables?
46
61
  owner_class.ann.self.join_tables! << join_table_info
62
+
63
+ # Create temporary classes to be able to use ann.descendants to cascade
64
+ # delete joins_many relations when one side of the relation is removed.
65
+
66
+ tmp_join_class_name = "OgTempJ_#{join_table}"
67
+
68
+ unless self.class.constants.include?(tmp_join_class_name)
69
+ tmp_join_class = self.class.const_set(tmp_join_class_name, Class.new)
70
+ tmp_join_class.const_set('OGTABLE', join_table)
71
+ else
72
+ tmp_join_class = self.class.const_get(tmp_join_class_name)
73
+ end
74
+
47
75
  end
48
76
 
49
77
  owner_key = join_table_info[:owner_key]
50
78
  target_key = join_table_info[:target_key]
79
+
80
+ # Add join class to ann.descendants to be able to use cascade deletes.
81
+
82
+ unless owner_class.ann.self[:descendants]
83
+ owner_class.ann(:self, :descendants => [])
84
+ end
85
+ owner_class.ann.self.descendants! << [tmp_join_class, owner_key]
51
86
 
52
87
  owner_class.module_eval %{
53
88
  attr_accessor :#{target_plural_name}
@@ -96,17 +131,13 @@ class JoinsMany < Relation
96
131
  #{target_class}.find(find_options)
97
132
  end
98
133
 
99
- #--
100
- # FIXME/TODO: optimize this!! use count.
101
- #++
102
-
103
134
  def count_#{target_plural_name}
104
135
  find_options = {
105
136
  :join_table => "#{join_table}",
106
137
  :join_condition => "#{join_table}.#{target_key}=\#{#{target_class}::OGTABLE}.oid",
107
- :condition => "#{join_table}.#{owner_key}=\#\{#{owner_class.primary_key}\}"
138
+ :condition => "#{join_table}.#{owner_key}=\#\{#{owner_class.primary_key}\}"
108
139
  }
109
- #{target_class}.find(find_options).size
140
+ #{target_class}.count(find_options)
110
141
  end
111
142
  }
112
143
 
@@ -122,7 +153,3 @@ class JoinsMany < Relation
122
153
  end
123
154
 
124
155
  end
125
-
126
- # * George Moschovitis <gm@navel.gr>
127
- # * Ysabel <deb@ysabel.org>
128
- # * Guillaume Pierronnet <guillaume.pierronnet@laposte.net>
@@ -12,6 +12,16 @@ module Og
12
12
  # many_to_many :categories, Category
13
13
 
14
14
  class ManyToMany < JoinsMany
15
+ #--
16
+ # FIXME: better enchant polymorphic parents and then add
17
+ # an alias to :objects.
18
+ #++
19
+
20
+ def resolve_polymorphic
21
+ target_class.module_eval %{
22
+ many_to_many :#{owner_class.to_s.demodulize.underscore.pluralize}
23
+ }
24
+ end
15
25
  end
16
26
 
17
27
  end
@@ -2,6 +2,15 @@ require 'og/relation'
2
2
 
3
3
  module Og
4
4
 
5
+ # A refers to relation. Stores the foreign key in the object.
6
+ #--
7
+ # TODO: No need to save user (should only call save aspects, for
8
+ # example sweepers)
9
+ #
10
+ # THINK: Investigate if we should only store the key in the
11
+ # foreign object.
12
+ #++
13
+
5
14
  class RefersTo < Relation
6
15
 
7
16
  def self.foreign_key(rel)
@@ -17,9 +26,14 @@ class RefersTo < Relation
17
26
  field = ", :field => :#{self[:field]}"
18
27
  end
19
28
 
29
+ target_primary_key_class = target_class.ann(target_class.primary_key).class
30
+
31
+ join_table_info = owner_class.ogmanager.store.join_table_info(self)
32
+ owner_key = join_table_info[:owner_key]
33
+
20
34
  owner_class.module_eval %{
21
35
  attr_accessor :#{target_singular_name}
22
- prop_accessor :#{foreign_key}, #{target_class.primary_key.klass}#{field}, :relation => true
36
+ attr_accessor :#{foreign_key}, #{target_primary_key_class}#{field}, :relation => true
23
37
 
24
38
  def #{target_singular_name}(reload = false)
25
39
  return nil if @#{foreign_key}.nil?
@@ -32,7 +46,13 @@ class RefersTo < Relation
32
46
  end
33
47
 
34
48
  def #{target_singular_name}=(obj)
35
- @#{foreign_key} = obj.#{target_class.primary_key} if obj
49
+ if obj
50
+ @#{foreign_key} = obj.#{target_class.primary_key}
51
+ obj.#{owner_key} = pk if obj.respond_to?(:#{owner_key}=)
52
+ # obj.save
53
+ save unless self.unsaved?
54
+ end
55
+ return obj
36
56
  end
37
57
  }
38
58
  end
@@ -40,6 +60,3 @@ class RefersTo < Relation
40
60
  end
41
61
 
42
62
  end
43
-
44
- # * George Moschovitis <gm@navel.gr>
45
- # * Michael Neumann <mneumann@ntecs.de>
@@ -1,161 +1,164 @@
1
1
  module Og
2
2
 
3
- # A Store is responsible for the peristance of the ObjectGraph.
3
+ # A Store is a backend of Og used to persist objects. An
4
+ # adapter specializes the Store. For example the SQL Store
5
+ # has the MySQL, PostgreSQL, Sqlite3 etc adapters as
6
+ # specializations.
4
7
 
5
8
  class Store
6
9
 
7
- # Options.
8
-
9
- attr_accessor :options
10
-
11
- # Transaction nesting.
12
-
13
- attr_accessor :transaction_nesting
14
-
15
- # :section: Store methods.
16
-
17
- # Return the store for the given name.
18
-
19
- def self.for_name(name)
20
- Logger.info "Og uses the #{name.to_s.capitalize} store."
21
- # gmosx: to keep RDoc happy.
22
- require('og/store/' + name.to_s)
23
- return Og.const_get("#{name.to_s.capitalize}Store")
24
- end
25
-
26
- # Creates a store.
27
-
28
- def self.create(options)
29
- end
30
-
31
- # Destroys a store.
32
-
33
- def self.destroy(options)
34
- end
35
-
36
- # :section: Misc methods.
10
+ attr_accessor :ogmanager
37
11
 
38
12
  # Create a session to the store.
39
-
40
- def initialize(options)
41
- @options = options
13
+ #
14
+ # === Default options
15
+ #
16
+ # * :evolve_schema => :warn
17
+
18
+ def initialize options
19
+ @options = {
20
+ :evolve_schema => :warn
21
+ }.merge(options)
42
22
  @transaction_nesting = 0
43
23
  end
44
24
 
45
25
  # Close the session to the store.
46
26
 
47
27
  def close
28
+ raise 'Not implemented'
48
29
  end
49
-
30
+
50
31
  # Enchants a class.
51
32
 
52
33
  def enchant(klass, manager)
53
- # gmosx, FIXME:
54
- # ARGH, have to update this!
55
- #
56
- # klass.class.send(:define_method, :index) do |arg|
57
- # meta :index, arg
58
- # end
59
-
60
- pk = klass.primary_key.symbol
61
-
34
+ pk = klass.primary_key
62
35
  klass.module_eval %{
36
+
37
+ # A managed object is considered saved if it has
38
+ # a valid primary key.
39
+
63
40
  def saved?
64
- return #{klass.primary_key.symbol}
41
+ return #{pk}
65
42
  end
66
43
 
44
+ # The inverse of saved.
45
+
67
46
  def unsaved?
68
- return !#{klass.primary_key.symbol}
47
+ return !#{pk}
69
48
  end
70
49
 
71
50
  # Evaluate an alias for the primary key.
72
51
 
73
52
  alias_method :pk, :#{pk}
74
53
  alias_method :pk=, :#{pk}=
75
-
76
- def self.pk_symbol
77
- :#{klass.primary_key.symbol}
78
- end
79
54
  }
80
-
81
55
  end
82
-
56
+
83
57
  # :section: Lifecycle methods.
84
58
 
85
59
  # Loads an object from the store using the primary key.
86
60
 
87
61
  def load(pk, klass)
62
+ raise 'Not implemented'
88
63
  end
64
+ alias_method :exist?, :load
89
65
 
90
66
  # Reloads an object from the store.
91
67
 
92
68
  def reload(obj)
69
+ raise 'Not implemented'
93
70
  end
94
71
 
95
72
  # Save an object to store. Insert if this is a new object or
96
- # update if this is already inserted in the database.
73
+ # update if this is already inserted in the database. Checks
74
+ # if the object is valid before saving. Returns false if the
75
+ # object is invalid and populates obj.errors.
97
76
 
98
77
  def save(obj, options = nil)
99
78
  return false unless obj.valid?
100
79
 
101
80
  if obj.saved?
102
- obj.og_update(self, options)
81
+ update_count = obj.og_update(self, options)
103
82
  else
104
- obj.og_insert(self)
83
+ update_count = 1 if obj.og_insert(self)
105
84
  end
85
+
86
+ # Save building collections if any.
87
+ obj.save_building_collections
88
+
89
+ return update_count
106
90
  end
107
91
  alias_method :<<, :save
108
92
  alias_method :validate_and_save, :save
109
93
 
94
+ # Force the persistence of an Object. Ignore any validation
95
+ # and/or other errors.
96
+
110
97
  def force_save!(obj, options)
111
98
  if obj.saved?
112
- obj.og_update(self, options)
99
+ obj.og_update self, options
113
100
  else
114
- obj.og_insert(self)
101
+ obj.og_insert self
115
102
  end
116
103
  end
117
104
 
118
105
  # Insert an object in the store.
119
106
 
120
107
  def insert(obj)
121
- obj.og_insert(self)
108
+ obj.og_insert self
122
109
  end
123
110
 
124
111
  # Update an object in the store.
125
112
 
126
- def update(obj, options = nil)
127
- obj.og_update(self, options)
113
+ def update obj, options = nil
114
+ obj.og_update self, options
128
115
  end
129
116
 
130
- # Update selected properties of an object or class of
117
+ # Update selected attributes of an object or class of
131
118
  # objects.
132
-
133
- def update_properties(obj_or_class, props, options = nil)
119
+
120
+ def update_attributes target, *attributes
121
+ update(target, :only => attributes)
134
122
  end
135
- alias_method :pupdate, :update_properties
136
- alias_method :update_property, :update_properties
123
+ alias_method :aupdate, :update_attributes
124
+ alias_method :update_attribute, :update_attributes
137
125
 
138
126
  # Permanently delete an object from the store.
139
127
 
140
128
  def delete(obj_or_pk, klass = nil, cascade = true)
141
- unless obj_or_pk.is_a?(EntityMixin)
142
- # create a dummy instance to keep the og_delete
129
+ unless obj_or_pk.is_a? EntityMixin
130
+ # create an instance to keep the og_delete
143
131
  # method as an instance method like the other lifecycle
144
- # methods.
145
- klass.allocate.og_delete(self, obj_or_pk, cascade)
132
+ # methods. This even allows to call og_delete aspects
133
+ # that use instance variable (for example, sophisticated
134
+ # cache sweepers).
135
+ #
136
+ # gmosx: the following is not enough!
137
+ # obj = klass.allocate
138
+ # obj.pk = obj_or_pk
139
+ obj = klass[obj_or_pk]
140
+ obj.og_delete(self, cascade)
146
141
  else
147
- obj_or_pk.og_delete(self, nil, cascade)
142
+ obj_or_pk.og_delete(self, cascade)
148
143
  end
149
144
  end
150
145
 
146
+ # Delete all instances of the given class.
147
+
148
+ def delete_all(klass)
149
+ raise 'Not implemented'
150
+ end
151
+
151
152
  # Perform a query.
152
153
 
153
154
  def find(klass, options)
155
+ raise 'Not implemented'
154
156
  end
155
157
 
156
158
  # Count the results returned by the query.
157
159
 
158
160
  def count(options)
161
+ raise 'Not implemented'
159
162
  end
160
163
 
161
164
  # :section: Transaction methods.
@@ -191,7 +194,7 @@ class Store
191
194
  start
192
195
  yield(self)
193
196
  commit
194
- rescue => ex
197
+ rescue Object => ex
195
198
  rollback
196
199
  raise ex
197
200
  end
@@ -199,22 +202,13 @@ class Store
199
202
 
200
203
  private
201
204
 
202
- def eval_og_insert(klass)
203
- end
204
-
205
- def eval_og_update(klass)
206
- end
207
-
208
- def eval_og_read(klass)
209
- end
210
-
211
- def eval_og_delete(klass)
212
- end
213
-
214
- def eval_og_create_schema(klass)
205
+ #--
206
+ # A helper for inserting advices in the precompiled methods.
207
+ #++
208
+
209
+ def insert_advices where, target, klass, advices # :nodoc: all
210
+ Aspects.gen_advice_code(target, klass.send(advices), where) if klass.respond_to?(advices)
215
211
  end
216
212
  end
217
213
 
218
214
  end
219
-
220
- # * George Moschovitis <gm@navel.gr>