Class: Baza::Model

Inherits:
Object
  • Object
show all
Defined in:
lib/baza/model.rb

Overview

This class helps create models in a framework with Baza::Db and Baza::ModelHandler.

Examples

db = Baza::Db.new(type: :sqlite3, path: "somepath.sqlite3")
ob = Baza::ModelHandler.new(db: db, datarow: true, path: "path_of_model_class_files")
user = ob.get(:User, 1) #=> <Models::User> that extends <Baza::Datarow>

Constant Summary collapse

@@refs =
{}

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data, args = nil) ⇒ Model

Initializes the object. This should be called from ‘Baza::ModelHandler’ and not manually.

Examples

user = ob.get(:User, 3)


512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
# File 'lib/baza/model.rb', line 512

def initialize(data, args = nil)
  if data.is_a?(Hash) && data.key?(:id)
    @data = data
    @id = @data[:id].to_i
  elsif data
    @id = data.to_i

    classname = self.class.classname.to_sym
    if self.class.ob.ids_cache_should.key?(classname)
      # ID caching is enabled for this model - dont reload until first use.
      raise Errno::ENOENT, "ID was not found in cache: '#{id}'." unless self.class.ob.ids_cache[classname].key?(@id)
      @should_reload = true
    else
      # ID caching is not enabled - reload now to check if row exists. Else set 'should_reload'-variable if 'skip_reload' is set.
      if !args || !args[:skip_reload]
        reload
      else
        @should_reload = true
      end
    end
  else
    raise ArgumentError, "Could not figure out the data from '#{data.class.name}'."
  end

  return unless @id.to_i <= 0

  raise "Invalid ID: '#{@id}' from '#{@data}'." if @data
  raise "Invalid ID: '#{@id}'."
end

Class Attribute Details

.autodelete_dataObject (readonly)

Returns the value of attribute autodelete_data.



69
70
71
# File 'lib/baza/model.rb', line 69

def autodelete_data
  @autodelete_data
end

.autozero_dataObject (readonly)

Returns the value of attribute autozero_data.



74
75
76
# File 'lib/baza/model.rb', line 74

def autozero_data
  @autozero_data
end

.classnameObject

Returns the value of attribute classname.



78
79
80
# File 'lib/baza/model.rb', line 78

def classname
  @classname
end

.dbObject (readonly)

Returns the value of attribute db.



26
27
28
# File 'lib/baza/model.rb', line 26

def db
  @db
end

.depending_dataObject (readonly)

Returns the value of attribute depending_data.



51
52
53
# File 'lib/baza/model.rb', line 51

def depending_data
  @depending_data
end

.obObject (readonly)

Returns the value of attribute ob.



21
22
23
# File 'lib/baza/model.rb', line 21

def ob
  @ob
end

.tableObject

Returns the value of attribute table.



488
489
490
# File 'lib/baza/model.rb', line 488

def table
  @table
end

.translationsObject (readonly)

Returns the value of attribute translations.



291
292
293
# File 'lib/baza/model.rb', line 291

def translations
  @translations
end

Class Method Details

.columns_sqlhelper_argsObject

Returns various data for the objects-sql-helper. This can be used to view various informations about the columns and more.



301
302
303
304
# File 'lib/baza/model.rb', line 301

def self.columns_sqlhelper_args
  raise "No SQLHelper arguments has been spawned yet." unless @columns_sqlhelper_args
  @columns_sqlhelper_args
end

.has_many(arr) ⇒ Object

This is used to define datarows that this object can have a lot of.

Examples

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.

class Models::User < Baza::Datarow
  has_many [
    [:Picture, :user_id, :pictures],
    {class: :File, col: :user_id, method: :files}
  ]
end


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/baza/model.rb', line 106

def self.has_many(arr)
  arr.each do |val|
    if val.is_a?(Array)
      classname, colname, methodname = *val
    elsif val.is_a?(Hash)
      classname = nil
      colname = nil
      methodname = nil

      val.each do |hkey, hval|
        case hkey
        when :class
          classname = hval
        when :col
          colname = hval
        when :method
          methodname = hval
        when :depends, :autodelete, :autozero, :where
          # Ignore
        else
          raise "Invalid key for 'has_many': '#{hkey}'."
        end
      end

      colname = "#{name.to_s.split("::").last.to_s.downcase}_id" if colname.to_s.empty?

      if val[:depends]
        @depending_data = [] unless @depending_data
        @depending_data << {
          colname: colname,
          classname: classname
        }
      end

      if val[:autodelete]
        @autodelete_data = [] unless @autodelete_data
        @autodelete_data << {
          colname: colname,
          classname: classname
        }
      end

      if val[:autozero]
        @autozero_data = [] unless @autozero_data
        @autozero_data << {
          colname: colname,
          classname: classname
        }
      end
    else
      raise "Unknown argument: '#{val.class.name}'."
    end

    raise "No classname given." unless classname
    methodname = "#{StringCases.camel_to_snake(classname)}s" unless methodname
    raise "No column was given for '#{name}' regarding has-many-class: '#{classname}'." unless colname

    if val.is_a?(Hash) && val.key?(:where)
      where_args = val[:where]
    else
      where_args = nil
    end

    define_many_methods(classname, methodname, colname, where_args)

    joined_tables(
      classname => {
        where: {
          colname.to_s => {type: :col, name: :id}
        }
      }
    )
  end
end

.has_one(arr) ⇒ Object

This define is this object has one element of another datarow-class. It define various methods and joins based on that.

Examples

class Models::User < Baza::Datarow
  has_one [
    #Defines the method 'group', which returns a 'Group'-object by the column 'group_id'.
    :Group,

    #Defines the method 'type', which returns a 'Type'-object by the column 'type_id'.
    {class: :Type, col: :type_id, method: :type}
  ]
end


192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/baza/model.rb', line 192

def self.has_one(arr)
  arr = [arr] if arr.is_a?(Symbol)

  arr.each do |val|
    methodname = nil
    colname = nil
    classname = nil

    if val.is_a?(Symbol)
      classname = val
      methodname = val.to_s.downcase.to_sym
      colname = "#{val.to_s.downcase}_id".to_sym
    elsif val.is_a?(Array)
      classname, colname, methodname = *val
    elsif val.is_a?(Hash)
      classname = nil
      colname = nil
      methodname = nil

      val.each do |hkey, hval|
        case hkey
        when :class
          classname = hval
        when :col
          colname = hval
        when :method
          methodname = hval
        when :required
          # Ignore
        else
          raise "Invalid key for class '#{name}' functionality 'has_many': '#{hkey}'."
        end
      end

      if val[:required]
        colname = "#{classname.to_s.downcase}_id".to_sym unless colname
        required_data << {
          col: colname,
          class: classname
        }
      end
    else
      raise "Unknown argument-type: '#{arr.class.name}'."
    end

    methodname = StringCases.camel_to_snake(classname) unless methodname
    colname = "#{classname.to_s.downcase}_id".to_sym unless colname
    define_one_methods(classname, methodname, colname)

    joined_tables(
      classname => {
        where: {
          "id" => {type: :col, name: colname}
        }
      }
    )
  end
end

.has_translation(arr) ⇒ Object

This method initializes joins, sets methods to update translations and makes the translations automatically be deleted when the object is deleted.

Examples

class Models::Article < Baza::Datarow
  #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.
  has_translation [:title, :content]
end

article = ob.get(:Article, 1)
print "The title in the current language is: '#{article.title}'."

article.title = 'Title in english if the language is english'


262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/baza/model.rb', line 262

def self.has_translation(arr)
  @translations = [] unless @translations

  arr.each do |val|
    @translations << val

    val_dc = val.to_s.downcase
    table_name = "Translation_#{val_dc}".to_sym

    joined_tables(
      table_name => {
        where: {
          "object_class" => name,
          "object_id" => {type: :col, name: :id},
          "key" => val.to_s,
          "locale" => proc { |_d| _session[:locale] }
        },
        parent_table: :Translation,
        datarow: Knj::Translations::Translation,
        ob: @ob
      }
    )

    define_translation_methods(val: val, val_dc: val_dc)
  end
end

.initialized?Boolean

Returns true if this class has been initialized.

Returns:

  • (Boolean)


55
56
57
58
# File 'lib/baza/model.rb', line 55

def self.initialized?
  false unless @columns_sqlhelper_args
  true
end

.joined_tables(hash) ⇒ Object

Returns data about joined tables for this class.



295
296
297
298
# File 'lib/baza/model.rb', line 295

def self.joined_tables(hash)
  @columns_joined_tables = {} unless @columns_joined_tables
  @columns_joined_tables.merge!(hash)
end

.list(d, &block) ⇒ Object

This method helps returning objects and supports various arguments. It should be called by Object#list.

Examples

ob.list(:User, {"username_lower" => "john doe"}) do |user|
  print user.id
end

array = ob.list(:User, {"id" => 1})
print array.length


395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
# File 'lib/baza/model.rb', line 395

