web-dev-qa-db-fra.com

Rails, Comment rendre une vue / partielle dans un modèle

Dans mon modèle, j'ai:

after_create :Push_create

I Push_create J'ai besoin de rendre une vue. J'essaie de faire ça comme ça:

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

Cela met en colère Rails car il n'aime pas que je rende une vue dans le modèle mais j'en ai besoin là-bas.

Erreur:

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

Suggestions? Dois-je le rendre ailleurs quelque part? Ou comment puis-je effectuer un rendu dans le modèle pour définir le contenu? Merci

58
AnApprentice

bonne solution

Eh bien, "ils" ont raison. Vous devez vraiment faire le rendu dans un contrôleur - mais il est juste d'appeler ce contrôleur à partir d'un modèle! Heureusement, AbstractController dans Rails 3 rend les choses plus faciles que je ne le pensais. J'ai fini par créer une classe ActionPusher simple, fonctionnant comme ActionMailer. Peut-être que je deviendrai ambitieux et en ferai un vrai bijou un jour, mais cela devrait être un bon début pour quiconque à ma place.

J'ai obtenu le plus d'aide de ce lien: http://www.amberbit.com/blog/2011/12/27/render-views-and-partials-outside-controllers-in-Rails-3/

dans 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

dans app/pushers/users_pusher.rb. Je suppose que l'exigence pourrait aller quelque part plus 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

Maintenant, dans mon modèle, je peux simplement faire ceci:

after_commit :Push_add_notice

private

def Push_add_notice
  UsersPusher.new(user).add_notice(self).Push
end

et puis vous voudrez un partiel, par exemple app/views/users_pusher/add_notice.js.haml, qui pourrait être aussi simple que:

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

Je suppose que vous n'avez pas vraiment besoin de le faire avec la classe interne Pushable et l'appel .Push à la fin, mais je voulais qu'il ressemble à ActiveMailer. J'ai également une méthode pusher_key sur mon modèle d'utilisateur, pour créer un canal pour chaque utilisateur - mais c'est mon premier jour avec quelque chose comme Pusher, donc je ne peux pas dire avec certitude si c'est la bonne stratégie. Il y a plus à étoffer, mais cela me suffit pour commencer.

Bonne chance!

(Ce fut ma première ébauche de réponse, en la laissant car cela pourrait aider quelqu'un)

J'ai les grandes lignes d'une solution qui fonctionne. Comme ça, dans votre modèle:

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

cela fonctionne définitivement - le modèle est rendu, et obtient eval () du côté client avec succès. Je prévois de le nettoyer, de déplacer presque certainement render_anywhere dans un endroit plus général, et d'essayer probablement quelque chose comme ça

Je peux voir que les push auront besoin de leurs propres modèles, appelant ceux généralement disponibles, et je peux essayer de les rassembler tous en un seul endroit. Un petit problème sympa est que j'utilise parfois controller_name dans mes partiels, comme pour allumer un élément de menu, mais je devrai évidemment prendre une tactique différente. Je suppose que je devrais peut-être faire quelque chose pour obtenir plus d'aides, mais je n'y suis pas encore arrivé.

Succès! Hourra! Cela devrait répondre à votre question et à la mienne - j'ajouterai plus de détails si cela semble approprié plus tard. Bonne chance!!!!

non-réponse originale d'il y a une heure pour plus de clarté

Je n'ai pas de réponse, mais cette question opportune mérite plus de précisions, et j'espère me rapprocher de mon réponse en aidant à poser :)

Je fais face au même problème. Pour expliquer un peu plus clairement, Pusher envoie de manière asynchrone du contenu à un navigateur utilisateur connecté. Un cas d'utilisation typique serait de montrer à l'utilisateur qu'il a un nouveau message d'un autre utilisateur. Avec Pusher, vous pouvez envoyer un message au navigateur du destinataire, afin qu'il reçoive une notification immédiate s'il est connecté. Pour une démonstration vraiment intéressante de ce que Pusher peut faire, consultez http://wordsquared.com/

Vous pouvez envoyer toutes les données que vous aimez, comme un hachage JSON pour interpréter comment vous l'aimez, mais il serait très pratique d'envoyer du RJS, comme avec tout autre appel ajax et eval () du côté client. De cette façon, vous pouvez (par exemple) rendre le modèle de votre barre de menus, le mettre à jour dans son intégralité, ou simplement le nouveau nombre de messages affiché à l'utilisateur, en utilisant tous les mêmes partiels pour le garder sec. En principe, vous pouvez rendre le partiel à partir du contrôleur sender's, mais cela n'a pas beaucoup de sens non plus, et il pourrait même ne pas y avoir de demande, cela pourrait être déclenché par un travail cron, par exemple, ou un autre événement, comme un changement de cours boursier. Le contrôleur de l'expéditeur ne devrait tout simplement pas avoir à le savoir - j'aime garder mes contrôleurs au régime de famine;)

