og 0.24.0 → 0.25.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ProjectInfo +2 -5
- data/README +2 -0
- data/doc/AUTHORS +4 -1
- data/doc/RELEASES +53 -0
- data/examples/run.rb +2 -2
- data/lib/{og/mixin → glue}/hierarchical.rb +19 -19
- data/lib/{og/mixin → glue}/optimistic_locking.rb +1 -1
- data/lib/glue/orderable.rb +235 -0
- data/lib/glue/revisable.rb +2 -0
- data/lib/glue/taggable.rb +176 -0
- data/lib/{og/mixin/taggable.rb → glue/taggable_old.rb} +6 -0
- data/lib/glue/timestamped.rb +37 -0
- data/lib/{og/mixin → glue}/tree.rb +3 -8
- data/lib/og.rb +21 -20
- data/lib/og/collection.rb +15 -1
- data/lib/og/entity.rb +256 -114
- data/lib/og/manager.rb +60 -27
- data/lib/og/{mixin/schema_inheritance_base.rb → markers.rb} +5 -2
- data/lib/og/relation.rb +70 -74
- data/lib/og/relation/belongs_to.rb +5 -3
- data/lib/og/relation/has_many.rb +1 -0
- data/lib/og/relation/joins_many.rb +5 -4
- data/lib/og/store.rb +25 -46
- data/lib/og/store/alpha/filesys.rb +1 -1
- data/lib/og/store/alpha/kirby.rb +30 -30
- data/lib/og/store/alpha/memory.rb +49 -49
- data/lib/og/store/alpha/sqlserver.rb +7 -7
- data/lib/og/store/kirby.rb +38 -38
- data/lib/og/store/mysql.rb +43 -43
- data/lib/og/store/psql.rb +222 -53
- data/lib/og/store/sql.rb +165 -105
- data/lib/og/store/sqlite.rb +29 -25
- data/lib/og/validation.rb +24 -14
- data/lib/{vendor → og/vendor}/README +0 -0
- data/lib/{vendor → og/vendor}/kbserver.rb +1 -1
- data/lib/{vendor → og/vendor}/kirbybase.rb +230 -79
- data/lib/{vendor → og/vendor}/mysql.rb +0 -0
- data/lib/{vendor → og/vendor}/mysql411.rb +0 -0
- data/test/og/mixin/tc_hierarchical.rb +1 -1
- data/test/og/mixin/tc_optimistic_locking.rb +1 -1
- data/test/og/mixin/tc_orderable.rb +1 -1
- data/test/og/mixin/tc_taggable.rb +2 -2
- data/test/og/mixin/tc_timestamped.rb +2 -2
- data/test/og/tc_finder.rb +33 -0
- data/test/og/tc_inheritance.rb +2 -2
- data/test/og/tc_scoped.rb +45 -0
- data/test/og/tc_store.rb +1 -7
- metadata +21 -18
- data/lib/og/mixin/orderable.rb +0 -174
- data/lib/og/mixin/revisable.rb +0 -0
- data/lib/og/mixin/timestamped.rb +0 -24
@@ -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
|
-
|
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>
|
data/lib/og/collection.rb
CHANGED
@@ -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)
|
data/lib/og/entity.rb
CHANGED
@@ -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
|
9
|
+
# Include this module to classes to make them managable by Og.
|
7
10
|
|
8
11
|
module EntityMixin
|
9
12
|
|
10
|
-
def
|
11
|
-
|
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
|
-
|
19
|
+
def insert
|
20
|
+
self.class.ogmanager.store.insert(self)
|
21
|
+
return self
|
22
|
+
end
|
14
23
|
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
42
|
+
def reload
|
43
|
+
self.class.ogmanager.store.reload(self, self.pk)
|
44
|
+
end
|
45
|
+
alias_method :reload!, :reload
|
60
46
|
|
61
|
-
|
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
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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.
|
120
|
-
options = find_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
|
-
|
187
|
-
|
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
|
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
|
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
|
-
|
235
|
-
|
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
|
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
|
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.
|
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.
|
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>
|