def self.list(d, &block)
  args = d.args

  if args["count"]
    count = true
    args.delete("count")
    sql = "SELECT COUNT(#{@table_str}.#{@sep_col}id#{@sep_col}) AS count"
  elsif args["select_col_as_array"]
    select_col_as_array = true
    sql = "SELECT #{@table_str}.#{@sep_col}#{args["select_col_as_array"]}#{@sep_col} AS id"
    args.delete("select_col_as_array")
  else
    sql = "SELECT #{@table_str}.*"
  end

  qargs = nil
  ret = list_helper(d)

  sql << " FROM #{@table_str}"
  sql << ret[:sql_joins]
  sql << " WHERE 1=1"
  sql << ret[:sql_where]

  args.each_key do |key|
    case key
    when "return_sql"
      # Ignore
    when :cloned_ubuf
      qargs = {cloned_ubuf: true}
    else
      raise "Invalid key: '#{key}' for '#{name}'. Valid keys are: '#{@columns_sqlhelper_args[:cols].keys.sort}'. Date-keys: '#{@columns_sqlhelper_args[:cols_date]}'."
    end
  end

  # The count will bug if there is a group-by-statement.
  grp_shown = false
  if !count && !ret[:sql_groupby]
    sql << " GROUP BY #{@table_str}.#{@sep_col}id#{@sep_col}"
    grp_shown = true
  end

  if ret[:sql_groupby]
    if !grp_shown
      sql << " GROUP BY"
    else
      sql << ", "
    end

    sql << ret[:sql_groupby]
  end

  sql << ret[:sql_order]
  sql << ret[:sql_limit]

  return sql.to_s if args["return_sql"]

  if select_col_as_array
    enum = Enumerator.new do |yielder|
      @db.q(sql, qargs) do |data|
        yielder << data[:id]
      end
    end

    if block
      enum.each(&block)
      return nil
    elsif @ob.args[:array_enum]
      return ArrayEnumerator.new(enum)
    else
      return enum.to_a
    end
  elsif count
    ret = @db.query(sql).fetch
    return ret[:count].to_i if ret
    return 0
  end

  @ob.list_bysql(classname, sql, qargs, &block)
end

.list_helper(d) ⇒ Object

Helps call ‘sqlhelper’ on Baza::ModelHandler to generate SQL-strings.



476
477
478
479
480
# File 'lib/baza/model.rb', line 476

def self.list_helper(d)
  load_columns(d) unless @columns_sqlhelper_args
  @columns_sqlhelper_args[:table] = @table
  @ob.sqlhelper(d.args, @columns_sqlhelper_args)
end

.load_columns(d) ⇒ Object

Called by Baza::ModelHandler to initialize the model and load column-data on-the-fly.



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/baza/model.rb', line 307

def self.load_columns(d)
  @ob = d.ob
  @db = d.db

  @classname = name.split("::").last.to_sym unless @classname
  @table = @classname unless @table
  @mutex = Monitor.new unless @mutex

  # Cache these to avoid method-lookups.
  @sep_col = @db.sep_col
  @sep_table = @db.sep_table
  @table_str = "#{@sep_table}#{@db.escape_table(@table)}#{@sep_table}"

  @mutex.synchronize do
    inst_methods = instance_methods(false)

    sqlhelper_args = {
      db: @db,
      table: @table,
      cols_bools: [],
      cols_date: [],
      cols_dbrows: [],
      cols_num: [],
      cols_str: [],
      cols: {}
    }

    sqlhelper_args[:table] = @table

    @db.tables[table].columns do |col_obj|
      col_name = col_obj.name.to_s
      col_name_sym = col_name.to_sym
      col_type = col_obj.type
      col_type = :int if col_type == :bigint || col_type == :tinyint || col_type == :mediumint || col_type == :smallint
      sqlhelper_args[:cols][col_name] = true

      define_bool_methods(inst_methods, col_name)

      if col_type == :enum && col_obj.maxlength == "'0','1'"
        sqlhelper_args[:cols_bools] << col_name
      elsif col_type == :int && col_name.slice(-3, 3) == "_id"
        sqlhelper_args[:cols_dbrows] << col_name
      elsif col_type == :int || col_type == :decimal
        sqlhelper_args[:cols_num] << col_name
      elsif col_type == :varchar || col_type == :text || col_type == :enum
        sqlhelper_args[:cols_str] << col_name
      elsif col_type == :date || col_type == :datetime
        sqlhelper_args[:cols_date] << col_name
        define_date_methods(inst_methods, col_name_sym)
      end

      if col_type == :int || col_type == :decimal
        define_numeric_methods(inst_methods, col_name_sym)
      end

      if col_type == :int || col_type == :varchar
        define_text_methods(inst_methods, col_name_sym)
      end

      define_time_methods(inst_methods, col_name_sym) if col_type == :time
    end

    if @columns_joined_tables
      @columns_joined_tables.each do |table_name, table_data|
        table_data[:where].each do |_key, val|
          val[:table] = @table if val.is_a?(Hash) && !val.key?(:table) && val[:type].to_sym == :col
        end

        table_data[:datarow] = @ob.args[:module].const_get(table_name.to_sym) unless table_data.key?(:datarow)
      end

      sqlhelper_args[:joined_tables] = @columns_joined_tables
    end

    @columns_sqlhelper_args = sqlhelper_args
  end

  init_class(d) if self.respond_to?(:init_class)
