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

ハッシュを配列に入れると、map関数を使用してエントリを変換できます

于 2013-06-04T13:47:45.070 に答える
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 に答える