Jak użyć metody pomocniczej „liczba_do_waluty” w modelu zamiast wyświetlać?

94

Chciałbym użyć to_dollarmetody w moim modelu w następujący sposób:

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

Niestety number_to_currency metoda nie jest tutaj rozpoznawana:

niezdefiniowana metoda `number_to_currency 'dla # <Job: 0x311eb00>

Jakieś pomysły, jak to działa?

Misha Moroshko
źródło

Odpowiedzi:

103

Nie jest dostępny, ponieważ jego użycie w modelu (zazwyczaj) narusza MVC (i wydaje się, że tak jest w Twoim przypadku). Pobierasz dane i manipulujesz nimi w celu prezentacji. To z definicji należy do widoku, a nie do modelu.

Oto kilka rozwiązań:

  • Użyj prezentera lub obiektu widoku modelu, aby pośredniczyć między modelem a widokiem. To prawie na pewno wymaga więcej wstępnej pracy niż inne rozwiązania, ale prawie zawsze jest lepszym projektem. Używanie pomocników w modelu prezentera / widoku nie narusza MVC, ponieważ znajdują się one w warstwie widoku, zastępując tradycyjne niestandardowe pomocniki Rails i widoki wypełnione logiką.

  • Jawnie include ActionView::Helpers::NumberHelperw JobsHelperzamiast polegać na Railsach, które magicznie go załadowały. To nadal nie jest świetne, ponieważ nie powinieneś mieć dostępu do pomocnika z modelu.

  • Naruszaj MVC i SRP . Zobacz odpowiedź fguillen, jak to zrobić. Nie powtórzę tego tutaj, ponieważ się z tym nie zgadzam. Tym bardziej jednak nie zgadzam się z zanieczyszczeniem twojego modelu metodami prezentacji, jak w odpowiedzi Sama .

Jeśli myślisz „ale naprawdę potrzebuję tego, aby zapisać moje to_csv& to_pdfmetody w moim modelu!”, To całe twoje założenie jest błędne - w końcu nie masz to_htmlmetody, prawda? A jednak twój obiekt jest bardzo często renderowany jako HTML. Rozważ utworzenie nowej klasy do generowania wyników, zamiast informowania modelu danych, czym jest plik CSV ( ponieważ nie powinien ).

Jeśli chodzi o używanie pomocników do błędów walidacji ActiveModel w modelu, cóż, przykro mi, ale ActiveModel / Rails nas w to wkręciły, zmuszając komunikaty o błędach do realizacji w warstwie danych, zamiast zwracać semantyczną ideę błędu do uświadomiłem sobie później - westchnij . Możesz to obejść, ale w zasadzie oznacza to, że nie używasz już ActiveModel :: Errors. Zrobiłem to, działa dobrze.

Na marginesie, oto przydatny sposób na włączenie pomocników do modelu prezentera / widoku bez zanieczyszczania jego zestawu metod (ponieważ możliwość robienia np. Nie MyPresenterOrViewModel.new.link_to(...)ma sensu):

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
źródło
5
Zwykle postępuję zgodnie z tą zasadą, ale łamię ją, gdy potrzebuję pomocnika widoku do sformatowania komunikatu o błędzie walidacji zdefiniowanego w modelu.
Florent2
44
To dobra rada, ale zła odpowiedź, ponieważ nie rozwiązuje problemu.
Jaryl
21
Są przypadki, w których nie jest to dobra odpowiedź, na przykład teraz, gdy buduję raport csv i muszę użyć czegoś takiego w metodzie to_csv w klasie, która nigdy nie zobaczy widoku. Samo kiełkowanie ideałów programowania nie zawsze jest pomocne.
nitecoder
1
Tak, co powiedział nitecoder. Mam ten sam problem. Generuję raporty PDF i po prostu chcę ładnie sformatować numer telefonu.
James Adam
3
@maurice To śliskie zbocze od „tylko tego jednego” do rozdętego modelu. Pomocnicy aplikacji w Railsach to szuflada śmieci, prezentery / modele widoków są łatwiejsze w zarządzaniu. Nie uważam tworzenia danych do raportu i generowania widoku (html | pdf | csv | itp.) Tych danych za jedną odpowiedzialność, tak samo jak w przypadku np. Strony pokazu osoby i osoby HTML.
Andrew Marshall
184

Zgadzam się z wami wszystkimi, że może to zepsuć wzorzec MVC, ale zawsze są powody do złamania wzorca, w moim przypadku potrzebowałem tych metod formatowania walut, aby użyć ich w filtrze szablonu ( Płynne w moim przypadku ).

W końcu dowiedziałem się, że mogę uzyskać dostęp do tych metod formatowania walut, używając takich rzeczy:

ActionController::Base.helpers.number_to_currency
fguillen
źródło
6
To dobrze, chociaż można to zrobić w nieco czystszy sposób. Zobacz http://railscasts.com/episodes/132-helpers-outside-views
user664833
4
Ścieżka komentarzy Yay w RailsCasts: W Rails 3 w 2013 roku, użycie pomocnika widoku w kontrolerze odbywa się jak view_context.number_to_currency (amount)
olleolleolle
3
Czy myślałeś o użyciu klejnotu "pieniędzy"? Ponieważ obiekt money udostępnia metodę format () i można ją wywołać w modelu, kontrolerze lub widoku.
Zack Xu
74

Wiem, że ten wątek jest bardzo stary, ale ktoś może poszukać rozwiązania tego problemu w Railsach 4+. Deweloperzy dodali ActiveSupport :: NumberHelper, z którego można korzystać bez dostępu do powiązanych modułów / klas za pomocą:

ActiveSupport::NumberHelper.number_to_currency(amount, precision: 0)
Michał Zalewski
źródło
To podejście zadziałało, gdy chciałem poeksperymentować z zachowaniem number_to_percentagekonsoli Railsów. Dzięki!
Jon Schneider
28

Musisz również dołączyć 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
źródło
2
Dzięki, wygląda dobrze, ale muszę zgodzić się z innymi, którzy twierdzą, że naruszam MVC. Włożę detailspomocnika.
Misha Moroshko
1
Pomocne, jeśli jesteś podobny do Florent2 i chcesz umieścić to w wiadomości walidacyjnej. Dzięki, Sam.
RyanJM
To zadziałało dla mnie. Uważam, że nie ma sensu zawsze przestrzegać MVC (lub jakiejkolwiek zasady), jeśli rozwiązanie, które narusza tę zasadę, jest wyraźnie lepsze niż takie, które jest do niej zgodne.
Jason Swett
2
Takie podejście nie jest zalecane. Dodaje wiele metod, których nie potrzebujesz, i zaśmieca twoją przestrzeń nazw, może nadpisywać niektóre metody, a niektóre moduły pomocnicze polegają na innych modułach pomocniczych (więc może być konieczne dołączenie wielu modułów), co powoduje problem nawet gorzej. Aby uzyskać wyjaśnienie i lepsze podejście, zobacz: http://railscasts.com/episodes/132-helpers-outside-views
user664833
6

Odsuwając się od @fguillenodpowiedzi, chciałem przesłonić number_to_currencymetodę w moim ApplicationHelpermodule, tak aby jeśli wartość była 0lub blank, zamiast tego wyświetlałaby myślnik.

Oto mój kod na wypadek, gdybyście uznali coś takiego za przydatne:

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
źródło
4

Możesz używać view_context.number_to_currencybezpośrednio ze swojego kontrolera lub modelu.

Felipe M Andrada
źródło
3

Sposób @ fguillen jest dobry, chociaż tutaj jest nieco czystsze podejście, szczególnie biorąc pod uwagę, że pytanie zawiera dwa odniesienia do to_dollar. Najpierw zademonstruję przy użyciu kodu Ryana Batesa ( 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

Zwróć uwagę na wezwanie helpers.pluralize. Jest to możliwe dzięki definicji metody ( def helpers), która po prostu zwraca ActionController::Base.helpers. Dlatego helpers.pluralizejest skrótem od ActionController::Base.helpers.pluralize. Teraz możesz użyćhelpers.pluralize wiele razy, bez powtarzania długich ścieżek modułów.

Więc przypuszczam, że odpowiedź na to konkretne pytanie mogłaby brzmieć:

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
źródło
2

To nie jest dobra praktyka, ale dla mnie działa!

aby zaimportować dołącz ActionView :: Helpers :: NumberHelper do kontrolera. Na przykład:

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

Mam nadzieję, że ci to pomoże!

alexventuraio
źródło
2

Naprawdę zaskoczony, że nikt nie mówił o używaniu dekoratora. Ich celem jest rozwiązanie napotkanego problemu i nie tylko.

https://github.com/drapergem/draper

EDYCJA: Wygląda na to, że zaakceptowana odpowiedź w zasadzie sugerowała zrobienie czegoś takiego. Ale tak, chcesz użyć dekoratorów. Oto świetna seria samouczków, które pomogą Ci lepiej zrozumieć:

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

PS - @ excid3 Akceptuję darmowe miesiące członkostwa LOL

Greg Blass
źródło
-5

Metody pomocnicze są zwykle używane do wyświetlania plików. Używanie tych metod w klasie Model nie jest dobrą praktyką. Ale jeśli chcesz użyć, odpowiedź Sama jest w porządku. LUB proponuję napisać własną niestandardową metodę.

Ashish
źródło
2
To nie jest odpowiedź.
Bonifacio 2