我有这个从数据库中检索的哈希:
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
答案 0 :(得分:4)
如果您使用的是Ruby> = 1.9,请尝试Object#tap
和Hash#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)
核心中不存在此抽象,但人们使用它(使用不同的名称pipe
,into
,as
,peg
,chain
, ...)。请注意,这种let-abstraction不仅适用于哈希,因此请将其添加到类Object
。