Class: Mxrb::RubyApp::Application

Inherits:
Object
  • Object
show all
Defined in:
lib/mxrb/ruby_app.rb

Overview

Loads generated Ruby classes and provides one shared in-memory backend.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root, environment: nil, process: ENV) ⇒ Application

Returns a new instance of Application.



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/mxrb/ruby_app.rb', line 330

def initialize(root, environment: nil, process: ENV)
  @root = File.expand_path(root)
  @runtime_monitor = Monitor.new
  @manifest = Manifest.load(@root)
  @environment = if environment.is_a?(Environment)
                   environment
                 else
                   Environment.load(
                     environment, root: @root, process:
                   )
                 end
  Registry.reset!
  load_adapters
  load_application_files
end

Instance Attribute Details

#environmentObject (readonly)

Returns the value of attribute environment.



328
329
330
# File 'lib/mxrb/ruby_app.rb', line 328

def environment
  @environment
end

#manifestObject (readonly)

Returns the value of attribute manifest.



328
329
330
# File 'lib/mxrb/ruby_app.rb', line 328

def manifest
  @manifest
end

#rootObject (readonly)

Returns the value of attribute root.



328
329
330
# File 'lib/mxrb/ruby_app.rb', line 328

def root
  @root
end

Instance Method Details

#access_controlObject



507
# File 'lib/mxrb/ruby_app.rb', line 507

def access_control = (@access_control ||= bridge.access_control)

#call_service(name, arguments = {}, synchronized_context: nil, context: nil) ⇒ Object



355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/mxrb/ruby_app.rb', line 355

def call_service(name, arguments = {}, synchronized_context: nil, context: nil)
  runtime_synchronize do
    implementation = Registry.fetch(:service, name.to_s)
    raise NativeRuntimeError, "microflow #{name} not found" unless implementation || microflow_exists?(name)

    authorize_document!(:microflow, name, :execute, context)
    normalized = arguments.to_h.transform_values { deserialize(_1, context:) }
    synchronize_context(synchronized_context, context:) if synchronized_context
    next native_call(name, normalized, context:) unless implementation

    keyword_arguments = normalized.transform_keys(&:to_sym)
    implementation.new(self, context:).call(**keyword_arguments)
  end
end

#closeObject



520
521
522
523
524
525
526
527
# File 'lib/mxrb/ruby_app.rb', line 520

def close
  @bridge&.close
  @shared_store&.close
  @bridge = nil
  @access_control = nil
  @session_manager = nil
  @shared_store = nil
end

#create_record(name, attributes = nil, context: nil, **keyword_attributes) ⇒ Object



448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# File 'lib/mxrb/ruby_app.rb', line 448

def create_record(name, attributes = nil, context: nil, **keyword_attributes)
  runtime_synchronize do
    attributes = keyword_attributes if attributes.nil?
    authorize_entity!(name, :create, context)
    bridge.interpreter.store.transaction do
      value = bridge.interpreter.store.create(name.to_s)
      attributes.to_h.each do |member, member_value|
        authorize_entity!(name, :write, context, member: member, record: value)
        value.members[member.to_s] = member_value
      end
      bridge.interpreter.store.commit(value)
      serialize(value, context:)
    end
  end
end

#delete_record(name, id, context: nil) ⇒ Object



464
465
466
467
468
469
470
471
472
473
# File 'lib/mxrb/ruby_app.rb', line 464

def delete_record(name, id, context: nil)
  runtime_synchronize do
    value = bridge.interpreter.store.find(name.to_s, id.to_s)
    next false unless value

    authorize_entity!(name, :delete, context, record: value)
    bridge.interpreter.store.delete(value)
    true
  end
end

#invoke_rest(route, path_parameters: {}, query: {}, body: nil, context: nil) ⇒ Object



401
402
403
404
405
# File 'lib/mxrb/ruby_app.rb', line 401

def invoke_rest(route, path_parameters: {}, query: {}, body: nil, context: nil)
  service = service_manifest(route.fetch('microflow'))
  arguments = request_arguments(service, path_parameters, query, body, context:)
  invoke_service(route.fetch('microflow'), arguments, context:).fetch(:result)
end

#invoke_service(name, arguments = nil, context: nil, **keyword_arguments) ⇒ Object



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/mxrb/ruby_app.rb', line 370

def invoke_service(name, arguments = nil, context: nil, **keyword_arguments)
  runtime_synchronize do
    arguments = keyword_arguments if arguments.nil?
    bridge.interpreter.clear_effects!
    invocation_arguments = arguments.to_h.dup
    synchronized_context = invocation_arguments.delete('__mxrb_context')
    active_context = synchronize_context(synchronized_context, context:) if synchronized_context
    result = call_service(name, invocation_arguments, context:)
    {
      result: serialize(result, context:), effects: serialize(bridge.interpreter.effects, context:),
      context: serialize(active_context, context:)
    }
  ensure
    release_runtime_cache
  end
