og 0.28.0 → 0.29.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.
@@ -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