Module: ModelClass

Includes:
OrientSupport::Support
Included in:
ActiveOrient::Model
Defined in:
lib/model/the_class.rb

Instance Method Summary collapse

Methods included from OrientSupport::Support

#compose_where, #generate_sql_list

Instance Method Details



590
591
592
593
594
595
# File 'lib/model/the_class.rb', line 590

def add_edge_link name:, direction: :out, edge:
  dir =  direction.to_s == "out" ? :out : :in
  define_method(name.to_sym) do
    return self["#{dir}_#{edge.classname}"].map{|x| x["in"]}
  end
end

#allObject

get all the elements of the class



313
314
315
# File 'lib/model/the_class.rb', line 313

def all
  db.get_records from: self
end

#alter_property(property:, attribute: "DEFAULT", alteration:) ⇒ Object



601
602
603
# File 'lib/model/the_class.rb', line 601

def alter_property property:, attribute: "DEFAULT", alteration:  # :nodoc:
  orientdb.alter_property self, property: property, attribute: attribute, alteration: alteration
end

#classnameObject

GET ###############



297
298
299
# File 'lib/model/the_class.rb', line 297

def classname  # :nodoc: #
   ref_name
end

#count(**args) ⇒ Object

Used to count of the elements in the class



330
331
332
# File 'lib/model/the_class.rb', line 330

def count **args
  orientdb.count from: self, **args
end

#create(**attributes) ⇒ Object

Universal method to create a new record. It’s overloaded to create specific kinds, eg. edge and vertex and is called only for abstract classes

Example:

ORD.create_class :test
Test.create string_attribute: 'a string', symbol_attribute: :a_symbol, array_attribute: [34,45,67]
Test.create link_attribute: Test.create( :a_new_attribute => 'new' )


148
149
150
151
152
153
154
155
156
157
158
# File 'lib/model/the_class.rb', line 148

def create **attributes
   attributes.merge :created_at => DateTime.new
  result = db.create_record self, attributes: attributes
  if result.nil
    logger.error('Model::Class'){ "Table #{refname}:  create failed:  #{attributes.inspect}" }
  elsif block_given?
    yield result
  else
    result  # return value
  end
end

#create_index(name, **attributes) ⇒ Object

Add an Index



287
288
289
# File 'lib/model/the_class.rb', line 287

def create_index name, **attributes
  orientdb.create_index self, name: name, **attributes
end

#create_properties(argument_hash, &b) ⇒ Object

Create more Properties in the Schema of the Class



281
282
283
# File 'lib/model/the_class.rb', line 281

def create_properties argument_hash, &b
  orientdb.create_properties self, argument_hash, &b
end

#create_property(field, type: :integer, index: nil, **args) ⇒ Object

Create a Property in the Schema of the Class and optionaly create an automatic index

Examples:

create_property  :customer_id, type: integer, index: :unique
create_property(  :name, type: :string ) {  :unique  }
create_property  :in,  type: :link, linked_class: V    (used by edges)