Cela peut sembler une violation de MVC, mais ce n'est vraiment pas le cas - et cela devrait vraiment être résolu avec quelque chose comme ActionMailer, mais en partageant des assistants et des partiels avec le reste de l'application. Je sais que dans mon application, j'aimerais envoyer un événement Pusher en même temps (ou à la place) qu'un appel ActionMailer. Je veux rendre un partiel arbitraire pour l'utilisateur B basé sur un événement de l'utilisateur A.

Ces liens peuvent indiquer la voie vers une solution:

Le dernier semble le plus prometteur, offrant cet extrait alléchant:

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

De même que ce lien fourni par une autre affiche ci-dessus.

Je ferai rapport si je fais fonctionner quelque chose

tl; dr: moi aussi!

65
stephan.com

Je fais juste ceci:

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

Rails 5 voies

Dans Rails 5 le rendu en dehors d'un contrôleur est devenu assez simple en raison de implémentérender méthode de classe de contrôleur:

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

Lors de l'appel de render en dehors d'un contrôleur, on peut affecter des variables d'instance via l'option assigns et utiliser toutes les autres options disponibles à partir d'un contrôleur:

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

Environnement de requête peut être personnalisé via les options par défaut

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'

ou explicitement en initialisant un nouveau rendu

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

J'espère que ça t'as aidé.

32
Oleg Afanasyev

Vous pouvez utiliser ActionView directement et rendre des partiels en chaîne sans avoir de contrôleur. Je trouve ce modèle utile pour créer des modèles qui encapsulent une génération javascript, par exemple.

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

Ensuite, mettez simplement votre _test.html.erb dans votre dossier et essayez-le!

24
hsgubert

Je suis presque sûr que les réponses que vous cherchez se trouvent dans Crafting Rails Applications where Jose Valim explique en détail comment et pourquoi vous voudriez rendre les vues directement depuis votre base de données

Désolé, je ne peux pas vous être encore plus utile car je viens de commencer à le lire moi-même ce soir.

Vous peut-être trouver de l'aide ici - c'est un article de blog sur ce genre de chose, mais en utilisant des méthodes différentes de la vôtre

2
stephenmurdoch

la manière "appropriée" de procéder consiste à envoyer un objet sous forme sérialisée (json), puis à faire en sorte que la vue le gère une fois l'événement reçu. Vous souhaitez peut-être utiliser le guidon pour rendre l'objet.

Edit: J'ai écrit à l'origine comment, malgré ma réponse, j'allais suivre votre exemple. Mais je viens de réaliser qu'il y a un énorme problème avec votre approche en ce qui concerne les notifications Push.

Dans votre problème, vous effectuez des notifications Push à un utilisateur. Pour moi, je diffusais à un ensemble d'utilisateurs. J'allais donc rendre le html avec une présomption de "current_user" et tout ce qui va avec (par exemple la logique, les permissions, etc.). Ce n'est PAS BUENO car chaque notification Push sera reçue par un "utilisateur actuel" différent.

Par conséquent, vous devez simplement renvoyer les données et laisser chaque vue individuelle les gérer.

2
Peter P.

Les méthodes de rendu sont définies sur la classe ActiveController et sa descendance. Intrinsèquement, vous n'y avez pas accès sur le modèle, ce n'est pas non plus une méthode de classe, vous ne pouvez donc pas l'utiliser sans une instance du contrôleur.

Je n'ai jamais essayé d'instancier un contrôleur dans le but exprès de simplement stringifier un partiel, mais si vous pouvez mettre la main sur un contrôleur, render_to_string semble être la voie à suivre.

Je répondrai en disant que si vous suivez cette voie, vous retirez RoR "des rails". C'est une violation de MVC et une conception de programme fondamentalement médiocre. Cela ne signifie pas que je pense que vous êtes une mauvaise personne: P Parfois, la vie nous pousse à quitter les Rails, pour ainsi dire.

Je ne peux pas parler des détails qui vous ont poussé à le faire, mais je vous suggère fortement de repenser votre approche.

0
jaydel

J'ai créé un Gist pour cela.
J'avais besoin de quelque chose de similaire, où les modèles ne sont pas nécessairement (ou dans mon cas, jamais) mis à jour via un contrôleur, donc la logique ne peut pas rester là.

Création d'un contrôleur basé sur le serveur Push:
https://Gist.github.com/4707055

0
cpuguy83

Vous devez appeler toutes les méthodes de rendu à partir d'un contrôleur. Ainsi, dans ce cas, vous pouvez informer le contrôleur que l'objet a été créé et le contrôleur peut alors rendre la vue. De plus, comme vous ne pouvez effectuer un rendu qu'une seule fois, je pense que vous pouvez attendre la fin de toutes vos opérations côté serveur avant d'appeler le rendu.

0
Chandranshu