This is an automated email from the ASF dual-hosted git repository.

erickguan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 74ebf547e fix(bindings/ruby): publish ruby binding gem (#6606)
74ebf547e is described below

commit 74ebf547e02c0d64e365ce65b644f69cc031ee50
Author: Erick Guan <297343+erickg...@users.noreply.github.com>
AuthorDate: Wed Oct 1 20:18:55 2025 +0200

    fix(bindings/ruby): publish ruby binding gem (#6606)
    
    * Fix path for the action patch file
    
    * Fix rake release task override
    
    * Fix release workflows
    
    Fixes file paths to include core files
    Fixes complilations
    Fixes artifact downloads
    Lock release tags
    
    * Fix metadata's inspect
    
    * Move middlewares to OpenDal::Middlware
    
    * Fix tests in CI
    
    * Push all native gems
    
    * Update links in README
    
    * Extend native build for more Rubies
    
    * Version bump
---
 .github/workflows/release_ruby.yml         | 67 ++++++++++++++-----------
 bindings/ruby/Cargo.toml                   |  7 +--
 bindings/ruby/README.md                    |  6 +--
 bindings/ruby/Rakefile                     | 71 +++++++++++++++++++++++----
 bindings/ruby/core                         |  1 +
 bindings/ruby/lib/opendal_ruby/metadata.rb |  2 +-
 bindings/ruby/opendal.gemspec              | 43 ++++++++++++----
 bindings/ruby/src/lib.rs                   |  5 +-
 bindings/ruby/src/metadata.rs              |  2 +-
 bindings/ruby/src/middlewares.rs           | 33 ++++++++-----
 bindings/ruby/test/blocking_op_test.rb     |  2 +-
 bindings/ruby/test/metadata_test.rb        | 78 ++++++++++++++++++++++++++++++
 bindings/ruby/test/middlewares_test.rb     |  8 +--
 13 files changed, 252 insertions(+), 73 deletions(-)

diff --git a/.github/workflows/release_ruby.yml 
b/.github/workflows/release_ruby.yml
index aab851d09..25bdde914 100644
--- a/.github/workflows/release_ruby.yml
+++ b/.github/workflows/release_ruby.yml
@@ -20,7 +20,7 @@ name: Release Ruby Binding
 on:
   push:
     tags:
-      - 'v[0-9]+.[0-9]+.[0-9]+*' # Triggers on version tags (v0.54.0, 
v0.54.0-rc.1, etc.)
+      - 'v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+' # Triggers on version tags (v0.54.0, 
v0.54.0-rc.1, etc.)
   pull_request:
     branches:
       - main
@@ -43,6 +43,8 @@ defaults:
 jobs:
   build:
     runs-on: ubuntu-latest
+    outputs:
+      version: ${{ steps.summary.outputs.version }}
 
     steps:
       - name: Checkout repository
@@ -68,12 +70,21 @@ jobs:
           retention-days: 30
 
       - name: Log Build Summary
+        id: summary
         run: |
+          # e.g. from `Cargo.toml` to released version:
+          # - 0.54.0 -> 0.54.0
+          # - 0.54.0-rc.1 -> 0.54.1.pre.rc.1
+          VERSION=$(bundle exec rake version)
+          echo "version=$VERSION" >> "$GITHUB_OUTPUT"
+
           echo "## Ruby gem built successfully! 📦" >> "$GITHUB_STEP_SUMMARY"
           echo "" >> "$GITHUB_STEP_SUMMARY"
           echo "**Ref:** ${{ github.ref_name || github.ref }}" >> 
"$GITHUB_STEP_SUMMARY"
-          echo "**Version:** $(bundle exec rake version)" >> 
"$GITHUB_STEP_SUMMARY"
+          echo "**Version:** $VERSION" >> "$GITHUB_STEP_SUMMARY"
 
+  # We maintain multiple build targets for native gems.
+  # We only provide best-effort support for native gems. We could withdraw the 
support to these builds at any time.
   build-native:
     strategy:
       fail-fast: false
@@ -82,17 +93,16 @@ jobs:
           - os: ubuntu-latest
             platform: x86_64-linux
             rust_target: x86_64-unknown-linux-gnu
-          - os: ubuntu-latest
+          - os: ubuntu-24.04-arm
             platform: aarch64-linux
             rust_target: aarch64-unknown-linux-gnu
           - os: macos-latest
-            platform: arm64-darwin
+            platform: arm64-darwin23
             rust_target: aarch64-apple-darwin
-          - os: macos-latest
-            platform: x86_64-darwin
-            rust_target: x86_64-apple-darwin
 
     runs-on: ${{ matrix.os }}
+    continue-on-error: true
+
     env:
       RB_SYS_CARGO_TARGET: ${{ matrix.rust_target }}
 
@@ -110,28 +120,17 @@ jobs:
           bundler-cache: true
           working-directory: bindings/ruby # must repeat because GitHub 
Actions will not use defaults.run
 
-      - name: Linux cross deps
-        if: runner.os == 'Linux' && matrix.platform == 'aarch64-linux'
-        run: sudo apt-get update && sudo apt-get install -y 
gcc-aarch64-linux-gnu
+      - name: Show available rake tasks
+        run: bundle exec rake --tasks
 
-      - name: Build gem
-        if: runner.os != 'Linux' || matrix.platform != 'aarch64-linux'
-        run: bundle exec rake native gem
+      - name: Build native gem
+        run: bundle exec rake native:opendal:${{ matrix.platform }} gem
 
-      # For Linux aarch64, tell Cargo which linker to use
-      - name: Build aarch64-linux gem
-        if: runner.os == 'Linux' && matrix.platform == 'aarch64-linux'
-        run: |
-          export 
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
-          bundle exec rake native gem
-
-      - name: Collect gems
+      - name: Collect built gem
         run: |
           echo "Built gem file:"
           ls -la pkg/
           echo ""
-          echo "Gem version:"
-          bundle exec rake version
 
           mkdir -p pkg_out
           cp -r pkg/*.gem pkg_out/
@@ -144,9 +143,14 @@ jobs:
           retention-days: 30
 
   publish:
+    # allow:
+    # - standard tag releases
+    # - workflow_dispatch:
+    #   - reattempt standard tag releases
+    #   - pre-releases
     if: >-
-      (github.event_name == 'workflow_dispatch' && github.ref == 
'refs/heads/main') ||
-      (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && 
!contains(github.ref, '-'))
+      startsWith(github.ref, 'refs/tags/v') ||
+        (github.event_name == 'workflow_dispatch' && 
contains(needs.build.outputs.version, 'rc'))
     needs: [build, build-native]
     runs-on: ubuntu-latest
 
@@ -162,12 +166,17 @@ jobs:
           bundler-cache: true
           working-directory: bindings/ruby
 
-      - name: Download gem artifact
-        if: github.event_name == 'push'
+      - name: Download gem artifacts
         uses: actions/download-artifact@v4
         with:
           pattern: opendal-ruby-gem-*
           path: bindings/ruby/pkg/
+          merge-multiple: true
+
+      - name: List downloaded artifacts
+        run: |
+          echo "Downloaded gems:"
+          ls -lh pkg/*.gem
 
       # Adapted from rubygems/release-gem@v1. Changed:
       # 1. publishing git tag
@@ -254,7 +263,7 @@ jobs:
       - name: Release gem
         run: bundle exec rake release
         env:
-          RUBYOPT: "${{ format('-r{0}/rubygems-attestation-patch.rb {1}', 
github.action_path, env.RUBYOPT) || env.RUBYOPT }}"
+          RUBYOPT: "${{ 
format('-r{0}/bindings/ruby/rubygems-attestation-patch.rb {1}', 
github.workspace, env.RUBYOPT) || env.RUBYOPT }}"
 
       - name: Wait for release to propagate
         run: gem exec rubygems-await pkg/*.gem
@@ -264,6 +273,8 @@ jobs:
           echo "## Ruby binding released! 🚀" >> "$GITHUB_STEP_SUMMARY"
           echo "" >> "$GITHUB_STEP_SUMMARY"
           echo "**Version:** $(bundle exec rake version)" >> 
"$GITHUB_STEP_SUMMARY"
+          echo "" >> "$GITHUB_STEP_SUMMARY"
+          echo "Check out the [RubyGems 
page](https://rubygems.org/gems/opendal) for more details." >> 
"$GITHUB_STEP_SUMMARY"
 
       - name: Clean up credentials
         if: always()
diff --git a/bindings/ruby/Cargo.toml b/bindings/ruby/Cargo.toml
index 46112650a..867355e24 100644
--- a/bindings/ruby/Cargo.toml
+++ b/bindings/ruby/Cargo.toml
@@ -25,7 +25,7 @@ homepage = "https://opendal.apache.org/";
 license = "Apache-2.0"
 repository = "https://github.com/apache/opendal";
 rust-version = "1.82"
-version = "0.1.0"
+version = "0.1.6-rc.2" # updates this version to bump Rubygem version during 
release process
 
 [lib]
 crate-type = ["cdylib"]
@@ -34,8 +34,9 @@ name = "opendal_ruby"
 
 [dependencies]
 magnus = { version = "0.8", features = ["bytes", "io"] }
-# this crate won't be published, we always use the local version
-opendal = { version = ">=0", path = "../../core", features = [
+# 1. this crate won't be published, we always use the local version
+# 2. we use the symbolink to allow released gem to find core's source files.
+opendal = { version = ">=0", path = "./core", features = [
   "blocking",
   "layers-throttle",
   # These are default features before v0.46. TODO: change to optional features
diff --git a/bindings/ruby/README.md b/bindings/ruby/README.md
index 409679837..11376f4d5 100644
--- a/bindings/ruby/README.md
+++ b/bindings/ruby/README.md
@@ -1,9 +1,9 @@
 # Apache OpenDALâ„¢ Ruby Binding
 
-![Gem Version](https://img.shields.io/gem/v/opendal)
-![Gem Downloads (for latest version)](https://img.shields.io/gem/dtv/opendal)
+[![Gem 
Version](https://img.shields.io/gem/v/opendal)](https://rubygems.org/gems/opendal)
+[![Gem Downloads (for latest 
version)](https://img.shields.io/gem/dtv/opendal)](https://rubygems.org/gems/opendal)
 
-OpenDAL's Ruby gem.
+OpenDAL's Ruby [gem](https://rubygems.org/gems/opendal).
 
 
![](https://github.com/apache/opendal/assets/5351546/87bbf6e5-f19e-449a-b368-3e283016c887)
 
diff --git a/bindings/ruby/Rakefile b/bindings/ruby/Rakefile
index 66c4b8d56..f21942d5b 100644
--- a/bindings/ruby/Rakefile
+++ b/bindings/ruby/Rakefile
@@ -19,26 +19,31 @@
 
 require "bundler/gem_tasks"
 require "rake/testtask"
-require "rake/extensiontask"
+require "rb_sys/extensiontask"
 require "standard/rake"
 
 GEMSPEC = Gem::Specification.load("opendal.gemspec")
 CRATE_PACKAGE_NAME = "opendal-ruby"
 
-Rake::ExtensionTask.new(CRATE_PACKAGE_NAME, GEMSPEC) do |ext|
+RbSys::ExtensionTask.new(CRATE_PACKAGE_NAME, GEMSPEC) do |ext|
   ext.name = "opendal_ruby"
   ext.ext_dir = "."
   ext.lib_dir = "lib/opendal_ruby"
 
   ext.cross_compile = true
   ext.cross_platform = %w[
-    x64-mingw-ucrt
-    x64-mingw32
     x86_64-linux
     arm64-linux
-    x86_64-darwin
     arm64-darwin
+    arm64-darwin23
   ]
+
+  # Override Ruby version requirement for native gems
+  # This prevents automatic constraint to the build Ruby version (e.g., >= 
3.3, < 3.4.dev)
+  ext.cross_compiling do |gem_spec|
+    # keep in sync with opendal.gemspec
+    gem_spec.required_ruby_version = ">= 3.2", "< 3.5.dev"
+  end
 end
 
 Rake::Task[:test].prerequisites << :compile
@@ -80,17 +85,65 @@ namespace :doc do
 end
 
 task doc: "doc:default"
-task default: %i[clobber compile test standard]
+task default: %i[compile test standard]
+task purge: %i[clean clobber]
 
 desc "report gem version"
 task :version do
   print GEMSPEC.version
 end
 
-desc "Multi-arch release (overrides bundler)"
+desc "Resolve symlinks in core directory for gem packaging" # Read more in 
`.release-ruby.yml`
+task :resolve_symlinks do
+  puts "Changing symlinks in core directory to actual files for packaging..."
+  Dir.chdir("core") do # a symbolic link to OpenDAL core for packaging
+    Dir.glob("**/*", File::FNM_DOTMATCH).each do |file|
+      next unless File.symlink?(file)
+
+      link_target = File.readlink(file)
+      resolved_path = File.expand_path(link_target, File.dirname(file))
+
+      if File.exist?(resolved_path)
+        puts "Resolving symlink: #{file} -> #{link_target}"
+        File.delete(file)
+        FileUtils.cp(resolved_path, file)
+      end
+    end
+  end
+end
+
+# Hook into build task to resolve symlinks first
+Rake::Task["build"].enhance([:resolve_symlinks]) if 
Rake::Task.task_defined?("build")
+
+Rake::Task["release"].clear # clear the existing release task to allow override
+Rake::Task["release:rubygem_push"].clear if 
Rake::Task.task_defined?("release:rubygem_push")
+
+# overrides bundler's default rake release task
+# we removed build and git tagging steps. Read more in `.release-ruby.yml`
+# read more 
https://github.com/rubygems/rubygems/blob/master/bundler/lib/bundler/gem_helper.rb
+desc "Multi-arch release"
 task release: [
-  # we removed build and git tagging steps. Read more in `.release-ruby.yml`
-  # read more 
https://github.com/rubygems/rubygems/blob/master/bundler/lib/bundler/gem_helper.rb
   "release:guard_clean",
   "release:rubygem_push"
 ]
+
+namespace :release do
+  desc "Push all gems to RubyGems"
+  task :rubygem_push do
+    # Push all gem files (source + platform-specific) like Nokogiri does
+    # Bundler's built_gem_path only returns the last modified gem, so we glob 
all gems instead
+    gem_files = Gem::Util.glob_files_in_dir("#{GEMSPEC.name}-*.gem", 
"pkg").sort
+
+    if gem_files.empty?
+      abort "No gem files found in pkg/"
+    end
+
+    puts "Found #{gem_files.length} gem(s) to push:"
+    gem_files.each { |f| puts "  - #{File.basename(f)}" }
+
+    gem_files.each do |gem_file|
+      puts "\nPushing #{File.basename(gem_file)}..."
+      sh "gem push #{gem_file}"
+    end
+  end
+end
diff --git a/bindings/ruby/core b/bindings/ruby/core
new file mode 120000
index 000000000..3d25ddeb2
--- /dev/null
+++ b/bindings/ruby/core
@@ -0,0 +1 @@
+../../core
\ No newline at end of file
diff --git a/bindings/ruby/lib/opendal_ruby/metadata.rb 
b/bindings/ruby/lib/opendal_ruby/metadata.rb
index 8194f530d..51e7782a0 100644
--- a/bindings/ruby/lib/opendal_ruby/metadata.rb
+++ b/bindings/ruby/lib/opendal_ruby/metadata.rb
@@ -36,7 +36,7 @@ module OpenDal
 
     def inspect
       # Be concise to keep a few attributes
-      "#<#{self.class.name} mode: #{entry_mode}, \
+      "#<#{self.class.name} mode: #{mode}, \
         content_type: #{content_type}, \
         content_length: #{content_length}>"
     end
diff --git a/bindings/ruby/opendal.gemspec b/bindings/ruby/opendal.gemspec
index 672da127a..90d5c9ef1 100644
--- a/bindings/ruby/opendal.gemspec
+++ b/bindings/ruby/opendal.gemspec
@@ -41,21 +41,44 @@ Gem::Specification.new do |spec|
   spec.homepage = "https://opendal.apache.org/";
   spec.license = "Apache-2.0"
 
-  spec.metadata["homepage_uri"] = spec.homepage
-  spec.metadata["source_code_uri"] = "https://github.com/apache/opendal";
-  spec.metadata["changelog_uri"] = "https://github.com/apache/opendal/releases";
-  spec.metadata["rubygems_mfa_required"] = "true"
+  spec.metadata = {
+    "bug_tracker_uri" => "https://github.com/apache/opendal/issues";,
+    "changelog_uri" => "https://github.com/apache/opendal/releases";,
+    "documentation_uri" => "https://opendal.apache.org/docs/ruby/";,
+    "homepage_uri" => spec.homepage,
+    "source_code_uri" => "https://github.com/apache/opendal";,
+    "rubygems_mfa_required" => "true"
+  }
 
-  # Specify which files should be added to the gem when it is released.
+  # Specify which files should be added to a source release gem when we 
release OpenDAL Ruby gem.
   # The `git ls-files -z` loads the files in the RubyGem that have been added 
into git.
   spec.files = Dir.chdir(__dir__) do
-    `git ls-files -z`.split("\x0").reject do |f|
-      (File.expand_path(f) == __FILE__) || f.start_with?(*%w[gems/ pkg/ 
target/ tmp/ .git])
+    git_files = `git ls-files -z`.split("\x0").reject do |f|
+      (File.expand_path(f) == __FILE__) || f.start_with?(*%w[gems/ pkg/ 
target/ tmp/ .git]) || f == "core"
     end
+
+    # Include core directory files, excluding symlinks
+    core_files = Dir.chdir("./core") do
+      `git ls-files -z`.split("\x0").reject do |f|
+        File.symlink?(File.join("./core", f))
+      end.map { |f| "core/#{f}" }
+    end
+
+    # Resolve symlinks: copy actual files from their target locations
+    # This handles recursive symbol link cases. e.g., core/CHANGELOG.md -> 
../CHANGELOG.md
+    symlink_targets = Dir.chdir("./core") do
+      `git ls-files -z`.split("\x0").select do |f|
+        File.symlink?(File.join("./core", f))
+      end.filter_map do |f|
+        link_target = File.readlink(File.join("./core", f))
+        resolved_path = File.expand_path(link_target, File.join(__dir__, 
"core"))
+        File.exist?(resolved_path) ? "core/#{f}" : nil
+      end
+    end
+
+    git_files + core_files + symlink_targets
   end
 
-  spec.bindir = "exe"
-  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
   spec.require_paths = ["lib"]
 
   spec.extensions = ["./extconf.rb"]
@@ -64,6 +87,8 @@ Gem::Specification.new do |spec|
   # use a Ruby version which:
   # - supports Rubygems with the ability of compilation of Rust gem
   # - not end of life
+  #
+  # keep in sync with `Rakefile`.
   spec.required_ruby_version = ">= 3.2"
 
   # intentionally skipping rb_sys gem because newer Rubygems will be present
diff --git a/bindings/ruby/src/lib.rs b/bindings/ruby/src/lib.rs
index f1783c54a..8bf2e8939 100644
--- a/bindings/ruby/src/lib.rs
+++ b/bindings/ruby/src/lib.rs
@@ -19,6 +19,7 @@ use std::sync::LazyLock;
 
 use magnus::function;
 use magnus::Error;
+use magnus::Module;
 use magnus::Ruby;
 
 // We will use `ocore::` to represents opendal rust core functionalities.
@@ -54,7 +55,9 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
     let _ = io::include(ruby, &gem_module);
     let _ = lister::include(ruby, &gem_module);
     let _ = operator_info::include(ruby, &gem_module);
-    let _ = middlewares::include(ruby, &gem_module);
+
+    let middleware_module = gem_module.define_module("Middleware")?;
+    let _ = middlewares::include(ruby, &middleware_module);
 
     Ok(())
 }
diff --git a/bindings/ruby/src/metadata.rs b/bindings/ruby/src/metadata.rs
index ef07d5c73..451bc0885 100644
--- a/bindings/ruby/src/metadata.rs
+++ b/bindings/ruby/src/metadata.rs
@@ -30,7 +30,7 @@ use magnus::RModule;
 use crate::*;
 
 /// @yard
-/// Metadata about the file.
+/// Metadata about an object.
 #[magnus::wrap(class = "OpenDal::Metadata", free_immediately, size)]
 pub struct Metadata(ocore::Metadata);
 
diff --git a/bindings/ruby/src/middlewares.rs b/bindings/ruby/src/middlewares.rs
index e6f194db7..71a74f272 100644
--- a/bindings/ruby/src/middlewares.rs
+++ b/bindings/ruby/src/middlewares.rs
@@ -64,7 +64,7 @@ where
 /// Adds retry for temporary failed operations.
 ///
 /// See [`opendal::layers::RetryLayer`] for more information.
-#[magnus::wrap(class = "OpenDal::RetryMiddleware")]
+#[magnus::wrap(class = "OpenDal::Middleware::Retry")]
 struct RetryMiddleware(Arc<Mutex<ocore::layers::RetryLayer>>);
 
 impl RetryMiddleware {
@@ -73,7 +73,7 @@ impl RetryMiddleware {
     }
 
     fn apply_to(ruby: &Ruby, rb_self: &Self, operator: &Operator) -> 
Result<Operator, Error> {
-        apply_layer(ruby, &rb_self.0, operator, "RetryMiddleware")
+        apply_layer(ruby, &rb_self.0, operator, "OpenDal::Middleware::Retry")
     }
 }
 
@@ -81,7 +81,7 @@ impl RetryMiddleware {
 /// Adds concurrent request limit.
 ///
 /// See [`opendal::layers::ConcurrentLimitLayer`] for more information.
-#[magnus::wrap(class = "OpenDal::ConcurrentLimitMiddleware")]
+#[magnus::wrap(class = "OpenDal::Middleware::ConcurrentLimit")]
 struct 
ConcurrentLimitMiddleware(Arc<Mutex<ocore::layers::ConcurrentLimitLayer>>);
 
 impl ConcurrentLimitMiddleware {
@@ -92,7 +92,12 @@ impl ConcurrentLimitMiddleware {
     }
 
     fn apply_to(ruby: &Ruby, rb_self: &Self, operator: &Operator) -> 
Result<Operator, Error> {
-        apply_layer(ruby, &rb_self.0, operator, "ConcurrentLimitMiddleware")
+        apply_layer(
+            ruby,
+            &rb_self.0,
+            operator,
+            "OpenDal::Middleware::ConcurrentLimit",
+        )
     }
 }
 
@@ -100,7 +105,7 @@ impl ConcurrentLimitMiddleware {
 /// Adds a bandwidth rate limiter to the underlying services.
 ///
 /// See [`opendal::layers::ThrottleLayer`] for more information.
-#[magnus::wrap(class = "OpenDal::ThrottleMiddleware")]
+#[magnus::wrap(class = "OpenDal::Middleware::Throttle")]
 struct ThrottleMiddleware(Arc<Mutex<ocore::layers::ThrottleLayer>>);
 
 impl ThrottleMiddleware {
@@ -111,7 +116,7 @@ impl ThrottleMiddleware {
     }
 
     fn apply_to(ruby: &Ruby, rb_self: &Self, operator: &Operator) -> 
Result<Operator, Error> {
-        apply_layer(ruby, &rb_self.0, operator, "ConcurrentLimitMiddleware")
+        apply_layer(ruby, &rb_self.0, operator, 
"OpenDal::Middleware::Throttle")
     }
 }
 
@@ -128,7 +133,7 @@ fn parse_duration(ruby: &Ruby, val: f64) -> 
Result<Duration, Error> {
 /// Adds timeout for every operation to avoid slow or unexpected hang 
operations.
 ///
 /// See [`opendal::layers::TimeoutLayer`] for more information.
-#[magnus::wrap(class = "OpenDal::TimeoutMiddleware")]
+#[magnus::wrap(class = "OpenDal::Middleware::Timeout")]
 struct TimeoutMiddleware(Arc<Mutex<ocore::layers::TimeoutLayer>>);
 
 impl TimeoutMiddleware {
@@ -140,26 +145,28 @@ impl TimeoutMiddleware {
     }
 
     fn apply_to(ruby: &Ruby, rb_self: &Self, operator: &Operator) -> 
Result<Operator, Error> {
-        apply_layer(ruby, &rb_self.0, operator, "TimeoutMiddleware")
+        apply_layer(ruby, &rb_self.0, operator, "OpenDal::Middleware::Timeout")
     }
 }
 
-pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
-    let retry = gem_module.define_class("RetryMiddleware", 
ruby.class_object())?;
+pub fn include(ruby: &Ruby, middleware_module: &RModule) -> Result<(), Error> {
+    middleware_module.define_class("Retry", ruby.class_object())?;
+
+    let retry = middleware_module.define_class("Retry", ruby.class_object())?;
     retry.define_singleton_method("new", function!(RetryMiddleware::new, 0))?;
     retry.define_method("apply_to", method!(RetryMiddleware::apply_to, 1))?;
 
     let concurrent_limit =
-        gem_module.define_class("ConcurrentLimitMiddleware", 
ruby.class_object())?;
+        middleware_module.define_class("ConcurrentLimit", 
ruby.class_object())?;
     concurrent_limit
         .define_singleton_method("new", 
function!(ConcurrentLimitMiddleware::new, 1))?;
     concurrent_limit.define_method("apply_to", 
method!(ConcurrentLimitMiddleware::apply_to, 1))?;
 
-    let throttle_middleware = gem_module.define_class("ThrottleMiddleware", 
ruby.class_object())?;
+    let throttle_middleware = middleware_module.define_class("Throttle", 
ruby.class_object())?;
     throttle_middleware.define_singleton_method("new", 
function!(ThrottleMiddleware::new, 2))?;
     throttle_middleware.define_method("apply_to", 
method!(ThrottleMiddleware::apply_to, 1))?;
 
-    let timeout_middleware = gem_module.define_class("TimeoutMiddleware", 
ruby.class_object())?;
+    let timeout_middleware = middleware_module.define_class("Timeout", 
ruby.class_object())?;
     timeout_middleware.define_singleton_method("new", 
function!(TimeoutMiddleware::new, 2))?;
     timeout_middleware.define_method("apply_to", 
method!(TimeoutMiddleware::apply_to, 1))?;
 
diff --git a/bindings/ruby/test/blocking_op_test.rb 
b/bindings/ruby/test/blocking_op_test.rb
index 846dd9acd..079669241 100644
--- a/bindings/ruby/test/blocking_op_test.rb
+++ b/bindings/ruby/test/blocking_op_test.rb
@@ -105,7 +105,7 @@ class OpenDalTest < ActiveSupport::TestCase
   end
 
   test "middleware applies a middleware" do
-    @op.middleware(OpenDal::RetryMiddleware.new)
+    @op.middleware(OpenDal::Middleware::Retry.new)
 
     assert @op.is_a?(OpenDal::Operator)
   end
diff --git a/bindings/ruby/test/metadata_test.rb 
b/bindings/ruby/test/metadata_test.rb
new file mode 100644
index 000000000..59a8c4c29
--- /dev/null
+++ b/bindings/ruby/test/metadata_test.rb
@@ -0,0 +1,78 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# frozen_string_literal: true
+
+require "test_helper"
+
+class MetadataTest < ActiveSupport::TestCase
+  setup do
+    @op = OpenDal::Operator.new("memory", {})
+    @op.write("/file", "OpenDAL Ruby is ready.")
+  end
+
+  test "returns metadata" do
+    metadata = @op.stat("file")
+
+    assert_nothing_raised do
+      metadata.inspect
+    end
+  end
+
+  test "#file? works" do
+    metadata = @op.stat("file")
+
+    assert metadata.file?
+  end
+
+  test "#dir? works" do
+    metadata = @op.stat("/")
+    assert metadata.dir?
+
+    assert !@op.stat("file").dir?
+  end
+
+  test "#mode works" do
+    metadata = @op.stat("file")
+    assert_equal OpenDal::Metadata::FILE, metadata.mode
+  end
+
+  test "#content_disposition works" do
+    metadata = @op.stat("file")
+    assert_nil metadata.content_disposition
+  end
+
+  test "#content_length works" do
+    metadata = @op.stat("file")
+    assert_equal 22, metadata.content_length
+  end
+
+  test "#content_md5 works" do
+    metadata = @op.stat("file")
+    assert_nil metadata.content_md5
+  end
+
+  test "#content_type works" do
+    metadata = @op.stat("file")
+    assert_nil metadata.content_type
+  end
+
+  test "#etag works" do
+    metadata = @op.stat("file")
+    assert_nil metadata.etag
+  end
+end
diff --git a/bindings/ruby/test/middlewares_test.rb 
b/bindings/ruby/test/middlewares_test.rb
index 8ab90748a..25fed3c9c 100644
--- a/bindings/ruby/test/middlewares_test.rb
+++ b/bindings/ruby/test/middlewares_test.rb
@@ -22,25 +22,25 @@ require "test_helper"
 class MiddlewaresTest < ActiveSupport::TestCase
   test "builds a retry middleware" do
     assert_nothing_raised do
-      OpenDal::RetryMiddleware.new
+      OpenDal::Middleware::Retry.new
     end
   end
 
   test "builds a concurrent limit middleware" do
     assert_nothing_raised do
-      OpenDal::ConcurrentLimitMiddleware.new(10)
+      OpenDal::Middleware::ConcurrentLimit.new(10)
     end
   end
 
   test "builds a throttle middleware" do
     assert_nothing_raised do
-      OpenDal::ThrottleMiddleware.new(100, 10)
+      OpenDal::Middleware::Throttle.new(100, 10)
     end
   end
 
   test "builds a timeout middleware" do
     assert_nothing_raised do
-      OpenDal::TimeoutMiddleware.new(1.23, 1.0)
+      OpenDal::Middleware::Timeout.new(1.23, 1.0)
     end
   end
 end

Reply via email to