0

Rails 3 アプリケーションには、高度に正規化されたデータベース スキーマがあり、これはさまざまな理由で必要です。さらに、仮想リソースが複数のモデルからの「結合された」(非正規化された) データから派生した読み取り専用の RESTful ルートをいくつか提供する必要があり、結果を JSON ドキュメントとして提示する必要があります。

たとえば、State、City、Neighborhood などのモデルがあり、それぞれに独自のデータと関連付けがあります。「RESTful リソース」はネイバーフッドですが、関連する州と都市の名前を常に含めたいと考えています。したがって、URI「 」への「GET」リクエストは/neighborhood/nm/albuquerque、ニューメキシコ州アルバカーキのすべての地域の JSON 配列を返します。次に例を示します。

[{"state":"NM","city":"Albuquerque","neighborhood":"North Valley"},
 {"state":"NM","city":"Albuquerque","neighborhood":"Northeast Heights"}, //... ]

ただし、URI (" GET /neighborhood/nm") で都市名を省略すると、ニュー メキシコ州のすべての都市のすべての地域のリストが返されます。

この状況でデータベースクエリを生成するためのRails 3の推奨アプローチは何ですか?

私が想像できる最も直接的なアプローチは、データベースから必要なデータを選択し、結果のレコードを JSON として返すカスタム SQL クエリを生成することです。ActiveRecord オブジェクトの逆シリアル化を実行する必要はありません (処理のない生データが必要なだけなので)。 :

def neighborhood # our Action in the target Controller...
  state, city, hood = params[:state], params[:city], params[:hood]
  query = <<-__HERE__
    SELECT s.name AS state, c.name AS city, n.name AS neighborhood
    FROM states AS s, cities AS c, neighborhoods AS n
    WHERE n.city_id=c.id AND c.state_id=s.id
  __HERE__
  query += ' AND s.slug=' + ActiveRecord::Base.sanitize(state) if state
  query += ' AND c.slug=' + ActiveRecord::Base.sanitize(city) if city
  query += ' AND n.slug=' + ActiveRecord::Base.sanitize(hood) if hood
  query += ' ORDER BY state, city, neighborhood ASC'
  render :json => ActiveRecord::Base.connection.select_all(query)
end

このソリューションは概念的には単純であり、重労働をデータベースに依存していますが、Rails らしくないことは明らかです。私は Arel/AR クエリと "as_json" オーバーライドの組み合わせを使用して同じ効果を達成しようとしましたが、しばらくしてイライラした後、正しく (そして効率的に) 取得できないようです。

Rails 3 で大きく、新しく、クールなものを見逃しているのでしょうか?それとも、これは単純なソリューションが適切な方法であるという状況にすぎないのでしょうか?

4

1 に答える 1

1

json ビルダーを試してみてください。いくつかのレールキャスト:

http://railscasts.com/episodes/322-rabl

http://railscasts.com/episodes/320-jbuilder

単純な sql を実行するのは、少し非レールっぽいです。パフォーマンスが必要ない場合は、基本的なクエリでいくつかの変数を設定し、ビルダーを使用して json を作成する方が簡単です。

パフォーマンスの点で最も効率的な方法ではありませんが、それは通常、レールの目標ではありません...開発速度とメンテナンスは.

パフォーマンスの問題がある場合は、「解決」する前にプロファイラーを使用して問題を見つけてください。

edit クエリに関しては、join と include を使用できます。州には has_many の都市、都市には has_many の近隣があると想定しています。

query = Neighborhood.includes(:city => :state)
query = query.order('states.name, cities.name, neighborhoods.name ASC')
query = query.where("cities.name =?", params[:city]) if params[:city]
query = query.where("neighborhoods.name =?", params[:hood]) if params[:hood]
query = query.where("states.name =?", params[:state]) if params[:state]

そこからjsonデータを構築できます...

query.each do |hood|
  # code to build json row using:   hood.name, hood.city.name, and hood.city.state.name
end

さらに良いことに、役立ついくつかのメソッドを定義します。

class Neighborhood ...
  def city_name
    city ? city.name : "No city"
  end

  def state_name
    (city && city.state) ? city.state.name : "No state"
  end
end

それが整ったので、ビルダーがやり過ぎであることがわかりました。ハッシュの配列をマップし、to_json を呼び出すだけです。

query = Neighborhood.includes(:city => :state)
query = query.order('states.name, cities.name, neighborhoods.name ASC')
query = query.where("cities.name =?", params[:city]) if params[:city]
query = query.where("neighborhoods.name =?", params[:hood]) if params[:hood]
query = query.where("states.name =?", params[:state]) if params[:state]

json = query.map { |hood|
  {"state" => hood.state_name, "city" => hood.city_name, "neighborhood" => hood.name}
}.to_json

(テストされていませんが、近いと確信しています)

于 2012-09-13T00:26:39.400 に答える