Como usar o método auxiliar “number_to_currency” no modelo em vez de na visualização?

93

Eu gostaria de usar o to_dollarmétodo em meu modelo assim:

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

Infelizmente, o number_to_currencymétodo não é reconhecido aqui:

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

Alguma ideia de como fazer funcionar?

Misha Moroshko
fonte

Respostas:

103

Não está disponível porque seu uso em um modelo (normalmente) viola o MVC (e parece que sim no seu caso). Você está pegando dados e manipulando-os para apresentação. Isso, por definição, pertence à visão, não ao modelo.

Aqui estão algumas soluções:

  • Use um apresentador ou objeto de modelo de exibição para mediar entre o modelo e a exibição. Isso quase definitivamente requer mais trabalho inicial do que outras soluções, mas quase sempre é um design melhor. O uso de helpers em um apresentador / modelo de visualização não viola o MVC, pois eles residem na camada de visualização, substituindo auxiliares Rails personalizados tradicionais e visualizações preenchidas com lógica.

  • Explicitamente include ActionView::Helpers::NumberHelperem JobsHelpervez de depender do Rails para carregá-lo magicamente para você. Isso ainda não é ótimo, pois você não deve acessar um auxiliar de um modelo.

  • Violar MVC e SRP . Veja a resposta de fguillen para saber como fazer isso. Não vou repetir aqui porque não concordo com isso. Ainda mais, eu discordo em poluir seu modelo com métodos de apresentação como na resposta de Sam .

Se você pensa “mas eu realmente preciso disso para escrever meus to_csv& to_pdfmétodos em meu modelo!”, Então toda a sua premissa está errada - afinal, você não tem um to_htmlmétodo, não é? E ainda assim, seu objeto é frequentemente renderizado como HTML. Considere a criação de uma nova classe para gerar sua saída em vez de fazer seu modelo de dados saber o que é um CSV ( porque não deveria ).

Quanto ao uso de auxiliares para erros de validação de ActiveModel no modelo, bem, sinto muito, mas ActiveModel / Rails nos ferrou, forçando mensagens de erro a serem realizadas na camada de dados, em vez de retornar a ideia semântica de um erro a ser percebi mais tarde - suspiro . Você pode contornar isso, mas basicamente significa não usar mais ActiveModel :: Errors. Eu fiz isso, funciona bem.

