Skip to content

Fog::Collection doesn't fetch while converted by Kernel.Array method #277

Open
@oakcask

Description

@oakcask

Hi.

I found out that Fog::Collection donsn't do lazy_load while being converted by Kernel.Array method that implicitly called by several Array methods.

For example, Array#concat converts its operand by Array() and Fog::Collection instance passed to Array#concat won't do lazy_load so an empty array will be concatinated.
This causes making flat_map (this internally uses Array#concat to gather the results) over Fog::Collections generate empty results.

It works well by manually calling all to load explicitly.

This is intentional? I think this behavior is bit confusable IMHO.

require "bundler/inline"

gemfile do
  source "https://rubygems.org"
  gem "fog-core"
  gem "minitest"
  gem "minitest-reporters"
end

require "securerandom"
require "minitest/unit"
require "minitest/autorun"

ENV["MINITEST_REPORTER"] ||= "SpecReporter"
Minitest::Reporters.use!

def simulate_fetch(n)
  n.times.map do
    { 'id' => SecureRandom.uuid, 'value' => Time.now.iso8601 }
  end
end

class Foo < Fog::Model
  identity :id
  attribute :value
end

class FooCollection < Fog::Collection
  model Foo

  def initialize(fetch_size)
    super()
    @fetch_size = fetch_size 
  end

  # Roughly same implementation with
  # https://github.com/fog/fog-aws/blob/e0d9ad4a1a78f46634c51e80583281b389c5212e/lib/fog/aws/models/storage/versions.rb#L12-L20
  # 
  # `lazy_load` will call `all`
  # https://github.com/fog/fog-core/blob/e359e66ddd81e7b0811bc0ff00b133722fb49ef8/lib/fog/core/collection.rb#L111
  def all(options = {})
    data = simulate_fetch @fetch_size
    load data
  end

  def new(attr = {})
    $count = $count.to_i + 1
    super
  end
end

class Test < MiniTest::Unit::TestCase
  def test_fetch
    a = FooCollection.new 1
    b = FooCollection.new 2
    assert_equal 1, a.size
    assert_equal 2, b.size
  end

  def test_concat
    a = FooCollection.new 1
    b = FooCollection.new 2

    result = a.concat b

    assert_equal 3, result.size
  end

  def test_flat_map
    a = FooCollection.new 1
    b = FooCollection.new 2

    result = [a, b].flat_map { |item| item }

    assert_equal 3, result.size
  end

  def test_flat_map_2
    a = FooCollection.new 1
    b = FooCollection.new 2

    result = [a, b].each(&:all).flat_map { |item| item }

    assert_equal 3, result.size
  end

  def setup
    puts ">> $count = #{$count}"
  end

  def teardown
    puts "<< $count = #{$count}"
  end
end

__END__
$ ruby test.rb 
MiniTest::Unit::TestCase is now Minitest::Test. From test.rb:52:in `<main>'
Started with run options --seed 36760

Test
>> $count = 
<< $count = 3
  test_fetch                                                      PASS (0.00s)
>> $count = 3
<< $count = 3
  test_flat_map                                                   FAIL (0.00s)
        Expected: 3
          Actual: 0
        test.rb:75:in `test_flat_map'

>> $count = 3
<< $count = 6
  test_flat_map_2                                                 PASS (0.00s)
>> $count = 6
<< $count = 7
  test_concat                                                     FAIL (0.00s)
        Expected: 3
          Actual: 1
        test.rb:66:in `test_concat'


Finished in 0.00171s
4 tests, 5 assertions, 2 failures, 0 errors, 0 skips

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions