是否有内置的Ruby方法来“重塑”哈希?

时间:2013-06-04 13:36:46

标签: ruby

我有这个从数据库中检索的哈希:

original_hash = {
  :name => "Luka",
  :school => {
    :id => "123",
    :name => "Ieperman"
  },
  :testScores => [0.8, 0.5, 0.4, 0.9]
}

我正在编写API并希望向客户端返回稍微不同的哈希值:

result = {
  :name => "Luka",
  :schoolName => "Ieperman",
  :averageScore => 0.65
}

这不起作用,因为方法reshape不存在。它是否以另一个名字存在?

result = original_hash.reshape do |hash|
  {
    :name => hash[:name],
    :school => hash[:school][:name],
    :averageScore => hash[:testScores].reduce(:+).to_f / hash[:testScores].count
  }
end

我是Ruby的新手,所以我想在离开核心课程之前先问一下。我确信它必须存在,因为我总是发现自己在编写API时重塑哈希。或者我完全错过了什么?

实现很简单,但就像我说的那样,如果我不需要,我不想重写哈希:

class Hash
  def reshape
    yield(self)
  end
end
顺便说一句,我知道这个:

result = {
  :name => original_hash[:name],
  :school => original_hash[:school][:name],
  :averageScore => original_hash[:testScores].reduce(:+).to_f / original_hash[:testScores].count
}

但有时候我没有original_hash变量,而是直接从返回值开始操作,或者我在一个单行内,这种基于块的方法很方便。

真实世界的例子:

#get the relevant user settings from the database, and reshape the hash into the form we want
settings = users.find_one({:_id => oid(a[:userID])}, {:emailNotifications => 1, :newsletter => 1, :defaultSocialNetwork => 1}).reshape do |hash|
  {
    :emailNotifications => hash[:emailNotifications] == 1,
    :newsletter => hash[:newsletter] == 1,
    :defaultSocialNetwork => hash[:defaultSocialNetwork]
  }
end rescue fail

7 个答案:

答案 0 :(得分:4)

如果您使用的是Ruby> = 1.9,请尝试Object#tapHash#replace的组合

def foo(); { foo: "bar" }; end

foo().tap { |h| h.replace({original_foo: h[:foo]}) }
# => { :original_foo => "bar" }

由于Hash#replace就地工作,您可能会发现这更安全一点:

foo().clone.tap { |h| h.replace({original_foo: h[:foo]}) }

但这有点吵。在这个阶段,我可能会继续进行猴子补丁Hash

答案 1 :(得分:3)

从API的角度来看,您可能正在寻找一个代表对象,它位于您的内部模型和API表示之间(在基于格式的序列化之前)。对于散列,使用最短,方便的Ruby语法内联不起作用,但这是一种很好的声明性方法。

例如,Grape gem (其他API框架可用!)可能会解决同样的现实问题:

# In a route
get :user_settings do
  settings = users.find_one({:_id => oid(a[:userID])}, {:emailNotifications => 1, :newsletter => 1, :defaultSocialNetwork => 1})
  present settings, :with => SettingsEntity
end

# Wherever you define your entities:
class SettingsEntity < Grape::Entity
  expose( :emailNotifications ) { |hash,options| hash[:emailNotifications] == 1 }
  expose( :newsletter ) { |hash,options| hash[:newsletter] == 1 }
  expose( :defaultSocialNetwork ) { |hash,options| hash[:defaultSocialNetwork] }
end

这种语法更适合处理ActiveRecord或类似模型,而不是哈希。所以不是直接回答您的问题,但我认为您构建API意味着隐含。如果您现在放入某种代表层(不一定是grape-entity),您将在以后感谢它,因为当您需要时,您将能够更好地管理模型到API数据映射。变化

答案 2 :(得分:1)

您可以使用内置方法Object#instance_eval替换“重塑”调用,它将完全正常工作。但请注意,由于您在接收对象的上下文中评估代码(例如,如果使用“self”),可能会出现一些意外行为。

result = original_hash.instance_eval do |hash|
  # ...

答案 3 :(得分:0)

如果将哈希值放入数组中,可以使用map函数转换条目

答案 4 :(得分:0)

我无法想到任何能够神奇地做到这一点的事情,因为你基本上想要重新映射任意数据结构。

你可以做的事情是:

require 'pp'

original_hash = {
    :name=>'abc',
    :school => {
        :name=>'school name'
    },
    :testScores => [1,2,3,4,5]
}

result  = {}

original_hash.each {|k,v| v.is_a?(Hash) ? v.each {|k1,v1| result[ [k.to_s, k1.to_s].join('_') ] = v1 } : result[k] = v}

result # {:name=>"abc", "school_name"=>"school name", :testScores=>[1, 2, 3, 4, 5]}

但这非常混乱,我个人对此不满意。对已知密钥执行手动转换可以更好,更易于维护。

答案 5 :(得分:0)

答案 6 :(得分:0)

核心中不存在此抽象,但人们使用它(使用不同的名称pipeintoaspegchain, ...)。请注意,这种let-abstraction不仅适用于哈希,因此请将其添加到类Object

Is there a `pipe` equivalent in ruby?