Как использовать вспомогательный метод number_to_currency в модели, а не в представлении?

94

Я хотел бы использовать to_dollarв своей модели такой метод:

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

К сожалению, здесь number_to_currencyметод не распознается:

неопределенный метод `number_to_currency 'для # <Job: 0x311eb00>

Есть идеи, как заставить его работать?

Миша Морошко
источник

Ответы:

103

Он недоступен, потому что его использование в модели (обычно) нарушает MVC (и, похоже, в вашем случае). Вы берете данные и манипулируете ими для презентации. Это, по определению, принадлежит взгляду, а не модели.

Вот несколько решений:

  • Используйте объект модели презентатора или представления в качестве посредника между моделью и представлением. Это почти наверняка требует больше первоначальной работы, чем другие решения, но почти всегда лучше. Использование помощников в модели презентатора / представления не нарушает MVC, так как они находятся на уровне представления, заменяя традиционные пользовательские помощники Rails и представления с логикой.

  • Явно include ActionView::Helpers::NumberHelperin JobsHelperвместо того, чтобы зависеть от Rails, который волшебным образом загрузил его за вас. Это все еще не очень хорошо, так как вам не нужно обращаться к помощнику из модели.

  • Нарушают MVC и SRP . См . Ответ fguillen, чтобы узнать, как это сделать. Я не буду повторять это здесь, потому что я не согласен с этим. Тем не менее, я не согласен с загрязнением вашей модели методами представления, как в ответе Сэма .

Если вы думаете: «Но мне это действительно нужно, чтобы написать свои to_csv& to_pdfметоды в моей модели!», То вся ваша предпосылка неверна - в конце концов, у вас нет to_htmlметода, не так ли? И все же ваш объект очень часто отображается как HTML. Рассмотрите возможность создания нового класса для генерации вашего вывода вместо того, чтобы ваша модель данных знала, что такое CSV ( потому что он не должен ).

Что касается использования помощников для ошибок валидации ActiveModel в модели, что ж, извините, но ActiveModel / Rails нас всех обманули, заставив сообщения об ошибках реализовываться на уровне данных, вместо того, чтобы возвращать семантическую идею ошибки, которая должна быть понял позже - вздох . Вы можете обойти это, но в основном это означает, что ActiveModel :: Errors больше не используется. Я сделал это, работает хорошо.

В стороне, вот полезный способ включить помощников в модель презентатора / представления, не загрязняя ее набор методов (потому что способность делать, например, MyPresenterOrViewModel.new.link_to(...)не имеет смысла):

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
Эндрю Маршалл
источник
5
Обычно я следую этому правилу, но нарушаю его, когда мне нужен помощник представления для форматирования сообщения об ошибке проверки, определенного в модели.
Florent2 03
44
Это хороший совет, но это плохой ответ, потому что он не решает вопрос.
Jaryl
21
Бывают случаи, когда это не лучший ответ, например, прямо сейчас, когда я создаю отчет csv и мне нужно использовать что-то подобное в методе to_csv в классе, который никогда не увидит представление. Простое развитие идеалов программирования не всегда помогает.
nitecoder
1
Да, что сказал nitecoder. Я столкнулся с той же проблемой. Я создаю отчеты в формате PDF и просто хочу красиво отформатировать номер телефона.
Джеймс Адам
3
@maurice Это скользкий путь от «ну вот только вот это» к раздутой модели. Помощники приложений в Rails - это ящик для мусора, с презентаторами / моделями представления легче управлять. Я не рассматриваю создание данных для отчета и создание представления (html | pdf | csv | etc.) Этих данных как единую ответственность не больше, чем я делаю, например, для человека и страницы показа человека в HTML.
Эндрю Маршалл,
184

Я согласен со всеми вами, что это может нарушить шаблон MVC, но всегда есть причины для нарушения шаблона, в моем случае мне понадобились эти методы форматирования валюты, чтобы использовать их в фильтре шаблона ( в моем случае Liquid ).

В конце концов я обнаружил, что могу получить доступ к этим методам форматирования валют, используя такие вещи:

ActionController::Base.helpers.number_to_currency
Fguillen
источник
6
Это хорошо, хотя есть более простой способ сделать это. См http://railscasts.com/episodes/132-helpers-outside-views
user664833
4
Ура, комментарий в RailsCasts: в Rails 3 в 2013 году использование помощника View в контроллере выполняется так же, как view_context.number_to_currency (amount)
olleolleolle
3
Вы думали об использовании драгоценного камня «деньги»? Поскольку объект money предоставляет метод format (), вы можете вызывать его в модели, контроллере или представлении.
Zack Xu
74

Я знаю, что эта ветка очень старая, но кто-то может поискать решение этой проблемы в Rails 4+. Разработчики добавили ActiveSupport :: NumberHelper, который можно использовать без доступа к модулям / классам, связанным с просмотром, используя:

ActiveSupport::NumberHelper.number_to_currency(amount, precision: 0)
Михал Залевски
источник
Этот подход работал у меня, когда я хотел поэкспериментировать с поведением number_to_percentageв консоли Rails. Благодарность!
Джон Шнайдер
28

Вам также необходимо включить 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
Сэм
источник
2
Спасибо, выглядит хорошо, но я должен согласиться с другими, которые говорят, что я нарушаю MVC. Я поставлю detailsпомощника.
Миша Морошко 03
1
Полезно, если вы похожи на Florent2 и вам нужно поместить его как часть сообщения проверки. Спасибо, Сэм.
RyanJM
Это сработало для меня. Я не думаю, что имеет смысл всегда следовать MVC (или какому-либо принципу), если решение, которое нарушает этот принцип, явно лучше, чем то, которое его придерживается.
Jason Swett
2
Такой подход не рекомендуется. Он добавляет множество методов, которые вам не нужны, и загромождает ваше пространство имен, он может перезаписывать некоторые методы, а некоторые вспомогательные модули полагаются на другие вспомогательные модули (поэтому вам может потребоваться включить несколько модулей), тем самым создавая проблему еще хуже. Для объяснения и лучшего подхода см .: http://railscasts.com/episodes/132-helpers-outside-views
user664833
6

Воспользовавшись @fguillenответом, я хотел переопределить number_to_currencyметод в моем ApplicationHelperмодуле, чтобы, если бы значение было 0или blankоно выводило вместо этого тире.

Вот мой код на случай, если вы, ребята, найдете что-то вроде этого полезным:

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
Аарона
источник
4

Вы можете использовать его view_context.number_to_currencyпрямо с вашего контроллера или модели.

Фелипе М. Андрада
источник
3

Способ @fguillen хорош, хотя здесь есть немного более чистый подход, особенно учитывая, что в вопросе есть две ссылки на to_dollar. Сначала я продемонстрирую использование кода Райана Бейтса ( 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

Обратите внимание на звонок helpers.pluralize. Это возможно благодаря определению метода ( def helpers), который просто возвращает ActionController::Base.helpers. Следовательно, helpers.pluralizeэто сокращение от ActionController::Base.helpers.pluralize. Теперь вы можете использовать helpers.pluralizeнесколько раз, не повторяя длинные пути модуля.

Итак, я полагаю, что ответ на этот конкретный вопрос может быть следующим:

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
источник
2

Это не очень хорошая практика, но у меня она работает!

для импорта включите ActionView :: Helpers :: NumberHelper в контроллер. Например:

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

Надеюсь, это вам поможет!

Alexventuraio
источник
2

Действительно удивлен, что ни один человек не рассказал об использовании декоратора. Их цель - решить проблему, с которой вы столкнулись, и многое другое.

https://github.com/drapergem/draper

РЕДАКТИРОВАТЬ: Похоже, что принятый ответ в основном предлагал сделать что-то вроде этого. Но да, вы хотите использовать декораторы. Вот отличная серия руководств, которая поможет вам понять больше:

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

PS - @ excid3 Я принимаю месяцы бесплатного членства LOL

Грег Бласс
источник
-5

Вспомогательные методы обычно используются для файлов просмотра. Не рекомендуется использовать эти методы в классе Model. Но если вы хотите использовать, то ответ Сэма в порядке. ИЛИ Я предлагаю вам написать свой собственный метод.

Ашиш
источник
2
Это не ответ.
Bonifacio2