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


644
645
646
647
648
649
# File 'lib/model/the_class.rb', line 644

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


339
340
341
# File 'lib/model/the_class.rb', line 339

def all
  db.get_records from: self
end

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


655
656
657
# File 'lib/model/the_class.rb', line 655

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

#classnameObject

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


323
324
325
# File 'lib/model/the_class.rb', line 323

def classname  # :nodoc: #
   ref_name
end

#count(**args) ⇒ Object

Used to count of the elements in the class


356
357
358
# File 'lib/model/the_class.rb', line 356

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' )

154
155
156
157
158
159
160
161
162
163
164
# File 'lib/model/the_class.rb', line 154

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_class(*c) ⇒ Object

creates an inherented class


138
139
140
# File 'lib/model/the_class.rb', line 138

def create_class *c
	orientdb.create_class( *c ){ self }
end

#create_index(name, **attributes) ⇒ Object

Pagination.create_property :col2, type: :integer Pagination.create_property :col3, type: :string Pagination.create_property :col4, type: :integer Pagination.create_index :composite, :on => [:col1, :col2, :col3], type: 'dictionary'


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

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


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

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)

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
278
279
280
281
282
283
284
# File 'lib/model/the_class.rb', line 224

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


442
443
444
445
446
# File 'lib/model/the_class.rb', line 442

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


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

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


594
595
596
# File 'lib/model/the_class.rb', line 594

def delete_record *rid
  db.delete_record rid
end

#delete_records(where: {}, **args) ⇒ Object Also known as: delete

Query the database and delete the records of the resultset

Returns the count of datasets effected


602
603
604
605
606
607
608
609
610
# File 'lib/model/the_class.rb', line 602

def delete_records where: {} , **args
	if args[:all] == true 
		where = {}
	else
		where.merge!(args) if where.is_a?(Hash)
		return 0 if where.empty?
	end
   orientdb.delete_records( self, where: where   ).count
end

#first(where: {}) ⇒ Object

get the first element of the class


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

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

#get(rid) ⇒ Object

get elements by rid


329
330
331
332
333
334
335
# File 'lib/model/the_class.rb', line 329

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 Execute.

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 »distinct«.
distinct: [:property] --> the result replaces the property
distinct: {property: :some_name} -->  the result is mapped to ModelInstance.some_name

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.

436
437
438
# File 'lib/model/the_class.rb', line 436

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

#indexesObject

list all Indexes


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

def indexes
	properties[:indexes]
end

#last(where: {}) ⇒ Object

get the last element of the class


351
352
353
# File 'lib/model/the_class.rb', line 351

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.


515
516
517
518
519
520
521
522
523
524
# File 'lib/model/the_class.rb', line 515

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


371
372
373
# File 'lib/model/the_class.rb', line 371

def print_properties
  orientdb.print_class_properties self
end

#propertiesObject Also known as: get_class_properties

Get the properties of the class


362
363
364
365
366
# File 'lib/model/the_class.rb', line 362

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 query returns a hash if a resultset is expected

select  {something} as {result} (...)

leads to

[ { :{result}  =>  {result of query} } ]

It can be modified further by passing a block, ie

	q =  OrientSupport::OrientQuery.new( from: :base )

.projection( 'first_list.second_list as second_list' ) .where( label: 9 )

q.to_s  => 'select first_list[5].second_list[9] as second_list from base where label = 9 '

second_list = Base.query_database( q ){|x| x}.first

The query returns (a list of) documents of type ActiveOrient::Model if a document is queried i.e.

q = OrientSupport::OrientQuery.new from: :base q.projection 'expand( first_list.second_list)' #note: no 'as' statement result2 = Base.query_database( q ).first => #<SecondList:0x000000000284e840 @metadata={}, @d=nil, @attributes=“@class”=>“second_list”>

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}

565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
# File 'lib/model/the_class.rb', line 565

def query_database query, set_from: true
  # note: the parameter is not used anymore
query.from self if 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
result = 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

#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 Also known as: update

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

IB::Account.update_all connected: false
IB::Account.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.


191
192
193
194
195
196
197
198
199
# File 'lib/model/the_class.rb', line 191

def update_all where: {} , set: {},  **arg
  if where.empty?
    set.merge! arg
  end
	# the result is a hash. We are intersted in the value only
# expected: {"count" => n}
  db.update_records( self, set: set, where: where).values.first

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


176
177
178
179
180
181
# File 'lib/model/the_class.rb', line 176

def upsert set: nil, where: 
set = where if set.nil?
	# the result is a hash. We are intersted in the value only
# expected: {"@rid" => "#aa:bb"}
  db.upsert( self, set: set, where: where) &.values.first.reload!
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")

469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/model/the_class.rb', line 469

def where *attributes 
  query= OrientSupport::OrientQuery.new kind: :match, start:{ class: self.classname }.merge( where: attributes )
#    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) { | record | record[classname.pluralize.to_sym] }
#			record.map do | key, value | 

#			record.is_a?(ActiveOrient::Model) ? record : record.send( self.classname.pluralize.to_sym ) }
end