表演和优雅!使用 ScyllaDB 和 Ruby 的 CLI CRUD 工具

2025-05-26

表演和优雅!使用 ScyllaDB 和 Ruby 的 CLI CRUD 工具

Boas pessoas desenvolvedoras precisam saber fazer CRUD não é mesmo?您是否可以使用 CRUD 产品和 NoSQL 银行进行高级升级并使用优雅而简单的语言?呐?无论如何,您可以使用 CLI 构建一个宝石,或者使用 ScyllaDBdry-cli银行来构建一个宝石cassandra-driver

Para saber um pouco mais sobre o que é ScyllaDB e em quais contextos essa ferramenta é util, recomendo ler a documentação oficial

免责声明:Esse artigo 假设银行的基本信息是与 ruby​​ 结合使用的,因此 Scylla DB 推荐的具体信息如下:

  1. Os artigos criados pelo 开发者倡导者 da ScyllaDB DanielHe4rt no Dev.To
  2. ScyllaDB 大学无偿提供 ScyllaDB 优惠

目录

1. 开始或项目

1.1 驱动程序安装系统的解决方案

使用 ScyllaDB 和cassandra 驱动程序进行通讯的绝妙方法,可以与 cassandra 银行相关的系统相关的图书馆进行安装,安装 cassandra 所需的图书馆的安装形式非常简单纳马奎纳:

免责声明:请不要使用卡桑德拉银行的任何实用程序,请务必安装宝石巴士作为必要的图书馆。访问 ScyllaDB 的原始基础,使用安静的 Cassandra 模式。

在 MacOS 上安装 cassandra:

brew install cassandra
Enter fullscreen mode Exit fullscreen mode

在 Linux(Debian) 上安装 cassandra:

sudo apt-get install -y cassandra
Enter fullscreen mode Exit fullscreen mode

Caso sua Distro/Sistema operacional não tenha sido listada, recomendo semper recorrer a Documentação Oficial

1.2 开始或项目

com 作为安装相关系统的图书馆,启动项目使用捆绑程序

mkdir project_scylla && cd project_scylla && bundle init
Enter fullscreen mode Exit fullscreen mode

1.3 项目依赖的安装

Agora Podemos Finalmente 是项目所需的宝石:

bundle add cassandra-driver
bundle add dry-auto_inject
bundle add dry-system
bundle add zeitwerk
bundle add dry-cli
bundle add dotenv
Enter fullscreen mode Exit fullscreen mode

1.4 定义 REPL

如果没有相关项目,那么 IRB 的设置是否有问题?

Para isso crie um script executável em bin/consolecom:

touch bin/console && chmod +x bin/console
Enter fullscreen mode Exit fullscreen mode

Nesse arquivo vamos 包括 um setup básico com IRB + Dotenv:

#!/usr/bin/env ruby

require 'dotenv/load'

require 'irb'
IRB.start
Enter fullscreen mode Exit fullscreen mode

非常好! Agora temos o nosso setup concluído! Vamos 程序定义了依赖项的注入和与 ScyllaDB 功能合并的验证器。

2. 依赖注入的定义

下一步是使用 sinatra 的模型,并根据 sinatra 的设置进行设置,并参考esse 链接。

2.1 Criando o 容器主体

根据描述,没有任何相关参考信息,因此需要定义容器主体的依赖功能,以提供与总线依赖和新注册提供商相关的参考服务。

Agora vamos criar um arquivo em config/application.rbcom o seguinte conteúdo:

# frozen_string_literal: true

require 'dry/system'

class Application < Dry::System::Container
  configure do |config|
    config.root = Pathname('.')

    config.component_dirs.loader = Dry::System::Loader::Autoloading

    config.component_dirs.add 'lib'
    config.component_dirs.add 'config'
  end
end

loader = Zeitwerk::Loader.new
loader.push_dir(Application.config.root.join('lib').realpath)
loader.push_dir(Application.config.root.join('config').realpath)
loader.setup
Enter fullscreen mode Exit fullscreen mode

下一步,我们将定义一个本地化的组件,该组件在应用程序中很长一段时间,这是一个自动加载文件的原则,该文件将永远精确地用于使用阿尔古玛类阿尔古姆阿尔基沃require

容器定义可以通过修改 REPL 脚本来定义容器的应用程序和方法finalize!

#!/usr/bin/env ruby

