如何通过重写像 respond_with 这样的方法来干燥 Rails 3 控制器?

发布于 2024-10-08 09:25:28 字数 1346 浏览 12 评论 0原文

我正在尝试为我的 Rails 3 应用程序创建 JSONP API。现在在我的控制器中,我有很多遵循此模式的操作:

# This is from my users_controller.rb, as an example

def index
  @users = User.all
  respond_with(@users, :callback => params[:callback])
end

虽然这按原样工作,但我想通过不必重复 :callback => 来干燥它。 params[:callback] 在每个操作的 respond_with 调用中。我该怎么做?

更新:我意识到上面的代码丑陋的一件事是 :callback => params[:callback] 选项将为任何响应格式传递,而不仅仅是 JSON。下面的代码可能更正确:

def index
  @users = User.all
  respond_with(@users) do |format|
    format.json { render :json => @users, :callback => params[:callback]}
  end
end

我考虑过几种方法来解决这个问题,但我不知道如何让它们工作:

  • 覆盖render(也许在应用程序控制器中) ),以便它接受自动包含 :callback => 的 :jsonp 选项。 params[:callback] 参数。这样我可以将上面的代码更改为以下代码,该代码稍微短一些:
def index
  @users = User.all
  respond_with(@users) do |format|
    format.json { render :jsonp => @users}
  end
end
  • 创建一个覆盖 to_json 的响应程序以解决我的问题。这样我就可以省略该块,只需调用 respond_with(@users, :responder => 'MyResponder') 来解决问题。或者,也许我可以使用 plataformatec 的响应程序 gem 将此代码包含在应用程序响应程序中,以便 respond_with(@用户) 本身就足够了。

I'm trying to create a JSONP API for my Rails 3 application. Right now in my controllers, I have a lot of actions which follow this pattern:

# This is from my users_controller.rb, as an example

def index
  @users = User.all
  respond_with(@users, :callback => params[:callback])
end

While this works as is, I would like to DRY it up by not having to repeat the :callback => params[:callback] in every action's call to respond_with. How can I do this?

Update: One thing I've realized that is ugly about my above code is that the :callback => params[:callback] option will be passed for any response format, not just JSON. The following code is probably more correct:

def index
  @users = User.all
  respond_with(@users) do |format|
    format.json { render :json => @users, :callback => params[:callback]}
  end
end

There are a couple ways I've considered to address this problem, but I can't figure out how to make them work:

  • Override render (perhaps in the application controller) so that it accepts a :jsonp option that automatically includes the :callback => params[:callback] parameter. This way I could change the above code to the following, which is somewhat shorter:
def index
  @users = User.all
  respond_with(@users) do |format|
    format.json { render :jsonp => @users}
  end
end
  • Create a responder that overrides to_json in order to solve my problem. That way I could leave out the block and just call respond_with(@users, :responder => 'MyResponder') to solve the issue. Or perhaps I could include this code in an application responder using plataformatec's responders gem so that respond_with(@users) by itself would be sufficient.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

少年亿悲伤 2024-10-15 09:25:28

请注意,从技术上讲,使用回调参数渲染 JSON 是不正确的,因为您得到的是 JavaScript 响应(对 JSON-P 回调的函数调用)而不是 JSON 结果。
因此,如果您收到

render :json => my_object, :callback => params[:callback]

/users?callback=func 的请求,Rails 会

func({…})

使用内容类型 application/json 进行响应,这是不正确的,因为上面的响应是显然不是 JSON,而是 JavaScript。

我使用的解决方案是

def respond_with_json(item)
  respond_with do |format|
    format.json { render :json => item }
    format.js   { render :json => item, :callback => params[:callback] }
  end
end

在有或没有回调的情况下都能正确响应。将其应用于上述解决方案,我们得到:

def custom_respond_with(*resources, &block)
  options = resources.extract_options!

  if params[:callback]
    old_block = block
    block = lambda do |format|
      old_block.call(format) if block_given?
      format.js { render :json => resources[0], :callback => params[:callback] }
    end
  end

  respond_with(*(resources << options), &block)
end

另请注意对 resources[0] 的更正,否则由于 splat,您最终会将 resources 包装在额外的数组中操作员。

Note that technically, it is incorrect to render JSON with a callback parameter, since you get a JavaScript response (a function call to the JSON-P callback) rather than a JSON result.
So if you have

render :json => my_object, :callback => params[:callback]

and a request for /users?callback=func comes in, Rails would answer

func({…})

with content type application/json, which is incorrect, since the above response is clearly not JSON but JavaScript.

The solution I use is

def respond_with_json(item)
  respond_with do |format|
    format.json { render :json => item }
    format.js   { render :json => item, :callback => params[:callback] }
  end
end

which responds correctly with or without callback. Applying this to the aforementioned solution, we get:

def custom_respond_with(*resources, &block)
  options = resources.extract_options!

  if params[:callback]
    old_block = block
    block = lambda do |format|
      old_block.call(format) if block_given?
      format.js { render :json => resources[0], :callback => params[:callback] }
    end
  end

  respond_with(*(resources << options), &block)
end

Also note the correction to resources[0], otherwise you end up wrapping resources in an extra array as a result of the splat operator.

oО清风挽发oО 2024-10-15 09:25:28

有一个 gem 可以做到这一点:rack-jsonp-middleware

网站上的设置说明非常少,但我确实创建了一个使用它的小 Rails 项目 - 您可以查看提交并了解我为启动和运行中间件所做的工作。

https://github.com/rwilcox/rack_jsonp_example

THere's a gem that can do this to: rack-jsonp-middleware.

The setup instructions are pretty scant on the site, but I did create a little Rails project that uses it - which you can take a look at the commits and see what I did to get the middleware up and running.

https://github.com/rwilcox/rack_jsonp_example

够运 2024-10-15 09:25:28

与响应器解决方案相比,这有点“低科技”,但是在您的 appliation_controller.rb 中创建一个私有方法来处理这个问题怎么样? params 变量将可供它使用,并且您可以将 @users 对象传递给它。

#application_controller.rb
private
  def jsonp(my_object)
    render :json => my_object, :callback => params[:callback]
  end

#controller
def index
  @users = User.all
  respond_with(@users) do |format|
    format.json { jsonp(@users)}
  end
end

This is bit 'low-tech' compared to the reponder solution, but what about just creating a private method in your appliation_controller.rb to handle this. The params variable will be available to it and you could pass the @users object to it.

#application_controller.rb
private
  def jsonp(my_object)
    render :json => my_object, :callback => params[:callback]
  end

#controller
def index
  @users = User.all
  respond_with(@users) do |format|
    format.json { jsonp(@users)}
  end
end
狼性发作 2024-10-15 09:25:28

感谢 samuelkadolph 今天在 #rubyonrails IRC 频道中为我提供的帮助。他在 this gist 中提供了一个解决方案,为了方便起见,复制如下:

def custom_respond_with(*resources, &block)
  options = resources.extract_options!

  if options[:callback]
    old_block = block
    block = lambda do |format|
      old_block.call(format) if block_given?
      format.json { render :json => [] }
    end
  end

  respond_with(*(resources << options), &block)
end

我还没有在我的应用程序中尝试过这个,但我可以看到它应该有效。他还确认,我可以通过更改此方法的名称并将定义的最后一行更改为 super(*(resources << options) 来类似地覆盖 respond_with 方法本身,&块)

我认为这对我有用。但是,我仍然有兴趣了解如何编写自定义响应程序来完成这项工作。 (恕我直言,这将是一个更优雅的解决方案。)

更新:我在我的应用程序中尝试了这个,它可以进行一些小的更改。这是我现在在 ApplicationController 的 private 部分中使用的版本,旨在自动提供 :callback => JSON 请求的 params[:callback] 选项:

def custom_respond_with(*resources, &block)
  options = resources.extract_options!

  if params[:callback]
    old_block = block
    block = lambda do |format|
      old_block.call(format) if block_given?
      format.json { render :json => resources, :callback => params[:callback] }
    end
  end

  respond_with(*(resources << options), &block)
end

请注意,我必须将 if options[:callback] 更改为 if params[:callback] 才能让它发挥作用。

Thanks to samuelkadolph for helping me in the #rubyonrails IRC channel today. He provided a solution in this gist, copied below for convenience:

def custom_respond_with(*resources, &block)
  options = resources.extract_options!

  if options[:callback]
    old_block = block
    block = lambda do |format|
      old_block.call(format) if block_given?
      format.json { render :json => [] }
    end
  end

  respond_with(*(resources << options), &block)
end

I haven't tried this in my application yet, but I can see that it should work. He also confirmed that I could similarly override the respond_with method itself simply by changing the name of this method and changing the last line of the definition to super(*(resources << options), &block).

I think this will work for me. However, I'm still interested in knowing how to write a custom responder to do the job. (It would be a more elegant solution, IMHO.)

Update: I tried this in my application and it works with some minor changes. Here is the version I'm using now in the private section of my ApplicationController, designed to automatically provide the :callback => params[:callback] option to JSON requests:

def custom_respond_with(*resources, &block)
  options = resources.extract_options!

  if params[:callback]
    old_block = block
    block = lambda do |format|
      old_block.call(format) if block_given?
      format.json { render :json => resources, :callback => params[:callback] }
    end
  end

  respond_with(*(resources << options), &block)
end

Note that I had to change if options[:callback] to if params[:callback] in order to get it working.

白日梦 2024-10-15 09:25:28

您还可以查看此答案。基本上,您可以为控制器创建一个“默认”respond_to,这样您就可以将所有操作默认为响应 json。

这就是你问的吗?

You can also check out this answer. basically you can create a "default" respond_to for your controller so you can just make your all your actions default to responding to json.

was that what you were asking?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文