3

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

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 时总是发现自己在重塑哈希值。还是我完全错过了什么?

实现非常简单,但是,就像我说的,如果我不需要,我不想覆盖 Hash:

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
4

7 回答 7

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在这个阶段,我可能会继续进行猴子补丁。

于 2013-06-04T13:48:35.660 回答
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 的数据映射时,您将能够更好地管理它们。

于 2013-06-04T13:56:50.510 回答
1

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

result = original_hash.instance_eval do |hash|
  # ...
于 2013-06-04T13:54:36.087 回答
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]}

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

于 2013-06-04T13:49:19.510 回答
0

检查Facets 哈希扩展

于 2013-06-04T14:19:38.873 回答
0

如果你把你的哈希值放在一个数组中,你可以使用该map函数来转换条目

于 2013-06-04T13:47:45.070 回答
0

这种抽象在核心中不存在,但人们使用它(有不同的名称,pipe, into, as, peg, chain, ...)。请注意,此 let-abstraction 不仅对哈希有用,因此请将其添加到 classObject中。

红宝石中是否有等效的“管道”?

于 2013-06-04T14:57:32.670 回答