Module: Rakit::Data::DataService

Defined in:
lib/rakit/data.rb

Overview

Persist and retrieve protobuf message instances under a configurable data root.

Storage layout: root is DataService.data_dir (default ~/.rakit/data). Path for a message is data_dir/TYPE_PATH/unique_name.pb. TYPE_PATH is the Ruby class name with :: replaced by File::SEPARATOR (e.g. Rakit::Shell::CommandRakit/Shell/Command). File content is binary protobuf; no character encoding.

Class Method Summary collapse

Class Method Details

._dir_for_type(type_name) ⇒ Object

PACKAGE_PATH/MESSAGE_NAME: e.g. Rakit::Shell::Command -> Rakit/Shell/Command

Raises:

  • (ArgumentError)


164
165
166
167
168
169
170
# File 'lib/rakit/data.rb', line 164

def self._dir_for_type(type_name)
  parts = type_name.split("::")
  raise ArgumentError, "type_name must be a qualified constant path" if parts.empty?

  relative = parts.join(File::SEPARATOR)
  File.join(data_dir, relative)
end

._path(type_name, unique_name) ⇒ Object



158
159
160
161
# File 'lib/rakit/data.rb', line 158

def self._path(type_name, unique_name)
  dir = _dir_for_type(type_name)
  File.join(dir, "#{unique_name}.pb")
end

._validate_unique_name!(unique_name) ⇒ Object

Raises:

  • (ArgumentError)


150
151
152
153
154
155
156
# File 'lib/rakit/data.rb', line 150

def self._validate_unique_name!(unique_name)
  u = unique_name.to_s
  raise ArgumentError, "unique_name must be a non-empty string" if u.strip.empty?
  if u.include?("/") || u.include?("\\") || u.include?("..")
    raise ArgumentError, "unique_name must not contain path separators or '..'"
  end
end

.data_dirString

Returns current data root (default: expanded ~/.rakit/data).

Returns:

  • (String)

    current data root (default: expanded ~/.rakit/data)



87
88
89
# File 'lib/rakit/data.rb', line 87

def self.data_dir
  @data_dir ||= File.expand_path("~/.rakit/data")
end

.data_dir=(path) ⇒ void

This method returns an undefined value.

Parameters:

  • path (String)

    set the data root for subsequent operations (e.g. tests use a temp dir)



93
94
95
# File 'lib/rakit/data.rb', line 93

def self.data_dir=(path)
  @data_dir = path
end

.get_names(type_name) ⇒ Array<String>

Return unique names (without .pb) for the given type.

Parameters:

  • type_name (String)

    Ruby class name for directory resolution

Returns:

  • (Array<String>)

    empty if directory missing or no .pb files

Raises:

  • (NameError)

    if type_name is not a valid constant

  • (ArgumentError)

    if type_name yields empty path segments (e.g. from _dir_for_type)



143
144
145
146
147
148
# File 'lib/rakit/data.rb', line 143

def self.get_names(type_name)
  dir = _dir_for_type(type_name.to_s)
  return [] unless File.directory?(dir)

  Dir.children(dir).select { |f| File.file?(File.join(dir, f)) && f.end_with?(".pb") }.map { |f| f.chomp(".pb") }
end

.load(type_name, unique_name) ⇒ Object

Load a stored message.

Parameters:

  • type_name (String)

    Ruby class name (e.g. “Rakit::Shell::Command”)

  • unique_name (String)

    same rules as for store

Returns:

  • (Object)

    decoded message instance

Raises:

  • (ArgumentError)

    if unique_name invalid

  • (NameError)

    if type_name is not a valid constant

  • (Errno::ENOENT)

    if the file does not exist



118
119
120
121
122
123
124
125
# File 'lib/rakit/data.rb', line 118

def self.load(type_name, unique_name)
  _validate_unique_name!(unique_name)
  klass = Object.const_get(type_name.to_s)
  path = _path(klass.name, unique_name.to_s)
  raise Errno::ENOENT, path unless File.file?(path)

  klass.decode(File.binread(path))
end

.remove(type_name, unique_name) ⇒ void

This method returns an undefined value.

Remove a stored message by type and unique name (no-op if file absent).

Parameters:

  • type_name (String)

    Ruby class name

  • unique_name (String)

    same rules as for store

Raises:

  • (ArgumentError)

    if unique_name invalid



132
133
134
135
136
# File 'lib/rakit/data.rb', line 132

def self.remove(type_name, unique_name)
  _validate_unique_name!(unique_name)
  path = _path(type_name.to_s, unique_name.to_s)
  File.delete(path) if File.file?(path)
end

.store(message, unique_name) ⇒ void

This method returns an undefined value.

Store a proto message under a unique name.

Parameters:

  • message (Object)

    instance of a generated protobuf message class

  • unique_name (String)

    non-empty, must not contain path separators or ..

Raises:

  • (ArgumentError)

    if unique_name is empty/blank or contains path traversal

  • (Errno::EACCES)

    etc. on permission failure



103
104
105
106
107
108
109
# File 'lib/rakit/data.rb', line 103

def self.store(message, unique_name)
  _validate_unique_name!(unique_name)
  klass = message.class
  path = _path(klass.name, unique_name)
  FileUtils.mkdir_p(File.dirname(path))
  File.binwrite(path, klass.encode(message))
end