Ruby-on-rails Rails,如何在模型中渲染视图/部分
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6318959/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Rails, How to render a view/partial in a model
提问by AnApprentice
In my model I have:
在我的模型中,我有:
after_create :push_create
I push_create I need to render a view. I'm trying to do that like so:
我 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
This angers rails as it doesn't like me rendering a view in the model but I need it there.
这激怒了 rails,因为它不喜欢我在模型中渲染视图,但我需要它。
Error:
错误:
NoMethodError (undefined method `render' for #<WallFeed:0x1039be070>):
Suggestions? Should I render it somewhere else somehow? Or how can I render in the model to set content? Thanks
建议?我应该以某种方式在其他地方渲染它吗?或者如何在模型中渲染以设置内容?谢谢
回答by stephan.com
proper solution
适当的解决方案
Well, "they" are right. You really have to do the rendering in a controller - but it's fair game to call that controller from a model! Fortunately, AbstractController in Rails 3 makes it easier than I thought. I wound up making a simple ActionPusher class, working just like ActionMailer. Perhaps I'll get ambitious and make this a proper gem someday, but this should serve as a good start for anyone else in my shoes.
嗯,“他们”是对的。您确实必须在控制器中进行渲染 - 但从模型调用该控制器是公平的游戏!幸运的是,Rails 3 中的 AbstractController 比我想象的更容易。我最终制作了一个简单的 ActionPusher 类,就像 ActionMailer 一样工作。也许有一天我会变得雄心勃勃,并把它变成一颗合适的宝石,但这对我来说应该是其他任何人的良好开端。
I got the most help from this link: http://www.amberbit.com/blog/2011/12/27/render-views-and-partials-outside-controllers-in-rails-3/
我从这个链接得到了最多的帮助:http: //www.amberbit.com/blog/2011/12/27/render-views-and-partials-outside-controllers-in-rails-3/
in lib/action_pusher.rb
在 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
in app/pushers/users_pusher.rb. I guess the require could go somewhere more global?
在 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
Now in my model, I can just do this:
现在在我的模型中,我可以这样做:
after_commit :push_add_notice
private
def push_add_notice
UsersPusher.new(user).add_notice(self).push
end
and then you'll want a partial, e.g. app/views/users_pusher/add_notice.js.haml, which could be as simple as:
然后你会想要一个部分的,例如 app/views/users_pusher/add_notice.js.haml,它可以很简单:
alert('#{@notice.body}')
I guess you don't really need to do it with Pushable inner class and the .push call at the end, but I wanted to make it look like ActiveMailer. I also have a pusher_key method on my user model, to make a channel for each user - but this is my first day with anything like Pusher, so I can't say for sure if that's the right strategy. There's more to be fleshed out, but this is enough for me to get started.
我想你真的不需要用 Pushable 内部类和最后的 .push 调用来做,但我想让它看起来像 ActiveMailer。我在我的用户模型上也有一个 pusher_key 方法,为每个用户创建一个频道 - 但这是我第一天使用类似 Pusher 的东西,所以我不能确定这是否是正确的策略。还有更多内容需要充实,但这足以让我开始。
Good luck!
祝你好运!
(this was my first draft answer, leaving it in because it might help someone)
(这是我的第一个答案草稿,保留它,因为它可能对某人有所帮助)
I've got the general outline of a solution working. Like this, in your model:
我有一个解决方案工作的大纲。像这样,在您的模型中:
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
that is definitely working - the template is rendering, and gets eval()'ed on the client side successfully. I'm planning to clean it up, almost certainly move render_anywhere somewhere more general, and probably try something like this
这绝对是有效的 - 模板正在呈现,并在客户端成功获得 eval() 。我打算清理它,几乎可以肯定将 render_anywhere 移到更通用的地方,并且可能尝试这样的操作
I can see that pushes will need their own templates, calling the generally available ones, and I may try to collect them all in one place. One nice little problem is that I sometimes use controller_name in my partials, like to light up a menu item, but I'll obviously have to take a different tactic there. I'm guessing I might have to do something to get more helpers available, but I haven't gotten there yet.
我可以看到推送将需要自己的模板,调用一般可用的模板,我可能会尝试将它们全部收集到一个地方。一个不错的小问题是,我有时会在我的部分中使用 controller_name,比如点亮菜单项,但我显然必须在那里采取不同的策略。我猜我可能需要做些事情来获得更多的助手,但我还没有到那里。
Success! Hooray! This should answer your question, and mine - I'll add more detail if it seems appropriate later. Good luck!!!!
成功!万岁!这应该能回答你和我的问题——如果以后觉得合适,我会添加更多细节。祝你好运!!!!
original non-answer from an hour ago left for clarity
为了清楚起见,一个小时前的原始未回答
I don't have an answer, but this timely question deserves more clarification, and I'm hoping to get closer to myanswer by helping ask :)
我没有答案,但这个及时的问题值得更多澄清,我希望通过帮助提问来更接近我的答案:)
I'm facing the same problem. To explain a little more clearly, Pusher asynchronously sends content to a connected user browser. A typical use case would be a showing the user they have a new message from another user. With Pusher, you can push a message to the receiver's browser, so they get an immediate notification if they are logged in. For a really great demo of what Pusher can do, check out http://wordsquared.com/
我面临同样的问题。为了更清楚地解释一下,Pusher 将内容异步发送到连接的用户浏览器。一个典型的用例是向用户展示他们有来自另一个用户的新消息。使用 Pusher,您可以将消息推送到接收者的浏览器,以便他们在登录后立即收到通知。有关 Pusher 功能的真正出色演示,请查看http://wordsquared.com/
You can send any data you like, such as a JSON hash to interpret how you like it, but it would be very convenient to send RJS, just like with any other ajax call and eval() it on the client side. That way, you could (for example) render the template for your menu bar, updating it in its entirety, or just the new message count displayed to the user, using all the same partials to keep it bone-DRY. In principle, you could render the partial from the sender'scontroller, but that doesn't make much sense either, and there might not even be a request, it could be triggered by a cron job, for example, or some other event, like a stock price change. The sender controller just should not have to know about it - I like to keep my controllers on a starvation diet ;)
你可以发送任何你喜欢的数据,比如一个 JSON 哈希来解释你喜欢它的方式,但是发送 RJS 会非常方便,就像在客户端使用任何其他 ajax 调用和 eval() 一样。这样,您可以(例如)为您的菜单栏渲染模板,对其进行整体更新,或者仅显示给用户的新消息计数,使用所有相同的部分来保持其骨干。原则上,您可以从发件人的控制器呈现部分,但这也没有多大意义,甚至可能没有请求,它可能由 cron 作业触发,例如,或其他一些事件,如股价变动。发件人控制器不应该知道它 - 我喜欢让我的控制器处于饥饿状态;)
It might sound like a violation of MVC, but it's really not - and it really should be solved with something like ActionMailer, but sharing helpers and partials with the rest of the app. I know in my app, I'd like to send a Pusher event at the same time as (or instead of) an ActionMailer call. I want to render an arbitrary partial for user B based on an event from user A.
这听起来像是违反了 MVC,但实际上并非如此——它真的应该用 ActionMailer 之类的东西来解决,但要与应用程序的其余部分共享助手和部分。我知道在我的应用程序中,我想在(或代替)ActionMailer 调用的同时发送一个 Pusher 事件。我想根据用户 A 的事件为用户 B 呈现任意部分。
These links may point the way towards a solution:
这些链接可能指向解决方案:
- http://blog.choonkeat.com/weblog/2006/08/rails-calling-r.html
- How to render a Partial from a Model in Rails 2.3.5
- http://mattwindsurfs.wordpress.com/2008/06/19/rails-render-in-a-model/
- http://davetroy.blogspot.com/2008/02/actsasrenderer-brings-output-to-models.html
- https://github.com/asapnet/acts_as_renderer
- http://ethilien.net/archives/render-rails-templates-anywhere-even-in-a-model/
- http://blog.choonkeat.com/weblog/2006/08/rails-calling-r.html
- 如何在 Rails 2.3.5 中从模型渲染部分
- http://mattwindsurfs.wordpress.com/2008/06/19/rails-render-in-a-model/
- http://davetroy.blogspot.com/2008/02/actsasrenderer-brings-output-to-models.html
- https://github.com/asapnet/acts_as_renderer
- http://ethilien.net/archives/render-rails-templates-anywhere-even-in-a-model/
The last one looks the most promising, offering up this tantalizing snippet:
最后一个看起来最有前途,提供了这个诱人的片段:
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
As does this linkprovided by another poster above.
正如上面另一张海报提供的链接一样。
I'll report back if I get something working
如果我有工作,我会回来报告
tl;dr: me too!
tl;博士:我也是!
回答by Kevin
I just do this:
我只是这样做:
ApplicationController.new.render_to_string(partial: 'messages/any', locals: { variable: 'value' })
回答by Oleg Afanasyev
Rails 5 way
导轨 5 路
In Rails 5 rendering outside a controllerbecame pretty straightforward due to implementedrendercontroller class method:
在 Rails 5 中,由于实现了控制器类方法,控制器外的渲染变得非常简单: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'
When calling renderoutside of a controller, one can assign instance variables via assignsoption and use any other options available from within a controller:
在render控制器外部调用时,可以通过assigns选项分配实例变量并使用控制器内可用的任何其他选项:
ApplicationController.render(
assigns: { article: Article.take },
template: 'articles/show',
layout: false
)
Request environmentcan be tailored either through default options
可以通过默认选项定制请求环境
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'
or explicitly by initializing a new renderer
或者显式地初始化一个新的渲染器
renderer = ApplicationController.renderer.new(
http_host: 'custom_host.org',
https: true
)
renderer.render inline: '<%= users_url %>'
# => 'https://custom_host.org/users'
Hope that helps.
希望有帮助。
回答by hsgubert
You can use ActionView directly and render partials to string without having a controller. I find that pattern useful to create models that encapsulate some javascript generation, for instance.
您可以直接使用 ActionView 并在没有控制器的情况下将部分渲染为字符串。例如,我发现这种模式对于创建封装一些 javascript 生成的模型很有用。
html = ActionView::Base.new(Rails.configuration.paths['app/views']).render(
partial: 'test',
formats: [:html],
handlers: [:erb],
locals: { variable: 'value' }
)
Then, just put your _test.html.erbin you view folder and try it out!
然后,只需将您_test.html.erb的视图文件夹放入并尝试一下!
回答by stephenmurdoch
I'm fairly sure the answers you seek lie within Crafting Rails Applicationswhere Jose Valimgoes into great detail about howand whyyou would want to render views straight from your db
我相当确定您在Crafting Rails Applications中寻求的答案在那里Jose Valim详细介绍了您希望如何以及为什么要直接从您的数据库呈现视图
Sorry I can't be of more help yet because I've just started reading it myself tonight.
抱歉,我无法提供更多帮助,因为我今晚才开始自己阅读。
You mightfind some help here- it's a blog post about doing this sort of thing, albeit using different methods than yours
你可能会在这里找到一些帮助——这是一篇关于做这种事情的博客文章,尽管使用的方法与你的不同
回答by Peter P.
the "proper" way to do this is to push an object in serialized form(json), and then have the view deal with it once the event is received. Perhaps you want to use Handlebars to render the object.
执行此操作的“正确”方法是以序列化形式(json)推送对象,然后在接收到事件后让视图处理它。也许您想使用 Handlebars 来渲染对象。
Edit: I originally wrote about how, despite my answer, I was going to follow your example. But I just realized there is a HUGE gotcha with your approach when it comes to push notifications.
编辑:我最初写的是关于如何,尽管我的回答,我将按照你的例子。但我刚刚意识到,在推送通知方面,你的方法有一个巨大的问题。
In your problem, you are doing push notifications to one user. For me, I was broadcasting out to a set of users. So I was going to render html with a presumption of a "current_user" and all that comes with it(eg logic, permissions, etc). This is NO BUENO as each push notification will be received by a different "current user".
在您的问题中,您正在向一位用户推送通知。对我来说,我正在向一组用户广播。因此,我打算以“current_user”及其附带的所有内容(例如逻辑、权限等)的假设呈现 html。这不是 BUENO,因为每个推送通知都会被不同的“当前用户”接收。
Therefore, really, you need to just send back the data, and let each individual view handle it.
因此,实际上,您只需要发回数据,并让每个单独的视图处理它。
回答by troelskn
Rails 6.0.0 compatible answer, since I ended up on this page while searching for a solution:
Rails 6.0.0 兼容答案,因为我在搜索解决方案时最终出现在此页面上:
lookup_context = ActionView::LookupContext.new(Rails.configuration.paths["app/views"])
renderer = ActionView::Base.new(lookup_context)
renderer.extend(Rails.application.helpers)
renderer.render \
template: "foo/bar",
formats: [:html],
handlers: [:erb],
locals: { user: User.new }
回答by cpuguy83
I have created a gist for this.
I needed something similar, where the models don't necessarily (or in my case, ever) get updated via a controller, so the logic can't sit there.
我为此创建了一个要点。
我需要类似的东西,模型不一定(或在我的情况下,永远)通过控制器更新,所以逻辑不能坐在那里。
Created a server-push based controller:
https://gist.github.com/4707055
创建了一个基于服务器推送的控制器:https:
//gist.github.com/4707055
回答by Chandranshu
You should call all render methods from a controller. So, in this case, you can notify the controller that the object has been created and the controller can then render the view. Also, since you can render only once, I think you can wait for all your server side operations to complete before invoking the render.
您应该从控制器调用所有渲染方法。因此,在这种情况下,您可以通知控制器对象已创建,然后控制器可以渲染视图。此外,由于您只能渲染一次,我认为您可以在调用渲染之前等待所有服务器端操作完成。
回答by jaydel
The render methods are defined on the ActiveController class and its progeny. Inherently you do not have access to it on the model, nor is it a class method so you can't use it without an instance of the controller.
渲染方法是在 ActiveController 类及其后代上定义的。从本质上讲,您无法在模型上访问它,也不是类方法,因此如果没有控制器的实例,您将无法使用它。
I've never tried to instantiate a controller for the express purpose of simply stringifying a partial, but if you can get your hands on a controller, render_to_string seems to be the way to go.
我从来没有为了简单地将部分字符串化的明确目的而尝试实例化控制器,但是如果您可以使用控制器,render_to_string 似乎是要走的路。
I will chime in by saying that if you're going down this path you're taking RoR "off the Rails". This is a violation of MVC and fundamentally poor program design.This doesn't mean I think you're a bad person :P Sometimes life drives us off the rails, so to speak.
我会插话说,如果你沿着这条路走下去,你就是在让 RoR“脱轨”。这违反了 MVC 并且从根本上来说程序设计很糟糕。这并不意味着我认为你是一个坏人 :P 可以这么说,有时生活会让我们偏离正轨。
I can't speak to the details that have driven you to do this, but I'd strongly suggest you rethink your approach.
我无法谈论促使您这样做的细节,但我强烈建议您重新考虑您的方法。

