Class: Arcade::Base

Inherits:
Dry::Struct
  • Object
show all
Extended by:
Support::Sql
Defined in:
lib/arcade/init.rb,
lib/arcade/base.rb

Overview

Provides method ‘db` to every Model class

Direct Known Subclasses

Document, Edge, Vertex

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Support::Sql

compose_where, generate_sql_list

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *key) ⇒ Object

enables to display values keys like methods



341
342
343
344
345
# File 'lib/arcade/base.rb', line 341

def method_missing method, *key
  if attributes[:values] &.keys &.include?  method
    return values.fetch(method)
  end
end

Class Method Details

.all(a = true, autoload: true, **args) ⇒ Object

Lists all records of a type

Accepts any parameter supported by Arcade::Query

Model.all false –> suppresses the autoload mechanism

Example

My::Names.all order: ‘name’, autoload: false



178
179
180
181
# File 'lib/arcade/base.rb', line 178

def all  a= true, autoload: true, **args
  autoload =  false if a != autoload
  query(**args).query.allocate_model( autoload )
end

.begin_transactionObject



34
35
36
# File 'lib/arcade/base.rb', line 34

def begin_transaction
  db.begin_transaction
end

.commitObject



37
38
39
# File 'lib/arcade/base.rb', line 37

def  commit
  db.commit
end

.count(**args) ⇒ Object

def create **attributes

s = Api.begin_transaction db.database
attributes.merge!( created: DateTime.now ) if timestamps
record = insert **attributes
Api.commit db.database, s
record

rescue HTTPX::HTTPError => e

db.logger.error "Dataset NOT created"
db.logger.error "Provided Attributes: #{ attributes.inspect }"
Api.rollback db.database  # --->  raises "transactgion not begun"

rescue Dry::Struct::Error => e

Api.rollback db.database
db.logger.error "#{ rid } :: Validation failed, record deleted."
db.logger.error e.message

end



163
164
165
166
# File 'lib/arcade/base.rb', line 163

def count  **args
  command = "count(*)"
  query( **( { projection:  command  }.merge args  ) ).query.first[command.to_sym] rescue 0
end

.create_typeObject



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/arcade/base.rb', line 44

def create_type
  the_class = nil   # declare as local var
  parent_present = ->(cl){ db.hierarchy.flatten.include? cl }
  e = ancestors.each
  myselfclass = e.next  # start with the actual class(self)
  loop do
    superclass = the_class  = e.next
    break if the_class.is_a? Class
  end
  begin
  loop do
    if the_class.respond_to?(:demodulize)
      if [ 'Document','Vertex', 'Edge'].include?(the_class.demodulize)
        if  the_class == superclass  # no inheritance
            db.create_type the_class.demodulize, to_s.snake_case
        else
          if superclass.is_a? Class  #  maybe its a module.
             extended = superclass.to_s.snake_case
           else
             extended = superclass.superclass.to_s.snake_case
           end
          if !parent_present[extended]
            superclass.create_type
          end
          db.create_type the_class.demodulize, to_s.snake_case, extends:  extended
        end
        break  # stop iteration
      end
    end
    the_class = e.next  # iterate through the enumerator
  end
  # todo
  # include `created`` and `updated` properties to the aradedb-database schema if timestamps are set
  # (it works without declaring them explicitly, its thus omitted for now )
  # Integration is easy: just execute two commands
  custom_setup = db_init rescue ""
  custom_setup.each_line do |  command |
    the_command =  command[0 .. -2]  #  remove '\n'
    next if the_command == ''
  #  db.logger.info "Custom Setup:: #{the_command}"
    db.transmit { the_command }
  end unless custom_setup.nil?

  rescue RollbackError => e
    db.logger.warn e
  rescue RuntimeError => e
    db.logger.warn e
  end
end

.database_nameObject

this has to be implemented on class level otherwise it interfere with attributes



30
31
32
# File 'lib/arcade/base.rb', line 30

def database_name
  self.name.snake_case
end

.descendantsObject



24
25
26
# File 'lib/arcade/base.rb', line 24

def descendants
    ObjectSpace.each_object(Class).select { |klass| klass < self }
end

.drop_typeObject



95
96
97
# File 'lib/arcade/base.rb', line 95

def drop_type
  db.drop_type to_s.snake_case
end

.find(**args) ⇒ Object

