¿Cómo utilizar el método auxiliar "number_to_currency" en el modelo en lugar de ver?

93

Me gustaría usar un to_dollarmétodo en mi modelo de esta manera:

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

Desafortunadamente, el number_to_currencymétodo no se reconoce aquí:

método indefinido `number_to_currency 'para # <Job: 0x311eb00>

¿Alguna idea de cómo hacerlo funcionar?

Misha Moroshko
fuente

Respuestas:

103

No está disponible porque su uso en un modelo (normalmente) viola MVC (y parece que sí en su caso). Está tomando datos y manipulándolos para su presentación. Esto, por definición, pertenece a la vista, no al modelo.

Aquí hay algunas soluciones:

  • Utilice un objeto de modelo de vista o presentador para mediar entre el modelo y la vista. Esto casi definitivamente requiere más trabajo inicial que otras soluciones, pero casi siempre es un mejor diseño. El uso de ayudantes en un presentador / modelo de vista no viola MVC, ya que residen en la capa de vista, reemplazando a los ayudantes tradicionales de Rails personalizados y las vistas llenas de lógica.

  • Explícitamente include ActionView::Helpers::NumberHelperen JobsHelperlugar de depender de Rails para que lo haya cargado mágicamente por ti. Esto todavía no es genial, ya que no debería acceder a un ayudante desde un modelo.

  • Violar MVC y SRP . Vea la respuesta de fguillen para saber cómo hacer esto. No me repetiré aquí porque no estoy de acuerdo con él. Aún más, sin embargo, no estoy de acuerdo con contaminar su modelo con métodos de presentación como en la respuesta de Sam .

Si piensas "¡pero realmente necesito esto para escribir mis métodos to_csv& to_pdfen mi modelo!", Entonces toda tu premisa es incorrecta; después de todo, no tienes un to_htmlmétodo, ¿verdad? Y, sin embargo, su objeto se representa a menudo como HTML. Considere crear una nueva clase para generar su salida en lugar de hacer que su modelo de datos sepa qué es un CSV ( porque no debería ).

En cuanto al uso de ayudantes para errores de validación de ActiveModel en el modelo, bueno, lo siento, pero ActiveModel / Rails nos ha jodido a todos al obligar a que se realicen mensajes de error en la capa de datos, en lugar de devolver la idea semántica de un error. más tarde me di cuenta - suspiro . Puede evitar esto, pero básicamente significa que ya no usa ActiveModel :: Errores. Lo he hecho, funciona bien.

Como acotación al margen, aquí hay una forma útil de incluir ayudantes en un presentador / modelo de vista sin contaminar su conjunto de métodos (porque poder hacerlo, por ejemplo, MyPresenterOrViewModel.new.link_to(...)no tiene sentido):

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
fuente
5
Normalmente sigo esta regla, pero la rompo cuando necesito un asistente de vista para formatear un mensaje de error de validación definido en el modelo.
Florent2
43
Este es un buen consejo, pero es una mala respuesta porque no resuelve la pregunta.
Jaryl
21
Hay casos en los que esta no es una gran respuesta, por ejemplo, ahora mismo cuando estoy construyendo un informe csv y necesito usar algo como esto en un método to_csv en una clase que nunca verá una vista. No siempre es útil desarrollar ideales de programación.
nitecoder
1
Sí, lo que dijo nitecoder. Me encuentro con el mismo problema. Estoy generando informes en PDF y simplemente quiero formatear un número de teléfono.
James Adam
3
@maurice Es una pendiente resbaladiza de "bueno, sólo esta cosa" a un modelo hinchado. Los asistentes de aplicaciones en Rails son un cajón de basura, los presentadores / modelos de vista son más fáciles de administrar. No veo la creación de los datos de un informe y la generación de la (html | PDF | csv |. Etc) vista de esos datos como una sola responsabilidad más de lo que hago para, por ejemplo, una persona y una página HTML persona espectáculo.
Andrew Marshall
185

Estoy de acuerdo con todos ustedes en que esto podría estar rompiendo el patrón MVC pero siempre hay razones para romper un patrón, en mi caso necesitaba estos métodos de formateador de moneda para usarlos en un filtro de plantilla ( Liquid en mi caso).

Al final, descubrí que podía acceder a estos métodos de formateador de moneda usando cosas como esta:

ActionController::Base.helpers.number_to_currency
fguillen
fuente
6
Esto es bueno, aunque hay una forma un poco más limpia de hacerlo. Ver http://railscasts.com/episodios/132-helpers-outside-views
user664833
4
Yay pista de comentarios en RailsCasts: en Rails 3 en 2013, el uso de un asistente de visualización en un controlador se hace como view_context.number_to_currency (cantidad)
olleolleolle
3
¿Ha pensado en utilizar la joya del "dinero"? Como objeto de dinero proporciona un método format () y puede invocarlo en el modelo, controlador o vista.
Zack Xu
71

Sé que este hilo es muy antiguo, pero alguien puede buscar una solución para este problema en Rails 4+. Los desarrolladores agregaron ActiveSupport :: NumberHelper, que se puede usar sin acceder a los módulos / clases relacionados con la vista usando:

ActiveSupport::NumberHelper.number_to_currency(amount, precision: 0)
Michał Zalewski
fuente
Este enfoque funcionó para mí cuando quería experimentar con el comportamiento de number_to_percentageen la consola de Rails. ¡Gracias!
Jon Schneider
27

También debe incluir 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
fuente
2
Gracias, se ve bien, pero tengo que estar de acuerdo con otros que dicen que violé la MVC. Pondré detailsal ayudante.
Misha Moroshko
1
Útil si eres como Florent2 y necesitas incluirlo como parte de un mensaje de validación. Gracias Sam.
RyanJM
Esto funcionó para mí. No creo que tenga sentido seguir siempre MVC (o cualquier principio) si una solución que viola ese principio es claramente mejor que una que se adhiere a él.
Jason Swett
2
No se recomienda este enfoque. Agrega muchos métodos que no necesita y desordena su espacio de nombres, puede sobrescribir algunos métodos y algunos módulos auxiliares se basan en otros módulos auxiliares (por lo que es posible que deba incluir varios módulos), lo que hace que el problema peor aún. Para obtener una explicación y un mejor enfoque, consulte: http://railscasts.com/episodios/132-helpers-outside-views
user664833
6

Aprovechando @fguillenla respuesta de ', quería anular el number_to_currencymétodo en mi ApplicationHelpermódulo para que si el valor fuera 0o, en blanksu lugar, mostrara un guión.

Aquí está mi código en caso de que ustedes encuentren útil algo como esto:

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
fuente
4

Puede usarlo view_context.number_to_currencydirectamente desde su controlador o modelo.

Felipe M Andrada
fuente
3

El método de @fguillen es bueno, aunque aquí hay un enfoque un poco más limpio, en particular dado que la pregunta hace dos referencias a to_dollar. Primero haré una demostración usando el código de Ryan Bates ( http://railscasts.com/episodios/132-helpers-outside-views ).

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

def helpers
  ActionController::Base.helpers
end

Note la llamada helpers.pluralize. Esto es posible debido a la definición del método ( def helpers), que simplemente devuelve ActionController::Base.helpers. Por eso helpers.pluralizees la abreviatura de ActionController::Base.helpers.pluralize. Ahora puede usar helpers.pluralizevarias veces, sin repetir las rutas de módulo largas.

Entonces supongo que la respuesta a esta pregunta en particular podría ser:

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
usuario664833
fuente
2

No es una buena práctica, ¡pero me funciona!

para importar incluya ActionView :: Helpers :: NumberHelper en el controlador. Por ejemplo:

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

¡Espero que te ayude!

alexventuraio
fuente
2

Realmente sorprendido de que nadie haya hablado sobre el uso de un Decorador. Su propósito es resolver el problema al que se enfrenta y más.

https://github.com/drapergem/draper

EDITAR: Parece que la respuesta aceptada básicamente sugirió hacer algo como esto. Pero sí, quieres usar decoradores. Aquí hay una gran serie de tutoriales para ayudarlo a comprender más:

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

PD - @ excid3 Acepto meses de membresía gratis LOL

Greg Blass
fuente
-5

Los métodos auxiliares se utilizan generalmente para archivos de visualización. No es una buena práctica utilizar estos métodos en la clase Model. Pero si quieres usar, la respuesta de Sam está bien. O le sugiero que pueda escribir su propio método personalizado.

Ashish
fuente
2
Ésta no es una respuesta.
Bonifacio2