:call-seq: create_property(field (required), type: :a_supported_type’, linked_class: nil

supported types: :bool :double :datetime :float :decimal

:embedded_list = :list :embedded_map = :map :embedded_set = :set

:int :integer :link_list :link_map :link_set

If :list, :map, :set, :link, :link_list, :link_map or :link_set is specified a linked_class: parameter can be specified. Argument is the OrientDB-Class-Constant

Raises:

  • (ArgumentError)


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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/model/the_class.rb', line 217

def create_property field, type: :integer, index: nil,  **args
  arguments =  args.values.map do |y| 
    if y.is_a?(Class)  && ActiveOrient.database_classes.values.include?(y) 
      y.ref_name 
    elsif  ActiveOrient.database_classes.keys.include?(y.to_s) 
      y 
    else
      puts ActiveOrient.database_classes.inspect
      puts "YY : #{y.to_s} #{y.class}"
      raise ArgumentError , "database class #{y.to_s} not allocated"
    end
  end.compact.join(',')

  supported_types = {
    :bool          => "BOOLEAN",
    :double        => "BYTE",
    :datetime      => "DATE",
    :float         => "FLOAT",
    :decimal       => "DECIMAL",
    :embedded_list => "EMBEDDEDLIST",
    :list          => "EMBEDDEDLIST",
    :embedded_map  => "EMBEDDEDMAP",
    :map           => "EMBEDDEDMAP",
    :embedded_set  => "EMBEDDEDSET",
    :set           => "EMBEDDEDSET",
    :string        => "STRING",
    :int           => "INTEGER",
    :integer       => "INTEGER",
    :link          => "LINK",
    :link_list     => "LINKLIST",
    :link_map      => "LINKMAP",
    :link_set      => "LINKSET",
  }

  ## if the »type« argument is a string, it is used unchanged
  type =  supported_types[type] if type.is_a?(Symbol)

  raise ArgumentError , "unsupported type" if type.nil?
s= " CREATE PROPERTY #{ref_name}.#{field} #{type} #{arguments}" 
puts s
db.execute {  s }

i =  block_given? ? yield : index
## supported format of block:  index: { name: 'something' , on: :automatic, type: :unique } 
## or                                 { name: 'something' , on: :automatic, type: :unique }  # 
## or                                 {                                some_name: :unique }  # manual index
## or                                 {                                           :unique }  # automatic index
if i.is_a? Hash  
  att=  i.key( :index ) ?   i.values.first : i
  name, on, type = if  att.size == 1  && att[:type].nil? 
                     [att.keys.first,  field,  att.values.first ]
                   else  
                     [ att[:name] || field , att[:on] || field , att[:type] || :unique ]
                   end
  create_index( name , on: on, type: type)
elsif i.is_a?(Symbol)  || i.is_a?(String)
  create_index field, type: i
end

# orientdb.create_property self, field, **keyword_arguments, &b
end

#custom_where(search_string) ⇒ Object



416
417
418
419
420
# File 'lib/model/the_class.rb', line 416

def custom_where search_string
  q = OrientSupport::OrientQuery.new from: self, where: search_string
  #puts q.compose
  query_database q
end

#delete_property(field) ⇒ Object

Delete a property from the class



541
542
543
# File 'lib/model/the_class.rb', line 541

def delete_property field
  orientdb.delete_property self, field
end

#delete_record(*rid) ⇒ Object Also known as: delete_document

Delete record(s) specified by their rid’s



547
548
549
# File 'lib/model/the_class.rb', line 547

def delete_record *rid
  db.delete_record rid
end

#delete_records(where: {}) ⇒ Object Also known as: delete_documents

Query the database and delete the records of the resultset



554
555
556
# File 'lib/model/the_class.rb', line 554

def delete_records where: {}
  orientdb.delete_records self, where: where
end

#first(where: {}) ⇒ Object

get the first element of the class



319
320
321
# File 'lib/model/the_class.rb', line 319

def first where: {}
  db.get_records(from: self, where: where, limit: 1).pop
end

#get(rid) ⇒ Object

get elements by rid



303
304
305
306
307
308
309
# File 'lib/model/the_class.rb', line 303

def get rid
  if @excluded.blank?
    db.get_record(rid)
  else
    db.execute{ "select expand( @this.exclude( #{@excluded.map(&:to_or).join(",")})) from #{rid} "} 
  end
end

#get_records(**args) ⇒ Object Also known as: get_documents

»GetRecords« uses the REST-Interface to query the database. The alternative »QueryDatabase« submits the query via Batch.

Both methods rely on OrientSupport::OrientQuery and its capacity to support complex query-builds. The method requires a hash of arguments. The following keys are supported:

projection:

SQL-Queries use »select« to specify a projection (ie. ‘select sum(a), b+5 as z from class where …`)

In ruby »select« is a method of enumeration. To specify anything etween »select« and »from« in the query-string we use »projection«, which acceps different arguments

projection: a_string --> inserts the sting as it appears
projection: an OrientSupport::OrientQuery-Object --> performs a sub-query and uses the result for further querying though the given parameters.
projection: [a, b, c] --> "a, b, c" (inserts a comma-separated list)
projection: {a: b, "sum(x)" => f} --> "a as b, sum(x) as f" (renames properties and uses functions)

distinct:

Constructs a query like »select distinct(property) [as property] from …«

distinct: :property -->  the result is mapped to the property 

order:

Sorts the result-set. If new properties were introduced via select:, distinct: etc. Sorting takes place on these properties

 order: :property {property: asc, property: desc}[property, property, ..  ](orderdirection is 'asc')

Further supported Parameter:

group_by
skip
limit
unwind

see orientdb- documentation (https://orientdb.com/docs/last/SQL-Query.html)

query:

Instead of providing the parameter to »get_records«, a OrientSupport::OrientQuery can build and tested prior to the method-call. The OrientQuery-Object is then provided with the query-parameter. I.e.

q = OrientSupport::OrientQuery.new
ORD.create_class :test_model
   q.from TestModel
   q.where {name: 'Thomas'}
   count = TestModel.count query: q
   q.limit 10
   0.step(count,10) do |x|
      q.skip = x
      puts TestModel.get_documents(query: q).map{|x| x.adress }.join('\t')
   end
  prints a Table with 10 columns.


410
411
412
# File 'lib/model/the_class.rb', line 410

def get_records **args
  db.get_records(from: self, **args){self}
end

#indexesObject

list all Indexes



292
293
294
# File 'lib/model/the_class.rb', line 292

def indexes
  properties[:indexes]
end

#last(where: {}) ⇒ Object

get the last element of the class



325
326
327
# File 'lib/model/the_class.rb', line 325

def last where: {}
  db.get_records(from: self, where: where, order: {"@rid" => 'desc'}, limit: 1).pop
end

setter method to initialise a dummy ActiveOrient::Model class to enable multi-level access to links and linklists



75
76
77
78
79
80
81
82
83
84
85
# File 'lib/model/the_class.rb', line 75

def link_list *property
  property.each do |p|
    
    the_dummy_class = orientdb.allocate_class_in_ruby("dummy_"+p.to_s)
    the_dummy_class.ref_name =  ref_name + "." +  p.to_s
    singleton_class.send :define_method, p do
      the_dummy_class
    end
  end

end

#match(where: {}) ⇒ Object

Performs a Match-Query

The Query starts at the given ActiveOrient::Model-Class. The where-cause narrows the sample to certain records. In the simplest version this can be returned:

Industry.match where:{ name: "Communications" }
=> #<ActiveOrient::Model::Query:0x00000004309608 @metadata={"type"=>"d", "class"=>nil, "version"=>0, "fieldTypes"=>"Industries=x"}, @attributes={"Industries"=>"#21:1", (...)}>

The attributes are the return-Values of the Match-Query. Unless otherwise noted, the pluralized Model-Classname is used as attribute in the result-set.

I.match( where: { name: 'Communications' }).first.Industries

is the same then

Industry.where name: "Communications"

The Match-Query uses this result-set as start for subsequent queries on connected records. These connections are defined in the Block

var = Industry.match do | query |
  query.connect :in, count: 2, as: 'Subcategories'
  puts query.to_s  # print the query before sending it to the database
  query            # important: block has to return the query 
end
=> MATCH {class: Industry, as: Industries} <-- {} <-- { as: Subcategories }  RETURN Industries, Subcategories

The result-set has two attributes: Industries and Subcategories, pointing to the filtered datasets.

By using subsequent »connect« and »statement« method-calls even complex Match-Queries can be clearly constructed.



486
487
488
489
490
491
492
493
494
495
# File 'lib/model/the_class.rb', line 486

def match where: {}
  query= OrientSupport::OrientQuery.new kind: :match, start:{ class: self.classname }
  query.match_statements[0].where =  where unless where.empty?
  if block_given?
    query_database yield(query), set_from: false
  else
    send :where, where
  end

end

#namespace_prefixObject

Set the namespace_prefix for database-classes.

If a namespace is set by

ActiveOrient::Init.define_namespace { ModuleName }

ActiveOrient translates this to

ModuleName::CamelizedClassName

The database-class becomes

modulename_class_name

If the namespace is set to a class (Object, ActiveOrient::Model ) namespace_prefix returns an empty string.

Override to change its behavior



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

def namespace_prefix 
  namespace.is_a?(Class )? '' : namespace.to_s.downcase+'_' 
end

#naming_convention(name = nil) ⇒ Object

NamingConvention provides a translation from database-names to class-names.

It can be overwritten to provide different conventions for different classes, eg. Vertexes or edges and to introduce distinct naming-conventions in differrent namespaces

To overwrite use

 class Model # < ActiveOrient::Model[:: ...]
   def self.naming_convention
   ( conversion code )
   end
end


25
26
27
28
29
30
31
32
33
34
# File 'lib/model/the_class.rb', line 25

def naming_convention name=nil  
  nc =  name.present?? name.to_s : ref_name
   if namespace_prefix.present?
     nc.split(namespace_prefix).last.camelize
   else
     nc.camelize
   end
  rescue
nil
end

#orientdb_class(name:, superclass: nil) ⇒ Object

orientdb_class is used to refer a ActiveOrient:Model-Object providing its name

Parameter: name: string or symbol


60
61
62
63
64
65
66
67
# File 'lib/model/the_class.rb', line 60

def orientdb_class name:, superclass: nil  # :nodoc:    # public method: autoload_class

  ActiveOrient.database_classes[name].presence || ActiveOrient::Model
rescue NoMethodError => e
  logger.error { "Error in orientdb_class: is ActiveOrient.database_classes initialized ? \n\n\n" }
  logger.error{ e.backtrace.map {|l| "  #{l}\n"}.join  }
  Kernel.exit
end

Print the properties of the class



345
346
347
# File 'lib/model/the_class.rb', line 345

def print_properties
  orientdb.print_class_properties self
end

#propertiesObject Also known as: get_class_properties

Get the properties of the class



336
337
338
339
340
# File 'lib/model/the_class.rb', line 336

def properties
  object = orientdb.get_class_properties self
  #HashWithIndifferentAccess.new :properties => object['properties'], :indexes => object['indexes']
  {:properties => object['properties'], :indexes => object['indexes']}
end

#query_database(query, set_from: true) ⇒ Object

QueryDatabase sends the Query directly to the database.

The result is not nessessary an Object of the Class.

The result can be modified further by passing a block. This is helpful, if a match-statement is used and the records should be autoloaded:

result = query_database(query, set_from: false){| record | record[ self.classname.pluralize ] }

This autoloads (fetches from the cache/ or database) the attribute self.classname.pluralize (taken from method: where )

query_database is used on model-level and submits

select (...) from class

#query performs queries on the instance-level and submits

select (...) from #{a}:{b}


519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
# File 'lib/model/the_class.rb', line 519

def query_database query, set_from: true
  query.from self if set_from && query.is_a?(OrientSupport::OrientQuery) && query.from.nil?
  sql_cmd = -> (command) {{ type: "cmd", language: "sql", command: command }}
  result = db.execute do
  query.to_s #  sql_cmd[query.to_s]
  end
  if block_given?
    result.is_a?(Array)? result.map{|x| yield x } : yield(result)
  else
    result
  end
  if result.is_a? Array  
    OrientSupport::Array.new work_on: self, work_with: result
  else
    result
  end  # return value
end

#remove(attribute, where: {}) ⇒ Object

removes a property from the collection (where given) or the entire class



192
193
194
# File 'lib/model/the_class.rb', line 192

def remove attribute, where:{}
  db.update_records self, remove: attribute, where: where
end

#require_model_file(the_directory = nil) ⇒ Object

requires the file specified in the model-dir

In fact, the model-files are loaded instead of required. Thus, even after recreation of a class (Class.delete_class, ORD.create_class classname) custom methods declared in the model files are present.

Required modelfiles are gone, if the class is destroyed.

The directory specified is expanded by the namespace. The parameter itself is the base-dir.

Example:

Namespace:  HC
model_dir : 'lib/model'
searched directory: 'lib/model/hc'


104
105
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
# File 'lib/model/the_class.rb', line 104

def require_model_file  the_directory = nil
  logger.progname = 'ModelClass#RequireModelFile'
  the_directory = Pathname( the_directory.presence ||  ActiveOrient::Model.model_dir ) rescue nil  # the_directory is a Pathname
  return nil if the_directory.nil?
  if File.exists?( the_directory )
    model= self.to_s.underscore + ".rb"
    filename =   the_directory +  model
    if  File.exists?(filename )
      if load filename
        logger.info{ "#{filename} sucessfully loaded"  }
        self #return_value
      else
        logger.error{ "#{filename} load error" }
        nil #return_value
      end
    else
      logger.info{ "model-file not present: #{filename}" }
      nil #return_value
    end
  else
    logger.info{ "Directory #{ the_directory  } not present " }
    nil  #return_value
  end
rescue TypeError => e
  puts "TypeError:  #{e.message}" 
  puts "Working on #{self.to_s} -> #{self.superclass}"
  puts "Class_hierarchy: #{orientdb.class_hierarchy.inspect}."
  print e.backtrace.join("\n") 
  raise
  #
end

#update_all(where: {}, set: {}, **arg) ⇒ Object

Sets a value to certain attributes, overwrites existing entries, creates new attributes if nessesary

IB::.update_all connected: false
IB::.update_all where: "account containsText 'F'", set:{ connected: false }

**note: By calling UpdateAll, all records of the Class previously stored in the rid-cache are removed from the cache. Thus autoload gets the updated records.



183
184
185
186
187
188
189
# File 'lib/model/the_class.rb', line 183

def update_all where: {} , set: {},  **arg
  if where.empty?
    set.merge! arg
  end
  db.update_records  self, set: set, where: where

end

#upsert(set: nil, where:) ⇒ Object

Creates or updates a record. Parameter:

  • set: A hash of attributes to insert or update unconditionally

  • where: A string or hash as condition which should return just one record.

The where-part should be covered with an unique-index.

returns the affected record



170
171
172
173
# File 'lib/model/the_class.rb', line 170

def upsert set: nil, where: 
set = where if set.nil?
  db.upsert self, set: set, where: where
end

#where(*attributes) ⇒ Object

Performs a query on the Class and returns an Array of ActiveOrient:Model-Records.

Example:

Log.where priority: 'high'
--> submited database-request: query/hc_database/sql/select from Log where priority = 'high'/-1
=> [ #<Log:0x0000000480f7d8 @metadata={ ... },  ...

Multible arguments are joined via “and” eg

Aktie.where symbol: 'TSL, exchange: 'ASX'
---> select  from aktie where symbol = 'TLS' and exchange = 'ASX'

Where performs a »match-Query« that returns only links to the queries records. These are autoloaded (and reused from the cache). If changed database-records should be obtained, custom_query should be used. It performs a “select form class where … ” query which returns records instead of links.

Property.custom_where( "'Hamburg' in exchanges.label")


443
444
445
446
447
448
449
450
451
452
# File 'lib/model/the_class.rb', line 443

def where *attributes 
  query= OrientSupport::OrientQuery.new kind: :match, start:{ class: self.classname }
  query.match_statements[0].where =  attributes unless attributes.empty?
# the block contains a result-record : 
#<ActiveOrient::Model:0x0000000003972e00 
#   @metadata={:type=>"d", :class=>nil, :version=>0, :fieldTypes=>"test_models=x"}, @d=nil, 
#   @attributes={:test_models=>"#29:3", :created_at=>Thu, 28 Mar 2019 10:43:51 +0000}>]
#                ^...........° -> classname.pluralize
  query_database( query, set_from: false){| record | record.is_a?(ActiveOrient::Model) ? record : record.send( self.classnamepluralize.to_sym ) }
end