end

#native_call(name, arguments = {}, context: nil) ⇒ Object



407
408
409
410
411
412
413
414
# File 'lib/mxrb/ruby_app.rb', line 407

def native_call(name, arguments = {}, context: nil)
  runtime_synchronize do
    authorize_document!(:microflow, name, :execute, context)
    serialize(
      bridge.interpreter.call(name.to_s, arguments.to_h.transform_keys(&:to_s), context:), context:
    )
  end
end

#page(name, context: nil) ⇒ Object



492
493
494
495
496
497
498
499
500
501
502
503
504
505
# File 'lib/mxrb/ruby_app.rb', line 492

def page(name, context: nil)
  implementation = Registry.fetch(:page, name.to_s)
  return unless implementation

  authorize_document!(:page, name, :read, context)

  {
    name: implementation.mendix_name, id: implementation.mendix_id,
    title: implementation.title, widgets: implementation.widgets,
    appearance_class: implementation.appearance_class,
    appearance_style: implementation.appearance_style,
    data_source: implementation.data_source
  }
end

#record(name, id, context: nil) ⇒ Object



438
439
440
441
442
443
444
445
446
# File 'lib/mxrb/ruby_app.rb', line 438

def record(name, id, context: nil)
  runtime_synchronize do
    value = bridge.interpreter.store.find(name.to_s, id.to_s)
    next unless value
    next unless !context || access_control.entity_allowed?(name, action: :read, context:, record: value)

    serialize(value, context:)
  end
end

#records(name, context: nil, association: nil, context_type: nil, context_id: nil) ⇒ Object



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/mxrb/ruby_app.rb', line 416

def records(name, context: nil, association: nil, context_type: nil, context_id: nil)
  runtime_synchronize do
    filter = [association, context_type, context_id]
    if filter.any? && !filter.all? { !_1.to_s.empty? }
      raise ArgumentError,
            'association filter requires association, context_type, and context_id'
    end
    store = bridge.interpreter.store
    values = if filter.all? { !_1.to_s.empty? }
               parent = store.find(context_type.to_s, context_id.to_s)
               parent ? store.retrieve_association(association.to_s, parent) : []
             else
               store.retrieve(name.to_s)
             end
    values = values.select { _1.entity == name.to_s }
    values = access_control.filter_readable(name.to_s, values, context:) if context
    values.map { serialize(_1, context:) }
  ensure
    release_runtime_cache
  end
end

#rest_routesObject



387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/mxrb/ruby_app.rb', line 387

def rest_routes
  @rest_routes ||= manifest.modules.flat_map do |mod|
    mod.fetch('endpoints', []).flat_map do |endpoint|
      endpoint.fetch('operations', []).map do |operation|
        operation.merge(
          'service' => endpoint.fetch('name'),
          'enable_cors' => endpoint.fetch('enable_cors', false),
          'requires_authentication' => endpoint.fetch('requires_authentication', false)
        )
      end
    end
  end
end

#schema(context: nil) ⇒ Object



346
347
348
349
350
351
352
353
# File 'lib/mxrb/ruby_app.rb', line 346

def schema(context: nil)
  result = {
    mode: 'ruby', environment: environment.name, project: manifest.data.fetch('project'),
    navigation: manifest.data.fetch('navigation', {}),
    modules: manifest.modules, coverage: manifest.coverage
  }
  context ? secure_schema(result, context) : result
end

#session_managerObject



509
510
511
512
513
514
515
516
# File 'lib/mxrb/ruby_app.rb', line 509

def session_manager
  @session_manager ||= SessionManager.new(
    access_control, users: environment['MXRB_USERS_JSON'],
                    tokens: environment['MXRB_AUTH_TOKENS'],
                    ttl: environment.fetch('MXRB_SESSION_TTL', '3600'),
                    store: shared_store
  )
end

#start_schedulerObject



518
# File 'lib/mxrb/ruby_app.rb', line 518

def start_scheduler = bridge.start_scheduler

#update_record(name, id, attributes, context: nil) ⇒ Object



475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# File 'lib/mxrb/ruby_app.rb', line 475

def update_record(name, id, attributes, context: nil)
  runtime_synchronize do
    value = bridge.interpreter.store.find(name.to_s, id.to_s)
    next unless value

    authorize_entity!(name, :write, context, record: value)
    bridge.interpreter.store.transaction do
      attributes.to_h.each do |member, member_value|
        authorize_entity!(name, :write, context, member: member, record: value)
        value.members[member.to_s] = deserialize(member_value)
      end
      bridge.interpreter.store.commit(value)
      serialize(value, context:)
    end
  end
end