如何在Rails中构建由多个模型组成的JSON响应

How to build a JSON response made up of multiple models in Rails

一,理想的结果

我有UserItem模型。我想构建一个如下所示的JSON响应:

1
2
3
4
5
6
7
8
9
{
 "user":
    {"username":"Bob!","foo":"whatever","bar":"hello!"},

 "items": [
    {"id":1,"name":"one","zim":"planet","gir":"earth"},
    {"id":2,"name":"two","zim":"planet","gir":"mars"}
  ]
}

但是,我的UserItem模型具有更多的属性。我找到了使它工作的方法,但是请注意,这还不是很漂亮...请帮助...

更新

下一部分包含原始问题。最后一部分显示了新的解决方案。

我的骇客

home_controller.rb

1
2
3
4
5
6
7
8
9
class HomeController < ApplicationController

  def observe
    respond_to do |format|
      format.js { render :json => Observation.new(current_user, @items).to_json }
    end
  end

end

观察文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# NOTE: this is not a subclass of ActiveRecord::Base
# this class just serves as a container to aggregate all"observable" objects
class Observation
  attr_accessor :user, :items

  def initialize(user, items)
    self.user = user
    self.items = items
  end

  # The JSON needs to be decoded before it's sent to the `to_json` method in the home_controller otherwise the JSON will be escaped...
  # What a mess!
  def to_json
    {
      :user => ActiveSupport::JSON.decode(user.to_json(:only => :username, :methods => [:foo, :bar])),
      :items => ActiveSupport::JSON.decode(auctions.to_json(:only => [:id, :name], :methods => [:zim, :gir]))
    }
  end
end

妈你看没有更多的骇客!

改写as_json

ActiveRecord :: Serialization#as_json文档非常稀疏。这是摘要:

1
2
as_json(options = nil)
  [show source]

有关to_jsonas_json的更多信息,请参见关于在Rails 2.3.5中覆盖to_json的可接受答案。

该代码没有技巧

user.rb

1
2
3
4
5
6
7
8
class User < ActiveRecord::Base

  def as_json(options)
    options = { :only => [:username], :methods => [:foo, :bar] }.merge(options)
    super(options)
  end

end

item.rb

1
2
3
4
5
6
7
8
class Item < ActiveRecord::Base

  def as_json(options)
    options = { :only => [:id, name], :methods => [:zim, :gir] }.merge(options)
    super(options)
  end

end

home_controller.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class HomeController < ApplicationController

  def observe
    @items = Items.find(...)
    respond_to do |format|
      format.js do
        render :json => {
          :user => current_user || {},
          :items => @items
        }
      end
    end
  end

end

已编辑为使用as_json而不是to_json。请参阅如何在Rails中覆盖to_json?详细说明。我认为这是最好的答案。

您可以在控制器中呈现所需的JSON,而无需帮助程序模型。

1
2
3
4
5
6
7
8
9
10
def observe
  respond_to do |format|
    format.js do
      render :json => {
        :user => current_user.as_json(:only => [:username], :methods => [:foo, :bar]),
        :items => @items.collect{ |i| i.as_json(:only => [:id, :name], :methods => [:zim, :gir]) }
      }
    end
  end
end

确保将ActiveRecord::Base.include_root_in_json设置为false,否则您将在\\'user \\'内获得一个\\'user \\'属性。不幸的是,数组似乎没有将options传递给每个元素,因此collect是必需的。


万一有人在寻找替代解决方案,这就是我在Rails 4.2中解决此问题的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def observe
  @item = some_item
  @user = some_user

  respond_to do |format|
    format.js do
      serialized_item = ItemSerializer.new(@item).attributes
      serialized_user = UserSerializer.new(@user).attributes
      render :json => {
        :item => serialized_item,
        :user => serialized_user
      }
    end
  end
end

这将两个对象的序列化版本作为JSON返回,可通过response.userresponse.item访问。


现在有很多新的Gems用于构建JSON,对于这种情况,我发现最合适的是Jsonify:

https://github.com/bsiggelkow/jsonify
https://github.com/bsiggelkow/jsonify-rails

这允许您从模型中建立属性和数组的混合。


有效答案#2为避免json被"转义",请手动建立数据结构,然后对其调用一次to_json。它可能会有些罗word,但是您可以在控制器中完成所有操作,或者将其作为to_hash或类似内容抽象到各个模型中。

1
2
3
4
5
6
7
8
9
10
def observe
  respond_to do |format|
    format.js do
      render :json => {
        :user => {:username => current_user.username, :foo => current_user.foo, :bar => current_user.bar},
        :items => @items.collect{ |i| {:id => i.id, :name => i.name, :zim => i.zim, :gir => i.gir} }
      }
    end
  end
end