Объект Ruby, возвращаемый из метода, теряет тип класса (возвращается к массиву базовых классов) - PullRequest
0 голосов
/ 12 сентября 2011

В следующем коде проблема заключается в том, что после вызова метода .find_name для типа объекта LogsCollection возвращаемый объект становится собственным массивом и не остается типом LogsCollection. Я считаю, что правильный подход может заключаться в создании конструктора / инициализатора, который принимает массив и возвращает новый объект правильного типа. Но я не уверен, что нет лучшего способа сделать это?

Может ли Ruby-pro-eye взглянуть на этот код и предложить (на уровне кода) лучший способ сделать возвращаемый объект из .find_name сохраняющим тип LogsCollection (не массив)?

class Log
    attr_accessor :name, :expense_id, :is_excluded, :amount, :paid_to
    def initialize(name, expense_id, is_excluded, amount, paid_to)
        @name = name
        @expense_id = expense_id
        @is_excluded = is_excluded
        @amount = amount
        @paid_to = paid_to
    end
end

class LogsCollection < Array
    def names
        collect do |i|
            i.name
        end
    end

    def find_name(name)
        @name = name
        self.select { |l| l.name == @name }
    end
end

logs = LogsCollection.new
logs.push(Log.new('Smith', 1, false, 323.95, nil))
logs.push(Log.new('Jones', 1, false, 1000, nil))

logs = logs.find_name('Smith')
puts logs.count
unless logs.empty?
    puts logs.first.name # works since this is a standard function in native array
    puts logs.names # TODO: figure out why this fails (we lost custom class methods--LogsCollection def find_name returns _native_ array, not type LogsCollection)
end

Финальный код пост-ответа для всех, кто ищет (обратите внимание на удаление базового класса <массив): </p>

class Log
    attr_accessor :name, :expense_id, :is_excluded, :amount, :paid_to
    def initialize(name, expense_id, is_excluded, amount, paid_to)
        @name = name
        @expense_id = expense_id
        @is_excluded = is_excluded
        @amount = amount
        @paid_to = paid_to
    end
end

class LogsCollection
    attr_reader :logs

    def initialize(logs)
        @logs = logs
    end

    def add(log)
        @logs.push(log)
    end

    def names
        @logs.collect { |l| l.name }
    end

    def find_name(name)     
        LogsCollection.new(@logs.select { |l| l.name == name })
    end
end

logs = LogsCollection.new([])

logs.add(Log.new('Smith', 1, false, 323.95, nil))
logs.add(Log.new('Jones', 1, false, 1000, nil))
puts logs.names

puts '--- post .find_name ---'

puts logs.find_name('Smith').names

1 Ответ

2 голосов
/ 12 сентября 2011

Как вы можете видеть в документы Enumerable#select с блоком всегда возвращают массив.Например,

{:a => 1, :b => 2, :c => 3}.select { |k,v | v > 1 }
=> [[:b, 2], [:c, 3]]

То, что вы могли бы сделать, - это создать некий конструктор для LogsCollection, который оборачивает обычный массив как объект LogsCollection и вызвать его в find_name.

Как и просили, вот пример класса (я на работе и пишу это, ожидая, пока что-то закончится, он полностью не проверен):

class LogsCollection
  attr_reader :logs

  def initialize(logs)
    @logs = logs
  end

  def names
    @logs.collect { |i| i.name }
  end

  def find_name(n)
    name = n
    LogsCollection.new(@logs.select { |l| l.name == n })
  end

  # if we don't know a method, forward it to the @logs array
  def method_missing(m, *args, &block)
    @logs.send(m, args, block)
  end
end

Использовать как

lc = LogsCollection.new
logs = lc.logs.find_name('Smith')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...