Rails, Cómo renderizar una vista / parcial en un modelo


En mi modelo tengo:

after_create :push_create

Push_create Necesito renderizar una vista. Estoy tratando de hacer eso así:

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

Esto enfurece a rails, ya que no le gusta que renderice una vista en el modelo, pero la necesito allí.

Error:

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

Sugerencias? ¿Debería renderizarlo en otro lugar de alguna manera? ¿O cómo puedo renderizar en el modelo para establecer el contenido? Gracias

Author: AnApprentice, 2011-06-12

9 answers

Solución adecuada

Bueno, "ellos" tienen razón. Realmente tienes que hacer el renderizado en un controlador - ¡pero es un juego justo llamar a ese controlador desde un modelo! Afortunadamente, AbstractController en Rails 3 es más fácil de lo que pensaba. Terminé haciendo un simple Clase ActionPusher, trabajando igual que ActionMailer. Tal vez voy a ser ambicioso y haz de esto una joya apropiada algún día, pero esto debería servir como un buen comienzo para cualquier otra persona en mi lugar.

Obtuve la mayor ayuda de esto enlace: http://www.amberbit.com/blog/2011/12/27/render-views-and-partials-outside-controllers-in-rails-3/

En 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

En app/pushers/users_pusher.rb. Supongo que el requerimiento podría ir a algún lugar más global.

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

Ahora en mi modelo, puedo hacer esto:

after_commit :push_add_notice

private

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

Y luego querrá un parcial, por ejemplo, app/views/users_pusher/add_notice.js.haml, que podría ser tan simple como:

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

Supongo que realmente no necesitas hacerlo con Clase interior empujable y el .empujar llame al final, pero quería que pareciera ActiveMailer. También tengo un método pusher_key en mi modelo de usuario, para hacer un canal para cada usuario-pero esto es mi primer día con algo como Pusher, así que no puedo decir con seguridad si eso es lo correcto estrategia. Hay más que desarrollar, pero esto es suficiente para empezar.

¡Buena suerte!

(este fue mi primer borrador de respuesta, dejándolo porque podría ayudar a alguien)

Tengo el esquema general de una solución de trabajo. Así, en tu modelo:

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

Eso definitivamente está funcionando - la plantilla está renderizando, y obtiene eval()'ed en el lado del cliente con éxito. Estoy planeando limpiarlo, casi con certeza mover render_anywhere a algún lugar más general, y probablemente intentar algo como esto

Puedo ver que los push necesitarán sus propias plantillas, llamando a las generalmente disponibles, y puedo tratar de recopilarlas todas en un solo lugar. Una bonita el pequeño problema es que a veces uso controller_name en mis parciales, como para iluminar un elemento del menú, pero obviamente tendré que tomar una táctica diferente allí. Supongo que podría tener que hacer algo para conseguir más ayudantes disponibles, pero no he llegado allí todavía.

Éxito! ¡Hurra! Esto debe responder a su pregunta, y la mía-Voy a añadir más detalles si parece apropiado más adelante. ¡Buena suerte!!!!

Falta de respuesta original de hace una hora left for clarity{[39]]}

No tengo un respuesta, pero esta pregunta oportuna merece más aclaración, y espero acercarme a mi respuesta ayudando a preguntar:)

Me enfrento al mismo problema. Para explicarlo un poco más claramente, Pusher envía contenido de forma asíncrona a un navegador de usuario conectado. Un caso de uso típico sería mostrar al usuario que tiene un nuevo mensaje de otro usuario. Con Pusher, puede enviar un mensaje al navegador del receptor, para que reciba una notificación inmediata si ha iniciado sesión. Para un realmente gran demostración de lo que Pusher puede hacer, echa un vistazo http://wordsquared.com /

Puede enviar cualquier dato que desee, como un hash JSON para interpretar cómo le gusta, pero sería muy conveniente enviar RJS, al igual que con cualquier otra llamada ajax y eval() en el lado del cliente. De esa manera, podría (por ejemplo) renderizar la plantilla para su barra de menús, actualizándola en su totalidad, o simplemente el nuevo recuento de mensajes que se muestra al usuario, utilizando todos los mismos parciales para mantenerla seca. En principio, podría renderizar el parcial desde el controlador del remitente , pero eso tampoco tiene mucho sentido, y puede que ni siquiera haya una solicitud, podría activarse por un trabajo cron, por ejemplo, o algún otro evento, como un cambio en el precio de las acciones. El controlador remitente simplemente no debería tener que saberlo - Me gusta mantener a mis controladores en una dieta de hambre;)

Puede sonar como una violación de MVC, pero realmente no lo es, y realmente debe resolverse con algo como ActionMailer, pero compartiendo ayudantes y parciales con el resto de la aplicación. Sé que en mi aplicación, me gustaría enviar un evento Pusher al mismo tiempo que (o en lugar de) una llamada de ActionMailer. Quiero renderizar un parcial arbitrario para el usuario B basado en un evento del usuario A.

Estos enlaces pueden señalar el camino hacia una solución:

El último parece el más prometedor, ofreciendo este tentador fragmento:

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 hace este enlace proporcionado por otro póster de arriba.

Te informaré si tengo algo funcionando

Tl;dr: ¡yo también!

 64
Author: stephan.com,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:11

Solo hago esto:

ApplicationController.new.render_to_string(partial: 'messages/any', locals: { variable: 'value' })
 57
Author: Kevin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-27 13:22:52

Puede usar ActionView directamente y renderizar parciales a string sin tener un controlador. Encuentro ese patrón útil para crear modelos que encapsulan alguna generación de javascript, por ejemplo.

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

Entonces, simplemente ponga su _test.html.erb en su carpeta de vista y pruébelo!

 21
Author: hsgubert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-27 13:11:44

Rails 5 way

En Rails 5 el renderizado fuera de un controlador se volvió bastante sencillo debido a que implementado render método de la clase del controlador:

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

Cuando se llama a render fuera de un controlador, se pueden asignar variables de instancia a través de la opción assigns y usar cualquier otra opción disponible dentro de un controlador:

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

El entorno de solicitud se puede personalizar a través de las opciones predeterminadas

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'

O explícitamente inicializando un nuevo renderizador

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

Espero que eso ayude.

 20
Author: Oleg Afanasyev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-21 10:49:29

Estoy bastante seguro de que las respuestas que busca se encuentran dentro de Crafting Rails Applications donde Jose Valim entra en gran detalle sobre cómo y por qué usted querría renderizar vistas directamente desde su db

Lo siento, no puedo ser de más ayuda todavía porque acabo de empezar a leerlo yo mismo esta noche.

You might find some help here - es una publicación de blog sobre hacer este tipo de cosas, aunque usando métodos diferentes a los tuyos

 2
Author: stephenmurdoch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-11 23:48:16

La forma "adecuada" de hacer esto es empujar un objeto en forma serializada(json), y luego hacer que la vista se ocupe de él una vez que se recibe el evento. Tal vez quieras usar Manillares para renderizar el objeto.

Editar: Originalmente escribí sobre cómo, a pesar de mi respuesta, iba a seguir tu ejemplo. Pero me acabo de dar cuenta de que hay una GRAN gotcha con su enfoque cuando se trata de notificaciones push.

En su problema, está haciendo notificaciones push a un usuario. Para mí, yo era difusión a un conjunto de usuarios. Así que iba a renderizar html con la presunción de un "current_user" y todo lo que viene con él(por ejemplo, lógica, permisos, etc.). Esto NO es BUENO, ya que cada notificación push será recibida por un "usuario actual"diferente.

Por lo tanto, realmente, necesita devolver los datos y dejar que cada vista individual los maneje.

 2
Author: Peter P.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-02 20:00:33

Debe llamar a todos los métodos de renderizado desde un controlador. Por lo tanto, en este caso, puede notificar al controlador que el objeto ha sido creado y el controlador puede renderizar la vista. Además, dado que solo puede renderizar una vez, creo que puede esperar a que se completen todas las operaciones del lado del servidor antes de invocar el renderizado.

 0
Author: Chandranshu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-11 22:52:20

Los métodos de renderizado se definen en la clase ActiveController y su progenie. Inherentemente no tiene acceso a él en el modelo, ni es un método de clase, por lo que no puede usarlo sin una instancia del controlador.

Nunca he intentado crear una instancia de un controlador con el propósito expreso de simplemente stringificar un parcial, pero si puedes tener en tus manos un controlador, render_to_string parece ser el camino a seguir.

Voy a intervenir diciendo que si vas a bajar esto camino que está tomando RoR "fuera de los rieles". Esto es una violación de MVC y fundamentalmente pobre diseño del programa.Esto no quiere decir que creo que eres una mala persona :P A veces la vida nos conduce fuera de los rieles, por así decirlo.

No puedo hablar de los detalles que te han llevado a hacer esto, pero te sugiero encarecidamente que reconsideres tu enfoque.

 0
Author: jaydel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-11 23:29:18

He creado una síntesis para esto.
Necesitaba algo similar, donde los modelos no necesariamente (o en mi caso, nunca) se actualizan a través de un controlador, por lo que la lógica no puede sentarse allí.

Se creó un controlador basado en push de servidor:
https://gist.github.com/4707055

 0
Author: cpuguy83,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-04 14:43:24