og 0.28.0 → 0.29.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ProjectInfo +2 -2
- data/README +1 -1
- data/doc/RELEASES +34 -0
- data/lib/glue/cacheable.rb +3 -1
- data/lib/glue/hierarchical.rb +3 -3
- data/lib/glue/orderable.rb +1 -1
- data/lib/glue/taggable.rb +71 -21
- data/lib/glue/timestamped.rb +2 -2
- data/lib/og.rb +16 -3
- data/lib/og/entity.rb +58 -10
- data/lib/og/ez/clause.rb +2 -1
- data/lib/og/manager.rb +38 -7
- data/lib/og/relation/joins_many.rb +1 -1
- data/lib/og/store/mysql.rb +2 -18
- data/lib/og/store/psql.rb +2 -1
- data/lib/og/store/sql.rb +79 -26
- data/lib/og/test/assertions.rb +1 -1
- data/lib/og/test/testcase.rb +3 -3
- data/test/glue/tc_revisable.rb +1 -1
- data/test/og/mixin/tc_hierarchical.rb +2 -0
- data/test/og/mixin/tc_orderable.rb +2 -3
- data/test/og/mixin/tc_taggable.rb +8 -11
- data/test/og/mixin/tc_timestamped.rb +1 -1
- data/test/og/store/tc_sti.rb +53 -0
- data/test/og/tc_aggregations_calculations.rb +44 -0
- data/test/og/tc_cacheable.rb +164 -29
- data/test/og/tc_inheritance.rb +4 -1
- data/test/og/tc_multi_validations.rb +1 -0
- data/test/og/tc_override.rb +6 -0
- metadata +11 -10
- data/lib/og/store/alpha/kirby.rb +0 -284
data/lib/og/store/mysql.rb
CHANGED
@@ -67,24 +67,6 @@ module MysqlUtils
|
|
67
67
|
return nil unless str
|
68
68
|
return Mysql.quote(str)
|
69
69
|
end
|
70
|
-
|
71
|
-
def quote(val)
|
72
|
-
case val
|
73
|
-
when Fixnum, Integer, Float
|
74
|
-
val ? val.to_s : 'NULL'
|
75
|
-
when String
|
76
|
-
val ? "'#{escape(val)}'" : 'NULL'
|
77
|
-
when Time
|
78
|
-
val ? "'#{timestamp(val)}'" : 'NULL'
|
79
|
-
when Date
|
80
|
-
val ? "'#{date(val)}'" : 'NULL'
|
81
|
-
when TrueClass
|
82
|
-
val ? "'1'" : 'NULL'
|
83
|
-
else
|
84
|
-
# gmosx: keep the '' for nil symbols.
|
85
|
-
val ? escape(val.to_yaml) : ''
|
86
|
-
end
|
87
|
-
end
|
88
70
|
end
|
89
71
|
|
90
72
|
# A Store that persists objects into a MySQL database.
|
@@ -110,6 +92,7 @@ class MysqlStore < SqlStore
|
|
110
92
|
# gmosx: system is used to avoid shell expansion.
|
111
93
|
system 'mysqladmin', '-f', "--user=#{options[:user]}",
|
112
94
|
"--password=#{options[:password]}",
|
95
|
+
"--host=#{options[:address]}",
|
113
96
|
'create', options[:name]
|
114
97
|
super
|
115
98
|
end
|
@@ -117,6 +100,7 @@ class MysqlStore < SqlStore
|
|
117
100
|
def self.destroy(options)
|
118
101
|
system 'mysqladmin', '-f', "--user=#{options[:user]}",
|
119
102
|
"--password=#{options[:password]}", 'drop',
|
103
|
+
"--host=#{options[:address]}",
|
120
104
|
options[:name]
|
121
105
|
super
|
122
106
|
end
|
data/lib/og/store/psql.rb
CHANGED
@@ -516,6 +516,7 @@ class PsqlStore < SqlStore
|
|
516
516
|
deleted += 1
|
517
517
|
end
|
518
518
|
|
519
|
+
nullified_relations = 0
|
519
520
|
sql_hash[:create].each do |sql|
|
520
521
|
con_retry = true
|
521
522
|
begin
|
@@ -572,7 +573,7 @@ class PsqlStore < SqlStore
|
|
572
573
|
text << "#{deleted} constraints were deleted, " if deleted != 0
|
573
574
|
if broken_relations != 0
|
574
575
|
text.gsub!(/,([^,]+)$/,' and \1')
|
575
|
-
text << "#{broken_relations} relations were broken
|
576
|
+
text << "#{broken_relations} relations were broken causing "
|
576
577
|
if nullified_relations != 0
|
577
578
|
text << "#{nullified_relations} relations to have non-existant foreign keys set to null"
|
578
579
|
text << (deleted_relations == 0 ? ", " : " and ")
|
data/lib/og/store/sql.rb
CHANGED
@@ -97,23 +97,40 @@ module SqlUtils
|
|
97
97
|
|
98
98
|
# Escape the various Ruby types.
|
99
99
|
|
100
|
-
def quote(
|
100
|
+
def quote(vals)
|
101
|
+
vals = [vals] unless vals.is_a?(Array)
|
102
|
+
quoted = vals.inject("") do |s,val|
|
103
|
+
s += case val
|
104
|
+
when Fixnum, Integer, Float
|
105
|
+
val ? val.to_s : 'NULL'
|
106
|
+
when String
|
107
|
+
val ? "'#{escape(val)}'" : 'NULL'
|
108
|
+
when Time
|
109
|
+
val ? "'#{timestamp(val)}'" : 'NULL'
|
110
|
+
when Date
|
111
|
+
val ? "'#{date(val)}'" : 'NULL'
|
112
|
+
when TrueClass
|
113
|
+
val ? "'t'" : 'NULL'
|
114
|
+
else
|
115
|
+
# gmosx: keep the '' for nil symbols.
|
116
|
+
val ? escape(val.to_yaml) : ''
|
117
|
+
end + ','
|
118
|
+
end
|
119
|
+
quoted.chop!
|
120
|
+
vals.size > 1 ? "(#{quoted})" : quoted
|
121
|
+
end
|
122
|
+
|
123
|
+
# Escape the Array Ruby type.
|
124
|
+
|
125
|
+
def quote_array(val)
|
101
126
|
case val
|
102
|
-
when
|
103
|
-
val
|
104
|
-
when String
|
105
|
-
val ? "'#{escape(val)}'" : 'NULL'
|
106
|
-
when Time
|
107
|
-
val ? "'#{timestamp(val)}'" : 'NULL'
|
108
|
-
when Date
|
109
|
-
val ? "'#{date(val)}'" : 'NULL'
|
110
|
-
when TrueClass
|
111
|
-
val ? "'t'" : 'NULL'
|
127
|
+
when Array
|
128
|
+
val.collect{ |v| quotea(v) }.join(',')
|
112
129
|
else
|
113
|
-
|
114
|
-
val ? escape(val.to_yaml) : ''
|
130
|
+
quote(val)
|
115
131
|
end
|
116
132
|
end
|
133
|
+
alias_method :quotea, :quote_array
|
117
134
|
|
118
135
|
# Apply table name conventions to a class name.
|
119
136
|
|
@@ -354,7 +371,7 @@ class SqlStore < Store
|
|
354
371
|
|
355
372
|
def load(pk, klass)
|
356
373
|
pk_field = klass.primary_key.field || klass.primary_key.symbol
|
357
|
-
sql = "SELECT * FROM #{klass::OGTABLE} WHERE #{pk_field}=#{pk}"
|
374
|
+
sql = "SELECT * FROM #{klass::OGTABLE} WHERE #{pk_field}=#{pk.to_i}"
|
358
375
|
sql << " AND ogtype='#{klass}'" if klass.schema_inheritance_child?
|
359
376
|
res = query sql
|
360
377
|
read_one(res, klass)
|
@@ -365,7 +382,7 @@ class SqlStore < Store
|
|
365
382
|
|
366
383
|
def reload(obj, pk)
|
367
384
|
raise 'Cannot reload unmanaged object' unless obj.saved?
|
368
|
-
sql = "SELECT * FROM #{obj.class.table} WHERE #{obj.class.pk_symbol}=#{pk}"
|
385
|
+
sql = "SELECT * FROM #{obj.class.table} WHERE #{obj.class.pk_symbol}=#{pk.to_i}"
|
369
386
|
sql << " AND ogtype='#{obj.class}'" if obj.class.schema_inheritance_child?
|
370
387
|
res = query sql
|
371
388
|
obj.og_read(res.next, 0)
|
@@ -441,8 +458,8 @@ class SqlStore < Store
|
|
441
458
|
|
442
459
|
def find_one(options)
|
443
460
|
klass = options[:class]
|
444
|
-
# gmosx, THINK: should not set this by default.
|
445
|
-
#
|
461
|
+
# gmosx, THINK: should not set this by default.
|
462
|
+
# options[:limit] ||= 1
|
446
463
|
sql = resolve_options(klass, options)
|
447
464
|
read_one(query(sql), klass, options)
|
448
465
|
end
|
@@ -464,14 +481,21 @@ class SqlStore < Store
|
|
464
481
|
end
|
465
482
|
alias_method :find_by_sql_one, :select_one
|
466
483
|
|
467
|
-
# Perform an aggregation over query results.
|
484
|
+
# Perform an aggregation or calculation over query results.
|
485
|
+
# This low level method is used by the Entity
|
486
|
+
# calculation / aggregation methods.
|
487
|
+
#
|
488
|
+
# === Example
|
489
|
+
#
|
490
|
+
# calculate 'COUNT(*)'
|
491
|
+
# calculate 'MIN(age)'
|
492
|
+
# calculate 'SUM(age)', :group => :name
|
468
493
|
|
469
|
-
def aggregate(options)
|
494
|
+
def aggregate(term = 'COUNT(*)', options = {})
|
470
495
|
if options.is_a?(String)
|
471
496
|
sql = options
|
472
497
|
else
|
473
|
-
|
474
|
-
sql = "SELECT #{aggregate} FROM #{options[:class].table}"
|
498
|
+
sql = "SELECT #{term} FROM #{options[:class].table}"
|
475
499
|
if condition = options[:condition]
|
476
500
|
sql << " WHERE #{condition}"
|
477
501
|
sql << " AND " if options[:class].schema_inheritance_child?
|
@@ -479,11 +503,38 @@ class SqlStore < Store
|
|
479
503
|
sql << " WHERE " if options[:class].schema_inheritance_child?
|
480
504
|
end
|
481
505
|
sql << "ogtype='#{options[:class]}'" if options[:class].schema_inheritance_child?
|
506
|
+
if group = options[:group]
|
507
|
+
sql << " GROUP BY #{group}"
|
508
|
+
end
|
509
|
+
if extra = options[:extra_sql]
|
510
|
+
sql << " {extra}"
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
if group
|
515
|
+
# This is an aggregation, so return the calculated values
|
516
|
+
# as an array.
|
517
|
+
values = []
|
518
|
+
res = query(sql)
|
519
|
+
res.each_row do |row, idx|
|
520
|
+
values << row[0].to_f
|
521
|
+
end
|
522
|
+
return values
|
523
|
+
else
|
524
|
+
#--
|
525
|
+
# gmosx, TODO: don't convert to float by default, perhaps
|
526
|
+
# should consult an option.
|
527
|
+
#++
|
528
|
+
return query(sql).first_value.to_f
|
482
529
|
end
|
530
|
+
end
|
531
|
+
alias_method :calculate, :aggregate
|
483
532
|
|
484
|
-
|
533
|
+
# Perform a count query.
|
534
|
+
|
535
|
+
def count(options = {})
|
536
|
+
calculate('COUNT(*)', options).to_i
|
485
537
|
end
|
486
|
-
alias_method :count, :aggregate
|
487
538
|
|
488
539
|
# Relate two objects through an intermediate join table.
|
489
540
|
# Typically used in joins_many and many_to_many relations.
|
@@ -605,7 +656,7 @@ private
|
|
605
656
|
# holds the class name.
|
606
657
|
fields << "ogtype VARCHAR(30)"
|
607
658
|
|
608
|
-
for desc in klass.descendents
|
659
|
+
for desc in klass.schema_inheritance_root_class.descendents
|
609
660
|
properties.update(desc.properties)
|
610
661
|
end
|
611
662
|
end
|
@@ -925,7 +976,9 @@ private
|
|
925
976
|
if condition.is_a?(Array)
|
926
977
|
args = condition.dup
|
927
978
|
str = args.shift
|
928
|
-
|
979
|
+
# ? handles a single type.
|
980
|
+
# ?* handles an array.
|
981
|
+
args.each { |arg| str.sub!(/\?\*/, quotea(arg)); str.sub!(/\?/, quote(arg)) }
|
929
982
|
condition = str
|
930
983
|
end
|
931
984
|
|
@@ -968,7 +1021,7 @@ private
|
|
968
1021
|
|
969
1022
|
# Dynamicaly deserializes a result set row into an object.
|
970
1023
|
# Used for specialized queries or join queries. Please
|
971
|
-
#
|
1024
|
+
# note that this deserialization method is slower than the
|
972
1025
|
# precompiled og_read method.
|
973
1026
|
|
974
1027
|
def read_row(obj, res, res_row, row)
|
data/lib/og/test/assertions.rb
CHANGED
data/lib/og/test/testcase.rb
CHANGED
@@ -20,9 +20,9 @@ class TestCase
|
|
20
20
|
def fixture(*classes)
|
21
21
|
|
22
22
|
for klass in classes
|
23
|
-
f = Fixture.new(klass)
|
23
|
+
f = Glue::Fixture.new(klass)
|
24
24
|
instance_variable_set "@#{klass.to_s.demodulize.underscore.pluralize}", f
|
25
|
-
Fixtures[klass] = f
|
25
|
+
Glue::Fixtures[klass] = f
|
26
26
|
|
27
27
|
# create variables for the fixture objects.
|
28
28
|
|
@@ -40,7 +40,7 @@ class TestCase
|
|
40
40
|
fixture(*classes)
|
41
41
|
|
42
42
|
for klass in classes
|
43
|
-
f = Fixtures[klass]
|
43
|
+
f = Glue::Fixtures[klass]
|
44
44
|
|
45
45
|
for obj in f.objects
|
46
46
|
obj.save
|
data/test/glue/tc_revisable.rb
CHANGED
@@ -61,6 +61,7 @@ class TC_OgHierarchical < Test::Unit::TestCase # :nodoc: all
|
|
61
61
|
|
62
62
|
c1.reload
|
63
63
|
|
64
|
+
=begin
|
64
65
|
Comment.all(:order => "lft, rgt").each { |c|
|
65
66
|
puts sprintf("%3d %3d %s", c.lft, c.rgt, c.body)
|
66
67
|
# p c
|
@@ -71,6 +72,7 @@ class TC_OgHierarchical < Test::Unit::TestCase # :nodoc: all
|
|
71
72
|
c1.full_comments(:order => "lft, rgt").each { |c| puts c.body }
|
72
73
|
puts '--3'
|
73
74
|
c1.direct_comments(:order => "lft, rgt").each { |c| puts c.body }
|
75
|
+
=end
|
74
76
|
|
75
77
|
assert_equal 6, c1.full_comments.size
|
76
78
|
assert_equal 5, c1.comments.size
|
@@ -34,7 +34,7 @@ class TestCaseOgOrderable < Test::Unit::TestCase # :nodoc: all
|
|
34
34
|
property :body, String
|
35
35
|
belongs_to :article, Article
|
36
36
|
|
37
|
-
is Orderable, :scope => :article
|
37
|
+
is Glue::Orderable, :scope => :article
|
38
38
|
|
39
39
|
def initialize(body = nil)
|
40
40
|
@body = body
|
@@ -48,7 +48,7 @@ class TestCaseOgOrderable < Test::Unit::TestCase # :nodoc: all
|
|
48
48
|
|
49
49
|
class Track
|
50
50
|
prop :name, String
|
51
|
-
is Orderable, :scope => :playlist
|
51
|
+
is Glue::Orderable, :scope => :playlist
|
52
52
|
|
53
53
|
belongs_to Playlist
|
54
54
|
|
@@ -62,7 +62,6 @@ class TestCaseOgOrderable < Test::Unit::TestCase # :nodoc: all
|
|
62
62
|
|
63
63
|
def test_all
|
64
64
|
a = Article.create('article')
|
65
|
-
a.save
|
66
65
|
|
67
66
|
c1 = Comment.create('1')
|
68
67
|
a.comments << c1
|
@@ -1,3 +1,5 @@
|
|
1
|
+
$DBG = true
|
2
|
+
|
1
3
|
require File.join(File.dirname(__FILE__), '..', 'CONFIG.rb')
|
2
4
|
|
3
5
|
require 'rubygems'
|
@@ -8,16 +10,6 @@ require 'ostruct'
|
|
8
10
|
require 'og'
|
9
11
|
require 'glue/taggable'
|
10
12
|
|
11
|
-
=begin
|
12
|
-
$og = Og.setup(
|
13
|
-
:store => 'mysql',
|
14
|
-
:name => 'test',
|
15
|
-
:user => 'root',
|
16
|
-
:password => 'navelrulez',
|
17
|
-
:destroy => true
|
18
|
-
)
|
19
|
-
=end
|
20
|
-
|
21
13
|
class TestOgTaggable < Test::Unit::TestCase # :nodoc: all
|
22
14
|
|
23
15
|
class Article
|
@@ -43,7 +35,9 @@ class TestOgTaggable < Test::Unit::TestCase # :nodoc: all
|
|
43
35
|
a3 = Article.create('George')
|
44
36
|
a3.tag('phd name')
|
45
37
|
a3.save
|
46
|
-
|
38
|
+
|
39
|
+
|
40
|
+
assert_equal 6, Tag.count
|
47
41
|
assert_equal 3, a1.tags.size
|
48
42
|
assert a1.tag_names.include?('navel')
|
49
43
|
|
@@ -63,6 +57,9 @@ class TestOgTaggable < Test::Unit::TestCase # :nodoc: all
|
|
63
57
|
|
64
58
|
res = Article.find_with_any_tag('navel', 'gmosx', 'phd')
|
65
59
|
assert_equal 3, res.size
|
60
|
+
|
61
|
+
a1.delete_all_tags
|
62
|
+
assert_equal 0, a1.tags.size
|
66
63
|
|
67
64
|
=begin
|
68
65
|
TODO:
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'CONFIG.rb')
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
require 'og'
|
6
|
+
require 'og/store/sql'
|
7
|
+
|
8
|
+
class TC_Sti < Test::Unit::TestCase
|
9
|
+
class StiParent
|
10
|
+
property :one
|
11
|
+
property :two
|
12
|
+
|
13
|
+
schema_inheritance
|
14
|
+
end
|
15
|
+
|
16
|
+
class StiChild < StiParent
|
17
|
+
property :three
|
18
|
+
end
|
19
|
+
|
20
|
+
def setup
|
21
|
+
@store = Og::SqlStore.new(nil)
|
22
|
+
end
|
23
|
+
|
24
|
+
def teardown
|
25
|
+
@store = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_fields_for_class_child
|
29
|
+
fields = @store.__send__(:fields_for_class, StiChild)
|
30
|
+
field_names = fields.map {|f| name, * = f.split(/\s/); name}
|
31
|
+
|
32
|
+
msg = "Sti Child not inheiriting parent fields"
|
33
|
+
|
34
|
+
assert_equal(5, field_names.size, msg)
|
35
|
+
assert(field_names.member?(:one.to_s), msg)
|
36
|
+
assert(field_names.member?(:two.to_s), msg)
|
37
|
+
assert(field_names.member?(:three.to_s), msg)
|
38
|
+
assert(field_names.member?(:ogtype.to_s), msg)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_fields_for_class_parent
|
42
|
+
fields = @store.__send__(:fields_for_class, StiParent)
|
43
|
+
field_names = fields.map {|f| name, * = f.split(/\s/); name}
|
44
|
+
|
45
|
+
msg = "Sti Parent not collecting child fields"
|
46
|
+
|
47
|
+
assert_equal(5, field_names.size, msg)
|
48
|
+
assert(field_names.member?(:one.to_s), msg)
|
49
|
+
assert(field_names.member?(:two.to_s), msg)
|
50
|
+
assert(field_names.member?(:three.to_s), msg)
|
51
|
+
assert(field_names.member?(:ogtype.to_s), msg)
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'CONFIG.rb')
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'facets'
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'og'
|
8
|
+
|
9
|
+
class TC_OgAggrCalc < Test::Unit::TestCase # :nodoc: all
|
10
|
+
include Og
|
11
|
+
|
12
|
+
class User
|
13
|
+
property :section, String
|
14
|
+
property :age, Fixnum
|
15
|
+
|
16
|
+
def initialize(section, age)
|
17
|
+
@section = section
|
18
|
+
@age = age
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
$og1.manage_classes(User)
|
23
|
+
|
24
|
+
def test_all
|
25
|
+
User.create('a', 12)
|
26
|
+
User.create('a', 16)
|
27
|
+
User.create('d', 34)
|
28
|
+
User.create('d', 33)
|
29
|
+
User.create('c', 27)
|
30
|
+
User.create('c', 31)
|
31
|
+
|
32
|
+
assert_equal 6, User.count
|
33
|
+
assert_equal 12, User.minimum(:age)
|
34
|
+
assert_equal 34, User.maximum(:age)
|
35
|
+
assert_equal 34, User.max(:age)
|
36
|
+
assert_equal 25.5, User.avg(:age)
|
37
|
+
assert_equal 153, User.sum(:age)
|
38
|
+
|
39
|
+
sums = User.sum(:age, :group => :section)
|
40
|
+
assert_equal 3, sums.size
|
41
|
+
assert_equal 28, sums[0]
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|