How do I create a virtual balance model in Rails?
This is for a game application I'm creating.
I have a user model, a stock model and a UserStock join table for the previous two models. User was created with devise. I intend to attach a certain balance ($100000) that can be used to buy and sell stocks. What's the most efficient way I can get this done? Any guides that can help me with this? Also, anything that can help me configure the buy and sell actions? (Simple action of incrementing the balance when a stock is sold by a user or decrement of balance when a stock is bought)
I would advise a transactions (or similar) model. You can then create a record with a "type" of "credit" of 100,000. You can then create a transaction for each purchase and sell event to track how that balance moves. This is give you nice auditability into how the balance changed and why. It is also very easy to get the balance in 1 query user.transactions.sum(:amount)
.
As far as performing the stock sale itself along with the balance update...it sounds like you have a classic service class situation. Example below.
class StockTransfer
def initialize(user:, stock:, quantity:)
@user = user
@stock = stock
@quantity = quantity
end
def call
# insert logic here for the stock transfer
# logic for the transaction
@user.transactions.create!(
type: (quantity > 0 ? 'credit' : 'debit'),
event: (quantity > 0 ? 'purchase' : 'sale'),
stock: @stock
)
end
end
Hey, thanks for the reply. Should I add a column "balance" to my devise user model? Because I intend to show users their remaining balance on their profile. Currently what I've done is, added a balance column to my user model, generated a model called AccountTransactions (transaction_type, amount, user_id and transaction_number). I have also created an "operations" folder (the part that you've called "service class" and have added your code.
How do I proceed from here? Also, could you elaborate on "events" and how to go about them?
EDIT: updating my code (This is from my operations/execute_transactions.rb service class file. Not sure why I have the @transaction_type instance variable specified, not sure what to do with it since it's already being specified within my execute method.
class StockTransfer
def initialize(user:, amount:, transaction_type:, quantity:, transaction_number:, stock:)
@user = user
@amount = amount
@transaction_type = transaction_type
@quantity = quantity
#@transaction_number = transaction_number
@stock = stock
end
def execute
# logic for the transaction
@user.transactions.create!(
# type: (quantity > 0 ? 'credit' : 'debit'),
event: (quantity > 0 ? 'purchase' : 'sale'),
stock: @stock,
transaction_type: (quantity > 0? 'credit' : 'debit')
)
# insert logic here for the stock transfer
if transaction_type == "credit"
@user.update!(balance: @user.balance + @amount)
elsif transaction_type == "debit"
@user.update!(balance: @user.balance - @amount)
end
end
end
One more question: How do I default the credited balance amount to 100000 to all users initially?
You are real close. If you re-read my post I answer your last question ;) It is generally not recommended to store the balance as a single field on user because of read/write concurrency issues. The sum method I have you would work perfectly for current balance purposes. Everything else for the most part looks fine, you just have some honing to do (adapting the cargo-culting).
Feel free to hit me up in the gorails slack community my username is caseyprovost
. I would be happy to pair review with you via screenhero or similar.
Hi. Unfortunately I have not subscribed to Gorails, how do I get access to the slack forum? Is there any other way I can contact you? (Maybe skype?) I sent you a request.
Although it does seem correct, nothing seems to be happening when I try to update AccountTransaction.create("enter parameters here"). user balance still remains as "nil".
After creating some basic associations for my AccountTransaction model and moving into my console, I type:
User.first.account_transactions.create(amount: "1000", transaction_type: "credit", transaction_number: "1")
With my transaction logic technically this should credit 1000 to my user balance, correct? But User.first.balance still returns nil.
You seem pretty close. balanace
should a method on user. Something like this:
def balance
account_transactions.sum(:amount)
end
You also want to make sure the record was created via User.first.account_transactions.count. If it is > than 0 then you should be all set. I hit you up on Skype too so we can chat a bit faster there and iterate if that works for you.
Hi every one:
I am having similar challenges regarding the creation of a payment system with a type of credits (simple virtual money).
Regarding the balance I am using this method in the User model and is working fine. Just in the case Rohan has some issues with Casey sugestion.
before_validation :load_defaults
def load_defaults
if self.new_record?
self.balance = 1000
end
end
This related I have a question:
I am trying the amount of @order pass and update the @balance. In my models both are integers.
I am trying this from Orders_controller but doesnt work.
Do you have any sugestion?
Do you need to see more code?
Any comment is welcome!!
class OrdersController < ApplicationController
def create
@order = current_user.orders.create(order_params)
@user.balance = balance: (@user.balance += @order.price)
redirect_to user_orders_path
end
end
private
def order_params
params.require(:order).permit(:price, :user_id)
end
end
I was trying as well with this diferent approach with code similar to the code Rohan use. But its not working.
Any sugestion?
class OrdersController < ApplicationController
def create
@order = current_user.orders.create(order_params)
redirect_to user_orders_path
end
end
def balance
if @order.save
@user.balance_update!(balance: @user.balance + @order.price)
end
end
private
def order_params
params.require(:order).permit(:price, :user_id)
end
end
All of you are attempting to re-implement book-keeping from first principles.
When what you need is Plutus. https://rubygems.org/gems/plutus