og 0.24.0 → 0.25.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.
Files changed (51) hide show
  1. data/ProjectInfo +2 -5
  2. data/README +2 -0
  3. data/doc/AUTHORS +4 -1
  4. data/doc/RELEASES +53 -0
  5. data/examples/run.rb +2 -2
  6. data/lib/{og/mixin → glue}/hierarchical.rb +19 -19
  7. data/lib/{og/mixin → glue}/optimistic_locking.rb +1 -1
  8. data/lib/glue/orderable.rb +235 -0
  9. data/lib/glue/revisable.rb +2 -0
  10. data/lib/glue/taggable.rb +176 -0
  11. data/lib/{og/mixin/taggable.rb → glue/taggable_old.rb} +6 -0
  12. data/lib/glue/timestamped.rb +37 -0
  13. data/lib/{og/mixin → glue}/tree.rb +3 -8
  14. data/lib/og.rb +21 -20
  15. data/lib/og/collection.rb +15 -1
  16. data/lib/og/entity.rb +256 -114
  17. data/lib/og/manager.rb +60 -27
  18. data/lib/og/{mixin/schema_inheritance_base.rb → markers.rb} +5 -2
  19. data/lib/og/relation.rb +70 -74
  20. data/lib/og/relation/belongs_to.rb +5 -3
  21. data/lib/og/relation/has_many.rb +1 -0
  22. data/lib/og/relation/joins_many.rb +5 -4
  23. data/lib/og/store.rb +25 -46
  24. data/lib/og/store/alpha/filesys.rb +1 -1
  25. data/lib/og/store/alpha/kirby.rb +30 -30
  26. data/lib/og/store/alpha/memory.rb +49 -49
  27. data/lib/og/store/alpha/sqlserver.rb +7 -7
  28. data/lib/og/store/kirby.rb +38 -38
  29. data/lib/og/store/mysql.rb +43 -43
  30. data/lib/og/store/psql.rb +222 -53
  31. data/lib/og/store/sql.rb +165 -105
  32. data/lib/og/store/sqlite.rb +29 -25
  33. data/lib/og/validation.rb +24 -14
  34. data/lib/{vendor → og/vendor}/README +0 -0
  35. data/lib/{vendor → og/vendor}/kbserver.rb +1 -1
  36. data/lib/{vendor → og/vendor}/kirbybase.rb +230 -79
  37. data/lib/{vendor → og/vendor}/mysql.rb +0 -0
  38. data/lib/{vendor → og/vendor}/mysql411.rb +0 -0
  39. data/test/og/mixin/tc_hierarchical.rb +1 -1
  40. data/test/og/mixin/tc_optimistic_locking.rb +1 -1
  41. data/test/og/mixin/tc_orderable.rb +1 -1
  42. data/test/og/mixin/tc_taggable.rb +2 -2
  43. data/test/og/mixin/tc_timestamped.rb +2 -2
  44. data/test/og/tc_finder.rb +33 -0
  45. data/test/og/tc_inheritance.rb +2 -2
  46. data/test/og/tc_scoped.rb +45 -0
  47. data/test/og/tc_store.rb +1 -7
  48. metadata +21 -18
  49. data/lib/og/mixin/orderable.rb +0 -174
  50. data/lib/og/mixin/revisable.rb +0 -0
  51. data/lib/og/mixin/timestamped.rb +0 -24