require 'dotenv/load'
require_relative '../config/application'

Application.finalize!

require 'irb'
IRB.start
Enter fullscreen mode Exit fullscreen mode

Também vamos criar um arquivo main.rbna raiz do nosso projeto apenas com o finalize!paraservir como ponto de entrada na nossa CLI。

require 'dotenv/load'
require_relative '../config/application'

Application.finalize!
Enter fullscreen mode Exit fullscreen mode

2.2 Criando o 银行提供商

Agora 是一个应用程序容器主体,定义了一个与 ScyllaDB 相关的项目。

Para isso crie um arquivo em config/provider/database.rbcom o seguinte conteúdo:

# frozen_string_literal: true

Application.register_provider(:database) do
  prepare do
    require 'cassandra'
    require_relative '../../lib/migration_utils'
    require_relative '../constants'
  end

  start do
    cluster = Cassandra.cluster(
      username: ENV.fetch('DB_USER', nil),
      password: ENV.fetch('DB_PASSWORD', nil),
      hosts: ENV.fetch('DB_HOSTS', nil).split(',')
    )

    connection = cluster.connect

    MigrationUtils.create_keyspace(session: connection) if MigrationUtils.keyspace_exist?(session: connection)
    MigrationUtils.create_table(session: connection) if MigrationUtils.table_exist?(session: connection)

    connection = cluster.connect(KEYSPACE_NAME)

    register('database.connection', connection)
  end
end
Enter fullscreen mode Exit fullscreen mode

作为推荐使用云服务 ScyllaDB 的凭证,您可以快速使用集群并获取所有超级简单的凭证。

嗯,例如 .env pode ser visto abaixo:

DB_USER=scylla
DB_PASSWORD=password
DB_HOSTS=node-0.amazonaws,node-1.amazonaws,node-2.amazonaws
Enter fullscreen mode Exit fullscreen mode

如果您想了解更多相关信息,请参阅以下内容:

定义为常量 para nosso projeto

使用键空间名称和常量定义的表格,可以更改接收参数或环境变量,以简化常量。

Crie um arquivo em config/constants.rbcom o seguinte conteúdo:

# frozen_string_literal: true

KEYSPACE_NAME = 'media_player'
TABLE_NAME = 'playlist'
Enter fullscreen mode Exit fullscreen mode

Criando uma Classe utilitária para criar nosso banco

与前面提供的示例不同,我们使用了一系列产品活动,包括MigrationUtils键空间和表格的初始化。

需要先采取必要的方法来处理键空间和表格。

Checando se um keyspace ou tabela 存在

前面的内容是通过键空间和表格进行的,并验证这些元素是否存在并执行所需的功能。接下来,请执行以下布尔操作方法:

首先,criaremos um arquivo chamado migration_utils.rb、localizado em
lib/migration_utils.rb、eo preencheremos com 或 código descrito abaixo:

class MigrationUtils
  # @param session [Cassandra#Cluster]
  # @return [Boolean]
  def self.keyspace_exist?(session:)
    query = <<~SQL
      select keyspace_name from system_schema.keyspaces WHERE keyspace_name=?
    SQL

    session.execute_async(query, arguments: [KEYSPACE_NAME]).join.rows.size.zero?
  end

  # @param session [Cassandra#Cluster]
  # @return [Boolean]
  def self.table_exist?(session:)
    query = <<~SQL
      select keyspace_name, table_name from system_schema.tables where keyspace_name = ? AND table_name = ?
    SQL

    session.execute_async(query, arguments: [KEYSPACE_NAME, TABLE_NAME]).join.rows.size.zero?
  end
end
Enter fullscreen mode Exit fullscreen mode

因此,您可以在配置依赖项之前执行配置方法。通过此操作,您可以在银行中进行相关操作。请参阅execute_asyncCQL 的使用方法或环境咨询方法。任何方法都允许使用占位符?来参数和特定值以及对象arguments: []

使用这种方法可以实现未来的目标,也可以使用该方法join来实现未来的最终目标。对于重要的对象,可以将所有行发送到列表中,作为查询最常用的链接。

完成布尔恢复方法的实施,将列表设置为当前列表或不设置列表或零列表.size.zero?

免责声明:使用枚举器时的注意事项.size.zero?但不适用或方法.empty?

Criando os 键空间和 tabelas

