0

在 Rails 3 应用程序中,我们有一个高度规范化的数据库模式,出于多种原因,这是必要的。此外,我们需要提供一些只读 RESTful 路由,其虚拟资源来自多个模型的“组合”(非规范化)数据,我们必须将结果呈现为 JSON 文档。

例如,我们会有像州、城市和邻里这样的模型,每个模型都有自己的数据和关联。“RESTful 资源”是 Neighborhood,但我们总是希望包含关联的州和城市的名称。因此,对 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 首选方法是什么?

我能想象的最直接的方法是生成一个自定义 SQL 查询,该查询从数据库中选择必要的数据并将结果记录作为 JSON 返回,而不执行任何 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 构建器。几个railscasts:

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

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

做直接的 sql 有点不合规矩。如果您不需要性能,基本查询会更容易填充一些变量,然后使用构建器来制作 json。

不是性能方面最有效的方式,但这通常不是 rails 的目标......开发速度和维护是。

如果您遇到性能问题,请在“解决”之前使用分析器来查找问题!

编辑 至于查询,您可以使用连接和包含。我假设州有_许多城市,城市有_许多社区。

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 回答