@@ -0,0 +1,2 @@
1
+
2
+ raise 'This is not working yet, do not require this file.'
@@ -0,0 +1,176 @@
1
+ require 'nano/inflect'
2
+
3
+ # The default Tag implementation. A tag attaches semantics to
4
+ # a given object.
5
+ #--
6
+ # FIXME: use index and char() instead of String.
7
+ #++
8
+
9
+ class Tag
10
+ property :name, String, :uniq => true
11
+ property :count, Fixnum
12
+
13
+ # An alias for count.
14
+
15
+ alias_method :freq, :count
16
+ alias_method :frequency, :count
17
+
18
+ def initialize(name = nil)
19
+ @name = name
20
+ @count = 0
21
+ end
22
+
23
+ #--
24
+ # FIXME: use update_properties here!
25
+ #++
26
+
27
+ def tag(obj)
28
+ send(obj.class.name.underscore.pluralize.to_sym) << obj
29
+ @count += 1
30
+ save!
31
+ end
32
+
33
+ # Return all tagged objects from all categories.
34
+
35
+ def tagged
36
+ # TODO.
37
+ end
38
+
39
+ # Helper method
40
+
41
+ def self.total_frequency(tags = Tag.all)
42
+ tags.inject(1) { |total, t| total += t.count }
43
+ end
44
+ end
45
+
46
+ module Glue
47
+
48
+ # Add tagging methods to the target class.
49
+ # For more information on the algorithms used surf:
50
+ # http://www.pui.ch/phred/archives/2005/04/tags-database-schemas.html
51
+ #
52
+ # === Example
53
+ #
54
+ # class Article
55
+ # include Taggable
56
+ # ..
57
+ # end
58
+ #
59
+ # article.tag('navel', 'gmosx', 'nitro')
60
+ # article.tags
61
+ # article.tag_names
62
+ # Article.find_with_tags('navel', 'gmosx')
63
+ # Article.find_with_any_tag('name', 'gmosx')
64
+ #
65
+ # Tag.find_by_name('ruby').articles
66
+
67
+ module Taggable
68
+ include Og::EntityMixin
69
+ many_to_many Tag
70
+
71
+ # Add a tag for this object.
72
+
73
+ def tag(the_tags, options = {})
74
+ options = {
75
+ :clear => true
76
+ }.merge(options)
77
+
78
+ self.tags.clear if options[:clear]
79
+
80
+ for name in Taggable.tags_to_names(the_tags)
81
+ Tag.find_or_create_by_name(name).tag(self)
82
+ end
83
+ end
84
+ alias_method :tag!, :tag
85
+
86
+ # Return the names of the tags.
87
+
88
+ def tag_names
89
+ tags.collect { |t| t.name }
90
+ end
91
+
92
+ # Checks to see if this object has been tagged
93
+ # with +tag_name+.
94
+
95
+ def tagged_with?(tag_name)
96
+ tag_names.include?(tag_name)
97
+ end
98
+ alias_method :tagged_by?, :tagged_with?
99
+
100
+ module ClassMethods
101
+ # Find objects with all of the provided tags.
102
+ # INTERSECTION (AND)
103
+
104
+ def find_with_tags(*names)
105
+ info = ogmanager.store.join_table_info(self, Tag)
106
+ count = names.size
107
+ names = names.map { |n| "'#{n}'" }.join(',')
108
+ sql = %{
109
+ SELECT o.*
110
+ FROM
111
+ #{info[:first_table]} AS o,
112
+ #{info[:second_table]} as t,
113
+ #{info[:table]} as j
114
+ WHERE o.oid = j.#{info[:first_key]}
115
+ AND t.oid = j.#{info[:second_key]}
116
+ AND (t.name in (#{names}))
117
+ GROUP BY o.oid
118
+ HAVING COUNT(o.oid) = #{count};
119
+ }
120
+ return self.select(sql)
121
+ end
122
+ alias_method :find_with_tag, :find_with_tags
123
+
124
+ # Find objects with any of the provided tags.
125
+ # UNION (OR)
126
+
127
+ def find_with_any_tag(*names)
128
+ info = ogmanager.store.join_table_info(self, tag)
129
+ count = names.size
130
+ names = names.map { |n| "'#{n}'" }.join(',')
131
+ sql = %{
132
+ SELECT o.*
133
+ FROM
134
+ #{info[:first_table]} AS o,
135
+ #{info[:second_table]} as t,
136
+ #{info[:table]} as j
137
+ WHERE
138
+ o.oid = j.#{info[:first_key]}
139
+ AND t.oid = j.#{info[:second_key]}
140
+ AND (t.name in (#{names}))
141
+ GROUP BY o.oid
142
+ }
143
+ return self.select(sql)
144
+ end
145
+ end
146
+
147
+ def self.included(base)
148
+ Tag.module_eval do
149
+ many_to_many base
150
+ end
151
+ base.extend(ClassMethods)
152
+ #--
153
+ # FIXME: Og should handle this automatically.
154
+ #++
155
+ base.send :include, Aspects
156
+ base.before 'tags.clear', :on => [:og_delete]
157
+ end
158
+
159
+ # Helper.
160
+
161
+ def self.tags_to_names(the_tags, separator = ' ')
162
+ if the_tags.is_a? Array
163
+ names = the_tags
164
+ elsif the_tags.is_a? String
165
+ names = the_tags.split(separator)
166
+ end
167
+
168
+ names = names.flatten.uniq.compact
169
+
170
+ return names
171
+ end
172
+ end
173
+
174
+ end
175
+
176
+ # * George Moschovitis <gm@navel.gr>
@@ -23,6 +23,10 @@ class Tag
23
23
  end
24
24
  end
25
25
 
26
+ end
27
+
28
+ module Glue
29
+
26
30
  # Add tagging methods to the target class.
27
31
  # For more information on the algorithms used surf:
28
32
  # http://www.pui.ch/phred/archives/2005/04/tags-database-schemas.html
@@ -97,6 +101,8 @@ module Taggable
97
101
 
98
102
  code << %{
99
103
  def tag(the_tags, options = {})
104
+ return unless the_tags
105
+
100
106
  options = {
101
107
  :clear => true
102
108
  }.merge(options)
@@ -0,0 +1,37 @@
1
+ require 'nano/time/stamp'
2
+
3
+ require 'glue/aspects'
4
+
5
+ module Glue
6
+
7
+ # Adds timestamping functionality.
8
+
9
+ module Timestamped
10
+ include Aspects
11
+
12
+ property :create_time, Time, :editor => :none
13
+ property :update_time, Time, :editor => :none
14
+ property :access_time, Time, :editor => :none
15
+
16
+ before "@create_time = @update_time = Time.now", :on => :og_insert
17
+ before "@update_time = Time.now", :on => :og_update
18
+
19
+ def touch!
20
+ @access_time = Time.now
21
+ end
22
+ end
23
+
24
+ # Adds simple timestamping functionality on create.
25
+ # Only the create_time field is added, to add
26
+ # create/update/access fields use the normal timestamped
27
+ # module.
28
+
29
+ module TimestampedOnCreate
30
+ include Aspects
31
+ property :create_time, Time, :editor => :none
32
+ before "@create_time = @update_time = Time.now", :on => :og_insert
33
+ end
34
+
35
+ end
36
+
37
+ # * George Moschovitis <gm@navel.gr>
@@ -4,17 +4,15 @@ raise 'This is not working yet, do not require this file.'
4
4
 
5
5
  require 'glue/attribute'
6
6
 
7
- module Og
8
-
9
7
  # A useful encapsulation of the nested intervals pattern for
10
8
  # hierarchical SQL queries. Slightly adapted from the original
11
9
  # article (http://www.dbazine.com/tropashko4.shtml)
12
10
 
13
11
  module TreeTraversal
14
-
12
+
15
13
  # The default prefix for the tree traversal helpers.
16
-
17
- cattr_accessor :prefix, 'tree'
14
+
15
+ cattr_accessor :prefix, 'tree'
18
16
 
19
17
  def self.child(sum, n)
20
18
  power = 2 ** n
@@ -23,9 +21,6 @@ module TreeTraversal
23
21
 
24
22
  end
25
23
 
26
- end
27
-
28
-
29
24
  __END__
30
25
 
31
26
  def xcoord(numer, denom)
data/lib/og.rb CHANGED
@@ -48,24 +48,24 @@ module Og
48
48
  # Library path.
49
49
 
50
50
  LibPath = File.dirname(__FILE__)
51
-
51
+
52
52
  # If true, check for implicit changes in the object
53
53
  # graph. For example when you add an object to a parent
54
54
  # the object might be removed from his previous parent.
55
55
  # In this case Og emmits a warning.
56
56
 
57
57
  setting :check_implicit_graph_changes, :default => false, :doc => 'If true, check for implicit changes in the object graph'
58
-
58
+
59
59
  # If true, only allow reading from the database. Usefull
60
60
  # for maintainance.
61
61
  # WARNING: not implemented yet.
62
-
62
+
63
63
  setting :read_only_mode, :default => false, :doc => 'If true, only allow reading from the database'
64
64
 
65
65
  # Prepend the following prefix to all generated SQL table names.
66
66
  # Usefull on hosting scenarios where you have to run multiple
67
67
  # web applications/sites on a single database.
68
- #
68
+ #
69
69
  # Don't set the table_prefix to nil, or you may face problems
70
70
  # with reserved words on some RDBM systems. For example User
71
71
  # maps to user which is reserved in postgresql). The prefix
@@ -74,7 +74,7 @@ module Og
74
74
  #--
75
75
  # TODO: move this to the sql store.
76
76
  #++
77
-
77
+
78
78
  setting :table_prefix, :default => 'og', :doc => 'Prepend the prefix to all generated SQL table names'
79
79
 
80
80
  # If true, Og tries to create/update the schema in the
@@ -82,7 +82,7 @@ module Og
82
82
  # false and only set to true when the object model is
83
83
  # upadated. For debug/development environments this should
84
84
  # stay true for convienience.
85
-
85
+
86
86
  setting :create_schema, :default => true, :doc => 'If true, Og tries to create/update the schema in the data store'
87
87
 
88
88
  # If true raises exceptions on store errors, usefull when
@@ -90,44 +90,42 @@ module Og
90
90
  # set to false to make the application more fault tolerant.
91
91
 
92
92
  setting :raise_store_exceptions, :default => true, :doc => 'If true raises exceptions on store errors'
93
-
93
+
94
94
  # Enable/dissable thread safe mode.
95
-
96
- setting :thread_safe, :default => true, :doc => 'Enable/dissable thread safe mode'
97
-
98
- # Marker module. If included in a class, the Og automanager
99
- # ignores this class.
100
95
 
101
- module Unmanageable; end
96
+ setting :thread_safe, :default => true, :doc => 'Enable/dissable thread safe mode'
102
97
 
103
98
  # The active manager
104
-
99
+
105
100
  mattr_accessor :manager
106
-
101
+
107
102
  # Pseudo type for binary data
108
-
103
+
109
104
  class Blob; end
110
105
 
111
106
  class << self
112
-
107
+
113
108
  # Helper method, useful to initialize Og.
109
+ # If no options are passed, sqlite is selected
110
+ # as the default store.
114
111
 
115
- def setup(options = {})
112
+ def setup(options = {:store => :sqlite})
116
113
  m = @@manager = Manager.new(options)
117
114
  m.manage_classes
118
115
  return m
119
116
  end
120
117
  alias_method :connect, :setup
121
118
  alias_method :options=, :setup
119
+ alias_method :start, :setup
122
120
 
123
121
  # Helper method.
124
-
122
+
125
123
  def escape(str)
126
124
  @@manager.store.escape(str)
127
125
  end
128
126
 
129
127
  end
130
-
128
+
131
129
  end
132
130
 
133
131
  #--
@@ -138,3 +136,6 @@ require 'og/manager'
138
136
  require 'og/errors'
139
137
  require 'og/types'
140
138
  require 'og/validation'
139
+ require 'og/markers'
140
+
141
+ # * George Moschovitis <gm@navel.gr>
@@ -15,6 +15,10 @@ class Collection
15
15
 
16
16
  attr_accessor :members
17
17
 
18
+ # The class of the members of this collection.
19
+
20
+ attr_accessor :member_class
21
+
18
22
  # A method used to add insert objects in the collection.
19
23
 
20
24
  attr_accessor :insert_proc
@@ -47,10 +51,11 @@ class Collection
47
51
 
48
52
  # Initialize the collection.
49
53
 
50
- def initialize(owner = nil, insert_proc = nil,
54
+ def initialize(owner = nil, member_class = nil, insert_proc = nil,
51
55
  remove_proc = nil, find_proc = nil,
52
56
  count_proc = nil, find_options = {})
53
57
  @owner = owner
58
+ @member_class = member_class
54
59
  @insert_proc = insert_proc
55
60
  @remove_proc = remove_proc
56
61
  @find_proc = find_proc
@@ -175,6 +180,7 @@ class Collection
175
180
  self.each { |obj| @owner.send(@remove_proc, obj) }
176
181
  end
177
182
  @members.clear
183
+ @loaded = false # gmosx: IS this needed?
178
184
  end
179
185
  alias_method :clear, :remove_all
180
186
 
@@ -199,6 +205,14 @@ class Collection
199
205
  end
200
206
  alias_method :count, :size
201
207
 
208
+ # Allows to perform a scoped query.
209
+
210
+ def find(options = {})
211
+ @member_class.with_scope(options) do
212
+ return @owner.send(@find_proc, @find_options)
213
+ end
214
+ end
215
+
202
216
  # Redirect all other methods to the members array.
203
217
 
204
218
  def method_missing(symbol, *args, &block)
@@ -1,101 +1,96 @@
1
+ require 'mega/class_inherit'
2
+ require 'nano/kernel/assign_with'
3
+
4
+ require 'glue/property'
1
5
  require 'og/relation'
2
- require 'og/mixin/schema_inheritance_base'
3
6
 
4
7
  module Og
5
8
 
6
- # Include this module to classes to make them managable by Og.
9
+ # Include this module to classes to make them managable by Og.
7
10
 
8
11
  module EntityMixin
9
12
 
10
- def self.append_features(base)
11
- super
13
+ def save(options = nil)
14
+ self.class.ogmanager.store.save(self, options)
15
+ # return self
16
+ end
17
+ alias_method :save!, :save
12
18
 
13
- base.extend(ClassMethods)
19
+ def insert
20
+ self.class.ogmanager.store.insert(self)
21
+ return self
22
+ end
14
23
 
15
- base.module_eval <<-end_eval, __FILE__, __LINE__
16
- def save(options = nil)
17
- self.class.ogmanager.store.save(self, options)
18
- # return self
19
- end
20
- alias_method :save!, :save
24
+ def update(options = nil)
25
+ self.class.ogmanager.store.update(self, options)
26
+ end
21
27
 
22
- def insert
23
- self.class.ogmanager.store.insert(self)
24
- return self
25
- end
26
-
27
- def update(options = nil)
28
- self.class.ogmanager.store.update(self, options)
29
- end
30
-
31
- def update_properties(*properties)
32
- self.class.ogmanager.store.update(self, :only => properties)
33
- end
34
- alias_method :update_property, :update_properties
35
- alias_method :pupdate, :update_properties
28
+ def update_properties(*properties)
29
+ self.class.ogmanager.store.update(self, :only => properties)
30
+ end
31
+ alias_method :update_property, :update_properties
32
+ alias_method :pupdate, :update_properties
36
33
 
37
- def update_by_sql(set)
38
- self.class.ogmanager.store.update_by_sql(self, set)
39
- end
40
- alias_method :update_sql, :update_by_sql
41
- alias_method :supdate, :update_by_sql
42
-
43
- # Reload this entity instance from the store.
44
-
45
- def reload
46
- self.class.ogmanager.store.reload(self, self.pk)
47
- end
48
- alias_method :reload!, :reload
34
+ def update_by_sql(set)
35
+ self.class.ogmanager.store.update_by_sql(self, set)
36
+ end
37
+ alias_method :update_sql, :update_by_sql
38
+ alias_method :supdate, :update_by_sql
49
39
 
50
- # Delete this entity instance from the store.
51
-
52
- def delete(cascade = true)
53
- self.class.ogmanager.store.delete(self, self.class, cascade)
54
- end
55
- alias_method :delete!, :delete
40
+ # Reload this entity instance from the store.
56
41
 
57
- def transaction(&block)
58
- self.class.ogmanager.store.transaction(&block)
59
- end
42
+ def reload
43
+ self.class.ogmanager.store.reload(self, self.pk)
44
+ end
45
+ alias_method :reload!, :reload
60
46
 
61
- def saved?
62
- not @oid.nil?
63
- end
64
- alias_method :serialized?, :saved?
65
-
66
- def og_quote(obj)
67
- self.class.ogmanager.store.quote(obj)
68
- end
69
-
70
- def assign_properties(values, options = {})
71
- Property.populate_object(self, values, options)
72
- return self
73
- end
74
- alias_method :assign, :assign_properties
75
- end_eval
47
+ # Delete this entity instance from the store.
76
48
 
77
- base.send :include, RelationDSL
49
+ def delete(cascade = true)
50
+ self.class.ogmanager.store.delete(self, self.class, cascade)
51
+ end
52
+ alias_method :delete!, :delete
78
53
 
79
- # If base is a Module setup a propageting
80
- # append_features method.
81
-
82
- unless base.is_a? Class
83
- base.module_eval do
84
- def self.append_features(cbase)
85
- super
86
- cbase.send :include, EntityMixin
87
- end
88
- end
89
- end
54
+ def transaction(&block)
55
+ self.class.ogmanager.store.transaction(&block)
56
+ end
57
+
58
+ def saved?
59
+ not @oid.nil?
60
+ end
61
+ alias_method :serialized?, :saved?
62
+
63
+ def og_quote(obj)
64
+ self.class.ogmanager.store.quote(obj)
65
+ end
66
+
67
+ def assign_properties(values, options = {})
68
+ Property.populate_object(self, values, options)
69
+ return self
90
70
  end
71
+ alias_method :assign, :assign_properties
72
+
73
+ include RelationDSL
74
+
75
+ class_inherit do
91
76
 
92
- module ClassMethods
93
77
  def create(*args)
94
78
  obj = self.new(*args)
95
79
  yield(obj) if block_given?
96
80
  ogmanager.store.save(obj)
97
81
  return obj
98
82
  end
83
+
84
+ # An alternative creation helper, only works
85
+ # with objects that have an initialize method
86
+ # tha works with no arguments.
87
+
88
+ def create_with(hash)
89
+ obj = self.new
90
+ obj.assign_with(hash)
91
+ ogmanager.store.save(obj)
92
+ return obj
93
+ end
99
94
 
100
95
  def assign_properties(values, options)
101
96
  Property.fill(self.new, values, options)
@@ -104,7 +99,7 @@ module EntityMixin
104
99
 
105
100
  # Load an instance of this Entity class using the primary
106
101
  # key.
107
-
102
+
108
103
  def load(pk)
109
104
  ogmanager.store.load(pk, self)
110
105
  end
@@ -114,15 +109,15 @@ module EntityMixin
114
109
  def update(set, options = nil)
115
110
  ogmanager.store.update_by_sql(self, set, options)
116
111
  end
117
-
112
+
118
113
  def find(options = {})
119
- if find_options = self.ann.this[:find_options]
120
- options = find_options.first.dup.update(options)
114
+ if find_options = self.ann.self[:find_options]
115
+ options = find_options.dup.update(options)
121
116
  end
122
117
 
123
118
  options[:class] = self
124
119
  options[:type] = self if self.schema_inheritance_child?
125
-
120
+
126
121
  ogmanager.store.find(options)
127
122
  end
128
123
  alias_method :all, :find
@@ -141,7 +136,7 @@ module EntityMixin
141
136
  def select_one(sql)
142
137
  ogmanager.store.select_one(sql, self)
143
138
  end
144
-
139
+
145
140
  def count(options = {})
146
141
  options[:class] = self
147
142
  ogmanager.store.count(options)
@@ -149,7 +144,7 @@ module EntityMixin
149
144
 
150
145
  # Delete an instance of this Entity class using the actual
151
146
  # instance or the primary key.
152
-
147
+
153
148
  def delete(obj_or_pk, cascade = true)
154
149
  ogmanager.store.delete(obj_or_pk, self, cascade)
155
150
  end
@@ -159,7 +154,7 @@ module EntityMixin
159
154
  #--
160
155
  # TODO: add cascade option.
161
156
  #++
162
-
157
+
163
158
  def delete_all
164
159
  ogmanager.store.delete_all(self)
165
160
  end
@@ -167,7 +162,7 @@ module EntityMixin
167
162
  def destroy
168
163
  ogmanager.store.send :destroy, self
169
164
  end
170
-
165
+
171
166
  def escape(str)
172
167
  ogmanager.store.escape(str)
173
168
  end
@@ -177,28 +172,29 @@ module EntityMixin
177
172
  end
178
173
 
179
174
  # Return the store (connection) for this class.
180
-
175
+
181
176
  def ogstore
182
177
  ogmanager.store
183
178
  end
184
-
179
+
185
180
  def primary_key
186
- if ann.this.primary_key.nil?
187
- ann :this, :primary_key => Entity.resolve_primary_key(self)
181
+ unless pk = ann.self[:primary_key]
182
+ pk = Entity.resolve_primary_key(self)
183
+ ann :self, :primary_key => pk
188
184
  end
189
- return ann.this.primary_key
185
+ return pk
190
186
  end
191
-
187
+
192
188
  # Set the default find options for this entity.
193
-
189
+
194
190
  def set_find_options(options)
195
- ann :this, :find_options => options
191
+ ann self, :find_options => options
196
192
  end
197
193
  alias_method :find_options, :set_find_options
198
194
 
199
195
  # Enable schema inheritance for this Entity class.
200
196
  # The Single Table Inheritance pattern is used.
201
-
197
+
202
198
  def set_schema_inheritance
203
199
  include Og::SchemaInheritanceBase
204
200
  end
@@ -219,7 +215,7 @@ module EntityMixin
219
215
  #--
220
216
  # farms/rp: is there not another way to access the root class?
221
217
  #++
222
-
218
+
223
219
  def schema_inheritance_root_class
224
220
  klass = self
225
221
  until !Og.manager.manageable?(klass) or klass.schema_inheritance_root?
@@ -229,68 +225,214 @@ module EntityMixin
229
225
  end
230
226
 
231
227
  # Set the default order option for this entity.
232
-
228
+
233
229
  def set_order(order_str)
234
- # FIXME
235
- # self.ann :this, :find_options, :order => order_str
230
+ unless ann.self.find_options.nil?
231
+ ann.self.find_options[:order] = order_str
232
+ else
233
+ ann self, :find_options => { :order => order_str }
234
+ end
236
235
  end
237
236
  alias_method :order, :set_order
237
+ alias_method :order_by, :set_order
238
238
 
239
239
  # Set a custom table name.
240
-
240
+
241
241
  def set_sql_table(table)
242
- ann :this, :sql_table => table.to_s
242
+ ann self, :sql_table => table.to_s
243
243
  end
244
244
  alias_method :set_table, :set_sql_table
245
245
 
246
246
  # Set the primary key.
247
-
247
+
248
248
  def set_primary_key(pk, pkclass = Fixnum)
249
- ann :this, :primary_key => Property.new(:symbol => pk, :klass => pkclass)
249
+ ann self, :primary_key => Property.new(:symbol => pk, :klass => pkclass)
250
250
  end
251
251
 
252
252
  # Is this entity a polymorphic parent?
253
-
253
+
254
254
  def polymorphic_parent?
255
- self.to_s == self.ann.this.polymorphic.to_s
255
+ self.to_s == self.ann.self.polymorphic.to_s
256
256
  end
257
257
 
258
258
  # Used internally to fix the forward reference problem.
259
-
259
+
260
260
  def const_missing(sym) # :nodoc: all
261
261
  return sym
262
- end
262
+ end
263
+
264
+ # Returns an array of all relations formed by other og managed
265
+ # classes with the class of this object.
266
+ #
267
+ # This is needed by the PostgreSQL foreign key constraints
268
+ # system.
269
+
270
+ def resolve_remote_relations
271
+ klass = self
272
+ manager = klass.ogmanager
273
+ relations = Array.new
274
+ manager.managed_classes.each do |managed_class|
275
+ next if managed_class == klass
276
+ managed_class.relations.each do |rel|
277
+ relations << rel if rel.target_class == klass
278
+ end
279
+ end
280
+ relations
281
+ end
282
+
283
+ # Define a scope for the following og method invocations
284
+ # on this managed class. The scope options are stored
285
+ # in a thread variable.
286
+ #
287
+ # At the moment the scope is only considered in find
288
+ # queries.
289
+
290
+ def set_scope(options)
291
+ Thread.current["#{self}_scope"] = options
292
+ end
293
+
294
+ # Get the scope.
295
+
296
+ def get_scope
297
+ Thread.current["#{self}_scope"]
298
+ end
299
+
300
+ # Execute some Og methods in a scope.
301
+
302
+ def with_scope(options)
303
+ set_scope(options)
304
+ yield
305
+ set_scope(nil)
306
+ end
307
+
308
+ # Handles dynamic finders. Handles methods such as:
309
+ #
310
+ # class User
311
+ # property :name, String
312
+ # property :age, Fixnum
313
+ # end
314
+ #
315
+ # User.find_by_name('tml')
316
+ # User.find_by_name_and_age('tml', 3)
317
+ # User.find_all_by_name_and_age('tml', 3)
318
+ # User.find_all_by_name_and_age('tml', 3, :name_op => 'LIKE', :age_op => '>', :limit => 4)
319
+ # User.find_or_create_by_name_and_age('tml', 3)
320
+ #--
321
+ # TODO: refactor this method.
322
+ #++
323
+
324
+ def method_missing(sym, *args)
325
+ if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(sym.to_s)
326
+ finder = (match.captures.first == 'all_by' ? :find : :find_one)
327
+ attrs = match.captures.last.split('_and_')
328
+
329
+ if args.last.is_a?(Hash)
330
+ options = args.pop
331
+ else
332
+ options = {}
333
+ end
334
+
335
+ condition = []
336
+ attrs.each_with_index do |a, idx|
337
+ condition << %|#{a} #{options.delete("#{a}_op".to_sym) || '='} #{ogmanager.store.quote(args[idx])}|
338
+ end
339
+
340
+ options.update(
341
+ :class => self,
342
+ :condition => condition.join(' AND ')
343
+ )
344
+
345
+ return ogmanager.store.send(finder, options)
346
+ elsif match = /find_or_create_by_([_a-zA-Z]\w*)/.match(sym.to_s)
347
+ finder = (match.captures.first == 'all_by' ? :find : :find_one)
348
+ attrs = match.captures.last.split('_and_')
349
+
350
+ if args.last.is_a?(Hash)
351
+ options = args.pop
352
+ else
353
+ options = {}
354
+ end
355
+
356
+ condition = []
357
+ attrs.each_with_index do |a, idx|
358
+ condition << %|#{a} #{options.delete("#{a}_op".to_sym) || '='} #{ogmanager.store.quote(args[idx])}|
359
+ end
360
+
361
+ options.update(
362
+ :class => self,
363
+ :condition => condition.join(' AND ')
364
+ )
365
+
366
+ unless obj = ogmanager.store.send(finder, options)
367
+ obj = self.create do |obj|
368
+ attrs.each_with_index do |a, idx|
369
+ obj.instance_variable_set "@#{a}", args[idx]
370
+ end
371
+ end
372
+ end
373
+
374
+ return obj
375
+ else
376
+ super
377
+ end
378
+ end
379
+
263
380
  end
381
+
264
382
  end
265
383
 
266
384
  # An Og Managed class. Also contains helper
267
385
  # methods.
268
386
 
269
- class Entity
387
+ class Entity
388
+
389
+ include EntityMixin
390
+
270
391
  class << self
392
+
271
393
  def resolve_primary_key(klass)
272
394
  # Is the class annotated with a primary key?
273
-
274
- if pk = klass.ann.this[:primary_key]
395
+
396
+ if pk = klass.ann.self[:primary_key]
275
397
  return pk
276
398
  end
277
-
399
+
278
400
  # Search the properties, try to find one annotated as primary_key.
279
-
401
+
280
402
  for p in klass.properties.values
281
403
  if p.primary_key
282
404
  return Property.new(:symbol => p.symbol, :klass => p.klass)
283
405
  end
284
406
  end
285
-
407
+
286
408
  # The default primary key is oid.
287
-
409
+
288
410
  return Property.new(:symbol => :oid, :klass => Fixnum)
289
411
  end
412
+
413
+ # Converts a string into it's corresponding class. Added to support STI.
414
+ # Ex: x = "Dave" becomes: (x.class.name == Dave) == true.
415
+ # Returns nil if there's no such class.
416
+ #--
417
+ # gmosx: investigate this patch!
418
+ #++
419
+
420
+ def entity_from_string(str)
421
+ res = nil
422
+ Og.manager.managed_classes.each do |klass|
423
+ if klass.name == str
424
+ res = klass
425
+ break
426
+ end
427
+ end
428
+ res
429
+ end
430
+
290
431
  end
291
-
292
- include EntityMixin
293
- end
294
432
 
433
+ end
295
434
 
296
435
  end
436
+
437
+ # * George Moschovitis <gm@navel.gr>
438
+ # * Tom Sawyer <transfire@gmail.com>