Comment utiliser la méthode d'assistance «number_to_currency» dans le modèle plutôt que dans la vue?

93

Je voudrais utiliser la to_dollarméthode dans mon modèle comme ceci:

module JobsHelper      
  def to_dollar(amount)
    if amount < 0
      number_to_currency(amount.abs, :precision => 0, :format => "-%u%n")
    else
      number_to_currency(amount, :precision => 0)
    end
  end      
end

class Job < ActiveRecord::Base
  include JobsHelper
  def details
    return "Only " + to_dollar(part_amount_received) + 
           " out of " + to_dollar(price) + " received."
  end
end

Malheureusement, la number_to_currencyméthode n'est pas reconnue ici:

méthode non définie `number_to_currency 'pour # <Job: 0x311eb00>

Des idées pour le faire fonctionner?

Misha Moroshko
la source

Réponses:

103

Il n'est pas disponible car son utilisation dans un modèle enfreint (généralement) MVC (et cela semble le cas dans votre cas). Vous prenez des données et vous les manipulez pour les présenter. Ceci, par définition, appartient à la vue, pas au modèle.

Voici quelques solutions:

  • Utilisez un objet de présentation ou de modèle de vue pour assurer la médiation entre le modèle et la vue. Cela nécessite presque certainement plus de travail initial que les autres solutions, mais c'est presque toujours une meilleure conception. L'utilisation des helpers dans un présentateur / modèle de vue ne viole pas MVC, car ils résident dans la couche de vue, remplaçant les assistants Rails personnalisés traditionnels et les vues à remplissage logique.

  • Explicitement include ActionView::Helpers::NumberHelperau JobsHelperlieu de dépendre de Rails pour l'avoir chargé comme par magie pour vous. Ce n'est toujours pas génial, car vous ne devriez pas accéder à un assistant d'un modèle.

  • Violer MVC et SRP . Voir la réponse de fguillen pour savoir comment procéder. Je ne vais pas en faire écho ici parce que je ne suis pas d'accord avec cela. Plus encore, cependant, je ne suis pas d'accord avec la pollution de votre modèle avec des méthodes de présentation comme dans la réponse de Sam .

Si vous pensez «mais j'en ai vraiment besoin pour écrire mes to_csv& to_pdfméthodes dans mon modèle!», Alors votre prémisse entière est fausse - après tout, vous n'avez pas de to_htmlméthode, n'est-ce pas ? Et pourtant, votre objet est très souvent rendu au format HTML. Envisagez de créer une nouvelle classe pour générer votre sortie au lieu de faire savoir à votre modèle de données ce qu'est un CSV ( car il ne devrait pas ).

En ce qui concerne l'utilisation des aides pour les erreurs de validation ActiveModel dans le modèle, eh bien, je suis désolé mais ActiveModel / Rails nous a tous vissés en forçant les messages d'erreur à se réaliser dans la couche de données, plutôt que de renvoyer l' idée sémantique d'une erreur. réalisé plus tard - soupir . Vous pouvez contourner cela, mais cela signifie essentiellement ne plus utiliser ActiveModel :: Errors. Je l'ai fait, ça marche bien.

En passant, voici un moyen utile d'inclure des helpers dans un présentateur / modèle de vue sans polluer son ensemble de méthodes (car pouvoir faire par exemple MyPresenterOrViewModel.new.link_to(...)n'a aucun sens):

class MyPresenterOrViewModel
  def some_field
    helper.number_to_currency(amount, :precision => 0)
  end

  private

  def helper
    @helper ||= Class.new do
      include ActionView::Helpers::NumberHelper
    end.new
  end
end
Andrew Marshall
la source
5
Je suis généralement cette règle mais je la brise lorsque j'ai besoin d'un assistant de visualisation pour formater un message d'erreur de validation défini dans le modèle.
Florent2
43
C'est un bon conseil, mais c'est une mauvaise réponse car cela ne résout pas la question.
Jaryl
21
Il y a des cas où ce n'est pas une bonne réponse, par exemple en ce moment où je construis un rapport csv et que je dois utiliser quelque chose comme ça dans une méthode to_csv dans une classe qui ne verra jamais une vue. Il n'est pas toujours utile de faire germer des idéaux de programmation.
nitecoder
1
Oui, ce que dit Nitecoder. Je rencontre le même problème. Je génère des rapports PDF et je veux simplement mettre en forme un numéro de téléphone.
James Adam
3
@maurice C'est une pente glissante de «bien juste cette chose» à un modèle gonflé. Les aides d'applications dans Rails sont un tiroir à ordures, les présentateurs / modèles de vue sont plus faciles à gérer. Je ne vois pas créer les données d'un rapport et générer le (html | pdf | csv |. Etc) vue de ces données en une seule responsabilité plus que moi pour, par exemple, une personne et une page show personne HTML.
Andrew Marshall
185

Je suis d'accord avec vous tous sur le fait que cela pourrait briser le modèle MVC, mais il y a toujours des raisons de casser un modèle, dans mon cas, j'avais besoin de ces méthodes de formateur de devises pour les utiliser dans un filtre de modèle ( Liquid dans mon cas).

À la fin, j'ai découvert que je pouvais accéder à ces méthodes de formateur de devises en utilisant des choses comme ceci:

ActionController::Base.helpers.number_to_currency
fguillen
la source
6
C'est bien, même s'il existe une manière légèrement plus propre de le faire. Voir http://railscasts.com/episodes/132-helpers-outside-views
user664833
4
Yay comment track dans RailsCasts: Dans Rails 3 en 2013, l'utilisation d'un assistant View dans un Controller se fait comme view_context.number_to_currency (amount)
olleolleolle
3
Avez-vous pensé à utiliser la gemme «argent»? As money object fournit une méthode format () et vous pouvez l'appeler dans le modèle, le contrôleur ou la vue.
Zack Xu
71

Je sais que ce fil est très ancien, mais quelqu'un peut chercher une solution à ce problème dans Rails 4+. Les développeurs ont ajouté ActiveSupport :: NumberHelper, qui peut être utilisé sans accéder aux modules / classes liés à la vue en utilisant:

ActiveSupport::NumberHelper.number_to_currency(amount, precision: 0)
Michał Zalewski
la source
Cette approche a fonctionné pour moi lorsque j'ai voulu expérimenter le comportement de number_to_percentagedans la console Rails. Merci!
Jon Schneider
27

Vous devez également inclure le ActionView :: Helpers :: NumberHelper

class Job < ActiveRecord::Base
  include ActionView::Helpers::NumberHelper
  include JobsHelper
  def details
    return "Only " + to_dollar(part_amount_received) + 
           " out of " + to_dollar(price) + " received."
  end
end
Sam
la source
2
Merci, ça a l'air bien, mais je suis d'accord avec d'autres qui disent que je viole le MVC. Je vais mettre detailsl'aide.
Misha Moroshko
1
Utile si vous êtes comme Florent2 et devez le mettre dans le cadre d'un message de validation. Merci Sam.
RyanJM
Cela a fonctionné pour moi. Je ne pense pas qu'il soit logique de toujours suivre MVC (ou tout autre principe) si une solution qui viole ce principe est clairement meilleure qu'une solution qui y adhère.
Jason Swett
2
Cette approche n'est pas recommandée. Il ajoute beaucoup de méthodes dont vous n'avez pas besoin et encombre votre espace de noms, il peut écraser certaines méthodes, et certains modules d'assistance dépendent d'autres modules d'assistance (vous devrez donc peut-être inclure plusieurs modules), ce qui rend le problème encore pire. Pour une explication et une meilleure approche, voir: http://railscasts.com/episodes/132-helpers-outside-views
user664833
6

En utilisant @fguillenla réponse de 's, je voulais remplacer la number_to_currencyméthode dans mon ApplicationHelpermodule de sorte que si la valeur était 0ou blankqu'elle produise un tiret à la place.

Voici mon code au cas où vous trouveriez quelque chose comme ça utile:

module ApplicationHelper
  def number_to_currency(value)
    if value == 0 or value.blank?
      raw "&ndash;"
    else
      ActionController::Base.helpers.number_to_currency(value)
    end
  end
end
Aarona
la source
4

Vous pouvez utiliser view_context.number_to_currencydirectement à partir de votre contrôleur ou modèle.

Felipe M Andrada
la source
3

La méthode de @ fguillen est bonne, bien que voici une approche légèrement plus claire, d'autant plus que la question fait deux références to_dollar. Je vais d'abord démontrer en utilisant le code de Ryan Bates ( http://railscasts.com/episodes/132-helpers-outside-views ).

def description
  "This category has #{helpers.pluralize(products.count, 'product')}."
end

def helpers
  ActionController::Base.helpers
end

Remarquez l'appel helpers.pluralize. Cela est possible grâce à la méthode definition ( def helpers), qui renvoie simplement ActionController::Base.helpers. Par conséquent, helpers.pluralizec'est l'abréviation de ActionController::Base.helpers.pluralize. Maintenant vous pouvez utiliserhelpers.pluralize plusieurs fois, sans répéter les longs chemins de module.

Je suppose donc que la réponse à cette question particulière pourrait être:

class Job < ActiveRecord::Base
  include JobsHelper
  def details
    return "Only " + helpers.to_dollar(part_amount_received) + 
           " out of " + helpers.to_dollar(price) + " received."
  end

  def helpers
    ActionView::Helpers::NumberHelper
  end
end
user664833
la source
2

Ce n'est pas une bonne pratique mais ça marche pour moi!

pour importer, incluez ActionView :: Helpers :: NumberHelper dans le contrôleur. Par exemple:

class ProveedorController < ApplicationController
    include ActionView::Helpers::NumberHelper
    # layout 'example'

    # GET /proveedores/filtro
    # GET /proveedores/filtro.json
    def filtro
        @proveedores = Proveedor.all

        respond_to do |format|
            format.html # filtro.html.erb
            format.json { render json: @proveedores }
        end
    end

    def valuacion_cartera
        @total_valuacion = 0
        facturas.each { |fac|
            @total_valuacion = @total_valuacion + fac.SumaDeImporte
        }

        @total = number_to_currency(@total_valuacion, :unit => "$ ")

        p '*'*80
        p @total_valuacion
    end
end

J'espère que cela vous aide!

alexventuraio
la source
2

Vraiment surpris, personne n'a parlé de l'utilisation d'un décorateur. Leur objectif est de résoudre le problème auquel vous êtes confronté, et plus encore.

https://github.com/drapergem/draper

EDIT: On dirait que la réponse acceptée a essentiellement suggéré de faire quelque chose comme ça. Mais oui, vous voulez utiliser des décorateurs. Voici une excellente série de tutoriels pour vous aider à mieux comprendre:

https://gorails.com/episodes/decorators-from-scratch?autoplay=1

PS - @ excid3 J'accepte les mois d'abonnement gratuits LOL

Greg Blass
la source
-5

Les méthodes d'assistance sont généralement utilisées pour les fichiers View. Il n'est pas recommandé d'utiliser ces méthodes dans la classe Model. Mais si vous voulez utiliser, la réponse de Sam est correcte. OU je suggère que vous puissiez écrire votre propre méthode personnalisée.

Ashish
la source
2
Ceci n'est pas une réponse.
Bonifacio2