og 0.28.0 → 0.29.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,7 +2,7 @@
2
2
 
3
3
  TITLE : &title Og
4
4
  NAME : &pkg og
5
- VERSION : '0.28.0'
5
+ VERSION : '0.29.0'
6
6
  STATUS : beta
7
7
 
8
8
  AUTHOR : George Moschovitis
@@ -17,7 +17,7 @@ DESCRIPTION: >
17
17
  KirbyBase, Filesystem and more.
18
18
 
19
19
  DEPENDENCIES:
20
- - [ glue, '= 0.28.0' ]
20
+ - [ glue, '= 0.29.0' ]
21
21
 
22
22
  DISTRIBUTE: [ gem, tgz, zip ]
23
23
 
data/README CHANGED
@@ -1,4 +1,4 @@
1
- = Og 0.28.0 README
1
+ = Og 0.29.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
@@ -1,3 +1,37 @@
1
+ == Version 0.29.0
2
+
3
+ A bold step towards maturity. Great care was taken to
4
+ fix reported bugs and fine tune many aspects of Og.
5
+ As always some great new features where added. Special thanks fly
6
+ to Jonas Pfenniger, Bryan Sotto, Rob Pitt and Guillaume
7
+ Pierronnet for making this release possible.
8
+
9
+ Most notable changes:
10
+
11
+ * Og now supports calculations and aggregations. Here are some
12
+ examples:
13
+
14
+ User.min(:age)
15
+ User.average(:age)
16
+ User.maximum(:age)
17
+ User.min(:age, :group => :profession) # => [..] (aggregation)
18
+ User.sum(:age, :group => :role) # => [..]
19
+
20
+ and more!
21
+
22
+ * Improved Taggable mixin, now provides more helpers and supports
23
+ tag garbage collection through reference counting.
24
+
25
+ * Added a new store for the generalized caching system that is
26
+ backed by a MemCache server. Useful to extract the last ounch
27
+ of performance in a production environment.
28
+
29
+ * Many Og bug fixes and optimizations.
30
+
31
+ * Many, many bug fixes and small improvements throughout the
32
+ code.
33
+
34
+
1
35
  == Version 0.28.0
2
36
 
3
37
  A snapshot of the latest developments. As always, cool new
@@ -125,6 +125,7 @@ module Cacheable
125
125
  # ...
126
126
 
127
127
  def self.cache_get(klass, pk)
128
+ obj.class.ogmanager.cache.get(klass.og_cache_key(pk))
128
129
  end
129
130
 
130
131
  # ...
@@ -140,7 +141,8 @@ module Cacheable
140
141
  # SQL query) you should call this method explicitly.
141
142
 
142
143
  def self.cache_delete(klass, pk)
143
- key = "og#{klass}:#{pk}"
144
+ #key = "og#{klass}:#{pk}"
145
+ key = klass.og_cache_key(pk)
144
146
  # self.og_local_cache.delete(key)
145
147
  klass.ogmanager.cache.delete(key)
146
148
  end
@@ -12,7 +12,7 @@ module Glue
12
12
 
13
13
  module NestedSets
14
14
 
