Я делаю одностраничное приложение (SPA) с Ruby on Rails в качестве бэкенда API и HTML/CSS/JavaScript в качестве внешнего интерфейса. Винный ресурс имеет атрибут цены. Какой тип данных я должен использовать, чтобы 19.00 (с двумя нулями после запятой) передавалось во внешний интерфейс, где я мог бы просто добавить знак $ и уйти?

Плавать

Первоначально я думал использовать тип данных с плавающей запятой для моего атрибута цены, так как он допускает десятичные дроби. Но когда я устанавливаю цену 50,00 в файле seed.rb, он преобразует ее в 50,0 и отбрасывает второй 0.

Проведя некоторое исследование, я обнаружил, что число с плавающей запятой — худший тип данных для денег, потому что он не выполняет вычисления (сложение, вычитание, умножение и т. д.) так же, как мы ожидаем, что числа будут вычисляться.

Десятичный

Подавляющее количество источников рекомендует использовать тип данных decimal, потому что он допускает использование десятичного числа (приблизительно) и более точен в вычислениях.

Точность определяется количеством цифр, разрешенных в числе, а масштаб — количеством цифр, следующих за десятичной точкой.

С точностью до 5 я полагаю, что ни одно из моих вин не будет стоить выше 999,99 долларов.

Звучит превосходно!

Но когда я добавляю его в свой интерфейс JavaScript, 50,00, которые были у меня в файле seed.rb, преобразуются в 50,0.

вздох… У меня были такие большие надежды.

В бэкенде я решаю проверить вывод цены в консоли rails (наберите «rails c» в терминале), введя Wine.last.price.

Он возвращает… 0.25e2

Что тут происходит? Фактическая цена в файле seed.rb 25.00.

Итак, я предполагаю, что десятичный тип данных преобразует 25,00 в 0,25e2. Меня устраивает, пока программа понимает, что происходит.

Но почему он не оставит 2 нуля нетронутыми, когда я перенесу его на интерфейс?

Чтобы спрятать это под ковер, я использовал встроенную в JavaScript функцию «редактирования», чтобы изменить цену с 25,0 на 24,99, и она оставила все числа нетронутыми.

Зачем переживать по этому поводу?

Поскольку я уже создаю много кода на JavaScript для своего интерфейса, я бы предпочел, чтобы цены устанавливались в бэкэнде, чтобы я мог просто вызывать их без необходимости выполнять дополнительную настройку во внешнем интерфейсе.

Самая большая конфигурация, которую я хотел сделать, это добавить «$» впереди.

div4.innerText = `Price: $${wine.price}`

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

До этого еще не дошло, но это не конец, пока не закончится, верно?

Жемчужина денег-рельсов

В поисках денег я нашел этот аккуратный драгоценный камень.



Вернулся к своим миграциям…

class CreateWines < ActiveRecord::Migration[6.0]
def change
create_table :wines do |t|
  t.string :name
  t.string :varietal
  t.string :wine_type
  t.string :country
  t.string :image_url
  t.monetize  :price, precision: 5, scale: 2
  t.timestamps
end
end
end

… который создает это в моем schema.rb …

create_table "wines", force: :cascade do |t|
  t.string "name"
  t.string "varietal"
  t.string "wine_type"
  t.string "country"
  t.string "image_url"
  t.integer "price_cents", default: 0, null: false
  t.string "price_currency", default: "USD", null: false
  t.datetime "created_at", precision: 6, null: false
  t.datetime "updated_at", precision: 6, null: false
end

(Обратите внимание, что теперь есть целочисленный столбец «price_cents» и строковый столбец «price_currency»).

… затем добавьте еще одну вещь в мою модель Wine …

class Wine < ApplicationRecord
  has_many :reviews, :dependent => :destroy
  has_many :users, through: :reviews
  monetize :price_cents, as: "price"
end

Ладно, все это работа и настройка. Момент истины. Я проверил свой API в формате JSON.

"id": 1,
"name": "Buena Vista",
"varietal": "Cabernet Sauvignon",
"wine_type": "red",
"country": "USA",
"image_url": "https://images.vivino.com/thumbs/f7tR4MRISRWWdrXFoGzG_w_pb_x600.png",
"price_cents": 5000,
"price_currency": "USD",
"created_at": "2020-10-23T05:01:00.008Z",
"updated_at": "2020-10-23T05:01:00.008Z"

😭Я еще больше отошел от своей цели. Я думаю, что когда я называл цену вина в интерфейсе, она отображалась как «5000 долларов США».

Примечание: я новичок в программировании, и я уверен, что этот крутой денежный камень мог бы работать для моих целей с некоторыми вспомогательными методами, но мне придется выяснить это в другой раз. У меня меньше недели, чтобы закончить этот проект для моего учебного курса по программированию.

После всего этого я вернулся к десятичному типу, и когда я наткнулся на вино по цене 25,0 долларов в своем приложении, я использовал созданную мной кнопку «Редактировать», чтобы «исправить» ее примерно до 24,99 долларов.😏

Далеко не конец

Наступила и прошла неделя, и мне не пришлось некоторое время думать о деньгах. До сих пор. Теперь я перешел к использованию React с бэкендом Rails. И поймите, мое следующее приложение связано с вином, и этому вину нужна цена.