讨论按键空间和表格中的存在问题的响应方法,具体的方法是什么?

Para isso, vamos continuar trabalhando na classe localizada em lib/migration_utils.rbcom o seguinte conteúdo:

class MigrationUtils
  # @param session [Cassandra#Cluster]
  # @return [void]
  def self.create_keyspace(session:)
    query = <<~SQL
      CREATE KEYSPACE #{KEYSPACE_NAME}
      WITH replication = {
        'class': 'NetworkTopologyStrategy',
        'replication_factor': '3'
      }
      AND durable_writes = true
    SQL

    session.execute_async(query).join
  end

  # @param session [Cassandra#Cluster]
  # @return [void]
  def self.create_table(session:)
    query = <<~SQL
      CREATE TABLE #{KEYSPACE_NAME}.#{TABLE_NAME} (
        id uuid,
        title text,
        album text,
        artist text,
        created_at timestamp,
        PRIMARY KEY (id, created_at)
      );
    SQL

    session.execute_async(query).join
  end
end
Enter fullscreen mode Exit fullscreen mode

Aqui vamos seguir or mesmo padrão onde recebemos or sessioncomo paraâmetro eo usamos para executar uma query assíncrona com execute_async,como não precisamos litdar com or retorno dessas query podemos apenas usar or joinpara esperar ela Finalizar.

注意:请参考ScyllaDB 大学的推荐链接作为查询

2.3 服务或新供应商的应用

银行的定义和相关内容、应用程序的主要注意事项:

我们无需main.rb添加或要求:

require_relative 'config/provider/database'
Enter fullscreen mode Exit fullscreen mode

我没有bin/console吃东西:

require_relative '../config/provider/database'
Enter fullscreen mode Exit fullscreen mode

3. CLI 的定义或样板

集市上的银行卡马达需要立即投入使用,vamos 会使用一个额外的宝石项目来定义我们的 CLI 操作。各位dry-cli先生们!

最初的时刻是不是需要预先定义 CLI 的样板,是否需要真正的实现?

Para isso, vamos definir or modulo que vai registrar todos os comandos, localizado em lib/cli.rbcom o seguinte conteúdo:

# frozen_string_literal: true

require 'dry/cli'

module Cli
  extend Dry::CLI::Registry

  register 'add', Commands::Add
end
Enter fullscreen mode Exit fullscreen mode

使用DSL来对注册商进行模数初始操作register,然后将 DSL 和模数扩展器进行模数化Dry::CLI::Registry

Agora que registramos um comando Add, vamos criar a classereferente a ele localizada em lib/cli/commands/add.rbcom o seguinte conteúdo:

# frozen_string_literal: true

require 'dry/cli'

module Cli
  module Commands
    class Add < Dry::CLI::Command
      desc 'This command add a new song to the playlist'

      argument :title, type: :string, required: true, desc: 'The title of the song'
      argument :album, type: :string, required: true, desc: 'The name of the album of that song'
      argument :artist, type: :string, required: true, desc: 'The name of the artist of band'

      def call(title:, album:, artist:)
        puts "Add command -> Title: #{title}, Album: #{album}, Artist: #{artist}"
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Nessa classe podemos ver outra DSL fornecida por herdar a classe Dry::CLI::Command,com ela podemos prover uma descrição para o comando usando desc,声明quais argumentos vamos receber para esse comando junto com seu Tipo,验证和描述com argumente muito mais!

徽标定义为我们的指挥官的元数据,定义了所有call参数定义的方法。

Em nosso arquivo main.rbpodemos inicializar a nossa CLI com:

require 'dry/cli'

Dry::CLI.new(Cli).call
Enter fullscreen mode Exit fullscreen mode

最后,执行应用程序ruby main.rb并继续输出:

$ ruby main.rb
Commands:
  main.rb add TITLE ALBUM ARTIST                 # This command add a new song to the playlist
Enter fullscreen mode Exit fullscreen mode

4. 实施我们的突击队

Agora que temos um 样板和 um entendimento basic quanto a funcionamento da gem dry-clipodemos nos preocupar em 实现alguns comandos simples para nosso CRUD。

4.1 执行主命令添加

如果您想要执行以下命令,请执行以下操作call

# frozen_string_literal: true

require 'dry/cli'