end

.nullstamp?(stamp) ⇒ Boolean

This tests if a certain string is a date-null-stamp.

Examples

time_str = dbrow[:date]
print "No valid date on the row." if Baza::Datarow.is_nullstamp?(time_str)

Returns:

  • (Boolean)


92
93
94
95
# File 'lib/baza/model.rb', line 92

def self.nullstamp?(stamp)
  return true if !stamp || stamp == "0000-00-00 00:00:00" || stamp == "0000-00-00"
  false
end

.required_dataObject

This is used by ‘Baza::ModelHandler’ to find out what data is required for this class. Returns the array that tells about required data.

Examples

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.

class Models::User < Baza::Datarow
  has_one [
    {class: :Group, col: :group_id, method: :group, required: true}
  ]
end


37
38
39
40
# File 'lib/baza/model.rb', line 37

def self.required_data
  @required_data = [] unless @required_data
  @required_data
end

Instance Method Details

#[](key) ⇒ Object

Returns a specific data from the object by key.

print "Username: #{user[:username]}\n"
print "ID: #{user[:id]}\n"
print "ID again: #{user.id}\n"


622
623
624
625
626
627
628
629
# File 'lib/baza/model.rb', line 622

def [](key)
  raise "Key was not a symbol: '#{key.class.name}'." unless key.is_a?(Symbol)
  return @id if !@data && key == :id && @id
  reload if @should_reload
  raise "No data was loaded on the object? Maybe you are trying to call a deleted object? (#{self.class.classname}(#{@id}), #{@should_reload})" unless @data
  return @data[key] if @data.key?(key)
  raise "No such key: '#{key}' on '#{self.class.name}' (#{@data.keys.join(", ")}) (#{@should_reload})."
end

#[]=(key, value) ⇒ Object

Writes/updates a keys value on the object.

user = ob.get_by(:User, {"username" => "John Doe"})
user[:username] = 'New username'


634
635
636
637
# File 'lib/baza/model.rb', line 634

def []=(key, value)
  update(key.to_sym => value)
  should_reload
end

#__object_unique_id__Object

This enable Wref to not return the wrong object.



647
648
649
650
# File 'lib/baza/model.rb', line 647

def __object_unique_id__
  return 0 if self.deleted?
  id
end

#dataObject

Returns the data-hash that contains all the data from the database.



563
564
565
566
# File 'lib/baza/model.rb', line 563

def data
  reload if @should_reload
  @data
end

#dbObject

Returns the Baza::Db which handels this model.



15
16
17
# File 'lib/baza/model.rb', line 15

def db
  self.class.db
end

#deleted?Boolean

Returns true if the object has been deleted.

Examples

print "That user is deleted." if user.deleted?

Returns:

  • (Boolean)


596
597
598
599
# File 'lib/baza/model.rb', line 596

def deleted?
  return true if !@data && !@id
  false
end

#deleted_from_db?Boolean

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.

Examples

print “That user is deleted.” if user.deleted_from_db?

Returns:

  • (Boolean)


604
605
606
607
608
609
610
611
612
613
614
615
616
# File 'lib/baza/model.rb', line 604

def deleted_from_db?
  # Try to avoid db-query if object is already deleted.
  return true if self.deleted?

  # Try to reload data. Destroy object and return true if the row is gone from the database.
  begin
    reload
    return false
  rescue Errno::ENOENT
    destroy
    return true
  end
end

#destroyObject

Forcefully destroys the object. This is done after deleting it and should not be called manually.



578
579
580
581
582
# File 'lib/baza/model.rb', line 578

def destroy
  @id = nil
  @data = nil
  @should_reload = nil
end

#each(*args, &block) ⇒ Object

Loops through the data on the object.

Examples

user = ob.get(:User, 1)
user.each do |key, val|
  print "#{key}: #{val}\n" #=> username: John Doe
end


