【问题标题】:Changing respond_to url for an ActiveModel object更改 ActiveModel 对象的 respond_to url
【发布时间】:2013-04-10 20:46:19
【问题描述】:

总结

如何自定义respond_to 为 ActiveModel 对象生成的路径?

更新:我正在寻找一个钩子、方法覆盖或配置更改来完成此操作,而不是解决方法。 (解决方法很简单,但并不优雅。)

背景与示例

这里有一个例子来说明。我有一个模型,Contract,它有很多字段:

class Contract < ActiveRecord::Base
  # cumbersome, too much for a UI form
end

为了让 UI 代码更易于使用,我有一个更简单的类,SimpleContract

class SimpleContract
  include ActiveModel::Model

  # ...

  def contract_attributes
    # convert SimpleContract attributes to Contract attributes
  end

  def save
    Contract.new(contract_attributes).save
  end
end

这很好用,但我的控制器有问题...

class ContractsController < ApplicationController
  # ...

  def create
    @contract = SimpleContract.new(contract_params)
    flash[:notice] = "Created Contract." if @contract.save
    respond_with(@contract)
  end

  # ...
end

问题是respond_with 指向simple_contract_url但是我希望它指向contract_url。最好的方法是什么? (请注意,我使用的是 ActiveModel。)

(注意:我使用的是 Rails 4 Beta,但这不是我的问题的核心。我认为 Rails 3 的一个好的答案也可以。)

边栏:如果您觉得这种将模型包装在轻量级 ActiveModel 类中的方法不明智,请在 cmets 中告诉我。就个人而言,我喜欢它,因为它使我的原始模型保持简单。 “包装器”模型处理一些 UI 细节,这些细节被有意简化并提供合理的默认值。

【问题讨论】:

    标签: ruby-on-rails routing activemodel ruby-on-rails-4


    【解决方案1】:

    首先,这是一个有效的答案:

    class SimpleContract
      include ActiveModel::Model
    
      def self.model_name
        ActiveModel::Name.new(self, nil, "Contract")
      end
    end
    

    我将此答案从 kinopyo's answer 改编为 Change input name of model

    现在,为什么。 respond_to 的调用栈有点牵扯。

    # Start with `respond_with` in `ActionController`. Here is part of it:
    
    def respond_with(*resources, &block)
      # ...
      (options.delete(:responder) || self.class.responder).call(self, resources, options)
    end
    
    # That takes us to `call` in `ActionController:Responder`:
    
    def self.call(*args)
      new(*args).respond
    end
    
    # Now, to `respond` (still in `ActionController:Responder`):
    
    def respond
      method = "to_#{format}"
      respond_to?(method) ? send(method) : to_format
    end
    
    # Then to `to_html` (still in `ActionController:Responder`):
    
    def to_html
      default_render
    rescue ActionView::MissingTemplate => e
      navigation_behavior(e)
    end
    
    # Then to `default_render`:
    
    def default_render
      if @default_response
        @default_response.call(options)
      else
        controller.default_render(options)
      end
    end
    

    这就是我目前所知道的。我实际上还没有找到构建 URL 的位置。我知道它是基于model_name 发生的,但我还没有找到它发生的代码行。

    【讨论】:

    【解决方案2】:

    我不确定我是否完全理解你的问题,但你能做这样的事情吗?

    class SimpleContract
      include ActiveModel::Model
      attr_accessor :contract
    
      # ...
    
      def contract_attributes
        # convert SimpleContract attributes to Contract attributes
      end
    
      def save
        self.contract = Contract.new(contract_attributes)
        contract.save
      end
    end
    

    -

    class ContractsController < ApplicationController
      # ...
    
      def create
        @simple_contract = SimpleContract.new(contract_params)
        flash[:notice] = "Created Contract." if @simple_contract.save
        respond_with(@simple_contract.contract)
      end
    
      # ...
    end
    

    我可能离基地很远。希望这至少能激发你的灵感。

    【讨论】:

    • 好笑!这与我首先尝试的类似,但我希望更简单/更好。注意:.save 返回 true 或 false,因此您的 self.contract = Contract.new(contract_attributes).save 不会按预期工作。
    • 关于.save 的事情很有趣,而且很好。你做整个SimpleContract 事情的原因是因为你不需要用户在创建/更新一个Contract 时告诉你关于Contract 的一切,还是你将合同保存分成多个屏幕或其他什么?你能澄清一下动机吗?
    • 对,我不需要用户从特定的 UI 表单告诉我有关 Contract 的所有信息。
    • 任何理由,对于那种形式,没有完全相同的模型,只是不同的控制器?任何未使用的合同属性都可以单独放置。
    • 这个讨论离我的特定问题有点远;请接受我这样做的充分理由。我正在寻找一种方法来更改 respond_with 生成 URL 的方式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多