module Cli
  module Commands
    class Add < Dry::CLI::Command
      desc 'This command add a new song to the playlist'

      argument :title, type: :string, required: true, desc: 'The title of the song'
      argument :album, type: :string, required: true, desc: 'The name of the album of that song'
      argument :artist, type: :string, required: true, desc: 'The name of the artist of band'

      def initialize
        super
        @repo = Application['database.connection']
      end

      def call(title:, album:, artist:)
        query = <<~SQL
          INSERT INTO #{KEYSPACE_NAME}.#{TABLE_NAME} (id,title,artist,album,created_at) VALUES (now(),?,?,?,?);
        SQL

        @repo.execute_async(query, arguments: [title, artist, album, Time.now]).join

        puts "Song '#{title}' from artist '#{artist}' Added!"
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

下一步,我们将首先实施与银行相关的银行,以建立与银行相关的银行,并使用该银行的相关方法来构建新的银行execute_async

这是重要的查询功能now(),它是银行的原生功能,可以为 ScyllaDB 提供新的 UUID 服务,该格式是快速激光雷达的格式,可直接使用 UUID 语言。

Bem simples certo? vamos 继续 com os próximos comandos do nosso CRUD seguindo a mesma arquitetura já proposta.

4.2 执行命令列表

详细介绍了一些新的音乐,并准备了一些关于未来的音乐列表。 Para isso vamos criar um arquivo em lib/cli/commands/list.rbcom o seguinte conteúdo:

# frozen_string_literal: true

require 'dry/cli'

module Cli
  module Commands
    class List < Dry::CLI::Command
      desc 'This command shows all the created songs'

      def initialize
        super
        @repo = Application['database.connection']
      end

      def call
        query = <<~SQL
          SELECT * FROM #{KEYSPACE_NAME}.#{TABLE_NAME};
        SQL

        @repo.execute_async(query).join.rows.each do |song|
          puts <<~MSG
            ID: #{song['id']} | Song Name: #{song['title']} | Album: #{song['album']} | Created At: #{song['created_at']}
          MSG
        end
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

新的,我们的查询SELECT很简单,但最终的结果是没有任何结果,rows重要的是,访问的字段与对应的row['title']字段相同,但没有CREATE TABLE提供者。

注册员的权限可以修改或修改lib/cli.rb

# frozen_string_literal: true

require 'dry/cli'

module Cli
  extend Dry::CLI::Registry

  register 'add', Commands::Add
  register 'list', Commands::List # <= Novo comando
end
Enter fullscreen mode Exit fullscreen mode

佩菲托! Agora podemos tanto adicionar Quanto listar musicas :D

Uma simples demo do que temos agora tanto com 添加双币种列表:

4.3 执行或命令删除

Vamos 探索集市和comando bastante interessante。接下来,我将列出索引的伴奏音乐列表,允许您选择对应位置的特定音乐。

为了实现这一点,请执行以下操作:将列表中的音乐响应为与数字相关的方法。不过,请注意不要使用任何输入。

接下来,vamos criar um novo arquivo localizado em lib/cli/commands/delete.rbcom o seguinte conteúdo:

# frozen_string_literal: true

require 'dry/cli'

module Cli
  module Commands
    class Delete < Dry::CLI::Command
      desc 'This command will prompt for a song to be deleted and then delete it'

      def initialize
        super
        @repo = Application['database.connection']
      end

      def call
        songs = @repo.execute_async("SELECT * FROM #{KEYSPACE_NAME}.#{TABLE_NAME}").join.rows

        song_to_delete_index = select_song_to_delete(songs:)

        query = <<~SQL
          DELETE FROM #{KEYSPACE_NAME}.#{TABLE_NAME} WHERE id = ?
        SQL

        song_to_delete = songs.to_a[song_to_delete_index]

        @repo.execute_async(query, arguments: [song_to_delete['id']]).join
      end

      private

      # @param songs [Array<Hash>]
      def select_song_to_delete(songs:)
        songs.each_with_index do |song, index|
          puts <<~DESC
            #{index + 1})  Song: #{song['title']} | Album: #{song['album']} | Artist: #{song['artist']} | Created At: #{song['created_at']}
          DESC
        end

        print 'Select a song to be deleted: '
        $stdin.gets.chomp.to_i - 1
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

没有方法select_song_to_delete可以接收音乐列表并根据使用的方法或方法进行操作,each_with_index以确保没有模型1) Song: | Album: | Artist: | Created At:。 Ainda nesse 方法esperamos 或输入usuário com$stdin.gets.chomp或repassamos 不返回como um 整数转换com .to_i