15
- def self.included_with_params(base, options)
15
+ def self.included_with_parameters(base, options)
16
16
  c = {
17
17
  :left => 'lft',
18
18
  :right => 'rgt',
@@ -100,8 +100,8 @@ module NestedSets
100
100
  @#{right} = pivot + 2
101
101
 
102
102
  #{base}.transaction do
103
- #{base}.update_property("#{left} = (#{left} + 2)", #{cond_and}"#{left} >= \#{pivot}")
104
- #{base}.update_property("#{right} = (#{right} + 2)", #{cond_and}"#{right} >= \#{pivot}")
103
+ #{base}.update("#{left} = (#{left} + 2)", #{cond_and}"#{left} >= \#{pivot}")
104
+ #{base}.update("#{right} = (#{right} + 2)", #{cond_and}"#{right} >= \#{pivot}")
105
105
  end
106
106
 
107
107
  self.save
@@ -69,7 +69,7 @@ module Orderable
69
69
  end
70
70
  end #dynamic_feature
71
71
 
72
- before "add_to_bottom", :on => :og_insert
72
+ before "add_to_bottom", :on => [:og_insert, :og_update]
73
73
  before "decrement_position_of_lower_items", :on => :og_delete
74
74
 
75
75
  # Move higher.
@@ -1,5 +1,7 @@
1
1
  require 'facet/inflect'
2
2
 
3
+ module Glue
4
+
3
5
  # The default Tag implementation. A tag attaches semantics to
4
6
  # a given object.
5
7
  #--
@@ -20,18 +22,49 @@ class Tag
20
22
  @count = 0
21
23
  end
22
24
 
23
- #--
24
- # FIXME: use update_properties here!
25
- #++
25
+ # Tag an object.
26
26
 
27
27
  def tag(obj)
28
- class_name = obj.class.name
29
- method_name = class_name.index('::') ? (class_name =~ /.*?\:\:(.*)/; $1) : class_name
30
-
31
- send(method_name.pluralize.underscore.to_sym) << obj
32
- @count += 1
33
- save!
28
+ #--
29
+ # FIXME: this does not work as expected :( it alters
30
+ # the @loaded flag in the obj.tags collection without
31
+ # setting the @members.
32
+ # INVESTIGATE: why this happens!
33
+ #
34
+ # return if obj.tagged_with?(@name)
35
+ # class_name = obj.class.name
36
+ # method_name = class_name.index('::') ? (class_name =~ /.*?\:\:(.*)/; $1) : class_name
37
+ # send(method_name.pluralize.underscore.to_sym) << obj
38
+ #++
39
+
40
+ unless obj.tagged_with?(name)
41
+ obj.tags << self
42
+ @count += 1
43
+ update_property :count
44
+ end
34
45
  end
46
+ alias_method :link, :tag
47
+
48
+ # Untags an object. If no object is passed, it just decrements
49
+ # the (reference) count. If the count reaches 0 the tag is
50
+ # deleted (garbage collection).
51
+
52
+ def untag(obj = nil)
53
+ if obj
54
+ # TODO: implement me.
55
+ end
56
+
57
+ @count -= 1
58
+
59
+ if @count > 0
60
+ p "-- dec ref count"
61
+ update_property :count
62
+ else
63
+ p "-- count = 0 -> delete"
64
+ self.delete()
65
+ end
66
+ end
67
+ alias_method :unlink, :untag
35
68
 
36
69
  # Return all tagged objects from all categories.
37
70
 
@@ -50,8 +83,6 @@ class Tag
50
83
  end
51
84
  end
52
85
 
53
- module Glue
54
-
55
86
  # Add tagging methods to the target class.
56
87
  # For more information on the algorithms used surf:
57
88
  # http://www.pui.ch/phred/archives/2005/04/tags-database-schemas.html
@@ -87,13 +118,32 @@ module Taggable
87
118
  :clear => true
88
119
  }.merge(options)
89
120
 
90
- self.tags.clear if options[:clear]
121
+ delete_all_tags() if options[:clear]
91
122
 
92
123
  for name in Taggable.tags_to_names(the_tags)
93
- Tag.find_or_create_by_name(name).tag(self) unless self.tagged_with?(name)
124
+ the_tag = Tag.find_or_create_by_name(name)
125
+ the_tag.tag(self)
94
126
  end
95
127
  end
96
128
  alias_method :tag!, :tag
129
+
130
+ # Delete a single tag from this taggable object.
131
+
132
+ def delete_tag(name)
133
+ if dtag = (tags.delete_if { |t| t.name == name }).first
134
+ dtag.unlink
135
+ end
136
+ end
137
+
138
+ # Delete all tags from this taggable object.
139
+
140
+ def delete_all_tags
141
+ for tag in tags
142
+ tag.unlink
143
+ end
144
+ tags.clear
145
+ end
146
+ alias_method :clear_tags, :delete_all_tags
97
147
 
98
148
  # Return the names of the tags.
99
149
 
@@ -124,16 +174,16 @@ module Taggable
124
174
  count = names.size
125
175
  names = names.map { |n| ogmanager.store.quote(n) }.join(',')
