lafcadio 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/lafcadio_schema +28 -26
- data/lib/lafcadio.rb +3 -3
- data/lib/lafcadio.rb~ +1 -1
- data/lib/lafcadio/domain.rb +29 -35
- data/lib/lafcadio/domain.rb~ +35 -42
- data/lib/lafcadio/mock.rb +37 -15
- data/lib/lafcadio/mock.rb~ +59 -36
- data/lib/lafcadio/objectField.rb +31 -20
- data/lib/lafcadio/objectField.rb~ +163 -142
- data/lib/lafcadio/objectStore.rb +125 -58
- data/lib/lafcadio/objectStore.rb~ +242 -177
- data/lib/lafcadio/query.rb +98 -95
- data/lib/lafcadio/query.rb~ +96 -95
- data/lib/lafcadio/schema.rb +4 -4
- data/lib/lafcadio/schema.rb~ +13 -17
- data/lib/lafcadio/test.rb +33 -7
- data/lib/lafcadio/test.rb~ +146 -20
- data/lib/lafcadio/util.rb +27 -11
- data/lib/lafcadio/util.rb~ +27 -43
- metadata +3 -7
- data/lib/lafcadio/dateTime.rb~ +0 -93
- data/lib/lafcadio/depend.rb~ +0 -8
- data/lib/lafcadio/objectStore.rb.~1.64.~ +0 -766
- data/lib/lafcadio/test/testconfig.dat~ +0 -13
data/lib/lafcadio/util.rb~
CHANGED
@@ -14,27 +14,41 @@ module Lafcadio
|
|
14
14
|
# dbname:lafcadio_test
|
15
15
|
# dbhost:localhost
|
16
16
|
class LafcadioConfig < Hash
|
17
|
-
@@
|
18
|
-
@@
|
19
|
-
|
20
|
-
def self.set_filename(filename); @@filename = filename; end
|
17
|
+
@@value_hash = {}
|
18
|
+
@@instances = []
|
21
19
|
|
22
|
-
def self.
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
def self.[]=( k, v )
|
21
|
+
@@value_hash[k] = v
|
22
|
+
@@instances.each do |instance| instance[k] = v; end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.new
|
26
|
+
inst = super
|
27
|
+
@@instances << inst
|
28
|
+
inst
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.set_filename(filename)
|
32
|
+
@@value_hash = {}
|
33
|
+
if filename
|
34
|
+
File.new( filename ).each_line { |line|
|
29
35
|
line.chomp =~ /^(.*?):(.*)$/
|
30
36
|
self[$1] = $2
|
31
37
|
}
|
32
38
|
end
|
33
39
|
end
|
34
|
-
|
40
|
+
|
41
|
+
def self.set_values( value_hash )
|
42
|
+
@@value_hash = ( value_hash.nil? ? {} : value_hash )
|
43
|
+
ObjectStore.db_type = @@value_hash['dbtype'] if @@value_hash['dbtype']
|
44
|
+
end
|
35
45
|
|
36
|
-
|
46
|
+
def initialize
|
47
|
+
@@value_hash.each { |key, value| self[key] = value }
|
48
|
+
end
|
37
49
|
end
|
50
|
+
|
51
|
+
class MissingError < RuntimeError; end
|
38
52
|
end
|
39
53
|
|
40
54
|
class String
|
@@ -47,36 +61,6 @@ class String
|
|
47
61
|
|
48
62
|
end
|
49
63
|
|
50
|
-
# Returns the number of times that <tt>regexp</tt> occurs in the string.
|
51
|
-
def count_occurrences(regexp)
|
52
|
-
count = 0
|
53
|
-
str = self.clone
|
54
|
-
while str =~ regexp
|
55
|
-
count += 1
|
56
|
-
str = $'
|
57
|
-
end
|
58
|
-
count
|
59
|
-
end
|
60
|
-
|
61
|
-
# Decapitalizes the first letter of the string, or decapitalizes the
|
62
|
-
# entire string if it's all capitals.
|
63
|
-
#
|
64
|
-
# 'InternalClient'.decapitalize -> "internalClient"
|
65
|
-
# 'SKU'.decapitalize -> "sku"
|
66
|
-
def decapitalize
|
67
|
-
string = clone
|
68
|
-
firstLetter = string[0..0].downcase
|
69
|
-
string = firstLetter + string[1..string.length]
|
70
|
-
newString = ""
|
71
|
-
while string =~ /([A-Z])([^a-z]|$)/
|
72
|
-
newString += $`
|
73
|
-
newString += $1.downcase
|
74
|
-
string = $2 + $'
|
75
|
-
end
|
76
|
-
newString += string
|
77
|
-
newString
|
78
|
-
end
|
79
|
-
|
80
64
|
# Left-pads a string with +fillChar+ up to +size+ size.
|
81
65
|
#
|
82
66
|
# "a".pad( 10, "+") -> "+++++++++a"
|
metadata
CHANGED
@@ -3,15 +3,15 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: lafcadio
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.9.
|
7
|
-
date: 2006-
|
6
|
+
version: 0.9.3
|
7
|
+
date: 2006-03-21 00:00:00 -08:00
|
8
8
|
summary: Lafcadio is an object-relational mapping layer
|
9
9
|
require_paths:
|
10
10
|
- lib
|
11
11
|
email: sera@fhwang.net
|
12
12
|
homepage: http://lafcadio.rubyforge.org/
|
13
13
|
rubyforge_project:
|
14
|
-
description: Lafcadio is an object-relational mapping layer for Ruby
|
14
|
+
description: Lafcadio is an object-relational mapping layer for Ruby. It lets you treat database rows like first-class Ruby objects, minimizing the amount of time you have to spend thinking about database vagaries so you can spend more time thinking about your program's logic. It also offers extensive support for unit-testing complex database logic without running cumbersome setup scripts. It currently supports MySQL and PostgreSQL.
|
15
15
|
autorequire: lafcadio
|
16
16
|
default_executable:
|
17
17
|
bindir: bin
|
@@ -31,9 +31,7 @@ files:
|
|
31
31
|
- lib/lafcadio
|
32
32
|
- lib/lafcadio.rb
|
33
33
|
- lib/lafcadio.rb~
|
34
|
-
- lib/lafcadio/dateTime.rb~
|
35
34
|
- lib/lafcadio/depend.rb
|
36
|
-
- lib/lafcadio/depend.rb~
|
37
35
|
- lib/lafcadio/domain.rb
|
38
36
|
- lib/lafcadio/domain.rb~
|
39
37
|
- lib/lafcadio/mock.rb
|
@@ -41,7 +39,6 @@ files:
|
|
41
39
|
- lib/lafcadio/objectField.rb
|
42
40
|
- lib/lafcadio/objectField.rb~
|
43
41
|
- lib/lafcadio/objectStore.rb
|
44
|
-
- lib/lafcadio/objectStore.rb.~1.64.~
|
45
42
|
- lib/lafcadio/objectStore.rb~
|
46
43
|
- lib/lafcadio/query.rb
|
47
44
|
- lib/lafcadio/query.rb~
|
@@ -53,7 +50,6 @@ files:
|
|
53
50
|
- lib/lafcadio/util.rb
|
54
51
|
- lib/lafcadio/util.rb~
|
55
52
|
- lib/lafcadio/test/testconfig.dat
|
56
|
-
- lib/lafcadio/test/testconfig.dat~
|
57
53
|
test_files: []
|
58
54
|
|
59
55
|
rdoc_options: []
|
data/lib/lafcadio/dateTime.rb~
DELETED
@@ -1,93 +0,0 @@
|
|
1
|
-
module Lafcadio
|
2
|
-
# Represents a specific month in time. With the exception of
|
3
|
-
# Month.month_names (which returns a zero-based array), every usage of the
|
4
|
-
# month value assumes that 1 equals January and 12 equals December.
|
5
|
-
class Month
|
6
|
-
# Returns an array of the full names of months (in English). Note that
|
7
|
-
# "January" is the 0th element, and "December" is the 11th element.
|
8
|
-
def Month.month_names
|
9
|
-
[ "January", "February", "March", "April", "May", "June", "July",
|
10
|
-
"August", "September", "October", "November", "December" ]
|
11
|
-
end
|
12
|
-
|
13
|
-
include Comparable
|
14
|
-
|
15
|
-
attr_reader :month, :year
|
16
|
-
|
17
|
-
# A new month can be set to a specific +month+ and +year+, or you can call
|
18
|
-
# Month.new with no arguments to receive the current month.
|
19
|
-
def initialize( year = nil, month = nil )
|
20
|
-
require 'date'
|
21
|
-
if month.nil? || year.nil?
|
22
|
-
date = Date.today
|
23
|
-
month = date.mon unless month
|
24
|
-
year = date.year unless year
|
25
|
-
end
|
26
|
-
fail "invalid month" if month < 1 || month > 12
|
27
|
-
@month = month
|
28
|
-
@year = year
|
29
|
-
end
|
30
|
-
|
31
|
-
# Returns a new Month that is +amountToAdd+ months later.
|
32
|
-
def +( amountToAdd )
|
33
|
-
( fullYears, remainingMonths ) = amountToAdd.divmod( 12 )
|
34
|
-
resultYear = @year + fullYears
|
35
|
-
resultMonth = @month + remainingMonths
|
36
|
-
if resultMonth > 12
|
37
|
-
resultMonth -= 12
|
38
|
-
resultYear += 1
|
39
|
-
end
|
40
|
-
Month.new( resultYear, resultMonth )
|
41
|
-
end
|
42
|
-
|
43
|
-
# Returns a new Month that is +amountToSubtract+ months earlier.
|
44
|
-
def -(amountToSubtract)
|
45
|
-
self + (-amountToSubtract)
|
46
|
-
end
|
47
|
-
|
48
|
-
# Compare this Month to another Month.
|
49
|
-
def <=>(anOther)
|
50
|
-
if @year == anOther.year
|
51
|
-
@month <=> anOther.month
|
52
|
-
else
|
53
|
-
@year <=> anOther.year
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
# Returns the last Date of the month.
|
58
|
-
def end_date
|
59
|
-
self.next.start_date - 1
|
60
|
-
end
|
61
|
-
|
62
|
-
# Is this Month equal to +anOther+? +anOther+ must be another Month of the
|
63
|
-
# same value.
|
64
|
-
def eql?(anOther)
|
65
|
-
self == anOther
|
66
|
-
end
|
67
|
-
|
68
|
-
# Calculate a hash value for this Month.
|
69
|
-
def hash
|
70
|
-
"#{@year}#{@month}".to_i
|
71
|
-
end
|
72
|
-
|
73
|
-
# Returns the next Month.
|
74
|
-
def next
|
75
|
-
self + 1
|
76
|
-
end
|
77
|
-
|
78
|
-
# Returns the previous Month.
|
79
|
-
def prev
|
80
|
-
self - 1
|
81
|
-
end
|
82
|
-
|
83
|
-
# Returns the first Date of the month.
|
84
|
-
def start_date
|
85
|
-
Date.new( @year, @month, 1 )
|
86
|
-
end
|
87
|
-
|
88
|
-
# Returns a string of the format "January 2001".
|
89
|
-
def to_s
|
90
|
-
Month.month_names[@month-1][0..2] + " " + @year.to_s
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
data/lib/lafcadio/depend.rb~
DELETED
@@ -1,766 +0,0 @@
|
|
1
|
-
require 'dbi'
|
2
|
-
require 'lafcadio/domain'
|
3
|
-
require 'lafcadio/query'
|
4
|
-
require 'lafcadio/util'
|
5
|
-
|
6
|
-
module Lafcadio
|
7
|
-
class Committer #:nodoc:
|
8
|
-
INSERT = 1
|
9
|
-
UPDATE = 2
|
10
|
-
DELETE = 3
|
11
|
-
|
12
|
-
attr_reader :commit_type, :db_object
|
13
|
-
|
14
|
-
def initialize(db_object, dbBridge)
|
15
|
-
@db_object = db_object
|
16
|
-
@dbBridge = dbBridge
|
17
|
-
@objectStore = ObjectStore.get_object_store
|
18
|
-
@commit_type = nil
|
19
|
-
end
|
20
|
-
|
21
|
-
def execute
|
22
|
-
@db_object.verify if LafcadioConfig.new()['checkFields'] == 'onCommit'
|
23
|
-
set_commit_type
|
24
|
-
@db_object.last_commit = get_last_commit
|
25
|
-
@db_object.pre_commit_trigger
|
26
|
-
update_dependent_domain_objects if @db_object.delete
|
27
|
-
@dbBridge.commit @db_object
|
28
|
-
unless @db_object.pk_id
|
29
|
-
@db_object.pk_id = @dbBridge.last_pk_id_inserted
|
30
|
-
end
|
31
|
-
@db_object.post_commit_trigger
|
32
|
-
end
|
33
|
-
|
34
|
-
def get_last_commit
|
35
|
-
if @db_object.delete
|
36
|
-
DomainObject::COMMIT_DELETE
|
37
|
-
elsif @db_object.pk_id
|
38
|
-
DomainObject::COMMIT_EDIT
|
39
|
-
else
|
40
|
-
DomainObject::COMMIT_ADD
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def set_commit_type
|
45
|
-
if @db_object.delete
|
46
|
-
@commit_type = DELETE
|
47
|
-
elsif @db_object.pk_id
|
48
|
-
@commit_type = UPDATE
|
49
|
-
else
|
50
|
-
@commit_type = INSERT
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def update_dependent_domain_objects
|
55
|
-
dependent_classes = @db_object.domain_class.dependent_classes
|
56
|
-
dependent_classes.keys.each { |aClass|
|
57
|
-
field = dependent_classes[aClass]
|
58
|
-
collection = @objectStore.get_filtered( aClass.name, @db_object,
|
59
|
-
field.name )
|
60
|
-
collection.each { |dependentObject|
|
61
|
-
if field.delete_cascade
|
62
|
-
dependentObject.delete = true
|
63
|
-
else
|
64
|
-
dependentObject.send( field.name + '=', nil )
|
65
|
-
end
|
66
|
-
@objectStore.commit(dependentObject)
|
67
|
-
}
|
68
|
-
}
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
class CouldntMatchDomainClassError < RuntimeError #:nodoc:
|
73
|
-
end
|
74
|
-
|
75
|
-
class DbBridge #:nodoc:
|
76
|
-
@@dbh = nil
|
77
|
-
@@last_pk_id_inserted = nil
|
78
|
-
|
79
|
-
def self._load(aString)
|
80
|
-
aString =~ /dbh:/
|
81
|
-
dbString = $'
|
82
|
-
begin
|
83
|
-
dbh = Marshal.load(dbString)
|
84
|
-
rescue TypeError
|
85
|
-
dbh = nil
|
86
|
-
end
|
87
|
-
new dbh
|
88
|
-
end
|
89
|
-
|
90
|
-
def initialize
|
91
|
-
@db_conn = DbConnection.get_db_connection
|
92
|
-
ObjectSpace.define_finalizer( self, proc { |id|
|
93
|
-
DbConnection.get_db_connection.disconnect
|
94
|
-
} )
|
95
|
-
end
|
96
|
-
|
97
|
-
def _dump(aDepth)
|
98
|
-
dbDump = @dbh.respond_to?( '_dump' ) ? @dbh._dump : @dbh.class.to_s
|
99
|
-
"dbh:#{dbDump}"
|
100
|
-
end
|
101
|
-
|
102
|
-
def commit(db_object)
|
103
|
-
sqlMaker = DomainObjectSqlMaker.new(db_object)
|
104
|
-
sqlMaker.sql_statements.each { |sql, binds| execute_commit( sql, binds ) }
|
105
|
-
if sqlMaker.sql_statements[0].first =~ /insert/
|
106
|
-
sql = 'select last_insert_id()'
|
107
|
-
result = execute_select( sql )
|
108
|
-
@@last_pk_id_inserted = result[0]['last_insert_id()'].to_i
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def execute_commit( sql, binds ); @db_conn.do( sql, *binds ); end
|
113
|
-
|
114
|
-
def execute_select(sql)
|
115
|
-
maybe_log sql
|
116
|
-
begin
|
117
|
-
@db_conn.select_all( sql )
|
118
|
-
rescue DBI::DatabaseError => e
|
119
|
-
raise $!.to_s + ": #{ e.errstr }"
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def get_collection_by_query(query)
|
124
|
-
domain_class = query.domain_class
|
125
|
-
execute_select( query.to_sql ).collect { |row_hash|
|
126
|
-
domain_class.new( SqlValueConverter.new( domain_class, row_hash ) )
|
127
|
-
}
|
128
|
-
end
|
129
|
-
|
130
|
-
def group_query( query )
|
131
|
-
execute_select( query.to_sql )[0].collect { |val|
|
132
|
-
if query.field_name != query.domain_class.sql_primary_key_name
|
133
|
-
a_field = query.domain_class.get_field( query.field_name )
|
134
|
-
a_field.value_from_sql( val )
|
135
|
-
else
|
136
|
-
val.to_i
|
137
|
-
end
|
138
|
-
}
|
139
|
-
end
|
140
|
-
|
141
|
-
def last_pk_id_inserted; @@last_pk_id_inserted; end
|
142
|
-
|
143
|
-
def maybe_log(sql)
|
144
|
-
config = LafcadioConfig.new
|
145
|
-
if config['logSql'] == 'y'
|
146
|
-
sqllog = Log4r::Logger['sql'] || Log4r::Logger.new( 'sql' )
|
147
|
-
filename = File.join( config['logdir'], config['sqlLogFile'] || 'sql' )
|
148
|
-
outputter = Log4r::FileOutputter.new( 'outputter',
|
149
|
-
{ :filename => filename } )
|
150
|
-
sqllog.outputters = outputter
|
151
|
-
sqllog.info sql
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
class DbConnection < ContextualService
|
157
|
-
@@connectionClass = DBI
|
158
|
-
@@db_name = nil
|
159
|
-
@@dbh = nil
|
160
|
-
|
161
|
-
def self.flush
|
162
|
-
DbConnection.set_db_connection( nil )
|
163
|
-
@@dbh = nil
|
164
|
-
end
|
165
|
-
|
166
|
-
def self.set_connection_class( aClass ); @@connectionClass = aClass; end
|
167
|
-
|
168
|
-
def self.set_db_name( db_name ); @@db_name = db_name; end
|
169
|
-
|
170
|
-
def self.set_dbh( dbh ); @@dbh = dbh; end
|
171
|
-
|
172
|
-
def initialize
|
173
|
-
@@dbh = load_new_dbh if @@dbh.nil?
|
174
|
-
@dbh = @@dbh
|
175
|
-
end
|
176
|
-
|
177
|
-
def disconnect; @dbh.disconnect if @dbh; end
|
178
|
-
|
179
|
-
def load_new_dbh
|
180
|
-
config = LafcadioConfig.new
|
181
|
-
dbName = @@db_name || config['dbname']
|
182
|
-
dbAndHost = nil
|
183
|
-
if dbName && config['dbhost']
|
184
|
-
dbAndHost = "dbi:Mysql:#{ dbName }:#{ config['dbhost'] }"
|
185
|
-
else
|
186
|
-
dbAndHost = "dbi:#{config['dbconn']}"
|
187
|
-
end
|
188
|
-
@@dbh = @@connectionClass.connect( dbAndHost, config['dbuser'],
|
189
|
-
config['dbpassword'] )
|
190
|
-
end
|
191
|
-
|
192
|
-
def method_missing( symbol, *args )
|
193
|
-
@dbh.send( symbol, *args )
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
class DomainObjectInitError < RuntimeError #:nodoc:
|
198
|
-
attr_reader :messages
|
199
|
-
|
200
|
-
def initialize(messages)
|
201
|
-
@messages = messages
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
class DomainObjectNotFoundError < RuntimeError #:nodoc:
|
206
|
-
end
|
207
|
-
|
208
|
-
# The DomainObjectProxy is used when retrieving domain objects that are
|
209
|
-
# linked to other domain objects with LinkFields. In terms of +domain_class+
|
210
|
-
# and
|
211
|
-
# +pk_id+, a DomainObjectProxy instance looks to the outside world like the
|
212
|
-
# domain object it's supposed to represent. It only retrieves its domain
|
213
|
-
# object from the database when member data is requested.
|
214
|
-
#
|
215
|
-
# In normal usage you will probably never manipulate a DomainObjectProxy
|
216
|
-
# directly, but you may discover it by accident by calling
|
217
|
-
# DomainObjectProxy#class (or DomainObject#class) instead of
|
218
|
-
# DomainObjectProxy#domain_class (or DomainObjectProxy#domain_class).
|
219
|
-
class DomainObjectProxy
|
220
|
-
include DomainComparable
|
221
|
-
|
222
|
-
attr_accessor :domain_class, :pk_id
|
223
|
-
|
224
|
-
def initialize(domain_classOrDbObject, pk_id = nil)
|
225
|
-
if pk_id
|
226
|
-
@domain_class = domain_classOrDbObject
|
227
|
-
@pk_id = pk_id
|
228
|
-
elsif domain_classOrDbObject.class < DomainObject
|
229
|
-
@db_object = domain_classOrDbObject
|
230
|
-
@d_obj_retrieve_time = Time.now
|
231
|
-
@domain_class = @db_object.class
|
232
|
-
@pk_id = @db_object.pk_id
|
233
|
-
else
|
234
|
-
raise ArgumentError
|
235
|
-
end
|
236
|
-
@db_object = nil
|
237
|
-
end
|
238
|
-
|
239
|
-
def get_db_object
|
240
|
-
object_store = ObjectStore.get_object_store
|
241
|
-
if @db_object.nil? || needs_refresh?
|
242
|
-
@db_object = object_store.get( @domain_class, @pk_id )
|
243
|
-
@d_obj_retrieve_time = Time.now
|
244
|
-
end
|
245
|
-
@db_object
|
246
|
-
end
|
247
|
-
|
248
|
-
def hash
|
249
|
-
get_db_object.hash
|
250
|
-
end
|
251
|
-
|
252
|
-
def method_missing(methodId, *args)
|
253
|
-
get_db_object.send(methodId.id2name, *args)
|
254
|
-
end
|
255
|
-
|
256
|
-
def needs_refresh?
|
257
|
-
object_store = ObjectStore.get_object_store
|
258
|
-
last_commit_time = object_store.last_commit_time( @domain_class, @pk_id )
|
259
|
-
!last_commit_time.nil? && last_commit_time > @d_obj_retrieve_time
|
260
|
-
end
|
261
|
-
|
262
|
-
def to_s
|
263
|
-
get_db_object.to_s
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
class DomainObjectSqlMaker #:nodoc:
|
268
|
-
attr_reader :bind_values
|
269
|
-
|
270
|
-
def initialize(obj); @obj = obj; end
|
271
|
-
|
272
|
-
def delete_sql( domain_class )
|
273
|
-
"delete from #{ domain_class.table_name} " +
|
274
|
-
"where #{ domain_class.sql_primary_key_name }=#{ @obj.pk_id }"
|
275
|
-
end
|
276
|
-
|
277
|
-
def get_name_value_pairs( domain_class )
|
278
|
-
nameValues = []
|
279
|
-
domain_class.class_fields.each { |field|
|
280
|
-
unless field.instance_of?( PrimaryKeyField )
|
281
|
-
value = @obj.send(field.name)
|
282
|
-
unless field.db_will_automatically_write
|
283
|
-
nameValues << field.name_for_sql
|
284
|
-
nameValues <<(field.value_for_sql(value))
|
285
|
-
end
|
286
|
-
if field.bind_write?
|
287
|
-
@bind_values << value
|
288
|
-
end
|
289
|
-
end
|
290
|
-
}
|
291
|
-
QueueHash.new( *nameValues )
|
292
|
-
end
|
293
|
-
|
294
|
-
def insert_sql( domain_class )
|
295
|
-
fields = domain_class.class_fields
|
296
|
-
nameValuePairs = get_name_value_pairs( domain_class )
|
297
|
-
if domain_class.is_based_on?
|
298
|
-
nameValuePairs[domain_class.sql_primary_key_name] = 'LAST_INSERT_ID()'
|
299
|
-
end
|
300
|
-
fieldNameStr = nameValuePairs.keys.join ", "
|
301
|
-
fieldValueStr = nameValuePairs.values.join ", "
|
302
|
-
"insert into #{ domain_class.table_name}(#{fieldNameStr}) " +
|
303
|
-
"values(#{fieldValueStr})"
|
304
|
-
end
|
305
|
-
|
306
|
-
def sql_statements
|
307
|
-
statements = []
|
308
|
-
if @obj.error_messages.size > 0
|
309
|
-
raise DomainObjectInitError, @obj.error_messages, caller
|
310
|
-
end
|
311
|
-
@obj.class.self_and_concrete_superclasses.each { |domain_class|
|
312
|
-
statements << statement_bind_value_pair( domain_class )
|
313
|
-
}
|
314
|
-
statements.reverse
|
315
|
-
end
|
316
|
-
|
317
|
-
def statement_bind_value_pair( domain_class )
|
318
|
-
@bind_values = []
|
319
|
-
if @obj.pk_id == nil
|
320
|
-
statement = insert_sql( domain_class )
|
321
|
-
else
|
322
|
-
if @obj.delete
|
323
|
-
statement = delete_sql( domain_class )
|
324
|
-
else
|
325
|
-
statement = update_sql( domain_class)
|
326
|
-
end
|
327
|
-
end
|
328
|
-
[statement, @bind_values]
|
329
|
-
end
|
330
|
-
|
331
|
-
def update_sql( domain_class )
|
332
|
-
nameValueStrings = []
|
333
|
-
nameValuePairs = get_name_value_pairs( domain_class )
|
334
|
-
nameValuePairs.each { |key, value|
|
335
|
-
nameValueStrings << "#{key}=#{ value }"
|
336
|
-
}
|
337
|
-
allNameValues = nameValueStrings.join ', '
|
338
|
-
"update #{ domain_class.table_name} set #{allNameValues} " +
|
339
|
-
"where #{ domain_class.sql_primary_key_name}=#{@obj.pk_id}"
|
340
|
-
end
|
341
|
-
end
|
342
|
-
|
343
|
-
class FieldMatchError < StandardError; end
|
344
|
-
|
345
|
-
# The ObjectStore represents the database in a Lafcadio application.
|
346
|
-
#
|
347
|
-
# = Configuring the ObjectStore
|
348
|
-
# The ObjectStore depends on a few values being set correctly in the
|
349
|
-
# LafcadioConfig file:
|
350
|
-
# [dbuser] The database username.
|
351
|
-
# [dbpassword] The database password.
|
352
|
-
# [dbname] The database name.
|
353
|
-
# [dbhost] The database host.
|
354
|
-
#
|
355
|
-
# = Instantiating ObjectStore
|
356
|
-
# The ObjectStore is a ContextualService, meaning you can't get an instance by
|
357
|
-
# calling ObjectStore.new. Instead, you should call
|
358
|
-
# ObjectStore.get_object_store. (Using a ContextualService makes it easier to
|
359
|
-
# make out the ObjectStore for unit tests: See ContextualService for more.)
|
360
|
-
#
|
361
|
-
# = Dynamic method calls
|
362
|
-
# ObjectStore uses reflection to provide a lot of convenience methods for
|
363
|
-
# querying domain objects in a number of ways.
|
364
|
-
# [ObjectStore#get< domain class > (pk_id)]
|
365
|
-
# Retrieves one domain object by pk_id. For example,
|
366
|
-
# ObjectStore#getUser( 100 )
|
367
|
-
# will return User 100.
|
368
|
-
# [ObjectStore#get< domain class >s (searchTerm, fieldName = nil)]
|
369
|
-
# Returns a collection of all instances of that domain class matching that
|
370
|
-
# search term. For example,
|
371
|
-
# ObjectStore#getProducts( aProductCategory )
|
372
|
-
# queries MySQL for all products that belong to that product category. You
|
373
|
-
# can omit +fieldName+ if +searchTerm+ is a non-nil domain object, and the
|
374
|
-
# field connecting the first domain class to the second is named after the
|
375
|
-
# domain class. (For example, the above line assumes that Product has a
|
376
|
-
# field named "productCategory".) Otherwise, it's best to include
|
377
|
-
# +fieldName+:
|
378
|
-
# ObjectStore#getUsers( "Jones", "lastName" )
|
379
|
-
#
|
380
|
-
# = Querying
|
381
|
-
# ObjectStore can also be used to generate complex, ad-hoc queries which
|
382
|
-
# emulate much of the functionality you'd get from writing the SQL yourself.
|
383
|
-
# Furthermore, these queries can be run against in-memory data stores, which
|
384
|
-
# is particularly useful for tests.
|
385
|
-
# date = Date.new( 2003, 1, 1 )
|
386
|
-
# ObjectStore#getInvoices { |invoice|
|
387
|
-
# Query.And( invoice.date.gte( date ), invoice.rate.equals( 10 ),
|
388
|
-
# invoice.hours.equals( 10 ) )
|
389
|
-
# }
|
390
|
-
# is the same as
|
391
|
-
# select * from invoices
|
392
|
-
# where (date >= '2003-01-01' and rate = 10 and hours = 10)
|
393
|
-
# See lafcadio/query.rb for more.
|
394
|
-
#
|
395
|
-
# = SQL Logging
|
396
|
-
# Lafcadio uses log4r to log all of its SQL statements. The simplest way to
|
397
|
-
# turn on logging is to set the following values in the LafcadioConfig file:
|
398
|
-
# [logSql] Should be set to "y" to turn on logging.
|
399
|
-
# [logdir] The directory where log files should be written. Required if
|
400
|
-
# +logSql+ is "y"
|
401
|
-
# [sqlLogFile] The name of the file (not including its directory) where SQL
|
402
|
-
# should be logged. Default is "sql".
|
403
|
-
#
|
404
|
-
# = Triggers
|
405
|
-
# Domain classes can be set to fire triggers either before or after commits.
|
406
|
-
# Since these triggers are executed in Ruby, they're easy to test. See
|
407
|
-
# DomainObject#pre_commit_trigger and DomainObject#post_commit_trigger for more.
|
408
|
-
class ObjectStore < ContextualService
|
409
|
-
def self.set_db_name(dbName) #:nodoc:
|
410
|
-
DbConnection.set_db_name dbName
|
411
|
-
end
|
412
|
-
|
413
|
-
def initialize( dbBridge = nil ) #:nodoc:
|
414
|
-
@dbBridge = dbBridge == nil ? DbBridge.new : dbBridge
|
415
|
-
@cache = ObjectStore::Cache.new( @dbBridge )
|
416
|
-
end
|
417
|
-
|
418
|
-
# Commits a domain object to the database. You can also simply call
|
419
|
-
# myDomainObject.commit
|
420
|
-
def commit(db_object)
|
421
|
-
@cache.commit( db_object )
|
422
|
-
db_object
|
423
|
-
end
|
424
|
-
|
425
|
-
# Flushes one domain object from its cache.
|
426
|
-
def flush(db_object)
|
427
|
-
@cache.flush db_object
|
428
|
-
end
|
429
|
-
|
430
|
-
# Returns the domain object corresponding to the domain class and pk_id.
|
431
|
-
def get( domain_class, pk_id )
|
432
|
-
query = Query.new domain_class, pk_id
|
433
|
-
@cache.get_by_query( query )[0] ||
|
434
|
-
( raise( DomainObjectNotFoundError,
|
435
|
-
"Can't find #{domain_class} #{pk_id}", caller ) )
|
436
|
-
end
|
437
|
-
|
438
|
-
# Returns all domain objects for the given domain class.
|
439
|
-
def get_all(domain_class); @cache.get_by_query( Query.new( domain_class ) ); end
|
440
|
-
|
441
|
-
# Returns the DbBridge; this is useful in case you need to use raw SQL for a
|
442
|
-
# specific query.
|
443
|
-
def get_db_bridge; @dbBridge; end
|
444
|
-
|
445
|
-
def get_field_name( domain_object )
|
446
|
-
domain_object.domain_class.basename.decapitalize
|
447
|
-
end
|
448
|
-
|
449
|
-
def get_filtered(domain_class_name, searchTerm, fieldName = nil) #:nodoc:
|
450
|
-
domain_class = DomainObject.get_domain_class_from_string(
|
451
|
-
domain_class_name
|
452
|
-
)
|
453
|
-
fieldName = get_field_name( searchTerm ) unless fieldName
|
454
|
-
get_subset( Query::Equals.new( fieldName, searchTerm, domain_class ) )
|
455
|
-
end
|
456
|
-
|
457
|
-
def get_map_match( domain_class, mapped ) #:nodoc:
|
458
|
-
Query::Equals.new( get_field_name( mapped ), mapped, domain_class )
|
459
|
-
end
|
460
|
-
|
461
|
-
def get_map_object( domain_class, map1, map2 ) #:nodoc:
|
462
|
-
unless map1 && map2
|
463
|
-
raise ArgumentError,
|
464
|
-
"ObjectStore#get_map_object needs two non-nil keys", caller
|
465
|
-
end
|
466
|
-
mapMatch1 = get_map_match domain_class, map1
|
467
|
-
mapMatch2 = get_map_match domain_class, map2
|
468
|
-
condition = Query::CompoundCondition.new mapMatch1, mapMatch2
|
469
|
-
get_subset(condition)[0]
|
470
|
-
end
|
471
|
-
|
472
|
-
def get_mapped(searchTerm, resultTypeName) #:nodoc:
|
473
|
-
resultType = DomainObject.get_domain_class_from_string resultTypeName
|
474
|
-
firstTypeName = searchTerm.class.basename
|
475
|
-
secondTypeName = resultType.basename
|
476
|
-
mapTypeName = firstTypeName + secondTypeName
|
477
|
-
get_filtered( mapTypeName, searchTerm ).collect { |mapObj|
|
478
|
-
mapObj.send( resultType.name.decapitalize )
|
479
|
-
}
|
480
|
-
end
|
481
|
-
|
482
|
-
# Retrieves the maximum value across all instances of one domain class.
|
483
|
-
# ObjectStore#get_max( Client )
|
484
|
-
# returns the highest +pk_id+ in the +clients+ table.
|
485
|
-
# ObjectStore#get_max( Invoice, "rate" )
|
486
|
-
# will return the highest rate for all invoices.
|
487
|
-
def get_max( domain_class, field_name = 'pk_id' )
|
488
|
-
@dbBridge.group_query( Query::Max.new( domain_class, field_name ) ).only
|
489
|
-
end
|
490
|
-
|
491
|
-
# Retrieves a collection of domain objects by +pk_id+.
|
492
|
-
# ObjectStore#get_objects( Clients, [ 1, 2, 3 ] )
|
493
|
-
def get_objects( domain_class, pk_ids )
|
494
|
-
if pk_ids.is_a?( Array ) && pk_ids.all? { |elt| elt.is_a?( Integer ) }
|
495
|
-
get_subset Query::In.new( 'pk_id', pk_ids, domain_class )
|
496
|
-
else
|
497
|
-
raise(
|
498
|
-
ArgumentError,
|
499
|
-
"ObjectStore#get_objects( domain_class, pk_ids ): pk_ids needs to " +
|
500
|
-
"be an array of integers",
|
501
|
-
caller
|
502
|
-
)
|
503
|
-
end
|
504
|
-
end
|
505
|
-
|
506
|
-
def get_subset(conditionOrQuery) #:nodoc:
|
507
|
-
if conditionOrQuery.class <= Query::Condition
|
508
|
-
condition = conditionOrQuery
|
509
|
-
query = Query.new condition.domain_class, condition
|
510
|
-
else
|
511
|
-
query = conditionOrQuery
|
512
|
-
end
|
513
|
-
@cache.get_by_query( query )
|
514
|
-
end
|
515
|
-
|
516
|
-
def last_commit_time( domain_class, pk_id ) #:nodoc:
|
517
|
-
@cache.last_commit_time( domain_class, pk_id )
|
518
|
-
end
|
519
|
-
|
520
|
-
def method_missing(methodId, *args) #:nodoc:
|
521
|
-
proc = block_given? ? ( proc { |obj| yield( obj ) } ) : nil
|
522
|
-
dispatch = MethodDispatch.new( methodId, proc, *args )
|
523
|
-
self.send( dispatch.symbol, *dispatch.args )
|
524
|
-
end
|
525
|
-
|
526
|
-
def respond_to?( symbol, include_private = false )
|
527
|
-
begin
|
528
|
-
dispatch = MethodDispatch.new( symbol )
|
529
|
-
rescue NoMethodError
|
530
|
-
super
|
531
|
-
end
|
532
|
-
end
|
533
|
-
|
534
|
-
class Cache #:nodoc:
|
535
|
-
def initialize( dbBridge )
|
536
|
-
@dbBridge = dbBridge
|
537
|
-
@objects = {}
|
538
|
-
@collections_by_query = {}
|
539
|
-
@commit_times = {}
|
540
|
-
end
|
541
|
-
|
542
|
-
def commit( db_object )
|
543
|
-
committer = Committer.new db_object, @dbBridge
|
544
|
-
committer.execute
|
545
|
-
update_after_commit( committer )
|
546
|
-
end
|
547
|
-
|
548
|
-
# Flushes a domain object.
|
549
|
-
def flush(db_object)
|
550
|
-
hash_by_domain_class( db_object.domain_class ).delete db_object.pk_id
|
551
|
-
flush_collection_cache( db_object.domain_class )
|
552
|
-
end
|
553
|
-
|
554
|
-
def flush_collection_cache( domain_class )
|
555
|
-
@collections_by_query.keys.each { |query|
|
556
|
-
if query.domain_class == domain_class
|
557
|
-
@collections_by_query.delete( query )
|
558
|
-
end
|
559
|
-
}
|
560
|
-
end
|
561
|
-
|
562
|
-
# Returns a cached domain object, or nil if none is found.
|
563
|
-
def get( domain_class, pk_id )
|
564
|
-
hash_by_domain_class( domain_class )[pk_id].clone
|
565
|
-
end
|
566
|
-
|
567
|
-
# Returns an array of all domain objects of a given type.
|
568
|
-
def get_all( domain_class )
|
569
|
-
hash_by_domain_class( domain_class ).values.collect { |d_obj|
|
570
|
-
d_obj.clone
|
571
|
-
}
|
572
|
-
end
|
573
|
-
|
574
|
-
def get_by_query( query )
|
575
|
-
unless @collections_by_query[query]
|
576
|
-
superset_query, pk_ids =
|
577
|
-
@collections_by_query.find { |other_query, pk_ids|
|
578
|
-
query.implies?( other_query )
|
579
|
-
}
|
580
|
-
if pk_ids
|
581
|
-
@collections_by_query[query] = ( pk_ids.collect { |pk_id|
|
582
|
-
get( query.domain_class, pk_id )
|
583
|
-
} ).select { |dobj| query.object_meets( dobj ) }.collect { |dobj|
|
584
|
-
dobj.pk_id
|
585
|
-
}
|
586
|
-
elsif @collections_by_query.values
|
587
|
-
newObjects = @dbBridge.get_collection_by_query(query)
|
588
|
-
newObjects.each { |dbObj| save dbObj }
|
589
|
-
@collections_by_query[query] = newObjects.collect { |dobj|
|
590
|
-
dobj.pk_id
|
591
|
-
}
|
592
|
-
end
|
593
|
-
end
|
594
|
-
collection = []
|
595
|
-
@collections_by_query[query].each { |pk_id|
|
596
|
-
dobj = get( query.domain_class, pk_id )
|
597
|
-
collection << dobj if dobj
|
598
|
-
}
|
599
|
-
collection
|
600
|
-
end
|
601
|
-
|
602
|
-
def hash_by_domain_class( domain_class )
|
603
|
-
unless @objects[domain_class]
|
604
|
-
@objects[domain_class] = {}
|
605
|
-
end
|
606
|
-
@objects[domain_class]
|
607
|
-
end
|
608
|
-
|
609
|
-
def last_commit_time( domain_class, pk_id )
|
610
|
-
by_domain_class = @commit_times[domain_class]
|
611
|
-
by_domain_class ? by_domain_class[pk_id] : nil
|
612
|
-
end
|
613
|
-
|
614
|
-
def set_commit_time( d_obj )
|
615
|
-
by_domain_class = @commit_times[d_obj.domain_class]
|
616
|
-
if by_domain_class.nil?
|
617
|
-
by_domain_class = {}
|
618
|
-
@commit_times[d_obj.domain_class] = by_domain_class
|
619
|
-
end
|
620
|
-
by_domain_class[d_obj.pk_id] = Time.now
|
621
|
-
end
|
622
|
-
|
623
|
-
# Saves a domain object.
|
624
|
-
def save(db_object)
|
625
|
-
hash = hash_by_domain_class( db_object.domain_class )
|
626
|
-
hash[db_object.pk_id] = db_object
|
627
|
-
flush_collection_cache( db_object.domain_class )
|
628
|
-
end
|
629
|
-
|
630
|
-
def update_after_commit( committer ) #:nodoc:
|
631
|
-
if committer.commit_type == Committer::UPDATE ||
|
632
|
-
committer.commit_type == Committer::INSERT
|
633
|
-
save( committer.db_object )
|
634
|
-
elsif committer.commit_type == Committer::DELETE
|
635
|
-
flush( committer.db_object )
|
636
|
-
end
|
637
|
-
set_commit_time( committer.db_object )
|
638
|
-
end
|
639
|
-
end
|
640
|
-
|
641
|
-
class MethodDispatch #:nodoc:
|
642
|
-
attr_reader :symbol, :args
|
643
|
-
|
644
|
-
def initialize( orig_method, *other_args )
|
645
|
-
@orig_method = orig_method
|
646
|
-
@orig_args = other_args
|
647
|
-
if @orig_args.size > 0
|
648
|
-
@maybe_proc = @orig_args.shift
|
649
|
-
end
|
650
|
-
@methodName = orig_method.id2name
|
651
|
-
if @methodName =~ /^get(.*)$/
|
652
|
-
dispatch_get_method
|
653
|
-
else
|
654
|
-
raise_no_method_error
|
655
|
-
end
|
656
|
-
end
|
657
|
-
|
658
|
-
def dispatch_get_plural
|
659
|
-
if @orig_args.size == 0 && @maybe_proc.nil?
|
660
|
-
@symbol = :get_all
|
661
|
-
@args = [ @domain_class ]
|
662
|
-
else
|
663
|
-
searchTerm = @orig_args[0]
|
664
|
-
fieldName = @orig_args[1]
|
665
|
-
if searchTerm.nil? && @maybe_proc.nil? && fieldName.nil?
|
666
|
-
msg = "ObjectStore\##{ @orig_method } needs a field name as its " +
|
667
|
-
"second argument if its first argument is nil"
|
668
|
-
raise( ArgumentError, msg, caller )
|
669
|
-
end
|
670
|
-
dispatch_get_plural_by_query_block_or_search_term( searchTerm,
|
671
|
-
fieldName )
|
672
|
-
end
|
673
|
-
end
|
674
|
-
|
675
|
-
def dispatch_get_plural_by_query_block
|
676
|
-
inferrer = Query::Inferrer.new( @domain_class ) { |obj|
|
677
|
-
@maybe_proc.call( obj )
|
678
|
-
}
|
679
|
-
@symbol = :get_subset
|
680
|
-
@args = [ inferrer.execute ]
|
681
|
-
end
|
682
|
-
|
683
|
-
def dispatch_get_plural_by_query_block_or_search_term( searchTerm,
|
684
|
-
fieldName )
|
685
|
-
if !@maybe_proc.nil? && searchTerm.nil?
|
686
|
-
dispatch_get_plural_by_query_block
|
687
|
-
elsif @maybe_proc.nil? && ( !( searchTerm.nil? && fieldName.nil? ) )
|
688
|
-
@symbol = :get_filtered
|
689
|
-
@args = [ @domain_class.name, searchTerm, fieldName ]
|
690
|
-
else
|
691
|
-
raise( ArgumentError,
|
692
|
-
"Shouldn't send both a query block and a search term",
|
693
|
-
caller )
|
694
|
-
end
|
695
|
-
end
|
696
|
-
|
697
|
-
def dispatch_get_method
|
698
|
-
begin
|
699
|
-
dispatch_get_singular
|
700
|
-
rescue CouldntMatchDomainClassError
|
701
|
-
domain_class_name = English.singular(
|
702
|
-
camel_case_method_name_after_get
|
703
|
-
)
|
704
|
-
begin
|
705
|
-
@domain_class =
|
706
|
-
DomainObject.get_domain_class_from_string( domain_class_name )
|
707
|
-
dispatch_get_plural
|
708
|
-
rescue CouldntMatchDomainClassError
|
709
|
-
raise_no_method_error
|
710
|
-
end
|
711
|
-
end
|
712
|
-
end
|
713
|
-
|
714
|
-
def dispatch_get_singular
|
715
|
-
domain_class = DomainObject.get_domain_class_from_string(
|
716
|
-
camel_case_method_name_after_get
|
717
|
-
)
|
718
|
-
if @orig_args[0].class <= Integer
|
719
|
-
@symbol = :get
|
720
|
-
@args = [ domain_class, @orig_args[0] ]
|
721
|
-
elsif @orig_args[0].class <= DomainObject
|
722
|
-
@symbol = :get_map_object
|
723
|
-
@args = [ domain_class, @orig_args[0], @orig_args[1] ]
|
724
|
-
end
|
725
|
-
end
|
726
|
-
|
727
|
-
def camel_case_method_name_after_get
|
728
|
-
@orig_method.id2name =~ /^get(.*)$/
|
729
|
-
$1.underscore_to_camel_case
|
730
|
-
end
|
731
|
-
|
732
|
-
def raise_no_method_error
|
733
|
-
raise( NoMethodError, "undefined method '#{ @methodName }'", caller )
|
734
|
-
end
|
735
|
-
end
|
736
|
-
end
|
737
|
-
|
738
|
-
class SqlValueConverter #:nodoc:
|
739
|
-
attr_reader :domain_class, :row_hash
|
740
|
-
|
741
|
-
def initialize( domain_class, row_hash )
|
742
|
-
@domain_class = domain_class
|
743
|
-
@row_hash = row_hash
|
744
|
-
end
|
745
|
-
|
746
|
-
def []( key )
|
747
|
-
begin
|
748
|
-
field = @domain_class.get_field( key )
|
749
|
-
val = field.value_from_sql( @row_hash[ field.db_field_name ] )
|
750
|
-
if field.instance_of?( PrimaryKeyField ) && val.nil?
|
751
|
-
raise FieldMatchError, error_msg, caller
|
752
|
-
else
|
753
|
-
val
|
754
|
-
end
|
755
|
-
rescue MissingError
|
756
|
-
nil
|
757
|
-
end
|
758
|
-
end
|
759
|
-
|
760
|
-
def error_msg
|
761
|
-
"The field \"" + @domain_class.sql_primary_key_name +
|
762
|
-
"\" can\'t be found in the table \"" +
|
763
|
-
@domain_class.table_name + "\"."
|
764
|
-
end
|
765
|
-
end
|
766
|
-
end
|