在Rails 3应用程序中,我们有一个高度规范化的数据库模式,这是出于多种原因所必需的。此外,我们需要提供一些只读RESTful路由,其虚拟资源来自多个模型的“组合”(非规范化)数据,我们必须将结果显示为JSON文档。
例如,我们会有像State,City和Neighborhood这样的模型,每个模型都有自己的数据和关联。 “RESTful资源”是邻居,但我们总是希望包含关联的州和城市的名称。因此,对URI“/neighborhood/nm/albuquerque
”的“GET”请求将返回新墨西哥州阿尔伯克基所有邻域的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-ish。我尝试使用Arel / AR查询和“as_json”覆盖的组合来实现相同的效果但是在经过一段时间和挫折后我似乎无法正确(并且高效)。
我是否在Rails 3中遗漏了大,新,酷的东西,或者这只是一种快速而肮脏的解决方案才是正确的方法?
答案 0 :(得分:1)
您可能想尝试使用json构建器。几个轨道广播:
http://railscasts.com/episodes/322-rabl
http://railscasts.com/episodes/320-jbuilder
直接sql有点un-rails-ish。如果您不需要性能,基本查询将更容易填充一些变量,然后使用构建器来制作json。
如果您遇到性能问题,请在“解决”之前使用分析器找到问题!
修改强>
对于查询,您可以使用连接和包含。我假设州有很多城市,城市有很多社区。 p>
从那里你可以构建你的json数据...... 更好的是,定义一些方法来帮助: 有了这个,我现在看到一个建筑师是矫枉过正的。只需映射一个哈希数组,然后调用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]
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
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