og 0.21.2 → 0.22.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 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: