【问题标题】:ruby object array... or hashruby 对象数组...或哈希
【发布时间】:2010-11-26 20:40:30
【问题描述】:

我现在有一个对象:

class Items
  attr_accessor :item_id, :name, :description, :rating

  def initialize(options = {})
      options.each {
        |k,v|
        self.send( "#{k.to_s}=".intern, v)
      }
  end

end

我将它作为单个对象分配到数组中...

@result = []

some loop>>
   @result << Items.new(options[:name] => 'name', options[:description] => 'blah')
end loop>>

但是,与其将我的单一对象分配给一个数组......我怎样才能使对象本身成为一个集合?

基本上希望以这样的方式拥有对象,以便我可以定义诸如

之类的方法
def self.names
   @items.each do |item|
      item.name
   end
end

我希望这是有道理的,可能我忽略了一些宏大的计划,这将使我的生活在两行中变得无限轻松。

【问题讨论】:

    标签: ruby-on-rails ruby arrays class methods


    【解决方案1】:

    在我发布如何修改它的示例之前,先进行一些观察。

    • 在声明新对象时,为类指定复数名称可能会导致很多语义问题,因为在这种情况下,您会调用 Items.new,这意味着您正在创建多个项目,而实际上是在创建一个项目。对单个实体使用单数形式。
    • 调用任意方法时要小心,因为任何未命中都会引发异常。要么检查你是否可以先打电话给他们,要么在适用的情况下从不可避免的灾难中解救出来。

    解决您的问题的一种方法是专门为 Item 对象创建一个自定义集合类,它可以为您提供您需要的名称等信息。例如:

    class Item
      attr_accessor :item_id, :name, :description, :rating
    
      def initialize(options = { })
        options.each do |k,v|
          method = :"#{k}="
    
          # Check that the method call is valid before making it
          if (respond_to?(method))
            self.send(method, v)
          else
            # If not, produce a meaningful error
            raise "Unknown attribute #{k}"
          end
        end
      end
    end
    
    class ItemsCollection < Array
      # This collection does everything an Array does, plus
      # you can add utility methods like names.
    
      def names
        collect do |i|
          i.name
        end
      end
    end
    
    # Example
    
    # Create a custom collection
    items = ItemsCollection.new
    
    # Build a few basic examples
    [
      {
        :item_id => 1,
        :name => 'Fastball',
        :description => 'Faster than a slowball',
        :rating => 2
      },
      {
        :item_id => 2,
        :name => 'Jack of Nines',
        :description => 'Hypothetical playing card',
        :rating => 3
      },
      {
        :item_id => 3,
        :name => 'Ruby Book',
        :description => 'A book made entirely of precious gems',
        :rating => 1
      }
    ].each do |example|
      items << Item.new(example)
    end
    
    puts items.names.join(', ')
    # => Fastball, Jack of Nines, Ruby Book
    

    【讨论】:

    • 有没有办法让项目的所有属性自动表现得像'names'方法? IE。调用 .descriptions 会返回与 .names 相同的结果...?
    • 如果你想冒险,你可以动态构建这些方法,或者使用 method_missing 来捕捉调用。
    • 我可以使用 Ruby mixins 将此类功能添加到所有包含 ActiveRecord 对象的数组中吗?一些指针会很有用。
    • 您可以随心所欲地扩展基本 Array 类,尽管对于“名称”等特定要求而言,这种事情可能有点不合常规。但是,如果您使用 method_missing 方法,您可能会提出一个约定,例如收集所有 :x 结果的“collect_Xs”。
    【解决方案2】:

    你知道 Ruby 关键字 yield 吗?

    我不太确定你到底想做什么。我对你的意图有两种解释,所以我举一个例子来说明两种完全不同的东西,其中之一希望能回答你的问题:

    class Items
      @items = []
      class << self
        attr_accessor :items
      end
      attr_accessor :name, :description
      def self.each(&args)
        @items.each(&args)
      end
      def initialize(name, description)
        @name, @description = name, description
        Items.items << self
      end
      def each(&block)
        yield name
        yield description
      end
    end
    
    a = Items.new('mug', 'a big cup')
    b = Items.new('cup', 'a small mug')
    Items.each {|x| puts x.name}
    puts
    a.each {|x| puts x}
    

    这个输出

    mug
    cup
    
    mug
    a big cup
    

    您要求的是 Items.eacha.each 之类的东西还是完全不同的东西?

    【讨论】:

    • 这里使用两个产量似乎很奇怪,而一个带有两个参数的产量可能更有用。
    • 取决于一个人想做什么。方法“each”通常为插入当前元素的每个元素调用一次给定块。当然,在我的例子中你是对的(这没有多大意义,我只是想展示一下用法)。产生两个参数只会在插入两个元素时调用该块。
    【解决方案3】:

    仅回答您在评论中对 tadman 解决方案提出的附加问题:如果您在 tadman 的代码中将 ItemsCollection 类中的方法 names 的定义替换为

    def method_missing(symbol_s, *arguments)
      symbol, s = symbol_s.to_s[0..-2], symbol_s.to_s[-1..-1]
      if s == 's' and arguments.empty?
        select do |i|
          i.respond_to?(symbol) && i.instance_variables.include?("@#{symbol}")
        end.map {|i| i.send(symbol)}
      else
        super
      end
    end
    

    对于他的示例数据,您将获得以下输出:

    puts items.names.join(', ')
    # => Fastball, Jack of Nines, Ruby Book
    puts items.descriptions.join(', ')
    # => Faster than a slowball, Hypothetical playing card, A book made entirely of precious gems
    

    由于我不知道有什么方法可以检查方法名称是来自属性还是来自另一个方法(除非您在类模块中重新定义 attr_accessor、attr 等),所以我添加了一些健全性检查:我测试是否存在相应的方法和同名的实例变量。由于 ItemsCollection 类不强制只添加 Item 类的对象,因此我只选择满足这两项检查的元素。您还可以删除选择并将测试放入映射中,如果检查失败则返回 nil。

    【讨论】:

    • 我刚刚在上面添加了我的评论后在这里看到了这个。如果您使用的是 Rails,则可以使用单数化方法为您进行去复数化,而不是使用 's'-stripping。
    【解决方案4】:

    关键是返回值。如果没有给出'return'语句,则返回最后一条语句的结果。您的最后一条语句返回一个哈希。

    添加'return self'作为初始化的最后一行,你就可以了。

    Class Item
      def initialize(options = {})
        ## Do all kinds of stuff. 
    
        return self
      end
    end
    

    【讨论】:

    • 嗯,这部分实际上工作正常,只是当我想要一个可以执行功能的巨型对象(集合)时,我将我的对象放入数组中
    • Item.new 总是返回新创建的对象,而不管 initialize() 的返回值。添加 return self 完全没有区别。
    • 与其他语言不同,Ruby 初始化程序的返回值被忽略,并且始终返回创建的对象。这可以通过重写 Class 新方法来改变,但这可能是个坏主意。
    猜你喜欢
    • 2015-01-06
    • 1970-01-01
    • 2020-07-13
    • 1970-01-01
    • 1970-01-01
    • 2019-04-01
    • 1970-01-01
    • 2021-02-05
    • 2021-04-25
    相关资源
    最近更新 更多