From cf9e88dc81da8c0963fa04577b66b377a231f617 Mon Sep 17 00:00:00 2001 From: Lukas Pol Date: Wed, 25 Jan 2023 09:46:52 -0300 Subject: [PATCH 1/9] feat(Trading): create model --- app/models/asset.rb | 2 + app/models/stock.rb | 1 + app/models/trading.rb | 15 ++++ app/models/user.rb | 1 + config/locales/activerecord/pt-BR.yml | 14 ++++ config/locales/pt-BR.yml | 87 +++++++++++--------- db/migrate/20230125120827_create_tradings.rb | 16 ++++ db/schema.rb | 21 ++++- 8 files changed, 118 insertions(+), 39 deletions(-) create mode 100644 app/models/trading.rb create mode 100644 db/migrate/20230125120827_create_tradings.rb diff --git a/app/models/asset.rb b/app/models/asset.rb index cecff00..0414515 100644 --- a/app/models/asset.rb +++ b/app/models/asset.rb @@ -2,6 +2,8 @@ class Asset < ApplicationRecord belongs_to :user belongs_to :stock + has_many :tradings, dependent: :restrict_with_error + validates :stock, :user, :amount, :average_price, :total_invested, presence: true validates :stock_id, uniqueness: { scope: [:user_id] } diff --git a/app/models/stock.rb b/app/models/stock.rb index c51aa08..6867092 100644 --- a/app/models/stock.rb +++ b/app/models/stock.rb @@ -1,5 +1,6 @@ class Stock < ApplicationRecord has_many :assets, dependent: :restrict_with_error + has_many :tradings, dependent: :restrict_with_error validates :code, presence: true validates :code, length: { in: 5..6 } diff --git a/app/models/trading.rb b/app/models/trading.rb new file mode 100644 index 0000000..634590a --- /dev/null +++ b/app/models/trading.rb @@ -0,0 +1,15 @@ +class Trading < ApplicationRecord + belongs_to :user + belongs_to :stock + belongs_to :asset + + enum kind: { buy: 0, sell: 1 } + + validates :amount, :value_unit, :total_value, :date, :kind, :asset, :stock, :user, presence: true + + validates :amount, :value_unit, :total_value, numericality: { greater_than_or_equal_to: 0 } + + validate do + errors.add(:date, I18n.t(:after_today, scope: 'errors.messages')) if date && date > Time.zone.today + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 05650ea..c72d7b3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -4,6 +4,7 @@ class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable + has_many :tradings, dependent: :delete_all has_many :assets, dependent: :delete_all has_many :stocks, through: :assets diff --git a/config/locales/activerecord/pt-BR.yml b/config/locales/activerecord/pt-BR.yml index 0433838..d2c7de2 100644 --- a/config/locales/activerecord/pt-BR.yml +++ b/config/locales/activerecord/pt-BR.yml @@ -15,6 +15,20 @@ pt-BR: user: 'Usuário' stock: 'Ação' stock_code: 'Código' + trading: + amount: 'Quant.' + date: 'Data' + kind: 'C / V' + total_value: 'Valor total' + value_unit: 'Valor unit.' + asset: 'Ativo' + stock: 'Ação' + stock_code: 'Código' + user: 'Usuário' + kind_enum: + buy: 'Compra' + sell: 'Venda' + errors: messages: record_invalid: 'A validação falhou: %{errors}' diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 04e0f95..8562056 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -73,42 +73,53 @@ pt-BR: format: delimiter: + date: + formats: + default: "%d/%m/%Y" + long: "%d de %B de %Y" + short: "%d de %B" + order: + - :day + - :month + - :year + errors: - format: '%{attribute} %{message}' - messages: - accepted: 'deve ser aceito' - access_denied: 'Acesso Negado' - blank: 'não pode ficar em branco' - confirmation: 'não é igual a %{attribute}' - empty: 'não pode ficar vazio' - equal_to: 'deve ser igual a %{count}' - even: 'deve ser par' - exclusion: 'não está disponível' - greater_than: 'deve ser maior que %{count}' - greater_than_or_equal_to: 'deve ser maior ou igual a %{count}' - inclusion: 'não está incluído na lista' - invalid: 'não é válido' - less_than: 'deve ser menor que %{count}' - less_than_or_equal_to: 'deve ser menor ou igual a %{count}' - model_invalid: 'A validação falhou: %{errors}' - not_a_number: 'não é um número' - not_an_integer: 'não é um número inteiro' - odd: 'deve ser ímpar' - other_than: 'deve ser diferente de %{count}' - present: 'deve ficar em branco' - required: 'é obrigatório(a)' - taken: 'já está em uso' - too_long: - one: 'é muito longo (máximo: 1 caracter)' - other: 'é muito longo (máximo: %{count} caracteres)' - too_short: - one: 'é muito curto (mínimo: 1 caracter)' - other: 'é muito curto (mínimo: %{count} caracteres)' - wrong_length: - one: 'não possui o tamanho esperado (1 caracter)' - other: 'não possui o tamanho esperado (%{count} caracteres)' - template: - body: 'Por favor, verifique o(s) seguinte(s) campo(s):' - header: - one: 'Não foi possível gravar %{model}: 1 erro' - other: 'Não foi possível gravar %{model}: %{count} erros' + format: '%{attribute} %{message}' + messages: + accepted: 'deve ser aceito' + access_denied: 'Acesso Negado' + after_today: 'não pode ser do futuro' + blank: 'não pode ficar em branco' + confirmation: 'não é igual a %{attribute}' + empty: 'não pode ficar vazio' + equal_to: 'deve ser igual a %{count}' + even: 'deve ser par' + exclusion: 'não está disponível' + greater_than: 'deve ser maior que %{count}' + greater_than_or_equal_to: 'deve ser maior ou igual a %{count}' + inclusion: 'não está incluído na lista' + invalid: 'não é válido' + less_than: 'deve ser menor que %{count}' + less_than_or_equal_to: 'deve ser menor ou igual a %{count}' + model_invalid: 'A validação falhou: %{errors}' + not_a_number: 'não é um número' + not_an_integer: 'não é um número inteiro' + odd: 'deve ser ímpar' + other_than: 'deve ser diferente de %{count}' + present: 'deve ficar em branco' + required: 'é obrigatório(a)' + taken: 'já está em uso' + too_long: + one: 'é muito longo (máximo: 1 caracter)' + other: 'é muito longo (máximo: %{count} caracteres)' + too_short: + one: 'é muito curto (mínimo: 1 caracter)' + other: 'é muito curto (mínimo: %{count} caracteres)' + wrong_length: + one: 'não possui o tamanho esperado (1 caracter)' + other: 'não possui o tamanho esperado (%{count} caracteres)' + template: + body: 'Por favor, verifique o(s) seguinte(s) campo(s):' + header: + one: 'Não foi possível gravar %{model}: 1 erro' + other: 'Não foi possível gravar %{model}: %{count} erros' diff --git a/db/migrate/20230125120827_create_tradings.rb b/db/migrate/20230125120827_create_tradings.rb new file mode 100644 index 0000000..9e87de4 --- /dev/null +++ b/db/migrate/20230125120827_create_tradings.rb @@ -0,0 +1,16 @@ +class CreateTradings < ActiveRecord::Migration[7.0] + def change + create_table :tradings, id: :uuid do |t| + t.integer :amount, default: 0 + t.float :value_unit, default: 0 + t.float :total_value, default: 0 + t.integer :kind, default: 0 + t.date :date + t.references :user, null: false, foreign_key: true, type: :uuid + t.references :stock, null: false, foreign_key: true, type: :uuid + t.references :asset, null: false, foreign_key: true, type: :uuid + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index a27481d..586663d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_01_24_172554) do +ActiveRecord::Schema[7.0].define(version: 2023_01_25_120827) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -35,6 +35,22 @@ t.datetime "updated_at", null: false end + create_table "tradings", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.integer "amount", default: 0 + t.float "value_unit", default: 0.0 + t.float "total_value", default: 0.0 + t.integer "kind", default: 0 + t.date "date" + t.uuid "user_id", null: false + t.uuid "stock_id", null: false + t.uuid "asset_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["asset_id"], name: "index_tradings_on_asset_id" + t.index ["stock_id"], name: "index_tradings_on_stock_id" + t.index ["user_id"], name: "index_tradings_on_user_id" + end + create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false @@ -49,4 +65,7 @@ add_foreign_key "assets", "stocks" add_foreign_key "assets", "users" + add_foreign_key "tradings", "assets" + add_foreign_key "tradings", "stocks" + add_foreign_key "tradings", "users" end From 0651821a9b8be9964cb0109ba64812b08e3ca3fe Mon Sep 17 00:00:00 2001 From: Lukas Pol Date: Wed, 25 Jan 2023 09:46:59 -0300 Subject: [PATCH 2/9] test(Trading): create model --- spec/factories/tradings.rb | 12 ++++++++++++ spec/models/trading_spec.rb | 29 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 spec/factories/tradings.rb create mode 100644 spec/models/trading_spec.rb diff --git a/spec/factories/tradings.rb b/spec/factories/tradings.rb new file mode 100644 index 0000000..cabfa80 --- /dev/null +++ b/spec/factories/tradings.rb @@ -0,0 +1,12 @@ +FactoryBot.define do + factory :trading do + amount { 2 } + value_unit { 2.5 } + total_value { 2.5 } + kind { :buy } + date { Date.yesterday } + asset { create(:asset) } + stock { create(:stock) } + user { create(:user) } + end +end diff --git a/spec/models/trading_spec.rb b/spec/models/trading_spec.rb new file mode 100644 index 0000000..6d54247 --- /dev/null +++ b/spec/models/trading_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +RSpec.describe Trading, type: :model do + describe 'validates' do + context 'presence' do + it { should validate_presence_of(:value_unit) } + it { should validate_presence_of(:total_value) } + it { should validate_presence_of(:amount) } + it { should validate_presence_of(:user) } + it { should validate_presence_of(:asset) } + it { should validate_presence_of(:stock) } + end + + context 'validate numericality' do + it { should validate_numericality_of(:value_unit).is_greater_than_or_equal_to(0) } + it { should validate_numericality_of(:amount).is_greater_than_or_equal_to(0) } + it { should validate_numericality_of(:total_value).is_greater_than_or_equal_to(0) } + end + + context 'date after today' do + it 'should return error date after today' do + trading = build(:trading, date: Date.tomorrow) + + expect(trading.valid?).to be_falsey + expect(trading.errors.full_messages).to eq(['Data não pode ser do futuro']) + end + end + end +end From 623347990a34581c55f6d32d1989b1ff4ee07f44 Mon Sep 17 00:00:00 2001 From: Lukas Pol Date: Wed, 25 Jan 2023 10:12:26 -0300 Subject: [PATCH 3/9] feat(TradingHelper): create method trading_locale_kind --- app/helpers/tradings_helper.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 app/helpers/tradings_helper.rb diff --git a/app/helpers/tradings_helper.rb b/app/helpers/tradings_helper.rb new file mode 100644 index 0000000..1ae65a8 --- /dev/null +++ b/app/helpers/tradings_helper.rb @@ -0,0 +1,5 @@ +module TradingsHelper + def trading_locale_kind(kind) + I18n.t(kind, scope: 'activerecord.attributes.trading.kind_enum') + end +end From e7d8ecbce0d861717e6d6fc5ff5042301aed415c Mon Sep 17 00:00:00 2001 From: Lukas Pol Date: Wed, 25 Jan 2023 10:12:32 -0300 Subject: [PATCH 4/9] test(TradingHelper): create method trading_locale_kind --- spec/helpers/tradings_helper_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 spec/helpers/tradings_helper_spec.rb diff --git a/spec/helpers/tradings_helper_spec.rb b/spec/helpers/tradings_helper_spec.rb new file mode 100644 index 0000000..52340fc --- /dev/null +++ b/spec/helpers/tradings_helper_spec.rb @@ -0,0 +1,8 @@ +require 'rails_helper' + +RSpec.describe TradingsHelper, type: :helper do + context '.trading_locale_kind' do + it { expect(helper.trading_locale_kind('buy')).to eq('Compra') } + it { expect(helper.trading_locale_kind('sell')).to eq('Venda') } + end +end From e8d2cc165af205af14b25a9d1acb53ad07922a8e Mon Sep 17 00:00:00 2001 From: Lukas Pol Date: Wed, 25 Jan 2023 10:13:04 -0300 Subject: [PATCH 5/9] feat(Tradings): create controller #index --- app/controllers/tradings_controller.rb | 5 +++++ app/views/tradings/_trading.html.erb | 7 +++++++ app/views/tradings/index.html.erb | 9 +++++++++ config/locales/activerecord/pt-BR.yml | 1 + config/locales/en.yml | 1 + config/locales/pt-BR.yml | 1 + config/routes.rb | 1 + 7 files changed, 25 insertions(+) create mode 100644 app/controllers/tradings_controller.rb create mode 100644 app/views/tradings/_trading.html.erb create mode 100644 app/views/tradings/index.html.erb diff --git a/app/controllers/tradings_controller.rb b/app/controllers/tradings_controller.rb new file mode 100644 index 0000000..4854866 --- /dev/null +++ b/app/controllers/tradings_controller.rb @@ -0,0 +1,5 @@ +class TradingsController < ApplicationController + def index + @tradings = current_user.tradings + end +end diff --git a/app/views/tradings/_trading.html.erb b/app/views/tradings/_trading.html.erb new file mode 100644 index 0000000..6a3500d --- /dev/null +++ b/app/views/tradings/_trading.html.erb @@ -0,0 +1,7 @@ +
+