ИДЕЯ №1 — Конверсии во внешнем интерфейсе

Поместите все в форму центов, чтобы 50,00 были 5000.

Тип данных может быть целым числом.

var num = 5000/100
num = 50
num_with_decimals = num.toFixed(2)
num_with_decimals = “50.00”
var num = 4999/100
num = 49.99
num_with_decimals = num.toFixed(2)
num_with_decimals = “49.99”

Обратите внимание, что это превращает его в строку, но сохраняет 2 нуля после запятой.

После того, как это вычисление будет выполнено во внешнем интерфейсе, я могу отобразить его как есть в части рендеринга компонента React (или возврата функции). Также добавьте знак $. Синтаксис будет различаться в зависимости от того, является ли это возвратом функции… <div>${this.winePrice( )}<div>

…или пропуская через реквизит… <div>${this.props.winePrice}<div>

Это будет отображаться как $ 50,00 для зрителя, и они никогда не узнают, сколько проблем я прошел, просто чтобы заставить это сотрудничать.

Теперь, если я хочу сделать какое-то дополнение, мне нужно преобразовать эту строку в вычисляемое число. О, парень. Несколько способов сделать это. Я хочу быстро и просто, а вы?

winePrice = “50.00”
price_to_number = winePrice * 1
price_to_number = 50
winePrice = “49.99”
price_to_number = winePrice * 1
price_to_number = 49.99

Обратите внимание, что он не преобразуется в целое число, если число уже является числом с плавающей запятой.

Теперь я напишу функцию, которая складывает их вместе, чтобы получить итог.

Если wineTotal = 99,99, то ничего делать не нужно, потому что в нем уже есть числа после запятой.

Если wineTotal = 100, то не будет отображаться десятичная дробь и 2 нуля после нее. Из-за чего ряд чисел на экране выглядит бессистемно и подозрительно.

Итак, еще одно вычисление во внешнем интерфейсе, чтобы добавить .00 к общему количеству вина и просто убедиться, что любое число, на котором оно выполняется, будет иметь 2 цифры после десятичной точки.

(whatever number to convert).toFixed(2)
(50).toFixed(2) ===> “50.00”

Ура, теперь это можно где-то подключить и красиво отображать. Не забудьте знак $.🤑

ИДЕЯ № 2 — Бэкенд-сериализаторы + некоторые фронтенд-вычисления

  • цена по-прежнему задана как десятичный тип данных

Моя последняя запись в блоге была о сериализаторах в Ruby. Сериализаторы позволяют вам решать, какие атрибуты ресурса отправлять во внешний интерфейс И как вы хотите, чтобы они отправлялись!

В Ruby убедитесь, что у вас есть маршруты для ваших ресурсов в config/routes.rb.

Затем выполните действие в контроллере. Я решил определить индекс, чтобы отображать все свои вина.

class Api::V1::WinesController < ApplicationController
def index
  render json: Wine.all
end
end

Теперь создайте этот сериализатор. Я скачал и установил
gem ‘active_model_serializers’

Затем сгенерировал сериализатор из командной строки.
rails g serializer wine

class WineSerializer < ActiveModel::Serializer
  attributes :id
end

Обратите внимание, это отправляет только идентификатор каждого вина.

Я могу определить в этом сериализаторе метод, который будет преобразовывать атрибут цены в желаемый формат.

class WineSerializer < ActiveModel::Serializer
attributes :id, :price
  def price
    format = '%.2f' % object.price
    return format
  end
end

Этот метод воздействует на цену каждого объекта (винный объект в данном случае) и выполняет вычисление «%.2f» % преобразования десятичного числа в строку с двумя цифрами после десятичного числа. 19.00 в десятичном формате выводит 0.19e2. (У меня нет хорошего способа подробно объяснить эту математическую магию).

В любом случае, возвращенный JSON меня радует!😁

[{"id":1,"price":"19.00"},{"id":2,"price":"34.99"},{"id":3,"price":"46.00"},{"id":4,"price":"29.99"},{"id":5,"price":"11.00"},{"id":6,"price":"47.99"},{"id":7,"price":"106.00"},{"id":8,"price":"44.00"}]

Итак, теперь цена отправляется во внешний интерфейс в виде строки («19,00» или «29,99»), и мне просто нужно выполнить некоторые вычисления, чтобы преобразовать ее из строки, если я хочу сложить цены вместе. Затем преобразуйте его обратно в строку для отображения.

Вывод

Это лишь малая часть того, что я узнал о деньгах в программировании. Продолжаются споры о том, что делать в бэкэнде, а не конвертировать числа во внешнем интерфейсе. Десятичное число против целого.

Методы в сериализаторе, которые воздействуют на атрибуты ресурса перед его отправкой во внешний интерфейс. Или создать модуль в ruby ​​для определения методов для вызова этих атрибутов. Драгоценные камни в изобилии.

Оооочень много способов сделать это, и я думаю, что это будет варьироваться от проекта к проекту. И опыт! (Все еще новичок, здесь.)

Я бы хотел, чтобы был четкий прямой ответ или отраслевой стандарт.

Работа с деньгами настолько распространена, что я не уверен, почему это не тип данных. Или почему нет более простого способа.

В любом случае, я нашел то, что сработало для меня. Надеюсь, это станет легче.

Ресурсы