og 0.24.0 → 0.25.0

Sign up to get free protection for your applications and to get access to all the features.
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>