<%= Trading.human_attribute_name('stock_code') %>: <%= trading.stock.code %>

+

<%= Trading.human_attribute_name('amount') %>: <%= trading.amount %>

+

<%= Trading.human_attribute_name('value_unit') %>: <%= number_to_currency trading.value_unit %>

+

<%= Trading.human_attribute_name('total_value') %>: <%= number_to_currency trading.total_value %>

+

<%= Trading.human_attribute_name('kind') %>: <%= trading_locale_kind trading.kind %>

+
diff --git a/app/views/tradings/index.html.erb b/app/views/tradings/index.html.erb new file mode 100644 index 0000000..938b14f --- /dev/null +++ b/app/views/tradings/index.html.erb @@ -0,0 +1,9 @@ +
+
+

<%= t(:trading, scope: 'activerecord.models').pluralize %>

+
+ +
+ <%= render @tradings %> +
+
diff --git a/config/locales/activerecord/pt-BR.yml b/config/locales/activerecord/pt-BR.yml index d2c7de2..5d9f169 100644 --- a/config/locales/activerecord/pt-BR.yml +++ b/config/locales/activerecord/pt-BR.yml @@ -3,6 +3,7 @@ pt-BR: models: asset: 'Ativo' stock: 'Ação' + trading: 'Movimentações' user: 'Usuário' attributes: stock: diff --git a/config/locales/en.yml b/config/locales/en.yml index d236b15..8edd707 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3,3 +3,4 @@ en: routes: assets: 'my-assets' + tradings: 'tradings' diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 8562056..f2d17d2 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -4,6 +4,7 @@ pt-BR: routes: assets: 'ativos' + tradings: 'movimentacoes' users: 'users' helpers: diff --git a/config/routes.rb b/config/routes.rb index 2f0b7ac..dadbff0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,4 +11,5 @@ devise_for :users resources :assets, only: :index, path: I18n.t('routes.assets') + resources :tradings, only: :index, path: I18n.t('routes.tradings') end From 7fdf25d7f2cb6041c30dd7695da2be00eaeac54a Mon Sep 17 00:00:00 2001 From: Lukas Pol Date: Wed, 25 Jan 2023 10:13:09 -0300 Subject: [PATCH 6/9] test(Tradings): create controller #index --- spec/requests/tradings_spec.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 spec/requests/tradings_spec.rb diff --git a/spec/requests/tradings_spec.rb b/spec/requests/tradings_spec.rb new file mode 100644 index 0000000..f1fa5be --- /dev/null +++ b/spec/requests/tradings_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +RSpec.describe 'Tradings', type: :request do + describe 'GET /index' do + context 'when user is logged' do + before :each do + user = create(:user) + create_list(:trading, 3, user:) + + sign_in(user) + end + + it 'returns http success' do + get tradings_path + expect(response).to have_http_status(:success) + end + end + + context 'when not logged' do + it 'returns http success' do + get tradings_path + expect(response).to have_http_status(:redirect) + end + end + end + +end From baf3424ed4bd1d99df896f0e420846de909c92b7 Mon Sep 17 00:00:00 2001 From: Lukas Pol Date: Wed, 25 Jan 2023 10:13:24 -0300 Subject: [PATCH 7/9] setup(seed): create seeds --- db/seeds.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index bc25fce..77a334e 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,7 +1,7 @@ -# This file should contain all the record creation needed to seed the database with its default values. -# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). -# -# Examples: -# -# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) -# Character.create(name: "Luke", movie: movies.first) +user = User.create(email: 'test@test.com', password: '123456', password_confirmation: '123456') + +stock = Stock.create(code: 'PETR4') + +asset = Asset.create(amount: 10, total_invested: 20, average_price: 2, user:, stock:) + +Trading.create(amount: 10, total_value: 20, value_unit: 2, date: Date.yesterday, user:, stock:, asset:) From fe72daf5b2a9fdfdfb35f4f331b837c2560b3141 Mon Sep 17 00:00:00 2001 From: Lukas Pol Date: Wed, 25 Jan 2023 10:15:03 -0300 Subject: [PATCH 8/9] refactor(Trading): change sell to sale --- app/models/trading.rb | 2 +- config/locales/activerecord/pt-BR.yml | 2 +- spec/helpers/tradings_helper_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/trading.rb b/app/models/trading.rb index 634590a..e2be41c 100644 --- a/app/models/trading.rb +++ b/app/models/trading.rb @@ -3,7 +3,7 @@ class Trading < ApplicationRecord belongs_to :stock belongs_to :asset - enum kind: { buy: 0, sell: 1 } + enum kind: { buy: 0, sale: 1 } validates :amount, :value_unit, :total_value, :date, :kind, :asset, :stock, :user, presence: true diff --git a/config/locales/activerecord/pt-BR.yml b/config/locales/activerecord/pt-BR.yml index 5d9f169..5a9bed7 100644 --- a/config/locales/activerecord/pt-BR.yml +++ b/config/locales/activerecord/pt-BR.yml @@ -28,7 +28,7 @@ pt-BR: user: 'Usuário' kind_enum: buy: 'Compra' - sell: 'Venda' + sale: 'Venda' errors: messages: diff --git a/spec/helpers/tradings_helper_spec.rb b/spec/helpers/tradings_helper_spec.rb index 52340fc..9cb25f8 100644 --- a/spec/helpers/tradings_helper_spec.rb +++ b/spec/helpers/tradings_helper_spec.rb @@ -3,6 +3,6 @@ RSpec.describe TradingsHelper, type: :helper do context '.trading_locale_kind' do it { expect(helper.trading_locale_kind('buy')).to eq('Compra') } - it { expect(helper.trading_locale_kind('sell')).to eq('Venda') } + it { expect(helper.trading_locale_kind('sale')).to eq('Venda') } end end From 18eb02591b4c2a8e2c417d5e428225dd9b432be2 Mon Sep 17 00:00:00 2001 From: Lukas Pol Date: Wed, 25 Jan 2023 10:17:45 -0300 Subject: [PATCH 9/9] refactor: rubocop works --- spec/requests/tradings_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/requests/tradings_spec.rb b/spec/requests/tradings_spec.rb index f1fa5be..94da158 100644 --- a/spec/requests/tradings_spec.rb +++ b/spec/requests/tradings_spec.rb @@ -23,5 +23,4 @@ end end end - end