baza 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/include/dump.rb ADDED
@@ -0,0 +1,122 @@
1
+ #This class can be used to make SQL-dumps of databases, tables or however you want it.
2
+ class Baza::Dump
3
+ #Constructor.
4
+ #===Examples
5
+ # dump = Baza::Dump.new(:db => db)
6
+ def initialize(args)
7
+ @args = args
8
+ @debug = @args[:debug]
9
+ end
10
+
11
+ #Method used to update the status.
12
+ def update_status
13
+ return nil if !@on_status
14
+ rows_count = Knj::Locales.number_out(@rows_count, 0)
15
+ rows_count_total = Knj::Locales.number_out(@rows_count_total, 0)
16
+ percent = (@rows_count.to_f / @rows_count_total.to_f) * 100
17
+ percent_text = Knj::Locales.number_out(percent, 1)
18
+ @on_status.call(:text => "Dumping table: '#{@table_obj.name}' (#{rows_count}/#{rows_count_total} - #{percent_text}%).")
19
+ end
20
+
21
+ #Dumps all tables into the given IO.
22
+ def dump(io)
23
+ print "Going through tables.\n" if @debug
24
+ @rows_count = 0
25
+
26
+ if @args[:tables]
27
+ tables = @args[:tables]
28
+ else
29
+ tables = @args[:db].tables.list.values
30
+ end
31
+
32
+ if @on_status
33
+ @on_status.call(:text => "Preparing.")
34
+
35
+ @rows_count_total = 0
36
+ tables.each do |table_obj|
37
+ @rows_count_total += table_obj.rows_count
38
+ end
39
+ end
40
+
41
+ tables.each do |table_obj|
42
+ table_obj = @args[:db].tables[table_obj] if table_obj.is_a?(String) or table_obj.is_a?(Symbol)
43
+ next if table_obj.native?
44
+
45
+ #Figure out keys.
46
+ @keys = []
47
+ table_obj.columns do |col|
48
+ @keys << col.name
49
+ end
50
+
51
+ @table_obj = table_obj
52
+ self.update_status
53
+ print "Dumping table: '#{table_obj.name}'.\n" if @debug
54
+ self.dump_table(io, table_obj)
55
+ end
56
+ end
57
+
58
+ #A block can be executed when a new status occurs.
59
+ def on_status(&block)
60
+ @on_status = block
61
+ end
62
+
63
+ #Dumps the given table into the given IO.
64
+ def dump_table(io, table_obj)
65
+ #Get SQL for creating table and add it to IO.
66
+ sqls = @args[:db].tables.create(table_obj.name, table_obj.data, :return_sql => true)
67
+ sqls.each do |sql|
68
+ io.write("#{sql};\n")
69
+ end
70
+
71
+
72
+ #Try to find a primary column in the table.
73
+ prim_col = nil
74
+ table_obj.columns do |col|
75
+ if col.primarykey?
76
+ prim_col = col
77
+ break
78
+ end
79
+ end
80
+
81
+
82
+ #Set up rows and way to fill rows.
83
+ rows = []
84
+ block_data = proc do |row|
85
+ rows << row
86
+ @rows_count += 1
87
+
88
+ if rows.length >= 1000
89
+ self.update_status
90
+ self.dump_insert_multi(io, table_obj, rows)
91
+ end
92
+ end
93
+
94
+
95
+ #If a primary column is found then use IDQuery. Otherwise use cloned unbuffered query.
96
+ args = {:idquery => prim_col.name.to_sym} if prim_col
97
+
98
+
99
+ #Clone the connecting with array-results and execute query.
100
+ @args[:db].clone_conn(:result => "array") do |db|
101
+ db.select(table_obj.name, nil, args, &block_data)
102
+ end
103
+
104
+
105
+ #Dump the last rows if any.
106
+ self.dump_insert_multi(io, table_obj, rows) if !rows.empty?
107
+ end
108
+
109
+ #Dumps the given rows from the given table into the given IO.
110
+ def dump_insert_multi(io, table_obj, rows)
111
+ print "Inserting #{rows.length} into #{table_obj.name}.\n" if @debug
112
+ sqls = @args[:db].insert_multi(table_obj.name, rows, :return_sql => true, :keys => @keys)
113
+ sqls.each do |sql|
114
+ io.write("#{sql};\n")
115
+ end
116
+
117
+ rows.clear
118
+
119
+ #Ensure garbage collection or we might start using A LOT of memory.
120
+ GC.start
121
+ end
122
+ end
@@ -0,0 +1,109 @@
1
+ #This class takes a lot of IDs and runs a query against them.
2
+ class Baza::Idquery
3
+ #An array containing all the IDs that will be looked up.
4
+ attr_reader :ids
5
+
6
+ #Constructor.
7
+ #===Examples
8
+ # idq = Baza::Idquery(:db => db, :table => :users)
9
+ # idq.ids + [1, 5, 9]
10
+ # idq.each do |user|
11
+ # print "Name: #{user[:name]}\n"
12
+ # end
13
+ def initialize(args, &block)
14
+ @args = args
15
+ @ids = []
16
+ @debug = @args[:debug]
17
+
18
+ if @args[:query]
19
+ @args[:db].q(@args[:query]) do |data|
20
+ @args[:col] = data.keys.first if !@args[:col]
21
+
22
+ if data.is_a?(Array)
23
+ @ids << data.first
24
+ else
25
+ @ids << data[@args[:col]]
26
+ end
27
+ end
28
+ end
29
+
30
+ @args[:col] = :id if !@args[:col]
31
+ @args[:size] = 200 if !@args[:size]
32
+
33
+ if block
34
+ raise "No query was given but a block was." if !@args[:query]
35
+ self.each(&block)
36
+ end
37
+ end
38
+
39
+ #Fetches results.
40
+ #===Examples
41
+ # data = idq.fetch #=> Hash
42
+ def fetch
43
+ return nil if !@args
44
+
45
+ if @res
46
+ data = @res.fetch if @res
47
+ @res = nil if !data
48
+ return data if data
49
+ end
50
+
51
+ @res = new_res if !@res
52
+ if !@res
53
+ destroy
54
+ return nil
55
+ end
56
+
57
+ data = @res.fetch
58
+ if !data
59
+ destroy
60
+ return nil
61
+ end
62
+
63
+ return data
64
+ end
65
+
66
+ #Yields a block for every result.
67
+ #===Examples
68
+ # idq.each do |data|
69
+ # print "Name: #{data[:name]}\n"
70
+ # end
71
+ def each
72
+ while data = self.fetch
73
+ yield(data)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ #Spawns a new database-result to read from.
80
+ def new_res
81
+ table_esc = "`#{@args[:db].esc_table(@args[:table])}`"
82
+ col_esc = "`#{@args[:db].esc_col(@args[:col])}`"
83
+ ids = @ids.shift(@args[:size])
84
+
85
+ if ids.empty?
86
+ destroy
87
+ return nil
88
+ end
89
+
90
+ ids_sql = Knj::ArrayExt.join(
91
+ :arr => ids,
92
+ :callback => proc{|val| @args[:db].esc(val)},
93
+ :sep => ",",
94
+ :surr => "'"
95
+ )
96
+
97
+ query_str = "SELECT * FROM #{table_esc} WHERE #{table_esc}.#{col_esc} IN (#{ids_sql})"
98
+ print "Query: #{query_str}\n" if @debug
99
+
100
+ return @args[:db].q(query_str)
101
+ end
102
+
103
+ #Removes all variables on the object. This is done when no more results are available.
104
+ def destroy
105
+ @args = nil
106
+ @ids = nil
107
+ @debug = nil
108
+ end
109
+ end
data/include/model.rb ADDED
@@ -0,0 +1,873 @@
1
+ #This class helps create models in a framework with Baza::Db and Baza::ModelHandler.
2
+ #===Examples
3
+ # db = Baza::Db.new(:type => "sqlite3", :path => "somepath.sqlite3")
4
+ # ob = Baza::ModelHandler.new(:db => db, :datarow => true, :path => "path_of_model_class_files")
5
+ # user = ob.get(:User, 1) #=> <Models::User> that extends <Baza::Datarow>
6
+ class Baza::Model
7
+ @@refs = {}
8
+
9
+ #Returns the Baza::ModelHandler which handels this model.
10
+ def ob
11
+ return self.class.ob
12
+ end
13
+
14
+ #Returns the Baza::Db which handels this model.
15
+ def db
16
+ return self.class.db
17
+ end
18
+
19
+ #Returns the 'Baza::ModelHandler'-object that handels this class.
20
+ def self.ob
21
+ return @ob
22
+ end
23
+
24
+ #Returns the 'Baza::Db'-object that handels this class.
25
+ def self.db
26
+ return @db
27
+ end
28
+
29
+ #This is used by 'Baza::ModelHandler' to find out what data is required for this class. Returns the array that tells about required data.
30
+ #===Examples
31
+ #When adding a new user, this can fail if the ':group_id' is not given, or the ':group_id' doesnt refer to a valid group-row in the db.
32
+ # class Models::User < Baza::Datarow
33
+ # has_one [
34
+ # {:class => :Group, :col => :group_id, :method => :group, :required => true}
35
+ # ]
36
+ # end
37
+ def self.required_data
38
+ @required_data = [] if !@required_data
39
+ return @required_data
40
+ end
41
+
42
+ #This is used by 'Baza::ModelHandler' to find out what other objects this class depends on. Returns the array that tells about depending data.
43
+ #===Examples
44
+ #This will tell Baza::ModelHandler that files depends on users. It can prevent the user from being deleted, if any files depend on it.
45
+ # class Models::User < Baza::Datarow
46
+ # has_many [
47
+ # {:class => :File, :col => :user_id, :method => :files, :depends => true}
48
+ # ]
49
+ # end
50
+ def self.depending_data
51
+ return @depending_data
52
+ end
53
+
54
+ #Returns true if this class has been initialized.
55
+ def self.initialized?
56
+ return false if !@columns_sqlhelper_args
57
+ return true
58
+ end
59
+
60
+ #This is used by 'Baza::ModelHandler' to find out which other objects should be deleted when an object of this class is deleted automatically. Returns the array that tells about autodelete data.
61
+ #===Examples
62
+ #This will trigger Baza::ModelHandler to automatically delete all the users pictures, when deleting the current user.
63
+ # class Models::User < Baza::Datarow
64
+ # has_many [
65
+ # {:class => :Picture, :col => :user_id, :method => :pictures, :autodelete => true}
66
+ # ]
67
+ # end
68
+ def self.autodelete_data
69
+ return @autodelete_data
70
+ end
71
+
72
+ #Returns the autozero-data (if any).
73
+ def self.autozero_data
74
+ return @autozero_data
75
+ end
76
+
77
+ #This helps various parts of the framework determine if this is a datarow class without requiring it.
78
+ #===Examples
79
+ # print "This is a knj-object." if obj.respond_to?("is_knj?")
80
+ def is_knj?
81
+ return true
82
+ end
83
+
84
+ #This tests if a certain string is a date-null-stamp.
85
+ #===Examples
86
+ # time_str = dbrow[:date]
87
+ # print "No valid date on the row." if Baza::Datarow.is_nullstamp?(time_str)
88
+ def self.is_nullstamp?(stamp)
89
+ return true if !stamp or stamp == "0000-00-00 00:00:00" or stamp == "0000-00-00"
90
+ return false
91
+ end
92
+
93
+ #This is used to define datarows that this object can have a lot of.
94
+ #===Examples
95
+ #This will define the method "pictures" on 'Models::User' that will return all pictures for the users and take possible Objects-sql-arguments. It will also enabling joining pictures when doing Objects-sql-lookups.
96
+ # class Models::User < Baza::Datarow
97
+ # has_many [
98
+ # [:Picture, :user_id, :pictures],
99
+ # {:class => :File, :col => :user_id, :method => :files}
100
+ # ]
101
+ # end
102
+ def self.has_many(arr)
103
+ arr.each do |val|
104
+ if val.is_a?(Array)
105
+ classname, colname, methodname = *val
106
+ elsif val.is_a?(Hash)
107
+ classname, colname, methodname = nil, nil, nil
108
+
109
+ val.each do |hkey, hval|
110
+ case hkey
111
+ when :class
112
+ classname = hval
113
+ when :col
114
+ colname = hval
115
+ when :method
116
+ methodname = hval
117
+ when :depends, :autodelete, :autozero, :where
118
+ #ignore
119
+ else
120
+ raise "Invalid key for 'has_many': '#{hkey}'."
121
+ end
122
+ end
123
+
124
+ colname = "#{self.name.to_s.split("::").last.to_s.downcase}_id".to_sym if colname.to_s.empty?
125
+
126
+ if val[:depends]
127
+ @depending_data = [] if !@depending_data
128
+ @depending_data << {
129
+ :colname => colname,
130
+ :classname => classname
131
+ }
132
+ end
133
+
134
+ if val[:autodelete]
135
+ @autodelete_data = [] if !@autodelete_data
136
+ @autodelete_data << {
137
+ :colname => colname,
138
+ :classname => classname
139
+ }
140
+ end
141
+
142
+ if val[:autozero]
143
+ @autozero_data = [] if !@autozero_data
144
+ @autozero_data << {
145
+ :colname => colname,
146
+ :classname => classname
147
+ }
148
+ end
149
+ else
150
+ raise "Unknown argument: '#{val.class.name}'."
151
+ end
152
+
153
+ raise "No classname given." if !classname
154
+ methodname = "#{classname.to_s.downcase}s".to_sym if !methodname
155
+ raise "No column was given for '#{self.name}' regarding has-many-class: '#{classname}'." if !colname
156
+
157
+ if val.is_a?(Hash) and val.key?(:where)
158
+ where_args = val[:where]
159
+ else
160
+ where_args = nil
161
+ end
162
+
163
+ self.define_many_methods(classname, methodname, colname, where_args)
164
+
165
+ self.joined_tables(
166
+ classname => {
167
+ :where => {
168
+ colname.to_s => {:type => :col, :name => :id}
169
+ }
170
+ }
171
+ )
172
+ end
173
+ end
174
+
175
+ #This define is this object has one element of another datarow-class. It define various methods and joins based on that.
176
+ #===Examples
177
+ # class Models::User < Baza::Datarow
178
+ # has_one [
179
+ # #Defines the method 'group', which returns a 'Group'-object by the column 'group_id'.
180
+ # :Group,
181
+ #
182
+ # #Defines the method 'type', which returns a 'Type'-object by the column 'type_id'.
183
+ # {:class => :Type, :col => :type_id, :method => :type}
184
+ # ]
185
+ # end
186
+ def self.has_one(arr)
187
+ arr.each do |val|
188
+ methodname = nil
189
+ colname = nil
190
+ classname = nil
191
+
192
+ if val.is_a?(Symbol)
193
+ classname = val
194
+ methodname = val.to_s.downcase.to_sym
195
+ colname = "#{val.to_s.downcase}_id".to_sym
196
+ elsif val.is_a?(Array)
197
+ classname, colname, methodname = *val
198
+ elsif val.is_a?(Hash)
199
+ classname, colname, methodname = nil, nil, nil
200
+
201
+ val.each do |hkey, hval|
202
+ case hkey
203
+ when :class
204
+ classname = hval
205
+ when :col
206
+ colname = hval
207
+ when :method
208
+ methodname = hval
209
+ when :required
210
+ #ignore
211
+ else
212
+ raise "Invalid key for class '#{self.name}' functionality 'has_many': '#{hkey}'."
213
+ end
214
+ end
215
+
216
+ if val[:required]
217
+ colname = "#{classname.to_s.downcase}_id".to_sym if !colname
218
+ self.required_data << {
219
+ :col => colname,
220
+ :class => classname
221
+ }
222
+ end
223
+ else
224
+ raise "Unknown argument-type: '#{arr.class.name}'."
225
+ end
226
+
227
+ methodname = classname.to_s.downcase if !methodname
228
+ colname = "#{classname.to_s.downcase}_id".to_sym if !colname
229
+ self.define_one_methods(classname, methodname, colname)
230
+
231
+ self.joined_tables(
232
+ classname => {
233
+ :where => {
234
+ "id" => {:type => :col, :name => colname}
235
+ }
236
+ }
237
+ )
238
+ end
239
+ end
240
+
241
+ #This method initializes joins, sets methods to update translations and makes the translations automatically be deleted when the object is deleted.
242
+ #===Examples
243
+ # class Models::Article < Baza::Datarow
244
+ # #Defines methods such as: 'title', 'title=', 'content', 'content='. When used with Knjappserver these methods will change what they return and set based on the current language of the session.
245
+ # has_translation [:title, :content]
246
+ # end
247
+ #
248
+ # article = ob.get(:Article, 1)
249
+ # print "The title in the current language is: '#{article.title}'."
250
+ #
251
+ # article.title = 'Title in english if the language is english'
252
+ def self.has_translation(arr)
253
+ @translations = [] if !@translations
254
+
255
+ arr.each do |val|
256
+ @translations << val
257
+
258
+ val_dc = val.to_s.downcase
259
+ table_name = "Translation_#{val_dc}".to_sym
260
+
261
+ joined_tables(
262
+ table_name => {
263
+ :where => {
264
+ "object_class" => self.name,
265
+ "object_id" => {:type => :col, :name => :id},
266
+ "key" => val.to_s,
267
+ "locale" => proc{|d| _session[:locale]}
268
+ },
269
+ :parent_table => :Translation,
270
+ :datarow => Knj::Translations::Translation,
271
+ :ob => @ob
272
+ }
273
+ )
274
+
275
+ self.define_translation_methods(:val => val, :val_dc => val_dc)
276
+ end
277
+ end
278
+
279
+ #This returns all translations for this datarow-class.
280
+ def self.translations
281
+ return @translations
282
+ end
283
+
284
+ #Returns data about joined tables for this class.
285
+ def self.joined_tables(hash)
286
+ @columns_joined_tables = {} if !@columns_joined_tables
287
+ @columns_joined_tables.merge!(hash)
288
+ end
289
+
290
+ #Returns various data for the objects-sql-helper. This can be used to view various informations about the columns and more.
291
+ def self.columns_sqlhelper_args
292
+ raise "No SQLHelper arguments has been spawned yet." if !@columns_sqlhelper_args
293
+ return @columns_sqlhelper_args
294
+ end
295
+
296
+ #Called by Baza::ModelHandler to initialize the model and load column-data on-the-fly.
297
+ def self.load_columns(d)
298
+ @ob = d.ob
299
+ @db = d.db
300
+
301
+ @classname = self.name.split("::").last.to_sym if !@classname
302
+ @table = @classname if !@table
303
+ @mutex = Monitor.new if !@mutex
304
+
305
+ #Cache these to avoid method-lookups.
306
+ @sep_col = @db.sep_col
307
+ @sep_table = @db.sep_table
308
+ @table_str = "#{@sep_table}#{@db.esc_table(@table)}#{@sep_table}"
309
+
310
+ @mutex.synchronize do
311
+ inst_methods = self.instance_methods(false)
312
+
313
+ sqlhelper_args = {
314
+ :db => @db,
315
+ :table => @table,
316
+ :cols_bools => [],
317
+ :cols_date => [],
318
+ :cols_dbrows => [],
319
+ :cols_num => [],
320
+ :cols_str => [],
321
+ :cols => {}
322
+ }
323
+
324
+ sqlhelper_args[:table] = @table
325
+
326
+ @db.tables[table].columns do |col_obj|
327
+ col_name = col_obj.name.to_s
328
+ col_name_sym = col_name.to_sym
329
+ col_type = col_obj.type
330
+ col_type = :int if col_type == :bigint or col_type == :tinyint or col_type == :mediumint or col_type == :smallint
331
+ sqlhelper_args[:cols][col_name] = true
332
+
333
+ self.define_bool_methods(inst_methods, col_name)
334
+
335
+ if col_type == :enum and col_obj.maxlength == "'0','1'"
336
+ sqlhelper_args[:cols_bools] << col_name
337
+ elsif col_type == :int and col_name.slice(-3, 3) == "_id"
338
+ sqlhelper_args[:cols_dbrows] << col_name
339
+ elsif col_type == :int or col_type == :decimal
340
+ sqlhelper_args[:cols_num] << col_name
341
+ elsif col_type == :varchar or col_type == :text or col_type == :enum
342
+ sqlhelper_args[:cols_str] << col_name
343
+ elsif col_type == :date or col_type == :datetime
344
+ sqlhelper_args[:cols_date] << col_name
345
+ self.define_date_methods(inst_methods, col_name_sym)
346
+ end
347
+
348
+ if col_type == :int or col_type == :decimal
349
+ self.define_numeric_methods(inst_methods, col_name_sym)
350
+ end
351
+
352
+ if col_type == :int or col_type == :varchar
353
+ self.define_text_methods(inst_methods, col_name_sym)
354
+ end
355
+
356
+ if col_type == :time
357
+ self.define_time_methods(inst_methods, col_name_sym)
358
+ end
359
+ end
360
+
361
+ if @columns_joined_tables
362
+ @columns_joined_tables.each do |table_name, table_data|
363
+ table_data[:where].each do |key, val|
364
+ val[:table] = @table if val.is_a?(Hash) and !val.key?(:table) and val[:type].to_sym == :col
365
+ end
366
+
367
+ table_data[:datarow] = @ob.args[:module].const_get(table_name.to_sym) if !table_data.key?(:datarow)
368
+ end
369
+
370
+ sqlhelper_args[:joined_tables] = @columns_joined_tables
371
+ end
372
+
373
+ @columns_sqlhelper_args = sqlhelper_args
374
+ end
375
+
376
+ self.init_class(d) if self.respond_to?(:init_class)
377
+ end
378
+
379
+ #This method helps returning objects and supports various arguments. It should be called by Object#list.
380
+ #===Examples
381
+ # ob.list(:User, {"username_lower" => "john doe"}) do |user|
382
+ # print user.id
383
+ # end
384
+ #
385
+ # array = ob.list(:User, {"id" => 1})
386
+ # print array.length
387
+ def self.list(d, &block)
388
+ args = d.args
389
+
390
+ if args["count"]
391
+ count = true
392
+ args.delete("count")
393
+ sql = "SELECT COUNT(#{@table_str}.#{@sep_col}id#{@sep_col}) AS count"
394
+ elsif args["select_col_as_array"]
395
+ select_col_as_array = true
396
+ sql = "SELECT #{@table_str}.#{@sep_col}#{args["select_col_as_array"]}#{@sep_col} AS id"
397
+ args.delete("select_col_as_array")
398
+ else
399
+ sql = "SELECT #{@table_str}.*"
400
+ end
401
+
402
+ qargs = nil
403
+ ret = self.list_helper(d)
404
+
405
+ sql << " FROM #{@table_str}"
406
+ sql << ret[:sql_joins]
407
+ sql << " WHERE 1=1"
408
+ sql << ret[:sql_where]
409
+
410
+ args.each do |key, val|
411
+ case key
412
+ when "return_sql"
413
+ #ignore
414
+ when :cloned_ubuf
415
+ qargs = {:cloned_ubuf => true}
416
+ else
417
+ raise "Invalid key: '#{key}' for '#{self.name}'. Valid keys are: '#{@columns_sqlhelper_args[:cols].keys.sort}'. Date-keys: '#{@columns_sqlhelper_args[:cols_date]}'."
418
+ end
419
+ end
420
+
421
+ #The count will bug if there is a group-by-statement.
422
+ grp_shown = false
423
+ if !count and !ret[:sql_groupby]
424
+ sql << " GROUP BY #{@table_str}.#{@sep_col}id#{@sep_col}"
425
+ grp_shown = true
426
+ end
427
+
428
+ if ret[:sql_groupby]
429
+ if !grp_shown
430
+ sql << " GROUP BY"
431
+ else
432
+ sql << ", "
433
+ end
434
+
435
+ sql << ret[:sql_groupby]
436
+ end
437
+
438
+ sql << ret[:sql_order]
439
+ sql << ret[:sql_limit]
440
+
441
+ return sql.to_s if args["return_sql"]
442
+
443
+ if select_col_as_array
444
+ enum = Enumerator.new do |yielder|
445
+ @db.q(sql, qargs) do |data|
446
+ yielder << data[:id]
447
+ end
448
+ end
449
+
450
+ if block
451
+ enum.each(&block)
452
+ return nil
453
+ elsif @ob.args[:array_enum]
454
+ return Array_enumerator.new(enum)
455
+ else
456
+ return enum.to_a
457
+ end
458
+ elsif count
459
+ ret = @db.query(sql).fetch
460
+ return ret[:count].to_i if ret
461
+ return 0
462
+ end
463
+
464
+ return @ob.list_bysql(self.classname, sql, qargs, &block)
465
+ end
466
+
467
+ #Helps call 'sqlhelper' on Baza::ModelHandler to generate SQL-strings.
468
+ def self.list_helper(d)
469
+ self.load_columns(d) if !@columns_sqlhelper_args
470
+ @columns_sqlhelper_args[:table] = @table
471
+ return @ob.sqlhelper(d.args, @columns_sqlhelper_args)
472
+ end
473
+
474
+ #Returns the classname of the object without any subclasses.
475
+ def self.classname
476
+ return @classname
477
+ end
478
+
479
+ #Sets the classname to something specific in order to hack the behaviour.
480
+ def self.classname=(newclassname)
481
+ @classname = newclassname
482
+ end
483
+
484
+ #Returns the table-name that should be used for this datarow.
485
+ #===Examples
486
+ # db.query("SELECT * FROM `#{Models::User.table}` WHERE username = 'John Doe'") do |data|
487
+ # print data[:id]
488
+ # end
489
+ def self.table
490
+ return @table
491
+ end
492
+
493
+ #This can be used to manually set the table-name. Useful when meta-programming classes that extends the datarow-class.
494
+ #===Examples
495
+ # Models::User.table = "prefix_User"
496
+ def self.table=(newtable)
497
+ @table = newtable
498
+ @columns_sqlhelper_args[:table] = @table if @columns_sqlhelper_args.is_a?(Hash)
499
+ end
500
+
501
+ #Returns the class-name but without having to call the class-table-method. To make code look shorter.
502
+ #===Examples
503
+ # user = ob.get_by(:User, {:username => 'John Doe'})
504
+ # db.query("SELECT * FROM `#{user.table}` WHERE username = 'John Doe'") do |data|
505
+ # print data[:id]
506
+ # end
507
+ def table
508
+ return self.class.table
509
+ end
510
+
511
+ #Initializes the object. This should be called from 'Baza::ModelHandler' and not manually.
512
+ #===Examples
513
+ # user = ob.get(:User, 3)
514
+ def initialize(data, args = nil)
515
+ if data.is_a?(Hash) and data.key?(:id)
516
+ @data = data
517
+ @id = @data[:id].to_i
518
+ elsif data
519
+ @id = data.to_i
520
+
521
+ classname = self.class.classname.to_sym
522
+ if self.class.ob.ids_cache_should.key?(classname)
523
+ #ID caching is enabled for this model - dont reload until first use.
524
+ raise Errno::ENOENT, "ID was not found in cache: '#{id}'." if !self.class.ob.ids_cache[classname].key?(@id)
525
+ @should_reload = true
526
+ else
527
+ #ID caching is not enabled - reload now to check if row exists. Else set 'should_reload'-variable if 'skip_reload' is set.
528
+ if !args or !args[:skip_reload]
529
+ self.reload
530
+ else
531
+ @should_reload = true
532
+ end
533
+ end
534
+ else
535
+ raise ArgumentError, "Could not figure out the data from '#{data.class.name}'."
536
+ end
537
+
538
+ if @id.to_i <= 0
539
+ raise "Invalid ID: '#{@id}' from '#{@data}'." if @data
540
+ raise "Invalid ID: '#{@id}'."
541
+ end
542
+ end
543
+
544
+ #Reloads the data from the database.
545
+ #===Examples
546
+ # old_username = user[:username]
547
+ # user.reload
548
+ # print "The username changed in the database!" if user[:username] != old_username
549
+ def reload
550
+ @data = self.class.db.single(self.class.table, {:id => @id})
551
+ raise Errno::ENOENT, "Could not find any data for the object with ID: '#{@id}' in the table '#{self.class.table}'." if !@data
552
+ @should_reload = false
553
+ end
554
+
555
+ #Tells the object that it should reloads its data because it has changed. It wont reload before it is required though, which may save you a couple of SQL-calls.
556
+ #===Examples
557
+ # obj = _ob.get(:User, 5)
558
+ # obj.should_reload
559
+ def should_reload
560
+ @should_reload = true
561
+ @data = nil
562
+ end
563
+
564
+ #Returns the data-hash that contains all the data from the database.
565
+ def data
566
+ self.reload if @should_reload
567
+ return @data
568
+ end
569
+
570
+ #Writes/updates new data for the object.
571
+ #===Examples
572
+ # user.update(:username => 'New username', :date_changed => Time.now)
573
+ def update(newdata)
574
+ self.class.db.update(self.class.table, newdata, {:id => @id})
575
+ self.should_reload
576
+ self.class.ob.call("object" => self, "signal" => "update")
577
+ end
578
+
579
+ #Forcefully destroys the object. This is done after deleting it and should not be called manually.
580
+ def destroy
581
+ @id = nil
582
+ @data = nil
583
+ @should_reload = nil
584
+ end
585
+
586
+ #Returns true if that key exists on the object.
587
+ #===Examples
588
+ # print "Looks like the user has a name." if user.key?(:name)
589
+ def key?(key)
590
+ self.reload if @should_reload
591
+ return @data.key?(key.to_sym)
592
+ end
593
+ alias has_key? key?
594
+
595
+ #Returns true if the object has been deleted.
596
+ #===Examples
597
+ # print "That user is deleted." if user.deleted?
598
+ def deleted?
599
+ return true if !@data and !@id
600
+ return false
601
+ end
602
+
603
+ #Returns true if the given object no longer exists in the database. Also destroys the data on the object and sets it to deleted-status, if it no longer exists.
604
+ #===Examples
605
+ # print "That user is deleted." if user.deleted_from_db?
606
+ def deleted_from_db?
607
+ #Try to avoid db-query if object is already deleted.
608
+ return true if self.deleted?
609
+
610
+ #Try to reload data. Destroy object and return true if the row is gone from the database.
611
+ begin
612
+ self.reload
613
+ return false
614
+ rescue Errno::ENOENT
615
+ self.destroy
616
+ return true
617
+ end
618
+ end
619
+
620
+ #Returns a specific data from the object by key.
621
+ # print "Username: #{user[:username]}\n"
622
+ # print "ID: #{user[:id]}\n"
623
+ # print "ID again: #{user.id}\n"
624
+ def [](key)
625
+ raise "Key was not a symbol: '#{key.class.name}'." if !key.is_a?(Symbol)
626
+ return @id if !@data and key == :id and @id
627
+ self.reload if @should_reload
628
+ raise "No data was loaded on the object? Maybe you are trying to call a deleted object? (#{self.class.classname}(#{@id}), #{@should_reload})" if !@data
629
+ return @data[key] if @data.key?(key)
630
+ raise "No such key: '#{key}' on '#{self.class.name}' (#{@data.keys.join(", ")}) (#{@should_reload})."
631
+ end
632
+
633
+ #Writes/updates a keys value on the object.
634
+ # user = ob.get_by(:User, {"username" => "John Doe"})
635
+ # user[:username] = 'New username'
636
+ def []=(key, value)
637
+ self.update(key.to_sym => value)
638
+ self.should_reload
639
+ end
640
+
641
+ #Returns the objects ID.
642
+ def id
643
+ raise Errno::ENOENT, "This object has been deleted." if self.deleted?
644
+ raise "No ID on object." if !@id
645
+ return @id
646
+ end
647
+
648
+ #This enable Wref to not return the wrong object.
649
+ def __object_unique_id__
650
+ return 0 if self.deleted?
651
+ return self.id
652
+ end
653
+
654
+ #Tries to figure out, and returns, the possible name or title for the object.
655
+ def name
656
+ self.reload if @should_reload
657
+
658
+ if @data.key?(:title)
659
+ return @data[:title]
660
+ elsif @data.key?(:name)
661
+ return @data[:name]
662
+ end
663
+
664
+ obj_methods = self.class.instance_methods(false)
665
+ [:name, :title].each do |method_name|
666
+ return self.method(method_name).call if obj_methods.index(method_name)
667
+ end
668
+
669
+ raise "Couldnt figure out the title/name of the object on class #{self.class.name}."
670
+ end
671
+
672
+ #Calls the name-method and returns a HTML-escaped value. Also "[no name]" if the name is empty.
673
+ def name_html
674
+ name_str = self.name.to_s
675
+ name_str = "[no name]" if name_str.length <= 0
676
+ return name_str
677
+ end
678
+
679
+ alias title name
680
+
681
+ #Loops through the data on the object.
682
+ #===Examples
683
+ # user = ob.get(:User, 1)
684
+ # user.each do |key, val|
685
+ # print "#{key}: #{val}\n" #=> username: John Doe
686
+ # end
687
+ def each(*args, &block)
688
+ self.reload if @should_reload
689
+ return @data.each(*args, &block)
690
+ end
691
+
692
+ #Hash-compatible.
693
+ def to_hash
694
+ self.reload if @should_reload
695
+ return @data.clone
696
+ end
697
+
698
+ #Returns a default-URL to show the object.
699
+ def url
700
+ cname = self.class.classname.to_s.downcase
701
+ return "?show=#{cname}_show&#{cname}_id=#{self.id}"
702
+ end
703
+
704
+ #Returns the URL for editting the object.
705
+ def url_edit
706
+ cname = self.class.classname.to_s.downcase
707
+ return "?show=#{cname}_edit&#{cname}_id=#{self.id}"
708
+ end
709
+
710
+ #Returns the HTML for making a link to the object.
711
+ def html(args = nil)
712
+ if args and args[:edit]
713
+ url = self.url_edit
714
+ else
715
+ url = self.url
716
+ end
717
+
718
+ return "<a href=\"#{Knj::Web.ahref_parse(url)}\">#{self.name_html}</a>"
719
+ end
720
+
721
+ private
722
+
723
+ #Various methods to define methods based on the columns for the datarow.
724
+ def self.define_translation_methods(args)
725
+ define_method("#{args[:val_dc]}=") do |newtransval|
726
+ begin
727
+ _hb.trans_set(self, {
728
+ args[:val] => newtransval
729
+ })
730
+ rescue NameError
731
+ _kas.trans_set(self, {
732
+ args[:val] => newtransval
733
+ })
734
+ end
735
+ end
736
+
737
+ define_method("#{args[:val_dc]}") do
738
+ begin
739
+ return _hb.trans(self, args[:val])
740
+ rescue NameError
741
+ return _kas.trans(self, args[:val])
742
+ end
743
+ end
744
+
745
+ define_method("#{args[:val_dc]}_html") do
746
+ begin
747
+ str = _hb.trans(self, args[:val])
748
+ rescue NameError
749
+ str = _kas.trans(self, args[:val])
750
+ end
751
+
752
+ if str.to_s.strip.length <= 0
753
+ return "[no translation for #{args[:val]}]"
754
+ end
755
+
756
+ return str
757
+ end
758
+ end
759
+
760
+ #Defines the boolean-methods based on enum-columns.
761
+ def self.define_bool_methods(inst_methods, col_name)
762
+ #Spawns a method on the class which returns true if the data is 1.
763
+ if !inst_methods.include?("#{col_name}?".to_sym)
764
+ define_method("#{col_name}?") do
765
+ return true if self[col_name.to_sym].to_s == "1"
766
+ return false
767
+ end
768
+ end
769
+ end
770
+
771
+ #Defines date- and time-columns based on datetime- and date-columns.
772
+ def self.define_date_methods(inst_methods, col_name)
773
+ if !inst_methods.include?("#{col_name}_str".to_sym)
774
+ define_method("#{col_name}_str") do |*method_args|
775
+ if Datet.is_nullstamp?(self[col_name])
776
+ return self.class.ob.events.call(:no_date, self.class.name)
777
+ end
778
+
779
+ return Datet.in(self[col_name]).out(*method_args)
780
+ end
781
+ end
782
+
783
+ if !inst_methods.include?(col_name)
784
+ define_method(col_name) do |*method_args|
785
+ return false if Datet.is_nullstamp?(self[col_name])
786
+ return Datet.in(self[col_name])
787
+ end
788
+ end
789
+ end
790
+
791
+ #Define various methods based on integer-columns.
792
+ def self.define_numeric_methods(inst_methods, col_name)
793
+ if !inst_methods.include?("#{col_name}_format".to_sym)
794
+ define_method("#{col_name}_format") do |*method_args|
795
+ return Knj::Locales.number_out(self[col_name], *method_args)
796
+ end
797
+ end
798
+ end
799
+
800
+ #Define methods to look up objects directly.
801
+ #===Examples
802
+ # user = Models::User.by_username('John Doe')
803
+ # print user.id
804
+ def self.define_text_methods(inst_methods, col_name)
805
+ if !inst_methods.include?("by_#{col_name}".to_sym) and RUBY_VERSION.to_s.slice(0, 3) != "1.8"
806
+ define_singleton_method("by_#{col_name}") do |arg|
807
+ return self.class.ob.get_by(self.class.table, {col_name.to_s => arg})
808
+ end
809
+ end
810
+ end
811
+
812
+ #Defines dbtime-methods based on time-columns.
813
+ def self.define_time_methods(inst_methods, col_name)
814
+ if !inst_methods.include?("#{col_name}_dbt".to_sym)
815
+ define_method("#{col_name}_dbt") do
816
+ return Baza::Db::Dbtime.new(self[col_name.to_sym])
817
+ end
818
+ end
819
+ end
820
+
821
+ #Memory friendly helper method that defines methods for 'has_many'.
822
+ def self.define_many_methods(classname, methodname, colname, where_args)
823
+ define_method(methodname) do |*args, &block|
824
+ if args and args[0]
825
+ list_args = args[0]
826
+ else
827
+ list_args = {}
828
+ end
829
+
830
+ list_args.merge!(where_args) if where_args
831
+ list_args[colname.to_s] = self.id
832
+
833
+ return self.class.ob.list(classname, list_args, &block)
834
+ end
835
+
836
+ define_method("#{methodname}_count".to_sym) do |*args|
837
+ list_args = args[0] if args and args[0]
838
+ list_args = {} if !list_args
839
+ list_args[colname.to_s] = self.id
840
+ list_args["count"] = true
841
+
842
+ return self.class.ob.list(classname, list_args)
843
+ end
844
+
845
+ define_method("#{methodname}_last".to_sym) do |args|
846
+ args = {} if !args
847
+ return self.class.ob.list(classname, {"orderby" => [["id", "desc"]], "limit" => 1}.merge(args))
848
+ end
849
+ end
850
+
851
+ #Memory friendly helper method that defines methods for 'has_one'.
852
+ def self.define_one_methods(classname, methodname, colname)
853
+ define_method(methodname) do
854
+ return self.class.ob.get_try(self, colname, classname)
855
+ end
856
+
857
+ methodname_html = "#{methodname}_html".to_sym
858
+ define_method(methodname_html) do |*args|
859
+ obj = self.__send__(methodname)
860
+ return self.class.ob.events.call(:no_html, classname) if !obj
861
+
862
+ raise "Class '#{classname}' does not have a 'html'-method." if !obj.respond_to?(:html)
863
+ return obj.html(*args)
864
+ end
865
+
866
+ methodname_name = "#{methodname}_name".to_sym
867
+ define_method(methodname_name) do |*args|
868
+ obj = self.__send__(methodname)
869
+ return self.class.ob.events.call(:no_name, classname) if !obj
870
+ return obj.name(*args)
871
+ end
872
+ end
873
+ end