baza 0.0.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/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