126
176
  sql = %{
127
- SELECT o.*
177
+ SELECT t.*
128
178
  FROM
129
179
  #{info[:first_table]} AS o,
130
180
  #{info[:second_table]} as t,
131
181
  #{info[:table]} as j
132
182
  WHERE o.oid = j.#{info[:first_key]}
133
183
  AND t.oid = j.#{info[:second_key]}
134
- AND (t.name in (#{names}))
135
- GROUP BY o.oid
136
- HAVING COUNT(o.oid) = #{count};
184
+ AND (o.name in (#{names}))
185
+ GROUP BY j.article_oid
186
+ HAVING COUNT(j.article_oid) = #{count};
137
187
  }
138
188
  return self.select(sql)
139
189
  end
@@ -147,7 +197,7 @@ module Taggable
147
197
  count = names.size
148
198
  names = names.map { |n| ogmanager.store.quote(n) }.join(',')
149
199
  sql = %{
150
- SELECT o.*
200
+ SELECT t.*
151
201
  FROM
152
202
  #{info[:first_table]} AS o,
153
203
  #{info[:second_table]} as t,
@@ -155,8 +205,8 @@ module Taggable
155
205
  WHERE
156
206
  o.oid = j.#{info[:first_key]}
157
207
  AND t.oid = j.#{info[:second_key]}
158
- AND (t.name in (#{names}))
159
- GROUP BY o.oid
208
+ AND (o.name in (#{names}))
209
+ GROUP BY j.article_oid
160
210
  }
161
211
  return self.select(sql)
162
212
  end
@@ -170,7 +220,7 @@ module Taggable
170
220
  #--
171
221
  # FIXME: Og should handle this automatically.
172
222
  #++
173
- base.send :include, Aspects
223
+ base.send :include, Glue::Aspects
174
224
  base.before 'tags.clear', :on => [:og_delete]
175
225
  end
176
226
 
@@ -7,7 +7,7 @@ module Glue
7
7
  # Adds timestamping functionality.
8
8
 
9
9
  module Timestamped
10
- include Aspects
10
+ include Glue::Aspects
11
11
 
12
12
  property :create_time, Time, :control => :none
13
13
  property :update_time, Time, :control => :none
@@ -27,7 +27,7 @@ end
27
27
  # module.
28
28
 
29
29
  module TimestampedOnCreate
30
- include Aspects
30
+ include Glue::Aspects
31
31
  property :create_time, Time, :control => :none
32
32
  before "@create_time = Time.now", :on => :og_insert
33
33
  end
data/lib/og.rb CHANGED
@@ -43,7 +43,7 @@ module Og
43
43
 
44
44
  # The version.
45
45
 
46
- Version = '0.28.0'
46
+ Version = '0.29.0'
47
47
 
48
48
  # Library path.
49
49
 
@@ -93,7 +93,7 @@ module Og
93
93
 
94
94
  # Enable/dissable thread safe mode.
95
95
 
96
- setting :thread_safe, :default => true, :doc => 'Enable/dissable thread safe mode'
96
+ #setting :thread_safe, :default => true, :doc => 'Enable/dissable thread safe mode'
97
97
 
98
98
  # Address of the Og cache (if distributed caching enabled).
99
99
 
@@ -107,6 +107,10 @@ module Og
107
107
 
108
108
  mattr_accessor :manager
109
109
 
110
+ # thread safe state
111
+
112
+ mattr_reader :thread_safe
113
+
110
114
  # Pseudo type for binary data
111
115
 
112
116
  class Blob; end
@@ -124,7 +128,9 @@ module Og
124
128
  # additional, faster or enhanced functionality.
125
129
 
126
130
  options[:called_by_og_setup] = true if options[:called_by_og_setup].nil?
127
-
131
+
132
+ @@thread_safe = true
133
+
128
134
  m = @@manager = Manager.new(options)
129
135
  m.manage_classes
130
136
 
@@ -155,6 +161,13 @@ module Og
155
161
  def escape(str)
156
162
  @@manager.store.escape(str)
157
163
  end
164
+
165
+ # change thread_safe mode
166
+ def thread_safe=(bool)
167
+ @@thread_safe = bool
168
+ @@manager and @@manager.class.managers.each { |m| m.initialize_store }
169
+ @@thread_safe
170
+ end
158
171
  end
159
172
 
160
173
  end
@@ -221,13 +221,56 @@ module EntityMixin
221
221
  ogmanager.store.select_one(sql, self)
222
222
  end
223
223
 
224
+ # :section: Aggregations / Calculations
225
+
226
+ # Perform a general aggregation/calculation.
227
+
228
+ def aggregate(term, options = {})
229
+ options[:class] = self
230
+ ogmanager.store.calculate(term, options)
231
+ end
232
+ alias_method :calculate, :aggregate
233
+
224
234
  # Perform a count query.
225
235
 
226
236
  def count(options = {})
227
- options[:class] = self
228
- ogmanager.store.count(options)
237
+ calculate('COUNT(*)', options).to_i
238
+ end
239
+
240
+ # Find the minimum of a property.
241
+ # Pass a :group option to return an aggregation.
242
+
243
+ def minimum(min, options = {})
244
+ calculate("MIN(#{min})", options)
245
+ end
246
+ alias_method :min, :minimum
247
+
248
+ # Find the maximum of a property.
249
+ # Pass a :group option to return an aggregation.
250
+
251
+ def maximum(max, options = {})
252
+ calculate("MAX(#{max})", options)
229
253
  end
254
+ alias_method :max, :maximum
230
255
 
256
+ # Find the average of a property.
257
+ # Pass a :group option to return an aggregation.
258
+
259
+ def average(avg, options = {})
260
+ calculate("AVG(#{avg})", options)
261
+ end
262
+ alias_method :avg, :average
263
+
264
+ # Find the sum of a property.
265
+ # Pass a :group option to return an aggregation.
266
+
267
+ def summarize(sum, options = {})
268
+ calculate("SUM(#{sum})", options)
269
+ end
270
+ alias_method :sum, :summarize
271
+
272
+ # :section: Delete/Destroy methods.
273
+
231
274
  # Delete an instance of this Entity class using the actual
232
275
  # instance or the primary key.
233
276
 
@@ -497,19 +540,24 @@ private
497
540
  def finder(match, args)
498
541
  finder = (match.captures.first == 'all_by' ? :find : :find_one)
499
542
  attrs = match.captures.last.split('_and_')
500
-
501
- options = {}
543
+ options = (annotation[:find_options] || {}).dup
502
544
  options = args.pop if args.last.is_a?(Hash)
503
-
545
+ relations_map = relations.map{|r| [r.name.to_s,r]}
504
546
  condition = attrs.zip(args).map do |name, value|
505
- field_name = properties[name.to_sym][:field] ||
506
- properties[name.to_sym][:name] ||
507
- properties[name.to_sym][:symbol]
508
-
547
+ if relation = relations_map.assoc(name)
548
+ relation = relation.last
549
+ field_name = relation.foreign_key
550
+ value = value.send(relation.target_class.primary_key.symbol)
551
+ else
552
+ field_name = properties[name.to_sym][:field] ||
553
+ properties[name.to_sym][:name] ||
554
+ properties[name.to_sym][:symbol]
555
+ end
556
+ options["#{name}_op".to_sym] = 'IN' if value.is_a?(Array)
509
557
  %|#{field_name} #{options.delete("#{name}_op".to_sym) || '='} #{ogmanager.store.quote(value)}|
510
558
  end.join(' AND ')
511
559
 
512
- options.update(
560
+ options.merge!(
513
561
  :class => self,
514
562
  :condition => condition
515
563
  )
@@ -95,7 +95,8 @@ module Caboose
95
95
  when :between
96
96
  @negate ? ["#{@table_prefix}#{@name} NOT BETWEEN ? AND ?", @value.begin, @value.end] : ["#{@table_prefix}#{@name} BETWEEN ? AND ?", @value.begin, @value.end]
97
97
  when :in
98
- @negate ? ["#{@table_prefix}#{@name} NOT IN (?)", @value.to_a] : ["#{@table_prefix}#{@name} IN (?)", @value.to_a]
98
+ # @negate ? ["#{@table_prefix}#{@name} NOT IN (?)", @value.to_a] : ["#{@table_prefix}#{@name} IN (?)", @value.to_a]
99
+ @negate ? ["#{@table_prefix}#{@name} NOT IN (?*)", @value.to_a] : ["#{@table_prefix}#{@name} IN (?*)", @value.to_a]
99
100
  else
100
101
  ["#{@table_prefix}#{@name} #{@test} ?", @value]
101
102
  end
@@ -13,13 +13,18 @@ class Manager
13
13
  def self.managers
14
14
  managers = []
15
15
  ObjectSpace.each_object(self) { |o| managers << o }
16
- managers
16
+ return managers
17
17
  end
18
18
 
19
19
  def self.managed?(klass)
20
20
  self.managers.any? { |m| m.managed? klass }
21
21
  end
22
22
 
23
+ def self.managed_classes
24
+ managed = self.managers.collect {|m| m.managed_classes}
25
+ managed.flatten
26
+ end
27
+
23
28
  # Information about an Entity class.
24
29
 
25
30
  class EntityInfo
@@ -56,28 +61,51 @@ class Manager
56
61
  def initialize(options)
57
62
  @options = options
58
63
  @entities = {}
59
- @pool = Pool.new
60
64
 
61
65
  @store_class = Store.for_name(options[:store])
62
66
  @store_class.destroy(options) if options[:destroy]
63
67
  @store_class.destroy_tables(options) if options[:destroy_tables]
68
+ init_store
69
+ end
70
+
71
+ def initialize_store
72
+ if @pool
73
+ close_store
74
+ end
64
75
 
65
76
  if Og.thread_safe
77
+ @pool = Pool.new
66
78
  (options[:connection_count] || 5).times do
67
- @pool << @store_class.new(options)
79
+ @pool << @store_class.new(@options)
68
80
  end
69
81
  else
70
- @store = @store_class.new(options)
82
+ @store = @store_class.new(@options)
83
+ end
84
+ end
85
+ alias :init_store :initialize_store
86
+
87
+ # used when changing thread_safe mode
88
+
89
+ def close_store
90
+ if @pool.empty?
91
+ @store.close
92
+ else
93
+ @pool.each { |s| s.close }
94
+ @pool.clear
71
95
  end
96
+ @pool = nil
72
97
  end
73
98
 
74
99
  # Get a store from the pool.
75
100
 
76
101
  def get_store
77
- if Og.thread_safe
102
+ if @pool
78
103
  thread = Thread.current
79
104
 
80
105
  unless st = thread[:og_store] and st.is_a?(@store_class)
106
+ if 0 == @pool.size()
107
+ initialize_store()
108
+ end
81
109
  st = @pool.pop()
82
110
  thread[:og_store] = st
83
111
  end
@@ -93,7 +121,7 @@ class Manager
93
121
  # Return a store to the pool.
94
122
 
95
123
  def put_store
96
- if Og.thread_safe
124
+ if @pool
97
125
  thread = Thread.current
98
126
 
99
127
  if conn = thread[:og_store]
@@ -150,7 +178,7 @@ class Manager
150
178
  #++
151
179
 
152
180
  def manageable?(klass)
153
- klass.respond_to?(:properties) and (!klass.properties.empty?) # and !self.class.managed?(klass) # and klass.ann.self.polymorphic.nil?
181
+ klass.respond_to?(:properties) and (!klass.properties.empty?) # and klass.ann.self.polymorphic.nil?
154
182
  end
155
183
 
156
184
  # Is the class managed by Og?
@@ -186,6 +214,7 @@ class Manager
186
214
 
187
215
  def manage_classes(*classes)
188
216
  classes = manageable_classes.flatten # if classes.empty? FIXME!
217
+ classes = classes.reject { |c| self.class.managed?(c) }
189
218
 
190
219
  classes.each { |c| Relation.resolve_targets(c) }
191
220
  classes.each { |c| Relation.resolve_polymorphic_markers(c) }
@@ -194,6 +223,7 @@ class Manager
194
223
  # The polymorpic resolution step creates more manageable classes.
195
224
 
196
225
  classes = manageable_classes.flatten # if classes.empty? FIXME!
226
+ classes = classes.reject { |c| self.class.managed?(c) }
197
227
 
198
228
  Logger.debug "Og manageable classes: #{classes.inspect}" if $DBG
199
229
 
@@ -241,3 +271,4 @@ end
241
271
 
242
272
  # * George Moschovitis <gm@navel.gr>
243
273
  # * Guillaume Pierronnet <guillaume.pierronnet@laposte.net>
274
+ # * Rob Pitt