685
686
687
688
# File 'lib/baza/model.rb', line 685

def each(*args, &block)
  reload if @should_reload
  @data.each(*args, &block)
end

#html(args = nil) ⇒ Object

Returns the HTML for making a link to the object.



709
710
711
712
713
714
715
716
717
# File 'lib/baza/model.rb', line 709

def html(args = nil)
  if args && args[:edit]
    url = url_edit
  else
    url = self.url
  end

  "<a href=\"#{Knj::Web.ahref_parse(url)}\">#{name_html}</a>"
end

#idObject

Returns the objects ID.

Raises:

  • (Errno::ENOENT)


640
641
642
643
644
# File 'lib/baza/model.rb', line 640

def id
  raise Errno::ENOENT, "This object has been deleted." if deleted?
  raise "No ID on object." unless @id
  @id
end

#key?(key) ⇒ Boolean Also known as: has_key?

Returns true if that key exists on the object.

Examples

print "Looks like the user has a name." if user.key?(:name)

Returns:

  • (Boolean)


587
588
589
590
# File 'lib/baza/model.rb', line 587

def key?(key)
  reload if @should_reload
  @data.key?(key.to_sym)
end

#knj?Boolean

This helps various parts of the framework determine if this is a datarow class without requiring it.

Examples

print "This is a knj-object." if obj.respond_to?("is_knj?")

Returns:

  • (Boolean)


84
85
86
# File 'lib/baza/model.rb', line 84

def knj?
  true
end

#nameObject Also known as: title

Tries to figure out, and returns, the possible name or title for the object.



653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
# File 'lib/baza/model.rb', line 653

def name
  reload if @should_reload

  if @data.key?(:title)
    return @data[:title]
  elsif @data.key?(:name)
    return @data[:name]
  end

  obj_methods = self.class.instance_methods(false)
  [:name, :title].each do |method_name|
    return method(method_name).call if obj_methods.index(method_name)
  end

  raise "Couldnt figure out the title/name of the object on class #{self.class.name}."
end

#name_htmlObject

Calls the name-method and returns a HTML-escaped value. Also “[no name]” if the name is empty.



671
672
673
674
675
# File 'lib/baza/model.rb', line 671

def name_html
  name_str = name.to_s
  name_str = "[no name]" if name_str.empty?
  name_str
end

#obObject

Returns the Baza::ModelHandler which handels this model.



10
11
12
# File 'lib/baza/model.rb', line 10

def ob
  self.class.ob
end

#reloadObject

Reloads the data from the database.

Examples

old_username = user[:username]
user.reload
print "The username changed in the database!" if user[:username] != old_username

Raises:

  • (Errno::ENOENT)


547
548
549
550
551
# File 'lib/baza/model.rb', line 547

def reload
  @data = self.class.db.single(self.class.table, id: @id)
  raise Errno::ENOENT, "Could not find any data for the object with ID: '#{@id}' in the table '#{self.class.table}'." unless @data
  @should_reload = false
end

#should_reloadObject

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.

Examples

obj = _ob.get(:User, 5)
obj.should_reload


557
558
559
560
# File 'lib/baza/model.rb', line 557

def should_reload
  @should_reload = true
  @data = nil
end

#tableObject

Returns the class-name but without having to call the class-table-method. To make code look shorter.

Examples

user = ob.get_by(:User, {username: 'John Doe'})
db.query("SELECT * FROM `#{user.table}` WHERE username = 'John Doe'") do |data|
  print data[:id]
end


505
506
507
# File 'lib/baza/model.rb', line 505

def table
  self.class.table
end

#to_hashObject

Hash-compatible.



691
692
693
694
# File 'lib/baza/model.rb', line 691

def to_hash
  reload if @should_reload
  @data.clone
end

#update(newdata) ⇒ Object

Writes/updates new data for the object.

Examples

user.update(username: 'New username', date_changed: Time.now)


571
572
573
574
575
# File 'lib/baza/model.rb', line 571

def update(newdata)
  self.class.db.update(self.class.table, newdata, id: @id)
  should_reload
  self.class.ob.call("object" => self, "signal" => "update")
end

#urlObject

Returns a default-URL to show the object.



697
698
699
700
# File 'lib/baza/model.rb', line 697

def url
  cname = self.class.classname.to_s.downcase
  "?show=#{cname}_show&#{cname}_id=#{id}"
end

#url_editObject

Returns the URL for editting the object.



703
704
705
706
# File 'lib/baza/model.rb', line 703

def url_edit
  cname = self.class.classname.to_s.downcase
  "?show=#{cname}_edit&#{cname}_id=#{id}"
end