Finds the first matching record providing the parameters of a ‘where` query

Strategie.find symbol: 'Still'
is equivalent to
Strategie.all.find{|y| y.symbol == 'Still' }


237
238
239
240
241
# File 'lib/arcade/base.rb', line 237

def find **args
  where(**args).first
#        f= where( "#{ args.keys.first } like #{ args.values.first.to_or }" ).first if f.nil? || f.empty?
#        f
end

.first(a = true, autoload: true, **args) ⇒ Object

Lists the first record of a type or a query

Accepts any parameter supported by Arcade::Query

Model.first false –> suppresses the autoload mechanism

Example

My::Names.first where: ‘age < 50’, autoload: false



193
194
195
196
# File 'lib/arcade/base.rb', line 193

def first a= true, autoload: true, **args
  autoload =  false if a != autoload
  query( **( { order: "@rid"  , limit: 1  }.merge args ) ).query.allocate_model( autoload ) &.first
end

.insert(**attributes) ⇒ Object Also known as: create

—————————————– insert ———————————- ##

Adds a record to the database

returns the inserted record

Bucket and Index are supported

fired Database-command
INSERT INTO  <type>  BUCKET<bucket> INDEX <index> [CONTENT {<attributes>}]
      (not supported (jet): [RETURN <expression>] [FROM <query>] )


134
135
136
# File 'lib/arcade/base.rb', line 134

def insert **attributes
  db.insert type: database_name, session_id: attributes.delete(:session_id), **attributes
end

.last(a = true, autoload: true, **args) ⇒ Object

Lists the last record of a type or a query

Accepts any parameter supported by Arcade::Query

Model.last false –> suppresses the autoload mechanism

Example

My::Names.last where: ‘age > 50’, autoload: false



209
210
211
212
# File 'lib/arcade/base.rb', line 209

def last  a= true, autoload: true, **args
  autoload =  false if a != autoload
  query( **( { order: {"@rid" => 'desc'} , limit: 1  }.merge args ) ).query.allocate_model( autoload )&.first
end

.not_permitted(*m) ⇒ Object

## Immutable Support

To make a database type immutable add

`not_permitted :update, :upsert, :delete`

to the model-specification

Even after applying ‘not_permitted` the database-type can be modified via class-methods.



309
310
311
312
313
314
315
# File 'lib/arcade/base.rb', line 309

def not_permitted *m
  m.each do | def_m |
    define_method( def_m ) do | v = nil |
      raise  Arcade::ImmutableError.new( "operation  #{def_m} not permitted" )
    end
  end
end

.propertiesObject



99
100
101
# File 'lib/arcade/base.rb', line 99

def properties

end

.query(**args) ⇒ Object



297
298
299
# File 'lib/arcade/base.rb', line 297

def query **args
  Query.new( **{ from: self }.merge(args) )
end

.rollbackObject



40
41
42
# File 'lib/arcade/base.rb', line 40

def rollback
  db.rollback
end

.timestamps(set = nil) ⇒ Object

add timestamp attributes to the model

updated is optional

timestamps are included in create and update statements



111
112
113
114
115
116
117
118
# File 'lib/arcade/base.rb', line 111

def timestamps set=nil
  if set && @stamps.nil?
    @stamps = true
    attribute :created, Types::JSON::DateTime
    attribute :updated?, Types::JSON::DateTime
  end
  @stamps
end

.update(**args) ⇒ Object

update returns a list of updated records

It fires a query update <type> set <property> = <new value > upsert return after $current where < condition >

which returns a list of modified rid’s

required parameter:  set:
                   where:

todo refacture required parameters notification



253
254
255
256
257
258
259
260
261
262
# File 'lib/arcade/base.rb', line 253

def update **args
  if args.keys.include?(:set) && args.keys.include?(:where)
    args.merge!( updated: DateTime.now ) if timestamps
    query( **( { kind: :update }.merge args ) ).execute do |r|
      r[:"$current"] &.allocate_model(false)  #  do not autoload modelfiles
    end
  else
    raise "at least set: and where: are required to perform this operation"
  end
end

.update!(**args) ⇒ Object

update! returns the count of affected records

required parameter:  set:
                   where:


269
270
271
272
273
274
275
276
# File 'lib/arcade/base.rb', line 269

def update! **args
  if args.keys.include?(:set) && args.keys.include?(:where)
    args.merge!( updated: DateTime.now ) if timestamps
    query( **( { kind: :update! }.merge args ) ).execute{|y| y[:count] } &.first
  else
    raise "at least set: and where: are required to perform this operation"
  end
end

.upsert(**args) ⇒ Object

returns a list of updated records



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/arcade/base.rb', line 280

def upsert **args
  set_statement = args.delete :set
  args.merge!( updated: DateTime.now ) if timestamps
  where_statement = args[:where] || args
  statement = if set_statement
                { set: set_statement, where: where_statement }
              else
                { where: where_statement }
              end
  result= query( **( { kind: :upsert  }.merge statement ) ).execute do | answer|
    z=  answer[:"$current"] &.allocate_model(false)  #  do not autoload modelfiles
    raise LoadError "Upsert failed"   unless z.is_a?  Base
    z  #  return record
  end
end

.where(a = true, autoload: true, **args) ⇒ Object

Selects records of a type or a query

Accepts only parameters to restrict the query (apart from autoload).

Use ‘Model.query where: args“to use the full range of supported parameters

Model.where false –> suppresses the autoload mechanism

Example

My::Names.last where: ‘age > 50’, autoload: false



226
227
228
229
230
231
# File 'lib/arcade/base.rb', line 226

def where a= true, autoload: true, **args
  autoload =  false if a != autoload
   args = a if a.is_a?(String)
   ##  the result is always an array
   query( where: args ).query.allocate_model(autoload)
end

Instance Method Details

#==(arg) ⇒ Object



499
500
501
502
# File 'lib/arcade/base.rb', line 499

def == arg
 # self.attributes == arg.attributes
  self.rid == arg.rid
end

#accepted_methodsObject



17
18
19
# File 'lib/arcade/base.rb', line 17

def accepted_methods
  [ :rid, :to_human, :delete ]
end

#deleteObject



495
496
497
498
# File 'lib/arcade/base.rb', line 495

def delete
  response = db.transmit { "delete from #{ rid }" }
  true if response == [{ count: 1 }]
end

#html_attributesObject



400
401
402
# File 'lib/arcade/base.rb', line 400

def html_attributes
  invariant_attributes
end

#in_and_out_attributesObject



404
405
406
407
408
# File 'lib/arcade/base.rb', line 404

def in_and_out_attributes
  _modul, _class =  self.class.to_s.split "::"
  the_class =  _modul == 'Arcade' ? _class : self.class.to_s
  the_attributes = { :"CLASS" => the_class, :"IN" => self.in.count, :"OUT" =>  self.out.count, :"RID" => rid  }
end

#insert_document(name, obj) ⇒ Object

inserts or updates a embedded document



440
441
442
443
444
445
446
447
448
449
# File 'lib/arcade/base.rb', line 440

def insert_document name, obj
  value = if obj.is_a? Document
            obj.to_json
          else
            obj.to_or
          end
#      if send( name ).nil? || send( name ).empty?
    db.transmit { "update #{ rid } set  #{ name } =  #{ value }" }.first[:count]
#      end
end

#inspectObject

configure irb-output to to_human for all Arcade::Base-Objects



395
396
397
# File 'lib/arcade/base.rb', line 395

def inspect
  to_human
end

#invariant_attributesObject



330
331
332
333
334
335
336
337
# File 'lib/arcade/base.rb', line 330

def invariant_attributes
  result= attributes.except :rid, :in, :out, :values, :created_at,  :updated_at
  if  attributes.keys.include?(:values)
    result.merge values
  else
    result
  end
end

#query(**args) ⇒ Object



347
348
349
# File 'lib/arcade/base.rb', line 347

def query **args
  Query.new( **{ from: rid }.merge(args) )
end

#refreshObject



504
505
506
# File 'lib/arcade/base.rb', line 504

def refresh
  db.get(rid)
end

#rid?Boolean

Returns:

  • (Boolean)


362
363
364
# File 'lib/arcade/base.rb', line 362

def rid?
  true unless ["#0:0", "#-1:-1"].include?  rid
end

#to_htmlObject

iruby



410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/arcade/base.rb', line 410

def to_html  # iruby
  in_and_out = ->(r) { "[#{r}] : {#{self.in.count}->}{->#{self.out.count }}"    }
  the_rid =  rid? && rid != "0:0" ? in_and_out[rid] : ""
  _modul, _class =  self.class.to_s.split "::"
  the_class =  _modul == 'Arcade' ? _class : self.class.to_s
  #    the_attribute = ->(v) do
  #      case v
  #        when Base
  #          "< #{ self.class.to_s.snake_case  }: #{ v.rid  } >"
  #        when Array
  #          v.map{|x| x.to_s}
  #        else
  #          v.to_s
  #        end
  #    end
  #     last_part = invariant_attributes.map do |attr, value|
  #       [ attr, the_attribute[value] ].join(": ")
  #   end.join(', ') 

  # IRuby.display( [IRuby.html("<span style=\"color: #50953DFF\"><b>#{the_class}</b><#{ the_class }</b>#{the_rid}</span><br/> ")  , IRuby.table(html_attributes) ] )
  IRuby.display IRuby.html("<span style=\"color: #50953DFF\"><b>#{the_class}</b><#{ the_class }</b>#{the_rid}</span>< #{ html_attributes.map{|_,v|  v }.join(', ')  } >")
end

#to_humanObject



375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/arcade/base.rb', line 375

def to_human
    "<#{ self.class.to_s.snake_case }" + rid? ? "[#{ rid }]: " : " " + invariant_attributes.map do |attr, value|
      v= case value
 when Base
   "< #{ self.class.to_s.snake_case }: #{ value.rid } >"
 when Array
       value.map{|x| x.to_s}
 else
   value.from_db
 end
      "%s : %s" % [ attr, v]  unless v.nil?
    end.compact.sort.join(', ') + ">".gsub('"' , ' ')

rescue TypeError => e
  attributes
end

#to_json(*args) ⇒ Object

to JSON controlls the serialisation of Arcade::Base Objects for the HTTP-JSON API

ensure, that only the rid is transmitted to the database



355
356
357
358
359
360
361
# File 'lib/arcade/base.rb', line 355

def to_json *args
  unless ["#0:0", "#-1:-1"].include?  rid   #  '#-1:-1' is the null-rid
    rid
  else
    invariant_attributes.merge( :'@type' =>  self.class.database_name  ).to_json
  end
end

#to_orObject

enables usage of Base-Objects in queries



367
368
369
370
371
372
373
# File 'lib/arcade/base.rb', line 367

def to_or
  if rid?
    rid
  else
    to_json
  end
end

#update(**args) ⇒ Object



434
435
436
437
# File 'lib/arcade/base.rb', line 434

def update **args
  Query.new( from: rid , kind: :update, set: args).execute
  refresh   # return the updated record (the object itself is untouched!)
end

#update_embedded(embedded, embedded_property, value) ⇒ Object

updates a single property in an embedded document



452
453
454
# File 'lib/arcade/base.rb', line 452

def update_embedded embedded, embedded_property, value
  db.transmit { " update #{rid} set `#{embedded}`.`#{embedded_property}` =  #{value.to_or}" }
end

#update_list(list, value, modus: :auto) ⇒ Object

Adds List-Elements to embedded List

Arguments:

  • list: A symbol of the list property

  • value: A embedded document or a hash

  • modus: :auto, :first, :append

Prefered modus operandi

  • the-element.insert (…) , #list:[]

  • the_element.update_list list, value: :append



467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
# File 'lib/arcade/base.rb', line 467

def update_list list, value, modus: :auto
  value = if value.is_a? Document   # embedded mode
            value.to_json
          else
            value.to_or
          end
  if modus == :auto
    modus =  db.query( "select #{list}.size() from #{rid}" ).select_result.first.zero? ? :first : :append
  end

 if modus == :first
    db.transmit { "update #{ rid } set  #{ list } = [#{ value }]" }
  else
    db.transmit { "update #{ rid } set  #{ list } += #{ value }" }
  end
 # refresh
end

#update_map(m, key, value) ⇒ Object

updates a map property , actually adds the key-value pair to the property does not work on reduced model records



487
488
489
490
491
492
493
494
# File 'lib/arcade/base.rb', line 487

def update_map m, key, value
  if send( m ).nil?
    db.transmit { "update #{ rid } set #{ m } = MAP ( #{ key.to_s.to_or } , #{ value.to_or } ) "  }
  else
    db.transmit { "update #{ rid } set #{ m }.`#{ key.to_s }` = #{ value.to_or }" }
  end
  refresh
end