og 0.31.0 → 0.40.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/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>
|