没有任何方法call,请按照音乐注册表咨询所有内容。接下来,请结合使用方法来选择特定的具体信息并返回信息。使用时,请先阅读特定的音乐,然后再进行DELETE删除咨询。

免责声明:选择具体项目的前提,请先将数组转换为.to_a枚举

Uma 演示了大多数操作或使用命令的实践:

注册员的权限可以修改或修改lib/cli.rb

# frozen_string_literal: true

require 'dry/cli'

module Cli
  extend Dry::CLI::Registry

  register 'add', Commands::Add
  register 'list', Commands::List
  register 'delete', Commands::Delete # <= Novo comando
end
Enter fullscreen mode Exit fullscreen mode

4.4 实施和命令更新

Agora vamos criar um comando que vai juntar todos os conceitosmostrados
anteriormente, este será um updatee vai Performar da seguinte forma:

  • Vamos aceitar parâmetros como title, album, artistpara usar como parte do update (semelhante ao que fizemos no comando add)
  • Vamosmostrar para o usuário uma lista das musicas cadastradas e esperar o input do usuário com um índice (semelhante ao que fizemos no comando delete)

佩菲托! Vamos criar um comando localizado em lib/cli/commands/update.rbcom o seguinte conteúdo:

# frozen_string_literal: true

require 'dry/cli'

module Cli
  module Commands
    class Update < Dry::CLI::Command
      desc 'This command will prompt for a song to be updated and use the argument information to updated it'

      argument :title, type: :string, required: true, desc: 'The title of the song'
      argument :album, type: :string, required: true, desc: 'The name of the album of that song'
      argument :artist, type: :string, required: true, desc: 'The name of the artist of band'

      def initialize
        super
        @repo = Application['database.connection']
      end

      def call(title:, album:, artist:)
        songs = @repo.execute_async("SELECT * FROM #{KEYSPACE_NAME}.#{TABLE_NAME}").join.rows

        song_to_update_index = select_song_to_update(songs:)

        query = <<~SQL
          UPDATE #{KEYSPACE_NAME}.#{TABLE_NAME} SET title = ?, artist = ?, album = ? WHERE id = ? AND created_at = ?
        SQL

        song_to_update = songs.to_a[song_to_update_index]

        @repo.execute_async(query, arguments: [title, artist, album, song_to_update['id'] song_to_update['created_at']]).join
      end

      private

      # @param songs [Array<Hash>]
      def select_song_to_update(songs:)
        songs.each_with_index do |song, index|
          puts <<~DESC
            #{index + 1})  Song: #{song['title']} | Album: #{song['album']} | Artist: #{song['artist']} | Created At: #{song['created_at']}
          DESC
        end

        print 'Select a song to be updated: '
        $stdin.gets.chomp.to_i - 1
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Como pode ver, esse comando é realmente uma junção entre os conceitos do comando add(arguments) and conceitos do comando delete(método select pelo índice)。

重要的查询是updateScyllaDB 没有主键(实际上是这样idcreated_at,请使用它作为与银行相关的信息。

Uma 演示了命令的大部分功能:

结论

Espero que esse artigo tenha sido útil!最大程度地集成 Ruby 和 ScyllaDB,但我们无法使用简单的语言来启动。有关 ScyllaDB 的具体指示,建议加强DanielHe4rtScyllaDB 大学的工作

我们将继续进行这个项目! Deixo como um desafio abusca por aprimoramentos na arquitetura que construímos。 Abaixo,elenco alguns pontos de melhoria 明显,porém,encorajo você 一个识别的结尾 que possam ter passado despercebidos por mim。 A prática é o segredo para se Tornar uma pessoa desenvolvedora habilidosa!
😄

  • 作为 funções select_song_to_deletee select_song_to_updatesão iguais,talvez mover ela para algum local comum?
  • 没有指挥官在打印中add成功地完成我们的任务,如果没有任务结束,您将获得更好的经验吗?

愿原力与你同在!🍒

文章来源:https://dev.to/scylladb/performance-e-elegancia-escrevendo-uma-cli-crud-utilizando-scylladb-e-ruby-1452
PREV
什么是服务器端渲染?
NEXT
数据库 101:初学者数据一致性