Class: Ink::Model
- Inherits:
-
Object
- Object
- Ink::Model
- Defined in:
- lib/webink/model.rb
Overview
Model class
Usage
Models are usually derived from. So let’s assume there is a class called Apple < Ink::Model
apple = Apple.new {:color => "red", :diameter => 4}
apple = Apple.new [ "red", 4 ]
The constructor checks, if there are class methods ‘fields’ and ‘foreign’ defined. If that check is positive, it will match the parameter Hash to the fields, that are set for the database, and throw an exception if fields is lacking an entry (excluded the primary key). The other case just creates an Apple with the Hash as instance variables. An alternate method of creating a new apple is by providing an Array of values in the same order as in the fields definition.
puts apple.color
This prints “red” to the stdout, since getter and setter methods are automatically added for either the Hash, or the fields and foreign keys.
apple.save
You can save your apple by using the save method. New instances will create a new row in the database, and update its primary key. Old instances just update the fields. Relationships are set to nil by default and will not be touched while nil.
treeinstance.apple = [1,2,myapple]
treeinstance.save
To insert relationship data, you can provide them by array, value or reference, so setting treeinstance.apple to 1 is allowed, also to myapple, or an array or a combination. An empty array [] will remove all references. This works both ways, just consider the relationship type, as an apple cannot have more than one tree.
treeinstance.delete
The model provides a convenience method for deletion. It removes all references from relationships, but does not remove the relationships themselves, so you must fetch all related data, and delete them by ‘hand’ if you will.
treeinstance.find_references Apple
This convenience method finds all apples for this tree and makes them available in the accessor. If the Tree-Apple relationship is a *_one, then there is only one object in the accessor, otherwise an Array of objects.
Fields and foreign sample config
class Apple < Ink::Model
def self.fields
fields = {
:id => "PRIMARY KEY"
:color => [ "VARCHAR", "NOT NULL" ],
:diameter => [ "NUMERIC", "NOT NULL" ]
}
fields
end
def self.foreign
foreign = {
"Tree" => "one_many"
}
foreign
end
end
Let’s look at this construct. The constructor is inherited from Ink::Model, so are its methods. ‘fields’ defines a Hash of Arrays, that will create the Database table for us. ‘foreign’ handles the contraints to other classes, here it reads: one “Tree” has many Apples, other constructs could be: [one “Tree” has one Apple, many “Tree”s have many Apples, many “Tree”s have one Apple] => [one_one, many_many, many_one] Obviously the Tree class requires a foreign with “Apple” mapped to “many_one” to match this schema.
You can override the automatically generated getters and setters in any Model class you create by just redefining the methods.
Convenience methods
self.primary_key
self.primary_key_type
self.foreign_key
self.foreign_key_type
primary_key is the name of the primary key (default “id”). The foreign_key has a combination of “classname”_“primary_key” (i.e. “apple_id”)
self.class_name
Equivalent to class.name
self.table_name
Generates a table representation of the class. (Apple as “apple” and MyApple as “my_apple”)
self.str_to_classname(str)
Converts a table name to class name. This method takes a string.
self.str_to_tablename(str)
Converts a class name to table name. This method takes a string.
Class Method Summary collapse
-
.class_name ⇒ Object
Class method.
-
.classname(str) ⇒ Object
Class method.
-
.create ⇒ Object
Class method.
-
.foreign_key ⇒ Object
Class method.
-
.foreign_key_type ⇒ Object
Class method.
-
.make_safe(value) ⇒ Object
Class method.
-
.primary_key ⇒ Object
Class method.
-
.primary_key_type ⇒ Object
Class method.
-
.str_to_classname(str) ⇒ Object
Class method.
-
.str_to_tablename(str) ⇒ Object
Class method.
-
.table_name ⇒ Object
Class method.
Instance Method Summary collapse
-
#delete ⇒ Object
Instance method.
-
#find_references(foreign_class) ⇒ Object
Instance method.
-
#initialize(data) ⇒ Model
constructor
Constructor.
-
#save ⇒ Object
Instance method.
Constructor Details
#initialize(data) ⇒ Model
Constructor
Keys from the data parameter will be converted into instance variables with getters and setters in place. The data parameter can be an Array of length of the defined fields or without the primary key. The order needs to be the same as the defined fields. The primary key has no setter, but adds a getter called pk for convenience.
- param data:
-
Hash of String => Objects or Array of Objects
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 |
# File 'lib/webink/model.rb', line 137 def initialize(data) if self.class.respond_to? :fields i = 0 self.class.fields.each do |k,v| if data.is_a? Array if data.length < self.class.fields.length - 1 or data.length > self.class.fields.length raise LoadError.new(<<ERR) Model cannot be loaded, wrong number or arguments #{data.length} expected #{self.class.fields.length} or #{self.class.fields.length - 1} ERR end if self.class.primary_key != k or data.length == self.class.fields.length init_field k, data[i] i += 1 else init_field self.class.primary_key, nil end else if not data.key?(k.to_s) and self.class.primary_key != k raise LoadError.new(<<ERR) Model cannot be loaded, argument missing: #{k} ERR end init_field k, data[k.to_s] end end if self.class.respond_to? :foreign self.class.foreign.each do |k,v| init_foreign k end end else data.each do |k,v| init_no_fields k, v end end end |
Class Method Details
.class_name ⇒ Object
Class method
This will retrieve a string-representation of the model name
- returns:
-
valid classname
427 428 429 |
# File 'lib/webink/model.rb', line 427 def self.class_name self.name end |
.classname(str) ⇒ Object
Class method
This will check the parent module for existing classnames that match the input of the str parameter. Once found, it returns the class, not the string of the class.
- param str:
-
some string
- returns:
-
valid class or nil
478 479 480 481 482 483 484 485 486 487 488 489 490 491 |
# File 'lib/webink/model.rb', line 478 def self.classname(str) res = [] if str[0] =~ /^[a-z]/ str.scan(/((^|_)([a-z0-9]+))/) { |s| if s.length > 0 res.push(s[2][0].upcase + ((s[2].length > 1) ? s[2][1,s[2].length] : "")) end } else res.push str end Module.const_get(res.join).is_a?(Class) ? Module.const_get(res.join) : nil end |
.create ⇒ Object
Class method
This will create SQL statements for creating the database tables. ‘fields’ method is mandatory for this, and ‘foreign’ is optional.
- returns:
-
Array of SQL statements
381 382 383 384 385 386 387 388 389 390 391 392 393 394 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 |
# File 'lib/webink/model.rb', line 381 def self.create result = Array.new if not self.respond_to?(:fields) puts "Skipping #{self.name}...." return [] end string = "CREATE TABLE #{self.table_name} (" mfk = self.foreign_key string += self.fields.map do |k,v| if k != self.primary_key "`#{k}` #{v*" "}" else "#{Ink::Database.database.primary_key_autoincrement(k)*" "}" end end.join(",") if self.respond_to? :foreign tmp = self.foreign.map do |k,v| f_class = Ink::Model::classname(k) if v == "many_many" and (self.name <=> k) < 0 result.push <<QUERY CREATE TABLE #{self.table_name}_#{Ink::Model::str_to_tablename(k)} (#{Ink::Database.database.primary_key_autoincrement*" "}, `#{self.foreign_key}` #{self.foreign_key_type}, `#{f_class.foreign_key}` #{f_class.foreign_key_type}); QUERY nil end if v == "one_many" or (v == "one_one" and (self.name <=> k) < 0) "`#{f_class.foreign_key}` #{f_class.foreign_key_type}" else nil end end.compact.join(",") string += ",#{tmp}" if not tmp.empty? end string += ");" result.push string result end |
.foreign_key ⇒ Object
Class method
This will create the foreign key from the defined primary key
- returns:
-
key name or nil
524 525 526 527 |
# File 'lib/webink/model.rb', line 524 def self.foreign_key pk = self.primary_key return (pk) ? "#{self.table_name}_#{pk}" : nil end |
.foreign_key_type ⇒ Object
Class method
This will find the foreign key type, taken from the primary key in fields.
- returns:
-
key type or nil
534 535 536 |
# File 'lib/webink/model.rb', line 534 def self.foreign_key_type self.primary_key_type end |
.make_safe(value) ⇒ Object
Class method
Similar to making an Object sql safe. Escapes quotes.
- param value:
-
Object
- returns:
-
safe Object
183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/webink/model.rb', line 183 def self.make_safe(value) if value.nil? nil elsif value.is_a? String value.gsub(/'/, ''') elsif value.is_a? Numeric value else "\'#{value}\'" end end |
.primary_key ⇒ Object
Class method
This will find the primary key, as defined in the fields class method.
- returns:
-
key name or nil
498 499 500 501 502 503 504 |
# File 'lib/webink/model.rb', line 498 def self.primary_key if self.respond_to? :fields field = self.fields.select{|k,v| v.is_a?(String) and v == "PRIMARY KEY"} return field.keys.first end nil end |
.primary_key_type ⇒ Object
Class method
This will find the primary key type, as defined in the fields class method.
- returns:
-
key type or nil
511 512 513 514 515 516 517 518 |
# File 'lib/webink/model.rb', line 511 def self.primary_key_type if self.respond_to? :fields field = self.fields.select{|k,v| v.is_a?(String) and v == "PRIMARY KEY"} return Ink::Database.database. primary_key_autoincrement(field.keys.first)[1] end nil end |
.str_to_classname(str) ⇒ Object
Class method
This will check the parent module for existing classnames that match the input of the str parameter.
- param str:
-
some string
- returns:
-
valid classname or nil
445 446 447 448 449 450 451 452 453 454 |
# File 'lib/webink/model.rb', line 445 def self.str_to_classname(str) res = [] str.scan(/((^|_)([a-z0-9]+))/) { |s| if s.length > 0 res.push(s[2][0].upcase + ((s[2].length > 1) ? s[2][1,s[2].length] : "")) end } Module.const_get(res.join).is_a?(Class) ? res.join : nil end |
.str_to_tablename(str) ⇒ Object
Class method
This will check the parent module for existing classnames that match the input of the str parameter. Once found, it converts the string into the matching tablename.
- param str:
-
some string
- returns:
-
valid tablename or nil
463 464 465 466 467 468 469 |
# File 'lib/webink/model.rb', line 463 def self.str_to_tablename(str) res = [] str.scan(/([A-Z][a-z0-9]*)/) { |s| res.push (res.length>0) ? "_" + s.join.downcase : s.join.downcase } Module.const_get(str).is_a?(Class) ? res.join : nil end |
.table_name ⇒ Object
Class method
This will retrieve a tablename-representation of the model name
- returns:
-
valid tablename
435 436 437 |
# File 'lib/webink/model.rb', line 435 def self.table_name self.str_to_tablename self.name end |
Instance Method Details
#delete ⇒ Object
Instance method
Deletes the data from the database, essentially making the instance obsolete. Disregard from using the instance anymore. All links between models will be removed also.
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
# File 'lib/webink/model.rb', line 333 def delete if not self.class.respond_to? :fields raise NotImplementedError.new(<<ERR) Cannot delete from Database without field definitions ERR end if self.class.respond_to? :foreign self.class.foreign.each do |k,v| Ink::Database.database.delete_all_links(self, Ink::Model.classname(k), v) end end pkvalue = instance_variable_get "@#{self.class.primary_key}" Ink::Database.database.remove self.class.name, <<QUERY WHERE `#{self.class.primary_key}`=#{ (pkvalue.is_a?(Numeric)) ? pkvalue : "\'#{pkvalue}\'" } QUERY end |
#find_references(foreign_class) ⇒ Object
Instance method
Queries the database for foreign keys and attaches them to the matching foreign accessor
- param foreign_class:
-
Defines the foreign class name or class
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
# File 'lib/webink/model.rb', line 359 def find_references(foreign_class) c = (foreign_class.is_a? Class) ? foreign_class : Ink::Model.classname(foreign_class) relationship = self.class.foreign[c.class_name] if relationship result_array = (relationship == "many_many") ? Ink::Database.database.find_union(self.class, self.pk, c) : Ink::Database.database.find_references(self.class, self.pk, c) instance_variable_set("@#{c.table_name}", (relationship =~ /^one_/) ? result_array.first : result_array) true else false end end |
#save ⇒ Object
Instance method
Save the instance to the database. Set all foreign sets to nil if you do not want to change them. Old references are automatically removed.
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/webink/model.rb', line 271 def save if not self.class.respond_to?(:fields) raise NotImplementedError.new(<<ERR) Cannot save to Database without field definitions ERR end string = Array.new keystring = Array.new valuestring = Array.new pkvalue = nil self.class.fields.each do |k,v| value = instance_variable_get "@#{k}" value = "NULL" if value.nil? if k != self.class.primary_key string.push "`#{k}`=#{(value.is_a?(Numeric)) ? value : "\'#{value}\'"}" keystring.push "`#{k}`" valuestring.push "#{(value.is_a?(Numeric)) ? value : "\'#{value}\'"}" else pkvalue = "WHERE `#{self.class.primary_key}`=#{ (value.is_a?(Numeric)) ? value : "\'#{value}\'" }" end end if pkvalue response = Ink::Database.database.find self.class, pkvalue if response.empty? Ink::Database.database.query <<QUERY INSERT INTO #{self.class.table_name} (#{keystring * ","}) VALUES (#{valuestring * ","}); QUERY pk = Ink::Database.database.last_inserted_pk(self.class) if pk instance_variable_set("@#{self.class.primary_key}", pk.is_a?(Numeric) ? pk : "\'#{pk}\'") end else Ink::Database.database.query <<QUERY UPDATE #{self.class.table_name} SET #{string * ","} #{pkvalue}; QUERY end end if self.class.respond_to? :foreign self.class.foreign.each do |k,v| value = instance_variable_get "@#{self.class.str_to_tablename(k)}" if value Ink::Database.database.delete_all_links(self, Ink::Model.classname(k), v) Ink::Database.database.create_all_links(self, Ink::Model.classname(k), v, value) end end end end |