Rails,如何在模型中渲染视图/部分

时间:2011-06-11 22:42:40

标签: ruby-on-rails ruby ruby-on-rails-3

在我的模特中,我有:

after_create :push_create

我push_create我需要渲染一个视图。我正试图这样做:

  def push_event(event_type)
    X["XXXXX-#{Rails.env}"].trigger(event_type, 
      {
        :content => render( :partial =>"feeds/feed_item", :locals => { :feed_item => self })
      }
    )
  end

这会激怒rails,因为它不像我在模型中渲染视图但我需要它。

错误:

NoMethodError (undefined method `render' for #<WallFeed:0x1039be070>):

连连呢?我应该以某种方式在其他地方呈现它吗?或者我如何在模型中渲染以设置内容?感谢

10 个答案:

答案 0 :(得分:65)

正确的解决方案

嗯,“他们”是对的。你真的必须在控制器中进行渲染 - 但从模型中调用该控制器是公平的游戏!幸运的是,AbstractController 在Rails 3中使它比我想象的容易。我结束了做一个简单的事情 ActionPusher类,就像ActionMailer一样工作。也许我会野心勃勃 有一天它会成为一个合适的宝石,但这应该成为我鞋子里其他人的良好开端。

我从这个链接获得了最多的帮助:http://www.amberbit.com/blog/2011/12/27/render-views-and-partials-outside-controllers-in-rails-3/

lib / action_pusher.rb中的

class ActionPusher < AbstractController::Base
  include AbstractController::Rendering
  include AbstractController::Helpers
  include AbstractController::Translation
  include AbstractController::AssetPaths
  include Rails.application.routes.url_helpers
  helper ApplicationHelper
  self.view_paths = "app/views"

  class Pushable
    def initialize(channel, pushtext)
      @channel = channel
      @pushtext = pushtext
    end

    def push
      Pusher[@channel].trigger('rjs_push', @pushtext )
    end
  end
end
在app / pushers / users_pusher.rb中

。我猜这个要求可能会更加全球化吗?

require 'action_pusher'

class UsersPusher < ActionPusher
  def initialize(user)
    @user = user
  end

  def channel
    @user.pusher_key
  end

  def add_notice(notice = nil)
    @notice = notice
    Pushable.new channel, render(template: 'users_pusher/add_notice')
  end
end

现在在我的模型中,我可以这样做:

after_commit :push_add_notice

private

def push_add_notice
  UsersPusher.new(user).add_notice(self).push
end

然后你会想要一个部分,例如app / views / users_pusher / add_notice.js.haml,可以这么简单:

alert('#{@notice.body}')

我猜你真的不需要使用Pushable内部类和.push 最后打电话,但我想让它看起来像ActiveMailer。我也有 我的用户模型上的pusher_key方法,为每个用户创建一个频道 - 但是这个 是我和Pusher这样的第一天,所以我不能确定这是否正确 战略。还有更多可以充实,但这足以让我开始。

祝你好运!

(这是我的第一个回答草案,留下来因为它可能对某人有所帮助)

我已经得到了解决方案的大致轮廓。像这样,在你的模型中:

after_create :push_new_message

private

def render_anywhere(partial, assigns = {})
  view = ActionView::Base.new(ActionController::Base.view_paths, assigns)
  view.extend ApplicationHelper
  view.render(:partial => partial)
end  

def push_new_message
  pushstring = render_anywhere('notices/push_new_message', :message_text => self.body)
  Pusher[user.pusher_key].trigger!('new_message', pushstring)
end

肯定有效 - 模板正在渲染,并在客户端成功获得eval()。我打算清理它,几乎可以肯定地将render_any转移到更通用的地方,并且可能会尝试something like this

我可以看到推送将需要他们自己的模板,调用普遍可用的模板,我可能会尝试在一个地方收集它们。一个不错的小问题是我有时会在部分中使用controller_name,比如点亮一个菜单项,但我显然必须采取不同的策略。我猜我可能需要做一些事情来获得更多帮助,但我还没有到达那里。

成功!万岁!这应该回答你的问题,我的 - 如果以后看起来合适,我会添加更多细节。祝你好运!!!!

一小时前的原始非答案为了清晰起见

我没有答案,但这个及时的问题值得进一步澄清,我希望通过帮助提问来接近我的答案:)

我面临同样的问题。为了更清楚地解释一下,Pusher异步将内容发送到连接的用户浏览器。典型的用例是向用户显示他们有来自其他用户的新消息。使用Pusher,您可以将消息发送到接收者的浏览器,以便他们在登录时立即得到通知。有关Pusher可以做的非常棒的演示,请查看http://wordsquared.com/

您可以发送您喜欢的任何数据,例如JSON哈希,以解释您的喜好,但发送RJS非常方便,就像客户端的任何其他ajax调用和eval()一样。这样,您可以(例如)渲染菜单栏的模板,完整地更新它,或者仅显示给用户的新消息计数,使用所有相同的部分来保持其骨干。原则上,你可以从发送者的控制器中呈现部分,但这也没有多大意义,甚至可能没有请求,它可以由cron作业触发,例如或其他一些事件,如股价变动。发送者控制器不应该知道它 - 我喜欢让控制器保持饥饿状态;)

这可能听起来像是违反MVC,但实际上并非如此 - 而且它确实应该通过ActionMailer之类的东西来解决,但是与应用程序的其余部分共享帮助程序和部分内容。我知道在我的应用程序中,我想在(或代替)ActionMailer调用的同时发送Pusher事件。我想基于来自用户A的事件为用户B渲染任意部分。

这些链接可能指向解决方案:

最后一个看起来最有希望,提供这个诱人的片段:

def render_anywhere(partial, assigns)
  view = ActionView::Base.new(Rails::Configuration.new.view_path, assigns)
  ActionView::Base.helper_modules.each { |helper| view.extend helper }
  view.extend ApplicationHelper
  view.render(:partial => partial)
end

上面另一张海报提供的this link也是如此。

我会报告我是否有工作

tl;博士:我也是!

答案 1 :(得分:57)

我这样做:

ApplicationController.new.render_to_string(partial: 'messages/any', locals: { variable: 'value' })

答案 2 :(得分:27)

Rails 5路

在Rails 5中,由于implemented render控制器类方法,在控制器外部呈现变得非常简单:

# render template
ApplicationController.render 'templates/name'
# render action
FooController.render :index
# render file
ApplicationController.render file: 'path'
# render inline
ApplicationController.render inline: 'erb content'

在控制器外部调用render时,可以通过assigns选项分配实例变量,并使用控制器中提供的任何其他选项:

ApplicationController.render(
  assigns: { article: Article.take },
  template: 'articles/show',
  layout: false
)

请求环境可以通过默认选项

进行定制
ApplicationController.render inline: '<%= users_url %>'
# => 'http://default_host.com/users'

ApplicationController.renderer.defaults[:http_host] = 'custom_host.org'
# => "custom_host.org"

ApplicationController.render inline: '<%= users_url %>'
# => 'http://custom_host.org/users'

或通过初始化新渲染器显式

renderer = ApplicationController.renderer.new(
  http_host: 'custom_host.org',
  https: true
)
renderer.render inline: '<%= users_url %>'
# => 'https://custom_host.org/users'

希望有所帮助。

答案 3 :(得分:23)

您可以直接使用ActionView并将部分渲染为字符串而无需控制器。我发现该模式对于创建封装一些javascript生成的模型非常有用。例如。

html = ActionView::Base.new(Rails.configuration.paths['app/views']).render(
  partial: 'test', 
  formats: [:html],
  handlers: [:erb],
  locals: { variable: 'value' }
)

然后,只需将_test.html.erb放入您的视图文件夹中即可试用!

答案 4 :(得分:2)

我很确定你所寻找的答案位于Crafting Rails Applications,其中 Jose Valim 详细介绍了如何为什么您希望直接从数据库中呈现视图

对不起,我还没有更多的帮助,因为我今晚才开始自己阅读。

可能找到一些帮助here - 这是一篇关于做这类事情的博文,虽然使用的方法不同于你的

答案 5 :(得分:2)

执行此操作的“正确”方法是以序列化形式(json)推送对象,然后在收到事件后让视图处理它。也许您想使用Handlebars来渲染对象。

编辑:我最初写过关于如何,尽管我的回答,我将按照你的例子。但我刚刚意识到推送通知时你的方法存在巨大的问题。

在您的问题中,您正在向一个用户执行推送通知。对我来说,我正在向一组用户广播。因此,我将使用“current_user”的假设以及随之而来的所有内容(例如逻辑,权限等)来呈现html。这是NO BUENO,因为每个推送通知将由不同的“当前用户”接收。

因此,实际上,您需要发送回数据,并让每个视图处理它。

答案 6 :(得分:0)

您应该从控制器调用所有渲染方法。因此,在这种情况下,您可以通知控制器已创建对象,然后控制器可以呈现视图。此外,由于您只能渲染一次,我认为您可以在调用渲染之前等待所有服务器端操作完成。

答案 7 :(得分:0)

渲染方法是在ActiveController类及其后代上定义的。本质上,您无法在模型上访问它,也不是类方法,因此如果没有控制器实例,则无法使用它。

我从来没有尝试实例化一个控制器,只是为了简单地将一个部分字符串化,但是如果你可以得到一个控制器,那么render_to_string似乎就是这样。

我会说,如果你走这条路,你就会把RoR从“Rails”中拿走。这违反了MVC和基本上糟糕的程序设计。这并不意味着我认为你是一个坏人:P有时生活会让我们摆脱困境,可以这么说。

我无法谈论促使你这样做的细节,但我强烈建议你重新考虑你的做法。

答案 8 :(得分:0)

我为此创造了一个要点 我需要类似的东西,模型不一定(或者在我的情况下,永远)通过控制器更新,所以逻辑不能坐在那里。

创建基于服务器推送的控制器:
https://gist.github.com/4707055

答案 9 :(得分:0)

Rails 6.0.0兼容的答案,因为我在寻找解决方案时出现在此页面上:

pipe()