og 0.31.0 → 0.40.0
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/{AUTHORS → CONTRIBUTORS} +26 -10
- data/doc/LICENSE +2 -3
- data/doc/RELEASES +56 -7
- data/doc/tutorial.txt +15 -15
- data/lib/glue/cacheable.rb +2 -5
- data/lib/glue/hierarchical.rb +1 -4
- data/lib/glue/optimistic_locking.rb +0 -2
- data/lib/glue/orderable.rb +79 -75
- data/lib/glue/revisable.rb +19 -24
- data/lib/glue/searchable.rb +0 -2
- data/lib/glue/taggable.rb +31 -29
- data/lib/glue/timestamped.rb +4 -2
- data/lib/og.rb +50 -29
- data/lib/og/adapter.rb +19 -0
- data/lib/og/adapter/mysql.rb +212 -0
- data/lib/og/adapter/mysql/override.rb +34 -0
- data/lib/og/adapter/mysql/script.rb +15 -0
- data/lib/og/adapter/mysql/utils.rb +40 -0
- data/lib/og/adapter/postgresql.rb +231 -0
- data/lib/og/adapter/postgresql/override.rb +117 -0
- data/lib/og/adapter/postgresql/script.rb +15 -0
- data/lib/og/adapter/postgresql/utils.rb +35 -0
- data/lib/og/adapter/sqlite.rb +132 -0
- data/lib/og/adapter/sqlite/override.rb +33 -0
- data/lib/og/adapter/sqlite/script.rb +15 -0
- data/lib/og/collection.rb +35 -7
- data/lib/og/{evolution.rb → dump.rb} +4 -5
- data/lib/og/entity.rb +102 -173
- data/lib/og/entity/clone.rb +119 -0
- data/lib/og/errors.rb +0 -2
- data/lib/og/manager.rb +85 -37
- data/lib/og/relation.rb +52 -34
- data/lib/og/relation/belongs_to.rb +0 -2
- data/lib/og/relation/has_many.rb +27 -4
- data/lib/og/relation/joins_many.rb +41 -14
- data/lib/og/relation/many_to_many.rb +10 -0
- data/lib/og/relation/refers_to.rb +22 -5
- data/lib/og/store.rb +80 -86
- data/lib/og/store/sql.rb +710 -713
- data/lib/og/store/sql/evolution.rb +119 -0
- data/lib/og/store/sql/join.rb +155 -0
- data/lib/og/store/sql/utils.rb +149 -0
- data/lib/og/test/assertions.rb +1 -3
- data/lib/og/test/testcase.rb +0 -2
- data/lib/og/types.rb +2 -5
- data/lib/og/validation.rb +6 -9
- data/test/{og/mixin → glue}/tc_hierarchical.rb +3 -13
- data/test/glue/tc_og_paginate.rb +47 -0
- data/test/{og/mixin → glue}/tc_optimistic_locking.rb +2 -12
- data/test/{og/mixin → glue}/tc_orderable.rb +15 -23
- data/test/glue/tc_orderable2.rb +47 -0
- data/test/glue/tc_revisable.rb +3 -3
- data/test/{og/mixin → glue}/tc_taggable.rb +20 -10
- data/test/{og/mixin → glue}/tc_timestamped.rb +2 -12
- data/test/glue/tc_webfile.rb +36 -0
- data/test/og/CONFIG.rb +8 -11
- data/test/og/multi_validations_model.rb +14 -0
- data/test/og/store/tc_filesys.rb +3 -1
- data/test/og/store/tc_kirby.rb +16 -13
- data/test/og/store/tc_sti.rb +11 -11
- data/test/og/store/tc_sti2.rb +79 -0
- data/test/og/tc_build.rb +41 -0
- data/test/og/tc_cacheable.rb +3 -2
- data/test/og/tc_has_many.rb +96 -0
- data/test/og/tc_inheritance.rb +6 -4
- data/test/og/tc_joins_many.rb +93 -0
- data/test/og/tc_multi_validations.rb +5 -7
- data/test/og/tc_multiple.rb +7 -6
- data/test/og/tc_override.rb +13 -7
- data/test/og/tc_primary_key.rb +30 -0
- data/test/og/tc_relation.rb +8 -14
- data/test/og/tc_reldelete.rb +163 -0
- data/test/og/tc_reverse.rb +17 -14
- data/test/og/tc_scoped.rb +3 -11
- data/test/og/tc_setup.rb +13 -11
- data/test/og/tc_store.rb +21 -28
- data/test/og/tc_validation2.rb +2 -2
- data/test/og/tc_validation_loop.rb +17 -15
- metadata +109 -103
- data/INSTALL +0 -91
- data/ProjectInfo +0 -51
- data/README +0 -177
- data/doc/config.txt +0 -28
- data/examples/README +0 -23
- data/examples/mysql_to_psql.rb +0 -71
- data/examples/run.rb +0 -271
- data/lib/glue/tree.rb +0 -218
- data/lib/og/store/alpha/filesys.rb +0 -110
- data/lib/og/store/alpha/memory.rb +0 -295
- data/lib/og/store/alpha/sqlserver.rb +0 -256
- data/lib/og/store/kirby.rb +0 -490
- data/lib/og/store/mysql.rb +0 -415
- data/lib/og/store/psql.rb +0 -875
- data/lib/og/store/sqlite.rb +0 -348
- data/lib/og/store/sqlite2.rb +0 -241
- data/setup.rb +0 -1585
- data/test/og/tc_sti_find.rb +0 -35
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'og/store/sql/utils'
|
2
|
+
|
3
|
+
module Og
|
4
|
+
|
5
|
+
module PostgresqlUtils
|
6
|
+
include SqlUtils
|
7
|
+
|
8
|
+
def escape(str)
|
9
|
+
return nil unless str
|
10
|
+
return PGconn.escape(str.to_s)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Blobs are actually a lot faster (and use up less
|
14
|
+
# storage) for large data I think, as they need not to be
|
15
|
+
# encoded and decoded. I'd like to have both ;-) BYTEA is
|
16
|
+
# easier to handle than BLOBs, but if you implement BLOBs in a way
|
17
|
+
# that they are transparent to the user (as I did in Ruby/DBI),
|
18
|
+
# I'd prefer that way.
|
19
|
+
|
20
|
+
def blob(val)
|
21
|
+
val.gsub(/[\000-\037\047\134\177-\377]/) do |b|
|
22
|
+
"\\#{ b[0].to_s(8).rjust(3, '0') }"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse_blob(val)
|
27
|
+
return '' unless val
|
28
|
+
|
29
|
+
val.gsub(/\\(\\|'|[0-3][0-7][0-7])/) do |s|
|
30
|
+
if s.size == 2 then s[1,1] else s[1,3].oct.chr end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
begin
|
2
|
+
require 'sqlite3'
|
3
|
+
rescue Object => ex
|
4
|
+
Logger.error 'Ruby-Sqlite3 bindings are not installed!'
|
5
|
+
Logger.error ex
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'fileutils'
|
9
|
+
require 'set'
|
10
|
+
|
11
|
+
require 'og/store/sql'
|
12
|
+
require 'og/adapter/sqlite/override'
|
13
|
+
|
14
|
+
module Og
|
15
|
+
|
16
|
+
# A Store that persists objects into an Sqlite3 database.
|
17
|
+
#
|
18
|
+
# As well as the usual options to the constructor, you can also pass a
|
19
|
+
# :busy_timeout option which defines how quickly to retry a query, should the
|
20
|
+
# database be locked. The default value is 50ms. The retry will currently
|
21
|
+
# continue until successful.
|
22
|
+
#
|
23
|
+
# To read documentation about the methods, consult the documentation
|
24
|
+
# for SqlStore and Store.
|
25
|
+
|
26
|
+
class SqliteAdapter < SqlStore
|
27
|
+
include SqlUtils; extend SqlUtils
|
28
|
+
|
29
|
+
# Initialize the Sqlite store.
|
30
|
+
# This store provides a default name.
|
31
|
+
|
32
|
+
def initialize(options)
|
33
|
+
super
|
34
|
+
@busy_timeout = (options[:busy_timeout] || 50)/1000
|
35
|
+
@conn = SQLite3::Database.new(db_filename(options))
|
36
|
+
end
|
37
|
+
|
38
|
+
def close
|
39
|
+
@conn.close
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
# Override if needed.
|
44
|
+
|
45
|
+
def db_filename(options)
|
46
|
+
options[:name] ||= 'data'
|
47
|
+
if options[:name] == ':memory:'
|
48
|
+
':memory:'
|
49
|
+
else
|
50
|
+
"#{options[:name]}.db"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def destroy_db(options)
|
55
|
+
begin
|
56
|
+
FileUtils.rm(db_filename(options))
|
57
|
+
super
|
58
|
+
rescue Errno::ENOENT => ex # FIXME: Lookup Win32/Linux/BSD error
|
59
|
+
Logger.info "Cannot drop '#{options[:name]}'!"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# The type used for default primary keys.
|
64
|
+
|
65
|
+
def primary_key_type
|
66
|
+
'integer PRIMARY KEY'
|
67
|
+
end
|
68
|
+
|
69
|
+
def query_statement(sql)
|
70
|
+
return query(sql)
|
71
|
+
end
|
72
|
+
|
73
|
+
def exec_statement(sql)
|
74
|
+
query(sql).close()
|
75
|
+
end
|
76
|
+
|
77
|
+
def start
|
78
|
+
@conn.transaction if @transaction_nesting < 1
|
79
|
+
@transaction_nesting += 1
|
80
|
+
end
|
81
|
+
|
82
|
+
def commit
|
83
|
+
@transaction_nesting -= 1
|
84
|
+
@conn.commit if @transaction_nesting < 1
|
85
|
+
end
|
86
|
+
|
87
|
+
def rollback
|
88
|
+
@transaction_nesting -= 1
|
89
|
+
@conn.rollback if @transaction_nesting < 1
|
90
|
+
end
|
91
|
+
|
92
|
+
def sql_update(sql)
|
93
|
+
exec(sql)
|
94
|
+
@conn.changes
|
95
|
+
end
|
96
|
+
|
97
|
+
def last_insert_id(klass = nil)
|
98
|
+
query("SELECT last_insert_rowid()").first_value.to_i
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the Sqlite information of a table within the database or
|
102
|
+
# nil if it doesn't exist. Mostly for internal usage.
|
103
|
+
|
104
|
+
def table_info(table)
|
105
|
+
r = query_statement("SELECT name FROM sqlite_master WHERE type='table' AND name='#{self.class.escape(table.to_s)}'");
|
106
|
+
return r && r.blank? ? nil : r.next
|
107
|
+
end
|
108
|
+
|
109
|
+
# SQLite send back a BusyException if the database is locked.
|
110
|
+
# Currently we keep sending the query until success or the universe implodes.
|
111
|
+
# Note that the SQLite3 ruby library provides a busy_timeout, and busy_handler
|
112
|
+
# facility, but I couldn't get the thing to work.
|
113
|
+
def query(sql)
|
114
|
+
begin
|
115
|
+
return @conn.query(sql)
|
116
|
+
rescue SQLite3::BusyException
|
117
|
+
sleep(@busy_timeout)
|
118
|
+
retry
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# gmosx: any idea how to better test this?
|
125
|
+
|
126
|
+
def table_already_exists_exception?(ex)
|
127
|
+
ex.to_s =~ /table .* already exists/i
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#--
|
2
|
+
# Customize the standard Sqlite3 resultset to make
|
3
|
+
# more compatible with Og.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module SQLite3 # :nodoc: all
|
7
|
+
|
8
|
+
class ResultSet # :nodoc: all
|
9
|
+
alias_method :blank?, :eof?
|
10
|
+
|
11
|
+
def each_row
|
12
|
+
each do |row|
|
13
|
+
yield(row, 0)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def first_value
|
18
|
+
val = self.next[0]
|
19
|
+
close
|
20
|
+
return val
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :fields, :columns
|
24
|
+
end
|
25
|
+
|
26
|
+
class SQLException # :nodoc: all
|
27
|
+
def table_already_exists?
|
28
|
+
# gmosx: any idea how to better test this?
|
29
|
+
self.to_s =~ /table .* already exists/i
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Helper for sqlite scripts.
|
2
|
+
#
|
3
|
+
# === Example
|
4
|
+
#
|
5
|
+
# sqlite 'filename', %{
|
6
|
+
# drop database if exists weblog_development;
|
7
|
+
# create database weblog_development;
|
8
|
+
# grant all on weblog_development.* to #{`id -un`.strip}@localhost;
|
9
|
+
# }
|
10
|
+
|
11
|
+
def sqlite(opts, stream)
|
12
|
+
IO.popen("mysql #{opts}", 'w') do |io|
|
13
|
+
io.puts stream
|
14
|
+
end
|
15
|
+
end
|
data/lib/og/collection.rb
CHANGED
@@ -15,6 +15,14 @@ class Collection
|
|
15
15
|
|
16
16
|
attr_accessor :members
|
17
17
|
|
18
|
+
# When the collection is in building mode or the owner
|
19
|
+
# object is unsaved, added members are accumulated in the
|
20
|
+
# building_memebers array. These relations are serialized
|
21
|
+
# when the owner object is saved (or when save_building_members
|
22
|
+
# is explicitly called).
|
23
|
+
|
24
|
+
attr_accessor :building_members
|
25
|
+
|
18
26
|
# The class of the members of this collection.
|
19
27
|
|
20
28
|
attr_accessor :member_class
|
@@ -114,13 +122,21 @@ class Collection
|
|
114
122
|
|
115
123
|
# Add a new member to the collection.
|
116
124
|
# this method will overwrite any objects already
|
117
|
-
# existing in the collection
|
125
|
+
# existing in the collection.
|
126
|
+
#
|
127
|
+
# If the collection is in build mode or the object
|
128
|
+
# is unsaved, the member is accumulated in a buffer. All
|
129
|
+
# accumulated members relations are saved when the object
|
130
|
+
# is saved.
|
118
131
|
|
119
132
|
def push(obj, options = nil)
|
120
133
|
remove(obj) if members.include?(obj)
|
121
|
-
@members
|
134
|
+
@members << obj
|
122
135
|
unless @building or owner.unsaved?
|
123
136
|
@owner.send(@insert_proc, obj, options)
|
137
|
+
else
|
138
|
+
(@building_members ||= []) << obj
|
139
|
+
@owner.instance_variable_set '@pending_building_collections', true
|
124
140
|
end
|
125
141
|
end
|
126
142
|
alias_method :<<, :push
|
@@ -217,17 +233,32 @@ class Collection
|
|
217
233
|
# Allows to perform a scoped query.
|
218
234
|
|
219
235
|
def find(options = {})
|
236
|
+
tmp = nil
|
220
237
|
@member_class.with_scope(options) do
|
221
|
-
|
238
|
+
tmp = @owner.send(@find_proc, @find_options)
|
222
239
|
end
|
240
|
+
return tmp
|
223
241
|
end
|
224
242
|
|
225
243
|
# Find one object.
|
226
244
|
|
227
|
-
def find_one
|
245
|
+
def find_one options = {}
|
228
246
|
find(options).first
|
229
247
|
end
|
230
248
|
|
249
|
+
# In building mode, relations for this collection are
|
250
|
+
# accumulated in @building_relations. These relations are
|
251
|
+
# saved my calling this method.
|
252
|
+
|
253
|
+
def save_building_members(options = nil)
|
254
|
+
return unless @building_members
|
255
|
+
|
256
|
+
for obj in @building_members
|
257
|
+
@owner.send(@insert_proc, obj, options)
|
258
|
+
end
|
259
|
+
@building_members = nil
|
260
|
+
end
|
261
|
+
|
231
262
|
# Try to execute an accumulator or else
|
232
263
|
# redirect all other methods to the members array.
|
233
264
|
#
|
@@ -252,6 +283,3 @@ private
|
|
252
283
|
end
|
253
284
|
|
254
285
|
end
|
255
|
-
|
256
|
-
# * George Moschovitis <gm@navel.gr>
|
257
|
-
# * Julien Perrot <jperrot@exosec.fr>
|
@@ -3,10 +3,10 @@ require 'fileutils'
|
|
3
3
|
require 'facet/kernel/constant'
|
4
4
|
require 'facet/kernel/assign_with'
|
5
5
|
|
6
|
-
# $DBG = true
|
7
|
-
|
8
6
|
module Og
|
9
7
|
|
8
|
+
# Add import/export functionality to the Og Manager.
|
9
|
+
|
10
10
|
class Manager
|
11
11
|
|
12
12
|
# Dump Og managed objects to the filesystem.
|
@@ -23,7 +23,7 @@ class Manager
|
|
23
23
|
File.open("#{basedir}/#{c}.yml", 'w') { |f| f << all.to_yaml }
|
24
24
|
end
|
25
25
|
end
|
26
|
-
|
26
|
+
alias export dump
|
27
27
|
|
28
28
|
# Load Og managed objects from the filesystem. This method can apply
|
29
29
|
# optional transformation rules in order to evolve a schema.
|
@@ -70,8 +70,7 @@ class Manager
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
end
|
73
|
-
|
74
|
-
alias_method :evolve, :load
|
73
|
+
alias import load
|
75
74
|
|
76
75
|
end
|
77
76
|
|
data/lib/og/entity.rb
CHANGED
@@ -1,18 +1,15 @@
|
|
1
1
|
require 'facets/core/module/class_extension'
|
2
|
-
require '
|
2
|
+
require 'facets/core/kernel/assign_with'
|
3
3
|
|
4
|
-
require 'glue/property'
|
5
4
|
require 'og/relation'
|
6
5
|
require 'og/ez/clause'
|
7
6
|
require 'og/ez/condition'
|
8
7
|
|
9
|
-
require 'rexml/document' # for to_xml
|
10
|
-
|
11
8
|
module Og
|
12
9
|
|
13
10
|
# Include this module to classes to make them managable by Og.
|
14
11
|
#--
|
15
|
-
# gmosx,
|
12
|
+
# gmosx, WARNING: If you change the methods here, don't
|
16
13
|
# forget to update the Cacheable overrides.
|
17
14
|
#++
|
18
15
|
|
@@ -33,8 +30,8 @@ module EntityMixin
|
|
33
30
|
def save(options = nil)
|
34
31
|
self.class.ogmanager.store.save(self, options)
|
35
32
|
end
|
36
|
-
|
37
|
-
|
33
|
+
alias save! save
|
34
|
+
alias validate_and_save save
|
38
35
|
|
39
36
|
# Force saving of the objects, even if the validations
|
40
37
|
# don't pass.
|
@@ -56,24 +53,60 @@ module EntityMixin
|
|
56
53
|
self.class.ogmanager.store.update(self, options)
|
57
54
|
end
|
58
55
|
|
59
|
-
def
|
60
|
-
self.class.ogmanager.store.update(self, :only =>
|
56
|
+
def update_attributes(*attrs)
|
57
|
+
self.class.ogmanager.store.update(self, :only => attrs)
|
61
58
|
end
|
62
|
-
|
63
|
-
|
59
|
+
alias update_attribute update_attributes
|
60
|
+
alias aupdate update_attributes
|
61
|
+
# For backwards compatibility, deprecated.
|
62
|
+
alias update_properties update_attributes
|
63
|
+
alias update_property update_attributes
|
64
64
|
|
65
65
|
def update_by_sql(set)
|
66
66
|
self.class.ogmanager.store.update_by_sql(self, set)
|
67
67
|
end
|
68
|
-
|
69
|
-
|
68
|
+
alias update_sql update_by_sql
|
69
|
+
alias supdate update_by_sql
|
70
|
+
|
71
|
+
# Set attributes (update + save).
|
72
|
+
#
|
73
|
+
# === Examples
|
74
|
+
#
|
75
|
+
# a = Article[oid]
|
76
|
+
# a.set_attributes :accepted => true, :update_time => Time.now
|
77
|
+
# a.set_attribute :accepted => true
|
78
|
+
#
|
79
|
+
#--
|
80
|
+
# gmosx, THINK: maybe make this the default behaviour of
|
81
|
+
# update_attributes?
|
82
|
+
#++
|
83
|
+
|
84
|
+
def set_attributes(attrs = {})
|
85
|
+
for a, val in attrs
|
86
|
+
instance_variable_set "@#{a}", val
|
87
|
+
end
|
88
|
+
update_attributes(*attrs.keys)
|
89
|
+
end
|
90
|
+
alias set_attribute set_attributes
|
91
|
+
|
92
|
+
# Set attribute (like instance_variable_set)
|
93
|
+
#
|
94
|
+
# === Example
|
95
|
+
#
|
96
|
+
# a = Article[oid]
|
97
|
+
# a.instance_attribute_set :accepted, true
|
98
|
+
|
99
|
+
def instance_attribute_set(a, val)
|
100
|
+
instance_variable_set "@#{a}", val
|
101
|
+
update_attribute(a.to_sym)
|
102
|
+
end
|
70
103
|
|
71
104
|
# Reload this entity instance from the store.
|
72
105
|
|
73
106
|
def reload
|
74
107
|
self.class.ogmanager.store.reload(self, self.pk)
|
75
108
|
end
|
76
|
-
|
109
|
+
alias reload! reload
|
77
110
|
|
78
111
|
# Delete this entity instance from the store.
|
79
112
|
|
@@ -93,11 +126,11 @@ module EntityMixin
|
|
93
126
|
end
|
94
127
|
alias_method :serialized?, :saved?
|
95
128
|
|
96
|
-
def
|
97
|
-
|
129
|
+
def assign_attributes(values, options = {})
|
130
|
+
AttributeUtils.populate_object(self, values, options)
|
98
131
|
return self
|
99
132
|
end
|
100
|
-
alias_method :assign, :
|
133
|
+
alias_method :assign, :assign_attributes
|
101
134
|
|
102
135
|
# Returns a symbol => value hash of the object's
|
103
136
|
# properties.
|
@@ -109,36 +142,37 @@ module EntityMixin
|
|
109
142
|
end
|
110
143
|
end
|
111
144
|
|
112
|
-
|
113
|
-
|
114
|
-
|
145
|
+
# Save all building collections. Transparently called
|
146
|
+
# when saving an object, allows efficient object relationship
|
147
|
+
# setup. Example:
|
148
|
+
#
|
149
|
+
# a = Article.new
|
150
|
+
# a.categories << c1
|
151
|
+
# a.categories << c2
|
152
|
+
# a.tags << t1
|
153
|
+
# a.tags << t2
|
154
|
+
# a.save
|
155
|
+
#
|
156
|
+
#--
|
157
|
+
# TODO: at the moment, this only handles collection relations.
|
158
|
+
# Should handle belongs_to/refers_to as well.
|
159
|
+
#++
|
115
160
|
|
116
|
-
def
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
def to_rexml
|
125
|
-
xml = REXML::Element.new(self.class.to_s.downcase)
|
126
|
-
xml.add_attribute("oid", self.oid.to_s)
|
127
|
-
self.class.properties.keys.each do |key|
|
128
|
-
xml << REXML::Element.new(key.to_s).add_text(self.send(key).to_s) unless key == :oid
|
161
|
+
def save_building_collections(options = nil)
|
162
|
+
if @pending_building_collections
|
163
|
+
for rel in self.class.relations
|
164
|
+
next unless rel.class.ann.self.collection?
|
165
|
+
collection = send(rel.name.to_s)
|
166
|
+
collection.save_building_members
|
167
|
+
end
|
168
|
+
@pending_building_collections = false
|
129
169
|
end
|
130
|
-
return xml
|
131
|
-
end
|
132
|
-
alias_method :to_xml_dom, :to_rexml
|
133
|
-
|
134
|
-
# convert Og object to an XML-String
|
135
|
-
# example usage:
|
136
|
-
# User[1].to_xml
|
137
|
-
|
138
|
-
def to_xml
|
139
|
-
self.to_rexml.to_s
|
140
170
|
end
|
141
171
|
|
172
|
+
def og_quote(obj)
|
173
|
+
self.class.ogmanager.store.quote(obj)
|
174
|
+
end
|
175
|
+
|
142
176
|
include RelationDSL
|
143
177
|
|
144
178
|
class_extension do
|
@@ -161,16 +195,25 @@ module EntityMixin
|
|
161
195
|
return obj
|
162
196
|
end
|
163
197
|
|
164
|
-
def
|
165
|
-
|
198
|
+
def assign_attributes(values, options = {})
|
199
|
+
AttributeUtils.populate_object(self.new, values, options)
|
166
200
|
end
|
167
|
-
alias_method :assign, :
|
201
|
+
alias_method :assign, :assign_attributes
|
168
202
|
|
169
203
|
# Load an instance of this Entity class using the primary
|
170
|
-
# key.
|
204
|
+
# key. If the class defines a text key, this string key
|
205
|
+
# may optionally be used!
|
171
206
|
|
172
207
|
def load(pk)
|
173
|
-
|
208
|
+
# gmosx: leave the checks in this order (optimized)
|
209
|
+
if pk.to_i == 0 and (key = ann.self[:text_key])
|
210
|
+
# A string is passed as pk, try to use it as a
|
211
|
+
# tesxt key too.
|
212
|
+
send("find_by_#{key}", pk)
|
213
|
+
else
|
214
|
+
# A valid pk is always > 0
|
215
|
+
ogmanager.store.load(pk, self)
|
216
|
+
end
|
174
217
|
end
|
175
218
|
alias_method :[], :load
|
176
219
|
alias_method :exist?, :load
|
@@ -375,14 +418,7 @@ module EntityMixin
|
|
375
418
|
# Returns the primary key for this class.
|
376
419
|
|
377
420
|
def primary_key
|
378
|
-
|
379
|
-
# nasty bug in the current facets version.
|
380
|
-
pk = ann.self.primary_key
|
381
|
-
if pk.nil?
|
382
|
-
pk = Entity.resolve_primary_key(self)
|
383
|
-
ann :self, :primary_key => pk
|
384
|
-
end
|
385
|
-
return pk
|
421
|
+
Entity.resolve_primary_key(self)
|
386
422
|
end
|
387
423
|
|
388
424
|
# Set the default find options for this entity.
|
@@ -446,8 +482,8 @@ module EntityMixin
|
|
446
482
|
# Set the primary key.
|
447
483
|
|
448
484
|
def set_primary_key(pk, pkclass = Fixnum)
|
449
|
-
|
450
|
-
ann
|
485
|
+
self.ann[pk].primary_key = true
|
486
|
+
self.ann[pk].class ||= pkclass
|
451
487
|
end
|
452
488
|
|
453
489
|
# Is this entity a polymorphic parent?
|
@@ -615,9 +651,8 @@ private
|
|
615
651
|
field_name = relation.foreign_key
|
616
652
|
value = value.send(relation.target_class.primary_key.symbol)
|
617
653
|
else
|
618
|
-
|
619
|
-
|
620
|
-
properties[name.to_sym][:symbol]
|
654
|
+
anno = ann(name.to_sym)
|
655
|
+
field_name = anno[:field] || anno[:name] || name.to_sym
|
621
656
|
end
|
622
657
|
options["#{name}_op".to_sym] = 'IN' if value.is_a?(Array)
|
623
658
|
%|#{field_name} #{options.delete("#{name}_op".to_sym) || '='} #{ogmanager.store.quote(value)}|
|
@@ -645,23 +680,18 @@ class Entity
|
|
645
680
|
class << self
|
646
681
|
|
647
682
|
def resolve_primary_key(klass)
|
648
|
-
# Is the class annotated with a primary key?
|
649
|
-
|
650
|
-
if pk = klass.ann.self[:primary_key]
|
651
|
-
return pk
|
652
|
-
end
|
653
|
-
|
654
683
|
# Search the properties, try to find one annotated as primary_key.
|
655
684
|
|
656
|
-
for
|
657
|
-
|
658
|
-
|
685
|
+
for a in klass.attributes
|
686
|
+
anno = klass.ann(a)
|
687
|
+
if anno.primary_key?
|
688
|
+
return a
|
659
689
|
end
|
660
690
|
end
|
661
691
|
|
662
692
|
# The default primary key is oid.
|
663
693
|
|
664
|
-
return
|
694
|
+
return :oid
|
665
695
|
end
|
666
696
|
|
667
697
|
# Converts a string into it's corresponding class. Added to support STI.
|
@@ -673,7 +703,7 @@ class Entity
|
|
673
703
|
|
674
704
|
def entity_from_string(str)
|
675
705
|
res = nil
|
676
|
-
Og
|
706
|
+
Og.manager.managed_classes.each do |klass|
|
677
707
|
if klass.name == str
|
678
708
|
res = klass
|
679
709
|
break
|
@@ -681,110 +711,9 @@ class Entity
|
|
681
711
|
end
|
682
712
|
res
|
683
713
|
end
|
684
|
-
|
685
|
-
# Entity copying support. Eventually this should all
|
686
|
-
# be eval'd in at enchanting stage for the minor
|
687
|
-
# speed increase.
|
688
|
-
# TODO: Convert to enchantments on objects
|
689
|
-
|
690
|
-
# Accepts source object, destination and ignore.
|
691
|
-
# Source and destination are self explanatory; ignore
|
692
|
-
# is a list of properties not to copy (i.e.
|
693
|
-
# :create_time,:update_time).
|
694
|
-
# By default sets the class variables directly on the
|
695
|
-
# remote model instance, if you set use_setter_method to
|
696
|
-
# true, uses create_time= style copying tactics,
|
697
|
-
|
698
|
-
def copy_properties(source, destination, ignore = [], use_setter_method = false)
|
699
|
-
property_copier(source, destination, ignore, use_setter_method, false)
|
700
|
-
end
|
701
|
-
|
702
|
-
# Copies relations of one record to another. Only copies
|
703
|
-
# has_one, refers_to, belongs_to relationships as
|
704
|
-
# has_many requires modifying of other objects and
|
705
|
-
# cannot be copied (by design). If you think you need to copy
|
706
|
-
# these relations, what you need is a joins_many relationship
|
707
|
-
# which can be copied.
|
708
|
-
|
709
|
-
def copy_inferior_relations(source, destination, ignore = [])
|
710
|
-
real_ignore = Array.new
|
711
|
-
|
712
|
-
# Map relation symbols to foreign keys.
|
713
|
-
|
714
|
-
ignore.each do |symbol|
|
715
|
-
source.class.relations.reject{|r| [Og::JoinsMany, Og::ManyToMany, Og::HasMany].include?(r.class)}.each do |relation|
|
716
|
-
if relation.name == symbol.to_s
|
717
|
-
real_ignore << relation.foreign_key.to_sym
|
718
|
-
break
|
719
|
-
end
|
720
|
-
end
|
721
|
-
end
|
722
|
-
|
723
|
-
# Use instance variable property copier method.
|
724
|
-
|
725
|
-
property_copier(source, destination, real_ignore, false, true)
|
726
|
-
end
|
727
|
-
|
728
|
-
def copy_equal_relations(source, destination, ignore = [])
|
729
|
-
source.class.relations.reject{|r| not [Og::JoinsMany, Og::ManyToMany].include?(r.class)}.each do |relation|
|
730
|
-
next if relation.name == nil or ignore.include?(relation.name)
|
731
|
-
source.send(relation.name).each do |related|
|
732
|
-
destination.send(relation.name).send(:<<, related)
|
733
|
-
end
|
734
|
-
end
|
735
|
-
end
|
736
|
-
|
737
|
-
# Copies all relations *except* HasMany which is impossible
|
738
|
-
# to copy. Use a JoinsMany relation instead if you need a
|
739
|
-
# copyable HasMany (which is irrational).
|
740
|
-
|
741
|
-
def copy_relations(source, destination, ignore = [])
|
742
|
-
copy_inferior_relations(source, destination, ignore)
|
743
|
-
copy_equal_relations(source, destination, ignore)
|
744
|
-
end
|
745
|
-
|
746
|
-
# Clones an object in every possible way (cannot copy
|
747
|
-
# HasMany but can copy all others - BelongsTo, etc).
|
748
|
-
# Provide a source object as first arguments, the rest
|
749
|
-
# (if any) are passed along to the initialize constructor
|
750
|
-
# when calling new to make the copied object.
|
751
|
-
|
752
|
-
def clone(source,*args)
|
753
|
-
destination = source.class.new(*args)
|
754
|
-
copy_properties(source, destination, [], false)
|
755
|
-
# Must save here to copy join tables.
|
756
|
-
destination.save!
|
757
|
-
copy_relations(source, destination, [])
|
758
|
-
destination.save!
|
759
|
-
destination
|
760
|
-
end
|
761
|
-
|
762
|
-
# Does the work of clone_properties and copy_inferior_relations.
|
763
|
-
# Syntax is the same with one extra field to tell the
|
764
|
-
# routine what it is copying.
|
765
|
-
|
766
|
-
def property_copier(source,destination,ignore,use_setter_method,relations)
|
767
|
-
primary_key_symbol = source.class.primary_key.symbol
|
768
|
-
source.class.properties.to_a.each do |symbol, property|
|
769
|
-
next if primary_key_symbol == symbol or ignore.include?(symbol) or
|
770
|
-
(relations and not property.relation) or (not relations and property.relation)
|
771
|
-
|
772
|
-
variable = "@#{symbol}"
|
773
|
-
if use_setter_method
|
774
|
-
destination.send("#{symbol}=".to_sym,source.instance_variable_get(variable))
|
775
|
-
else
|
776
|
-
destination.instance_variable_set(variable, source.instance_variable_get(variable))
|
777
|
-
end
|
778
|
-
end
|
779
|
-
end
|
780
714
|
|
781
715
|
end
|
782
716
|
|
783
717
|
end
|
784
718
|
|
785
719
|
end
|
786
|
-
|
787
|
-
# * George Moschovitis <gm@navel.gr>
|
788
|
-
# * Tom Sawyer <transfire@gmail.com>
|
789
|
-
# * Rob Pitt <rob@motionpath.com>
|
790
|
-
# * Fabian Buch <fabian@fabian-buch.de>
|