Como um aparte, aqui está uma maneira útil de incluir auxiliares em um modelo de apresentador / visualização sem poluir seu conjunto de métodos (porque ser capaz de fazer, por exemplo, MyPresenterOrViewModel.new.link_to(...)não faz 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
fonte
5
Normalmente sigo esta regra, mas a quebro quando preciso de um view helper para formatar uma mensagem de erro de validação definida no modelo.
Florent2,
43
Este é um bom conselho, mas é uma resposta ruim porque não resolve a questão.
Jaryl
21
Há casos em que essa não é uma boa resposta, por exemplo, agora onde estou construindo um relatório csv e preciso usar algo assim em um método to_csv em uma classe que nunca verá uma visualização. Apenas desenvolver ideais de programação nem sempre é útil.
nitecoder
1
Sim, o que disse o nitecoder. Estou tendo o mesmo problema. Estou gerando relatórios em PDF e simplesmente quero formatar um número de telefone de maneira adequada.
James Adam
3
@maurice É uma ladeira escorregadia de “bem, apenas uma coisa” para um modelo inchado. Os ajudantes de aplicativos no Rails são uma gaveta de lixo, apresentadores / modelos de visualização são mais fáceis de gerenciar. Não vejo a criação de dados para um relatório e a geração da visualização (html | pdf | csv | etc.) Desses dados como uma responsabilidade única, assim como não vejo, por exemplo, uma pessoa e uma página de exibição pessoal em HTML.
Andrew Marshall
185

Eu concordo com todos vocês que isso pode estar quebrando o padrão MVC, mas sempre há motivos para quebrar um padrão, no meu caso eu precisava desses métodos de formatador de moeda para usá-los em um filtro de modelo ( Líquido no meu caso).

No final, descobri que poderia acessar esses métodos do formatador de moeda usando coisas como estas:

ActionController::Base.helpers.number_to_currency
Fguillen
fonte
6
Isso é bom, embora haja uma maneira um pouco mais limpa de fazer isso. Consulte http://railscasts.com/episodes/132-helpers-outside-views
user664833
4
Boa trilha de comentário em RailsCasts: No Rails 3 em 2013, o uso de um auxiliar de visualização em um controlador é feito como view_context.number_to_currency (amount)
olleolleolle
3
Você já pensou em usar a joia do "dinheiro"? Como o objeto money fornece um método format (), e você pode invocá-lo no modelo, controlador ou visualização.
Zack Xu
71

Eu sei que este tópico é muito antigo, mas alguém pode procurar solução para este problema no Rails 4+. Os desenvolvedores adicionaram ActiveSupport :: NumberHelper, que pode ser usado sem acessar módulos / classes relacionados à visualização usando:

ActiveSupport::NumberHelper.number_to_currency(amount, precision: 0)
Michał Zalewski
fonte
Essa abordagem funcionou para mim quando eu quis experimentar o comportamento do number_to_percentageconsole Rails. Obrigado!
Jon Schneider
27

Você também precisa incluir o 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
fonte
2
Obrigado, parece bom, mas tenho que concordar com outros que dizem que eu violo o MVC. Vou colocar detailso ajudante.
Misha Moroshko,
1
Útil se você é como o Florent2 e precisa colocá-lo como parte de uma mensagem de validação. Obrigado Sam.
RyanJM
Isso funcionou para mim. Não acho que faça sentido seguir sempre o MVC (ou qualquer princípio) se uma solução que viola esse princípio é claramente melhor do que uma que o segue.
Jason Swett
2
Essa abordagem não é recomendada. Ele adiciona muitos métodos dos quais você não precisa e confunde seu namespace, pode sobrescrever alguns métodos e alguns módulos auxiliares dependem de outros módulos auxiliares (portanto, você pode precisar incluir vários módulos), tornando o problema pior ainda. Para obter uma explicação e uma abordagem melhor, consulte: http://railscasts.com/episodes/132-helpers-outside-views
user664833
6

Pegando carona na @fguillenresposta de, eu queria substituir o number_to_currencymétodo em meu ApplicationHelpermódulo para que se o valor fosse 0ou blankque ele gerasse um traço.

Aqui está meu código, caso vocês achem algo assim útil:

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

Você pode usar view_context.number_to_currencydiretamente de seu controlador ou modelo.

Felipe m andrada
fonte
3

O método de @fguillen é bom, embora aqui esteja uma abordagem um pouco mais limpa, especialmente considerando que a pergunta faz duas referências a to_dollar. Vou demonstrar primeiro usando o código 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

Observe a chamada helpers.pluralize. Isso é possível devido à definição do método ( def helpers), que simplesmente retorna ActionController::Base.helpers. Portanto, helpers.pluralizeé abreviação de ActionController::Base.helpers.pluralize. Agora você pode usarhelpers.pluralize várias vezes, sem repetir os longos caminhos do módulo.

Então, suponho que a resposta a esta pergunta específica possa 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
user664833
fonte
2

Não é uma boa prática, mas funciona para mim!

para importar, inclua ActionView :: Helpers :: NumberHelper no controlador. Por exemplo:

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 ajude você!

alexventuraio
fonte
2

Realmente surpreso que nenhuma pessoa tenha falado sobre o uso de um decorador. O objetivo deles é resolver o problema que você está enfrentando e muito mais.

https://github.com/drapergem/draper

EDIT: Parece que a resposta aceita basicamente sugeriu fazer algo assim. Mas sim, você quer usar decoradores. Esta é uma ótima série de tutoriais para ajudá-lo a entender mais:

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

PS - @ excid3 eu aceito meses de adesão grátis LOL

Greg Blass
fonte
-5

Os métodos auxiliares geralmente são usados ​​para arquivos de exibição. Não é uma boa prática usar esses métodos na classe Model. Mas se você quiser usar, a resposta de Sam está ok. OU eu sugiro que você pode escrever seu próprio método personalizado.

Ashish
fonte
2
Esta não é uma resposta.
Bonifacio2