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.
- 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>
|