og 0.21.2 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,7 +1,66 @@
1
+ 07-08-2005 George Moschovitis <gm@navel.gr>
2
+
3
+ * lib/og/entity.rb: fixed schema_inheritance alias.
4
+
5
+ * lib/og.rb: updated comments.
6
+
7
+ * doc/RELEASES: updated.
8
+
9
+ 06-08-2005 George Moschovitis <gm@navel.gr>
10
+
11
+ * lib/og/entity.rb: helper method.
12
+
13
+ * lib/og/manager.rb (#manage_class): alias,
14
+ (#get_store): fixed bug in thread_safe mode, allows multiple
15
+ og's to run in thread safe mode.
16
+
17
+ * test/og/tc_multiple.rb: implemented.
18
+
19
+ 05-08-2005 George Moschovitis <gm@navel.gr>
20
+
21
+ * lib/og/relation/has_many.rb: support for legacy schemas,
22
+ :foreign_field option,
23
+ quote foreign_key values,
24
+ fixed foreign_field bug.
25
+
26
+ * lib/og/relation/refers_to.rb: support for legacy schemas.
27
+
28
+ * lib/og/entity.rb (#set_primary_key): also get primary key class,
29
+ (#og_quote): added.
30
+
31
+ 04-08-2005 George Moschovitis <gm@navel.gr>
32
+
33
+ * lib/og/store/*: use #field_for_property where needed,
34
+ don't create the oid property if not needed!
35
+ fixed enchant some more,
36
+ fixed finder calculation to honour field override.
37
+
38
+ * lib/og/store/sql.rb (#table): honour sql_table metadata,
39
+ (#field_for_property): implemented,
40
+ (#eval_og_read): updated to handle custom fields.
41
+
42
+ * lib/og/entity.rb (#sql_table): added helper [daval],
43
+ added set_xxx prefix to some helper.
44
+
45
+ * test/og/tc_reverse.rb: introduced,
46
+ Yeah, I got data written to a legacy table.
47
+
48
+ * lib/og/test/testcase.rb: introduced,
49
+ (fixture): implemented,
50
+ (og_fixture): implemented.
51
+
52
+ * lib/og/test/assertions.rb: introduced.
53
+
54
+ 30-07-2005 George Moschovitis <gm@navel.gr>
55
+
56
+ * test/: renamed from testing.
57
+
1
58
  28-07-2005 George Moschovitis <gm@navel.gr>
2
59
 
60
+ * --- VERSION 0.21.1 ---
61
+
3
62
  * lib/og/store/psql.rb: custom eval_og_allocate,
4
- fixed inheritance porblem.
63
+ fixed inheritance problem.
5
64
 
6
65
  * lib/og/store/sql.rb: factored out eval_og_allocate.
7
66
 
data/README CHANGED
@@ -1,4 +1,4 @@
1
- = Og 0.21.2 README
1
+ = Og 0.22.0 README
2
2
 
3
3
  Og (ObjectGraph) is a powerful and elegant object-relational mapping
4
4
  library. Og manages the lifecycle of Ruby objects and provides
@@ -28,21 +28,25 @@ distribution (http://www.nitrohq.com).
28
28
 
29
29
  The library provides the following features:
30
30
 
31
- * Object-Relational mapping.
31
+ * Object-Relational mapping, automatically maps standard
32
+ Ruby objects to sql schemas
32
33
  * Absolutely no configuration files.
33
34
  * Multiple stores (PostgreSQL, MySQL, SQLite, Oraclei, SqlServer, ..).
34
- * Supports non SQL stores.
35
+ * Supports non SQL stores (in-memory, filesystem, ..).
36
+ * Can 'reverse engineer' legacy database schemase.
37
+ * Fine-grained or High-level customization of the generated
38
+ schema.
35
39
  * ActiveRecord-style domain specific language and db synchronized
36
40
  collections.
37
41
  * Transforms resultsets from arbitrary sql queries to Ruby objects.
38
- * Automatically generate join tables for many to many relations.
39
- * Can use any Ruby object to join other Ruby objects.
42
+ * Independent store for each object class, can support multiple
43
+ stores in the same application.
40
44
  * Deserialize to Ruby Objects.
41
45
  * Deserialize sql join queries to Ruby Objects.
42
46
  * Eager associations.
43
47
  * Serialize arbitrary ruby object graphs through YAML.
44
48
  * Connection pooling.
45
- * Thread safety (temporarily dissabled).
49
+ * Thread safety.
46
50
  * SQL transactions.
47
51
  * Aspect oriented constructs allow interception of lifecycle callbacks.
48
52
  * Transparent support for cascading deletes for all backends.
data/doc/RELEASES CHANGED
@@ -1,3 +1,69 @@
1
+ == Version 0.22.0
2
+
3
+ A snapshot of the latest developments. Many requested features
4
+ where implemented, and many reported bugs fixed.
5
+
6
+ * The much requested Og 'reverse mode' is implemented. Og's
7
+ domain specific language and API is extended to allow fine-grained
8
+ customization of the schema-to-objects mapping. Og can now handle
9
+ most of the legacy schemas you can throw at it. Here is an
10
+ example:
11
+
12
+ class User
13
+ property :name, String, :field => :thename, :uniq => true
14
+ property :password, String
15
+ property :age, Fixnum, :field => :age3
16
+ has_many Comment, :foreign_field => :user
17
+ set_table :my_users
18
+ set_primary_key :name, String
19
+ end
20
+
21
+ class Comment
22
+ property :cid, Fixnum
23
+ property :body, String
24
+ belongs_to User, :field => :user
25
+ set_table :my_comments
26
+ set_primary_key :cid
27
+ end
28
+
29
+ As you can see, even relation fields can be customized. For
30
+ higher level customization you can overload methods like #table,
31
+ #field_for_property etc.
32
+
33
+ * Og now handles multiple connections to multiple stores even
34
+ in thread safe mode. This means you can now have objects serialized
35
+ in different RDBM systems in the same application. Or you can have
36
+ some objects managed by an RDBMS store and others by a FAST
37
+ in-memory store. You can still have relations between objects in
38
+ different stores. An example:
39
+
40
+ mysql = Og.setup(:store => :mysql, ..)
41
+ psql = Og.setup(:store => :psql, ..)
42
+
43
+ class User
44
+ has_many Comment
45
+ end
46
+
47
+ class Comment
48
+ belongs_to User
49
+ end
50
+
51
+ mysql.manage_class User
52
+ psql.manage_class Comment
53
+
54
+ user.comments << comment
55
+
56
+ * Greatly improved support for testing and test-driven-development.
57
+ Support for Og fixtures and automatic test database
58
+ setup is provided. Fixtures can be defined with yml or csv files.
59
+
60
+ For an example of the testing infrastructure check out the
61
+ Spark 0.4.0 sources. Spark is a Wiki powered by Nitro.
62
+
63
+ * Many smaller changes and feature implementations that make
64
+ development with Og so much more pleasurable.
65
+
66
+
1
67
  == Version 0.21.2
2
68
 
3
69
  This is a bug fix release.
data/lib/og.rb CHANGED
@@ -27,24 +27,32 @@ require 'glue/configuration'
27
27
  #
28
28
  # The library provides the following features:
29
29
  #
30
- # + Object-Relational mapping, automatically maps standard
30
+ # * Object-Relational mapping, automatically maps standard
31
31
  # Ruby objects to sql schemas
32
- # + Absolutely no configuration files.
33
- # + Multiple stores (PostgreSQL, MySQL, SQLite, Oraclei, SqlServer, ..).
34
- # + Supports non SQL stores (in-memory, filesystem, ..).
35
- # + ActiveRecord-style meta language and db aware methods.
36
- # + Deserialize to Ruby Objects.
37
- # + Deserialize sql join queries to Ruby Objects.
38
- # + Eager associations.
39
- # + Serialize arbitrary ruby object graphs through YAML.
40
- # + Connection pooling.
41
- # + Thread safety.
42
- # + SQL transactions.
43
- # + Aspect oriented constructs allow interception of lifecycle callbacks.
44
- # + Transparent support for cascading deletes for all backends.
45
- # + Hierarchical structures (nested sets)
46
- # + Works safely as part of distributed application.
47
- # + Simple implementation.
32
+ # * Absolutely no configuration files.
33
+ # * Multiple stores (PostgreSQL, MySQL, SQLite, Oraclei, SqlServer, ..).
34
+ # * Supports non SQL stores (in-memory, filesystem, ..).
35
+ # * Can 'reverse engineer' legacy database schemase.
36
+ # * Fine-grained or High-level customization of the generated
37
+ # schema.
38
+ # * ActiveRecord-style domain specific language and db synchronized
39
+ # collections.
40
+ # * Transforms resultsets from arbitrary sql queries to Ruby objects.
41
+ # * Independent store for each object class, can support multiple
42
+ # stores in the same application.
43
+ # * Deserialize to Ruby Objects.
44
+ # * Deserialize sql join queries to Ruby Objects.
45
+ # * Eager associations.
46
+ # * Serialize arbitrary ruby object graphs through YAML.
47
+ # * Connection pooling.
48
+ # * Thread safety.
49
+ # * SQL transactions.
50
+ # * Aspect oriented constructs allow interception of lifecycle callbacks.
51
+ # * Transparent support for cascading deletes for all backends.
52
+ # * Hierarchical structures (nested sets)
53
+ # * Works safely as part of distributed application.
54
+ # * Optimistic locking.
55
+ # * Simple implementation.
48
56
  #
49
57
  # === Property Metadata
50
58
  #
@@ -75,7 +83,7 @@ module Og
75
83
 
76
84
  # The version.
77
85
 
78
- Version = '0.21.2'
86
+ Version = '0.22.0'
79
87
 
80
88
  # Library path.
81
89
 
data/lib/og/entity.rb CHANGED
@@ -14,7 +14,7 @@ module EntityMixin
14
14
  base.module_eval <<-end_eval, __FILE__, __LINE__
15
15
  def save(options = nil)
16
16
  self.class.ogmanager.store.save(self, options)
17
- # return self
17
+ # return self
18
18
  end
19
19
  alias_method :save!, :save
20
20
 
@@ -56,6 +56,10 @@ module EntityMixin
56
56
  def transaction(&block)
57
57
  self.class.ogmanager.store.transaction(&block)
58
58
  end
59
+
60
+ def og_quote(obj)
61
+ self.class.ogmanager.store.quote(obj)
62
+ end
59
63
  end_eval
60
64
 
61
65
  base.send(:include, RelationMacros)
@@ -132,7 +136,13 @@ module EntityMixin
132
136
  def transaction(&block)
133
137
  ogmanager.store.transaction(&block)
134
138
  end
139
+
140
+ # Return the store (connection) for this class.
135
141
 
142
+ def ogstore
143
+ ogmanager.store
144
+ end
145
+
136
146
  def primary_key
137
147
  unless @__meta and @__meta[:primary_key]
138
148
  self.meta :primary_key, Entity.resolve_primary_key(self)
@@ -142,21 +152,37 @@ module EntityMixin
142
152
 
143
153
  # Set the default find options for this entity.
144
154
 
145
- def find_options(options)
155
+ def set_find_options(options)
146
156
  meta :find_options, options
147
157
  end
158
+ alias_method :find_options, :set_find_options
159
+
160
+ # Enable schema inheritance for this Entity class.
161
+ # The Single Table Inheritance pattern is used.
162
+
163
+ def set_schema_inheritance
164
+ meta :schema_inheritance
165
+ end
166
+ alias_method :schema_inheritance, :set_schema_inheritance
148
167
 
149
168
  # Set the default order option for this entity.
150
169
 
151
- def order(order_str)
170
+ def set_order(order_str)
152
171
  meta :find_options, :order => order_str
153
172
  end
173
+ alias_method :order, :set_order
154
174
 
155
- # Enable schema inheritance for this Entity class.
156
- # The Single Table Inheritance pattern is used.
157
-
158
- def schema_inheritance
159
- meta :schema_inheritance
175
+ # Set a custom table name.
176
+
177
+ def set_sql_table(table)
178
+ meta :sql_table, table.to_s
179
+ end
180
+ alias_method :set_table, :set_sql_table
181
+
182
+ # Set the primary key.
183
+
184
+ def set_primary_key(pk, pkclass = Fixnum)
185
+ meta :primary_key, pk, pkclass
160
186
  end
161
187
 
162
188
  # Is this entity a polymorphic parent?
@@ -178,6 +204,9 @@ end
178
204
  class Entity
179
205
  class << self
180
206
  def resolve_primary_key(klass)
207
+ if pk = klass.__meta[:primary_key]
208
+ return pk
209
+ end
181
210
  for p in klass.properties
182
211
  if p.meta[:primary_key]
183
212
  return p.symbol, p.klass
data/lib/og/manager.rb CHANGED
@@ -40,35 +40,36 @@ class Manager
40
40
  @entities = {}
41
41
  @pool = Glue::Pool.new
42
42
 
43
- store_class = Store.for_name(options[:store])
44
- store_class.destroy(options) if options[:destroy]
43
+ @store_class = Store.for_name(options[:store])
44
+ @store_class.destroy(options) if options[:destroy]
45
45
 
46
46
  if Og.thread_safe
47
47
  (options[:connection_count] || 5).times do
48
- @pool << store_class.new(options)
48
+ @pool << @store_class.new(options)
49
49
  end
50
50
  else
51
- @store = store_class.new(options)
51
+ @store = @store_class.new(options)
52
52
  end
53
53
  end
54
54
 
55
- # Get a store from the pool.
55
+ # Get a store from the pool.
56
56
 
57
57
  def get_store
58
58
  if Og.thread_safe
59
59
  thread = Thread.current
60
60
 
61
- unless conn = thread[:og_store]
62
- conn = @pool.pop()
63
- thread[:og_store] = conn
61
+ unless st = thread[:og_store] and st.is_a?(@store_class)
62
+ st = @pool.pop()
63
+ thread[:og_store] = st
64
64
  end
65
65
 
66
- return conn
66
+ return st
67
67
  else
68
68
  return @store
69
69
  end
70
70
  end
71
71
  alias_method :store, :get_store
72
+ alias_method :conn, :get_store
72
73
 
73
74
  # Return a store to the pool.
74
75
 
@@ -111,7 +112,7 @@ class Manager
111
112
  # subclass.
112
113
 
113
114
  manage(klass.superclass) if has_super?(klass)
114
-
115
+
115
116
  klass.module_eval %{
116
117
  def ==(other)
117
118
  other ? @#{klass.primary_key.first} == other.#{klass.primary_key.first} : false
@@ -181,6 +182,7 @@ class Manager
181
182
  classes.each { |c| Relation.resolve(c, :resolve_options) }
182
183
  classes.each { |c| manage(c) }
183
184
  end
185
+ alias_method :manage_class, :manage_classes
184
186
 
185
187
  end
186
188
 
@@ -1,3 +1,5 @@
1
+ require 'glue/aspects'
2
+
1
3
  module Og
2
4
 
3
5
  # Adds timestamping functionality.
@@ -7,8 +9,8 @@ module Timestamped
7
9
  property :update_time, Time
8
10
  property :access_time, Time
9
11
 
10
- pre "@create_time = @update_time = Time.now", :on => :og_insert
11
- pre "@update_time = Time.now", :on => :og_update
12
+ before "@create_time = @update_time = Time.now", :on => :og_insert
13
+ before "@update_time = Time.now", :on => :og_update
12
14
 
13
15
  def touch!
14
16
  @access_time = Time.now
data/lib/og/relation.rb CHANGED
@@ -42,7 +42,6 @@ class Relation
42
42
  # as a polymorphic parent class.
43
43
  # This class acts as template
44
44
  # to generate customized versions of this class.
45
-
46
45
  owner_class.meta(:polymorphic, owner_class)
47
46
  elsif target_class.respond_to?(:metadata) and target_class.metadata.polymorphic
48
47
  # If the target class is polymorphic, create a specialized
@@ -32,6 +32,8 @@ class HasMany < Relation
32
32
  self[:owner_singular_name] = owner_class.to_s.demodulize.underscore.downcase
33
33
  self[:target_singular_name] = target_plural_name.to_s.singular
34
34
  self[:foreign_key] = "#{foreign_name || owner_singular_name}_#{owner_pk}"
35
+ # gmosx: DON'T set self[:foreign_field]
36
+ foreign_field = self[:foreign_field] || self[:foreign_key]
35
37
 
36
38
  owner_class.module_eval %{
37
39
  attr_accessor :#{target_plural_name}
@@ -65,7 +67,7 @@ class HasMany < Relation
65
67
 
66
68
  def find_#{target_plural_name}(options = {})
67
69
  find_options = {
68
- :condition => "#{foreign_key} = \#\{@#{owner_pk}\}"
70
+ :condition => "#{foreign_field} = \#{og_quote(@#{owner_pk})}"
69
71
  }
70
72
  if options
71
73
  if condition = options.delete(:condition)
@@ -77,7 +79,7 @@ class HasMany < Relation
77
79
  end
78
80
 
79
81
  def count_#{target_plural_name}
80
- #{target_class}.count(:condition => "#{foreign_key} = \#\{@#{owner_pk}\}")
82
+ #{target_class}.count(:condition => "#{foreign_field} = \#{og_quote(@#{owner_pk})}")
81
83
  end
82
84
  }
83
85
  end
@@ -7,9 +7,13 @@ class RefersTo < Relation
7
7
  def enchant
8
8
  self[:foreign_key] = "#{foreign_name || target_singular_name}_#{target_pk}"
9
9
 
10
+ if self[:field]
11
+ field = ", :field => :#{self[:field]}"
12
+ end
13
+
10
14
  owner_class.module_eval %{
11
15
  attr_accessor :#{target_singular_name}
12
- prop_accessor :#{foreign_key}, #{target_pkclass}
16
+ prop_accessor :#{foreign_key}, #{target_pkclass}#{field}
13
17
 
14
18
  def #{target_singular_name}(reload = false)
15
19
  return nil if @#{foreign_key}.nil?
data/lib/og/store.rb CHANGED
@@ -89,7 +89,7 @@ class Store
89
89
  def self.find_by_#{p.symbol}(val, operator = '=', options = {})
90
90
  options.update(
91
91
  :class => #{klass},
92
- :condition => "#{p.symbol}\#{operator}\#{ogmanager.store.quote(val)}"
92
+ :condition => "#{p.meta[:field] || p.symbol}\#{operator}\#{ogmanager.store.quote(val)}"
93
93
  )
94
94
  ogmanager.store.#{finder}(options)
95
95
  end;
@@ -6,7 +6,7 @@ require 'og/store'
6
6
  module Og
7
7
 
8
8
  # A Store that saves the object on the Filesystem.
9
- # You can create multiple instances that point to the same store.
9
+ # An extension of the Memory store.
10
10
 
11
11
  class FilesysStore < Store
12
12
 
@@ -30,8 +30,6 @@ class FilesysStore < Store
30
30
  Logger.error "Cannot destroy '#{name}'"
31
31
  end
32
32
 
33
- # :section: Misc methods.
34
-
35
33
  # Initialize a connection to this store.
36
34
 
37
35
  def initialize(options)
@@ -54,7 +52,6 @@ class FilesysStore < Store
54
52
  FileUtils.mkdir_p(path(klass))
55
53
  File.open(spath(klass), 'w') { |f| f << '1' }
56
54
  rescue => ex
57
- p ex
58
55
  Logger.error "Cannot create directory to store '#{klass}' classes!"
59
56
  end
60
57
 
@@ -1,5 +1,5 @@
1
1
  begin
2
- require 'lib/og/store/kirby/kirbybase'
2
+ require 'lib/vendor/kirbybase'
3
3
  rescue Object => ex
4
4
  Logger.error 'KirbyBase is not installed!'
5
5
  Logger.error ex
@@ -282,4 +282,3 @@ end
282
282
  end
283
283
 
284
284
  # * George Moschovitis <gm@navel.gr>
285
- # * Ysabel <deb@isabel.org>
@@ -130,7 +130,11 @@ class MysqlStore < SqlStore
130
130
  end
131
131
 
132
132
  def enchant(klass, manager)
133
- klass.property :oid, Fixnum, :sql => 'integer AUTO_INCREMENT PRIMARY KEY'
133
+ if klass.metadata.primary_key.flatten.first == :oid
134
+ unless klass.properties.find { |p| p.symbol == :oid }
135
+ klass.property :oid, Fixnum, :sql => 'integer AUTO_INCREMENT PRIMARY KEY'
136
+ end
137
+ end
134
138
  super
135
139
  end
136
140
 
@@ -183,7 +187,7 @@ private
183
187
 
184
188
  # Create table constrains.
185
189
 
186
- if klass.__meta and constrains = klass.__meta[:sql_constrain]
190
+ if klass.metadata and constrains = klass.metadata.sql_constrain
187
191
  sql << ", #{constrains.join(', ')}"
188
192
  end
189
193
 
@@ -293,17 +297,18 @@ private
293
297
  def eval_og_insert(klass)
294
298
  props = klass.properties.dup
295
299
  values = props.collect { |p| write_prop(p) }.join(',')
296
-
300
+
297
301
  if klass.metadata.superclass or klass.metadata.subclasses
298
302
  props << Property.new(:ogtype, String)
299
303
  values << ", '#{klass}'"
300
304
  end
301
305
 
302
- sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})"
306
+ sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| field_for_property(p)}.join(',')}) VALUES (#{values})"
303
307
 
304
308
  klass.class_eval %{
305
309
  def og_insert(store)
306
310
  #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
311
+ puts "#{sql}"
307
312
  store.conn.query_with_result = false
308
313
  store.conn.query "#{sql}"
309
314
  @#{klass.pk_symbol} = store.conn.insert_id
data/lib/og/store/psql.rb CHANGED
@@ -132,7 +132,11 @@ class PsqlStore < SqlStore
132
132
  klass.const_set 'OGSEQ', "#{table(klass)}_oid_seq"
133
133
  end
134
134
 
135
- klass.property :oid, Fixnum, :sql => 'serial PRIMARY KEY'
135
+ if klass.metadata.primary_key.flatten.first == :oid
136
+ unless klass.properties.find { |p| p.symbol == :oid }
137
+ klass.property :oid, Fixnum, :sql => 'serial PRIMARY KEY'
138
+ end
139
+ end
136
140
  super
137
141
  end
138
142
 
@@ -278,7 +282,7 @@ private
278
282
  values << ", '#{klass}'"
279
283
  end
280
284
 
281
- sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})"
285
+ sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| field_for_property(p)}.join(',')}) VALUES (#{values})"
282
286
 
283
287
  klass.class_eval %{
284
288
  def og_insert(store)
data/lib/og/store/sql.rb CHANGED
@@ -110,7 +110,11 @@ module SqlUtils
110
110
  end
111
111
 
112
112
  def table(klass)
113
- "#{Og.table_prefix}#{tableize(klass)}"
113
+ if t = klass.metadata.sql_table
114
+ return t.first
115
+ else
116
+ "#{Og.table_prefix}#{tableize(klass)}"
117
+ end
114
118
  end
115
119
 
116
120
  def join_object_ordering(obj1, obj2)
@@ -227,8 +231,8 @@ class SqlStore < Store
227
231
  def initialize(options)
228
232
  super
229
233
 
230
- # The default Ruby <-> SQL type mappings, should be valid for most
231
- # RDBM systems.
234
+ # The default Ruby <-> SQL type mappings, should be valid
235
+ # for most RDBM systems.
232
236
 
233
237
  @typemap = {
234
238
  Integer => 'integer',
@@ -483,10 +487,23 @@ private
483
487
  raise 'Not implemented'
484
488
  end
485
489
 
490
+ # Drop the sql table where objects of this class are
491
+ # persisted.
492
+
486
493
  def drop_table(klass)
487
494
  exec "DROP TABLE #{klass.table}"
488
495
  end
489
496
 
497
+ # Return the field for the given property.
498
+
499
+ def field_for_property(property)
500
+ if f = property.meta[:field]
501
+ return f.to_s
502
+ else
503
+ return property.symbol.to_s
504
+ end
505
+ end
506
+
490
507
  # Create the fields that correpsond to the klass properties.
491
508
  # The generated fields array is used in create_table.
492
509
  # If the property has an :sql metadata this overrides the
@@ -513,7 +530,7 @@ private
513
530
  properties.each do |p|
514
531
  klass.index(p.symbol) if p.meta[:index]
515
532
 
516
- field = p.symbol.to_s
533
+ field = field_for_property(p)
517
534
 
518
535
  if p.meta and p.meta[:sql]
519
536
  field << " #{p.meta[:sql]}"
@@ -538,7 +555,7 @@ private
538
555
 
539
556
  return fields
540
557
  end
541
-
558
+
542
559
  def type_for_class(klass)
543
560
  @typemap[klass]
544
561
  end
@@ -609,7 +626,7 @@ private
609
626
  values << ", '#{klass}'"
610
627
  end
611
628
 
612
- sql = "INSERT INTO #{klass.table} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})"
629
+ sql = "INSERT INTO #{klass.table} (#{props.collect {|p| field_for_property(p)}.join(',')}) VALUES (#{values})"
613
630
 
614
631
  klass.module_eval %{
615
632
  def og_insert(store)
@@ -627,7 +644,7 @@ private
627
644
  props = klass.properties.reject { |p| pk == p.symbol }
628
645
 
629
646
  updates = props.collect { |p|
630
- "#{p.symbol}=#{write_prop(p)}"
647
+ "#{field_for_property(p)}=#{write_prop(p)}"
631
648
  }
632
649
 
633
650
  sql = "UPDATE #{klass::OGTABLE} SET #{updates.join(', ')} WHERE #{pk}=#\{@#{pk}\}"
@@ -655,7 +672,9 @@ private
655
672
  field_map = create_field_map(klass)
656
673
 
657
674
  props.each do |p|
658
- if col = field_map[p.symbol]
675
+ f = field_for_property(p).intern
676
+
677
+ if col = field_map[f]
659
678
  code << "@#{p.symbol} = #{read_prop(p, col)}"
660
679
  end
661
680
  end
@@ -64,7 +64,11 @@ class SqliteStore < SqlStore
64
64
  end
65
65
 
66
66
  def enchant(klass, manager)
67
- klass.property :oid, Fixnum, :sql => 'integer PRIMARY KEY'
67
+ if klass.metadata.primary_key.flatten.first == :oid
68
+ unless klass.properties.find { |p| p.symbol == :oid }
69
+ klass.property :oid, Fixnum, :sql => 'integer PRIMARY KEY'
70
+ end
71
+ end
68
72
  super
69
73
  end
70
74
 
@@ -192,7 +196,7 @@ private
192
196
  values << ", '#{klass}'"
193
197
  end
194
198
 
195
- sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})"
199
+ sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| field_for_property(p)}.join(',')}) VALUES (#{values})"
196
200
 
197
201
  klass.class_eval %{
198
202
  def og_insert(store)
data/lib/og/test.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'og/test/assertions'
2
+ require 'og/test/testcase'
@@ -0,0 +1,175 @@
1
+ require 'test/unit'
2
+ require 'test/unit/assertions'
3
+ require 'rexml/document'
4
+
5
+ #--
6
+ # Og related assertions.
7
+ #++
8
+
9
+ module Test::Unit::Assertions
10
+
11
+ STATUS_MAP = {
12
+ :success => 200,
13
+ :ok => 200,
14
+ :redirect => 307
15
+ }
16
+
17
+ # :section: General assertions.
18
+
19
+ # Check the status of the response.
20
+
21
+ def assert_response(options = {})
22
+ unless options.is_a? Hash
23
+ options = { :status => options }
24
+ end
25
+ msg = options[:msg]
26
+ if status = options.fetch(:status, :success)
27
+ status = STATUS_MAP[status] if STATUS_MAP.has_key?(status)
28
+ assert_status(status, msg)
29
+ end
30
+ end
31
+
32
+ def assert_status(status, msg)
33
+ msg = format_msg("Status not '#{status}'", msg)
34
+ assert_block(msg) { @context.status == status }
35
+ end
36
+
37
+ #--
38
+ # Compile some helpers.
39
+ #++
40
+
41
+ for m in [:get, :post, :put, :delete, :head]
42
+ eval %{
43
+ def assert_#{m}(uri, headers = {}, params = {}, session = nil)
44
+ #{m}(uri, headers, params, session)
45
+ assert_response :success
46
+ end
47
+ }
48
+ end
49
+
50
+ def assert_output(options = {})
51
+ msg = options[:msg]
52
+ if re = options[:match] || options[:contains]
53
+ assert_output_match(re, msg)
54
+ end
55
+ if re = options[:no_match] || options[:contains_no]
56
+ assert_output_not_match(re, msg)
57
+ end
58
+ if content_type = options[:content_type]
59
+ assert_content_type(content_type, msg)
60
+ end
61
+ end
62
+
63
+ def assert_output_match(re, msg)
64
+ msg = format_msg("Rendered output does not match '#{re.source}'", msg)
65
+ assert_block(msg) { @context.body =~ Regexp.new(re) }
66
+ end
67
+ alias_method :assert_output_contains, :assert_output_match
68
+
69
+ def assert_output_not_match(re, msg)
70
+ msg = format_msg("Rendered output matches '#{re.source}'", msg)
71
+ assert_block(msg) { @context.out =~ Regexp.new(re) }
72
+ end
73
+ alias_method :assert_output_contains_not, :assert_output_match
74
+
75
+ def assert_content_type(ctype, msg)
76
+ msg = format_msg("Content type is not '#{ctype}' as expected", msg)
77
+ assert_block(msg) { @context.content_type == ctype }
78
+ end
79
+
80
+ # :section: Session related assertions.
81
+
82
+ def assert_session(options = {})
83
+ msg = options[:msg]
84
+ if key = options[:has]
85
+ assert_session_has(key, msg)
86
+ end
87
+ if key = options[:has_no] || options[:no]
88
+ assert_session_has_no(key, msg)
89
+ end
90
+ if key = options[:key] and value = options[:value]
91
+ assert_session_equal(key, value, msg)
92
+ end
93
+ end
94
+
95
+ def assert_session_has(key, msg = nil)
96
+ msg = format_msg("Object '#{key}' not found in session", msg)
97
+ assert_block(msg) { @context.session[key] }
98
+ end
99
+
100
+ def assert_session_has_no(key, msg = nil)
101
+ msg = format_msg("Unexpected object '#{key}' found in session", msg)
102
+ assert_block(msg) { !@context.session[key] }
103
+ end
104
+
105
+ def assert_session_equal(key, value, msg = nil)
106
+ msg = format_msg("The value of session object '#{key}' is '#{@context.session[key]}' but was expected '#{value}'", msg)
107
+ assert_block(msg) { @context.session[key] == value }
108
+ end
109
+
110
+ # :section: Cookies related assertions.
111
+
112
+ def assert_cookie(options = {})
113
+ msg = options[:msg]
114
+ if key = options[:has]
115
+ assert_cookie_has(key, msg)
116
+ end
117
+ if key = options[:has_no] || options[:no]
118
+ assert_cookie_has_no(key, msg)
119
+ end
120
+ if key = options[:key] and value = options[:value]
121
+ assert_cookie_equal(key, value, msg)
122
+ end
123
+ end
124
+
125
+ def assert_cookie_has(name, msg = nil)
126
+ msg = format_msg("Cookie '#{name}' not found", msg)
127
+ assert_block(msg) { @context.response_cookie(name) }
128
+ end
129
+
130
+ def assert_cookie_has_no(name, msg = nil)
131
+ msg = format_msg("Unexpected cookie '#{name}' found", msg)
132
+ assert_block(msg) { !@context.response_cookie(name) }
133
+ end
134
+
135
+ def assert_cookie_equal(name, value, msg = nil)
136
+ unless cookie = @context.response_cookie(name)
137
+ msg = format_msg("Cookie '#{name}' not found", msg)
138
+ assert_block(msg) { false }
139
+ end
140
+ msg = format_msg("The value of cookie '#{name}' is '#{cookie.value}' but was expected '#{value}'", msg)
141
+ assert_block(msg) { cookie.value == value }
142
+ end
143
+
144
+ # :section: Template related assertions.
145
+
146
+ # :section: Redirection assertions.
147
+
148
+ def assert_redirected(options = {})
149
+ msg = options[:msg]
150
+
151
+ msg = format_msg("No redirection (status = #{@context.status})", msg)
152
+ assert_block(msg) { @context.redirect? }
153
+
154
+ if to = options[:to]
155
+ msg = format_msg("Not redirected to '#{to}'", msg)
156
+ assert_block(msg) { @context.response_headers['location'] == "http://#{to}" }
157
+ end
158
+ end
159
+
160
+ def assert_not_redirected(options = {})
161
+ msg = options[:msg]
162
+ msg = format_msg("Unexpected redirection (location = '#{@context.response_headers['location']}')", msg)
163
+ assert_block(msg) { !@context.redirect? }
164
+ end
165
+
166
+ # :section: Utility methods
167
+
168
+ def format_msg(message, extra) # :nodoc:
169
+ extra += ', ' if extra
170
+ return "#{extra}#{message}"
171
+ end
172
+
173
+ end
174
+
175
+ # * George Moschovitis <gm@navel.gr>
@@ -0,0 +1,57 @@
1
+ require 'test/unit'
2
+ require 'test/unit/assertions'
3
+
4
+ require 'facet/string/underscore'
5
+ require 'facet/string/demodulize'
6
+ require 'facet/string/pluralize'
7
+
8
+ require 'glue'
9
+ require 'glue/fixture'
10
+ require 'og'
11
+ require 'og/test/assertions'
12
+
13
+ module Test::Unit
14
+
15
+ class TestCase
16
+
17
+ # Include fixtures in this test case.
18
+ #--
19
+ # gmosx: this method should probably be moved to glue.
20
+ #++
21
+
22
+ def fixture(*classes)
23
+
24
+ for klass in classes
25
+ f = Fixture.new(klass)
26
+ instance_variable_set "@#{klass.to_s.demodulize.underscore.pluralize}", f
27
+ Fixtures[klass] = f
28
+
29
+ # create variables for the fixture objects.
30
+
31
+ for name, obj in f
32
+ instance_variable_set "@#{name}", obj
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ # Include fixtures in this test case, and serialize them in
39
+ # the active Og store.
40
+
41
+ def og_fixture(*classes)
42
+ fixture(*classes)
43
+
44
+ for klass in classes
45
+ f = Fixtures[klass]
46
+
47
+ for obj in f.objects
48
+ obj.save
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
57
+ # * George Moschovitis <gm@navel.gr>
@@ -40,8 +40,8 @@ class TestCaseOgFilesys < Test::Unit::TestCase # :nodoc: all
40
40
  end
41
41
 
42
42
  def test_all
43
- # p Comment.__meta
44
- # p Article.__meta
43
+ # p Comment.metadata
44
+ # p Article.metadata
45
45
 
46
46
  a1 = Article.new('Article 1')
47
47
  @og.store.save(a1)
@@ -65,7 +65,7 @@ class TestCaseOgFilesys < Test::Unit::TestCase # :nodoc: all
65
65
  # a = Article[1]
66
66
 
67
67
  @og.store.close
68
- @og.store.class.destroy(@og.options)
68
+ # @og.store.class.destroy(@og.options)
69
69
  end
70
70
 
71
71
  end
@@ -0,0 +1,68 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
2
+
3
+ $DBG = true
4
+
5
+ require 'test/unit'
6
+
7
+ require 'og'
8
+
9
+ $og1 = Og.setup(
10
+ :destroy => true,
11
+ :store => :mysql,
12
+ :user => 'root',
13
+ :password => 'navelrulez',
14
+ :name => 'test'
15
+ )
16
+
17
+ $og2 = Og.setup(
18
+ :destroy => true,
19
+ :store => :psql,
20
+ :user => 'postgres',
21
+ :password => 'navelrulez',
22
+ :name => 'test'
23
+ )
24
+
25
+ class TestMultiple < Test::Unit::TestCase # :nodoc: all
26
+ include Og
27
+
28
+ class User
29
+ property :name, String, :uniq => true
30
+ has_many Article
31
+
32
+ def initialize(name)
33
+ @name = name
34
+ end
35
+ end
36
+
37
+ class Article
38
+ property :title, String
39
+ belongs_to User
40
+
41
+ def initialize(title)
42
+ @title = title
43
+ end
44
+ end
45
+
46
+ def test_all
47
+ $og1.manage_class Article
48
+ $og2.manage_class User
49
+
50
+ assert Article.ogmanager.store.is_a?(Og::MysqlStore)
51
+ assert User.ogmanager.store.is_a?(Og::PsqlStore)
52
+
53
+ a1 = Article.create('hello')
54
+ a2 = Article.create('world')
55
+
56
+ u = User.create('gmosx')
57
+
58
+ assert_equal 2, Article.count
59
+
60
+ u.articles << a1
61
+ u.articles << a2
62
+
63
+ gmosx = User.find_by_name('gmosx')
64
+ assert_equal 2, gmosx.articles.size
65
+ assert_equal 'hello', gmosx.articles[0].title
66
+ end
67
+
68
+ end
@@ -39,9 +39,15 @@ class TC_OgPolymorphic < Test::Unit::TestCase # :nodoc: all
39
39
 
40
40
  def setup
41
41
  @og = Og.setup(
42
+ =begin
42
43
  :destroy => true,
43
44
  :store => :sqlite,
44
45
  :name => 'test'
46
+ =end
47
+ :store => :mysql,
48
+ :name => 'testreverse',
49
+ :user => 'root',
50
+ :password => 'navelrulez'
45
51
  )
46
52
  end
47
53
 
@@ -0,0 +1,78 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
2
+
3
+ $DBG = true
4
+
5
+ require 'test/unit'
6
+
7
+ require 'og'
8
+
9
+ # 'testreverse' is a legacy database schema. Og's advanced
10
+ # reverse engineering features allows us to map any schema
11
+ # to our objects.
12
+
13
+ $og = Og.setup(
14
+ :destroy => true,
15
+ :store => :mysql,
16
+ :name => 'testreverse',
17
+ :user => 'root',
18
+ :password => 'navelrulez'
19
+ )
20
+
21
+ # create a 'legacy' schema.
22
+
23
+ $og.store.exec "create table my_users (thename VARCHAR(32) PRIMARY KEY, password TEXT, age3 INTEGER);"
24
+ $og.store.exec "create table my_comments (cid MEDIUMINT NOT NULL AUTO_INCREMENT, body TEXT, user VARCHAR(32), PRIMARY KEY(cid));"
25
+
26
+ class User
27
+ property :name, String, :field => :thename, :uniq => true
28
+ property :password, String
29
+ property :age, Fixnum, :field => :age3
30
+ has_many Comment, :foreign_field => :user
31
+ set_table :my_users
32
+ set_primary_key :name, String
33
+
34
+ def initialize(name, password, age)
35
+ @name, @password, @age = name, password, age
36
+ end
37
+ end
38
+
39
+ class Comment
40
+ property :cid, Fixnum
41
+ property :body, String
42
+ belongs_to User, :field => :user
43
+ set_table :my_comments
44
+ set_primary_key :cid
45
+
46
+ def initialize(body)
47
+ @body = body
48
+ end
49
+ end
50
+
51
+ $og.manage_classes
52
+
53
+ class TestReverse < Test::Unit::TestCase # :nodoc: all
54
+ include Og
55
+
56
+ def test_all
57
+ assert_equal 'my_users', User.table
58
+ assert_equal 'my_comments', Comment.table
59
+
60
+ User.new('gmosx', 'navel', 30).insert
61
+ User.new('Helen', 'kontesa', 25).insert
62
+
63
+ gmosx = User.find_by_name('gmosx')
64
+ assert_equal 'gmosx', gmosx.name
65
+
66
+ helen = User.find_by_age(25).first
67
+ assert_equal 'Helen', helen.name
68
+
69
+ c = Comment.new('hello')
70
+ c.insert
71
+ helen.comments << c
72
+
73
+ assert_equal 1, helen.comments(:reload => true).size
74
+ assert_equal 'hello', helen.comments[0].body
75
+ end
76
+ end
77
+
78
+ # * George Moschovitis <gm@navel.gr>
data/test/og/tc_store.rb CHANGED
@@ -44,7 +44,7 @@ class TCOgStore < Test::Unit::TestCase # :nodoc: all
44
44
  class NewArticle < Article
45
45
  property :more_text, String
46
46
  end
47
-
47
+
48
48
  class Comment
49
49
  property :body, String
50
50
  property :hits, Fixnum
@@ -103,7 +103,7 @@ class TCOgStore < Test::Unit::TestCase # :nodoc: all
103
103
  conversions_test
104
104
  end
105
105
  =end
106
- =begin
106
+ #=begin
107
107
  def test_mysql
108
108
  @og = Og.setup(
109
109
  :destroy => true,
@@ -116,8 +116,8 @@ class TCOgStore < Test::Unit::TestCase # :nodoc: all
116
116
  features_test
117
117
  # conversions_test
118
118
  end
119
- =end
120
- #=begin
119
+ #=end
120
+ =begin
121
121
  def test_sqlite
122
122
  @og = Og.setup(
123
123
  :destroy => true,
@@ -127,7 +127,7 @@ class TCOgStore < Test::Unit::TestCase # :nodoc: all
127
127
  features_test
128
128
  conversions_test
129
129
  end
130
- #=end
130
+ =end
131
131
  =begin
132
132
  def test_memory
133
133
  @og = Og.setup(
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: og
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.21.2
7
- date: 2005-07-28 00:00:00 +03:00
6
+ version: 0.22.0
7
+ date: 2005-08-07 00:00:00 +03:00
8
8
  summary: Og (ObjectGraph)
9
9
  require_paths:
10
10
  - lib
@@ -60,8 +60,9 @@ files:
60
60
  - lib/og/errors.rb
61
61
  - lib/og/store.rb
62
62
  - lib/og/validation.rb
63
- - lib/og/testing
64
63
  - lib/og/types.rb
64
+ - lib/og/test
65
+ - lib/og/test.rb
65
66
  - lib/og/relation/belongs_to.rb
66
67
  - lib/og/relation/has_many.rb
67
68
  - lib/og/relation/has_one.rb
@@ -81,6 +82,8 @@ files:
81
82
  - lib/og/store/sqlite.rb
82
83
  - lib/og/store/kirby.rb
83
84
  - lib/og/store/memory.rb
85
+ - lib/og/test/assertions.rb
86
+ - lib/og/test/testcase.rb
84
87
  - lib/vendor/mysql411.rb
85
88
  - lib/vendor/mysql.rb
86
89
  - lib/vendor/kirbybase.rb
@@ -95,6 +98,8 @@ files:
95
98
  - test/og/tc_polymorphic.rb
96
99
  - test/og/tc_join.rb
97
100
  - test/og/tc_select.rb
101
+ - test/og/tc_reverse.rb
102
+ - test/og/tc_multiple.rb
98
103
  - test/og/store/tc_filesys.rb
99
104
  - test/og/mixin/tc_hierarchical.rb
100
105
  - test/og/mixin/tc_orderable.rb
@@ -124,5 +129,5 @@ dependencies:
124
129
  -
125
130
  - "="
126
131
  - !ruby/object:Gem::Version
127
- version: 0.21.2
132
+ version: 0.22.0
128
133
  version: