【问题标题】:Rails: Render view from outside controllerRails:从外部控制器渲染视图
【发布时间】:2015-04-04 15:20:21
【问题描述】:

我正在尝试使用视图创建 HTML 字符串。我想从一个不是控制器的类中渲染它。如何在控制器外部使用 rails 渲染引擎?类似于 ActionMailer 的做法?

谢谢!

【问题讨论】:

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


    【解决方案1】:

    Rails 5 和 6 以更方便的方式支持这一点,可以在后台处理创建请求等:

    rendered_string = ApplicationController.render(
      template: 'users/show',
      assigns: { user: @user }
    )
    

    这将呈现app/views/users/show.html.erb 并设置@user 实例变量,因此您无需对模板进行任何更改。它会自动使用ApplicationController 中指定的布局(默认为application.html.erb)。 Full documentation can be found here.

    The test shows a handful of additional options and approaches.

    【讨论】:

    • 如果有人想在没有控制器的情况下生成文本,请查找:ruby-doc.org/stdlib-2.4.2/libdoc/erb/rdoc/ERB.html#method-c-new 与此,您不必在views 目录中创建逻辑。
    • @EduardoSantana 如果您有一个要渲染的普通 ERB 模板,那就太好了,但它不会渲染 ActionView 视图。即,您不会获得实例变量、部分渲染、辅助方法等。
    • 使用控制器类触发了守望者和身份验证问题。这对我有用:ActionView::Base.new('app/views').render(file: 'invoices/show', locals: {invoiceObjIn: inv}, layout: 'layouts/invoice')
    • 我有这个错误:Devise could not find the 'Warden::Proxy' instance on your request environment.。你如何解决这个问题?
    • @alagaesia 当您遇到问题时,请打开一个新问题而不是发表评论,以便其他人可以介入并提供帮助。
    【解决方案2】:

    在 Rails 5 中:

    view = ActionView::Base.new(ActionController::Base.view_paths)
    view.render(file: 'template.html.erb')
    

    在 Rails 6.1 中:

    lookup_context = ActionView::LookupContext.new(ActionController::Base.view_paths)
    context = ActionView::Base.with_empty_template_cache.new(lookup_context, {}, nil)
    
    renderer = ActionView::Renderer.new(lookup_context)
    renderer.render(context, { file: 'app/views/template.html.erb' })
    
    • ActionView::Renderer.new() 采用 lookup_context arg,render() 方法采用 context,所以我们先设置它们
    • ActionView::Baseis the default ActiveView context,必须用with_empty_template_cache方法初始化,否则render()会报错
    • {}, nilrequired assignscontroller args,过去在 Rails 5 中默认为 {}, nil
    • Rails 6.1 需要完整的文件路径 file: 'app/views/template.html',而 Rails 5 只需要文件名

    【讨论】:

      【解决方案3】:

      在ApplicationController上直接调用render方法可能会报错

       Helper Devise: could not find the `Warden::Proxy` instance on request environment
      

      相反,我们可以像使用 ApplicationController.renderer.render 一样

       rendered_string = ApplicationController.renderer.render(
           partial: 'users/show',
           locals: { user: user }
      )
      

      【讨论】:

        【解决方案4】:

        最好使用控制器来呈现视图,因为这就是它们的用途,然后然后将其转换为字符串,因为这是您的最终目标。

        require 'open-uri'
        html = open(url, &:read)
        

        【讨论】:

        • 请不要对多个问题发布相同的答案。发布一个好的答案,然后投票/标记以关闭其他问题作为重复问题。如果问题不是重复的,调整您对该问题的回答。
        【解决方案5】:

        没有必要用太多的 gem 来过度膨胀你的应用程序。我们知道 ERB 已经包含在您的 Rails 应用程序中。

        @jdf = JDF.new
        @job = ERB.new(File.read(Rails.root + "app/views/entries/job.xml.erb"))
        result =  @job.result(binding)
        

        上面是我正在开发的应用程序的代码 sn-p。

        • @jdf 是要在 erb 视图中评估的对象。
        • 就我而言,我需要渲染xml
        • result 是一个字符串,可以保存或发送到您喜欢的任何地方。

        【讨论】:

        • 这很好,除非你依赖像 link_to 这样的助手。
        【解决方案6】:

        您可以使用ActionView::Base 来实现此目的。

        view = ActionView::Base.new(ActionController::Base.view_paths, {})
        view.render(file: 'template.html.erb')
        

        ActionView::Base 初始化需要:

        1. 上下文,表示模板搜索路径
        2. assigns 哈希,为模板提供变量

        如果您想包含助手,可以使用 class_eval 来包含它们:

        view.class_eval do
          include ApplicationHelper
          # any other custom helpers can be included here
        end
        

        【讨论】:

        • 使用 ActionView::Base 是最好的答案,IMO。为了方便起见,我可能会更进一步并将其子类化,但这只是我。我从来没有让 AbstractController 像其他人描述的那样工作,但也许我的 Rails 版本太新了。不管怎样,这种方式感觉不那么脏。
        • 嵌套布局怎么样?
        • 用布局渲染:view.render(file: 'template.html.erb', layout: 'layouts/layout.html.erb')
        【解决方案7】:

        为了将来参考,我最终找到了 这个方便的宝石让这变得轻而易举:

        https://github.com/yappbox/render_anywhere

        【讨论】:

        • 引用的库自 2015 年以来未更新,与 Rails 4 和 5 不兼容。
        【解决方案8】:

        “我正在尝试使用视图创建 HTML 字符串。” -- 如果您的意思是您在视图模板的上下文中,那么只需使用辅助方法或渲染部分。

        如果您使用其他“普通旧 Ruby 对象”,请记住您可以直接使用 ERB 模块:

        erb = ERB.new("path/to/template")
        result = erb.result(binding)
        

        诀窍是获取为模板中的代码提供上下文的“绑定”对象。 ActionController 和其他 Rails 类免费公开它,但我找不到解释它来自哪里的参考。

        http://www.ruby-doc.org/stdlib-2.2.0/libdoc/erb/rdoc/ERB.html#method-i-result

        【讨论】:

        【解决方案9】:

        从技术上讲,ActionMailerAbstractController::Base 的子类实现。如果您想自己实现此功能,您可能还想从AbstractController::Base 继承。

        这里有一篇很好的博文:https://www.amberbit.com/blog/2011/12/27/render-views-and-partials-outside-controllers-in-rails-3/,它解释了所需的步骤。

        【讨论】: