Rails update_column from within seeds.rb - ruby-on-rails

I have a seed file that generates numerous objects, including accounts and account transactions. After the accounts are created, a loop generates 80 transactions for each account (there are 150 accounts).
Everything works well, except I need the account's balance column to be updated to reflect the change caused by the transaction. I've tried this in 100 different ways. Here is my most recent approach:
from seeds.rb
accounts.each do |account|
80.times do
type = types.sample
case (type)
when 1
description = descriptions_ATM_Withdrawal.sample
amount = (atm_amounts.sample) *-1
when 2
description = descriptions_Check.sample
amount = ((500.0 - 5.0) * rand() + 5) *-1
when 3
description = descriptions_Deposit.sample
amount = (2000.0 - 5.0) * rand() + 5
when 4
description = descriptions_AutoDraft.sample
amount = ((350.0 - 5.0) * rand() + 5) *-1
when 5
description = descriptions_POS.sample
amount = ((150.0 - 5.0) * rand() + 5) *-1
when 6
description = descriptions_Transfer
amount = (500.0 - 5.0) * rand() + 5
when 7
description = descriptions_Withdrawal
amount = ((500.0 - 5.0) * rand() + 5) *-1
when 99
description = descriptions_Miscellaneous
amount = ((500.0 - 5.0) * rand() + 5) *-1
end
AcctTransaction.create do |transaction|
transaction.id = SecureRandom.random_number(99999999999999)
transaction.account_id = account.id
transaction.transaction_type_id = type
transaction.description = description
transaction.amount = amount
transaction.adjusted_bal = account.balance + transaction.amount
# keep transaction in chronological order unless it's the first one
unless AcctTransaction.exists?(account_id: transaction.account_id)
transaction.date = rand(account.date_opened..Time.now)
else
transaction.date = rand(AcctTransaction.where(account_id: transaction.account_id).last.date..Time.now)
end
end
Account.find(AcctTransaction.last.account_id).update_column(:balance, AcctTransaction.last.adjusted_bal)
end
end
Trying to update with the last line before the "ends". I've tried update, update_attribute, as well as just "=". Nothing seems to work. The account's "balance" field MUST be updated after each transaction in order to provide an accurate basis for calculation in the next iteration of the account transaction creation loop.
Any suggestions will be considered. This can't be that difficult. Again, the whole thing would work just fine if the balance gets updated like it should.
Rails 4.1.8 / Ruby 2.1.5
Please help.. THANK YOU!
EDIT
account.rb:
class Account < ActiveRecord::Base
belongs_to :customer
belongs_to :user
has_one :acct_type
has_many :acct_transactions, :dependent => :destroy
accepts_nested_attributes_for :acct_type
accepts_nested_attributes_for :acct_transactions
validates :acct_type_id, presence: true
end
acct_transaction.rb
class AcctTransaction < ActiveRecord::Base
belongs_to :account
has_one :transaction_type
accepts_nested_attributes_for :transaction_type, :allow_destroy => false
end
screen shot of (wrong) results
As you can see, what happens is the original balance (seen in header if you zoom in), persists. Every transaction, therefore, is calculated based on this amount.
methods in acct_transactions_controller that actually work to perform these updates when the app is live. Trying to replicate this functionality when creating the nested seeds:
public
def modify_acct_balance
account = Account.find(#acct_transaction.account_id)
case #acct_transaction.transaction_type_id
when 1,2,4,5,7
account.update(balance: account.balance - #acct_transaction.amount)
when 3
account.update(balance: account.balance + #acct_transaction.amount)
end
end
def adjust_balance
case #acct_transaction.transaction_type_id
when 2,4,5,7
#acct_transaction.adjusted_bal = Account.find(#acct_transaction.account_id).balance - #acct_transaction.amount
when 3
#acct_transaction.adjusted_bal = Account.find(#acct_transaction.account_id).balance + #acct_transaction.amount
end
end
Please note that the above methods calculate differently based on the transaction type (+ or -) - user always supplies positive value. Works though.
How to reproduce this kind of functionality in the above seed file?
Thanks

There are many ways to do this, for sure, but at first glance it looks like your approach would work. My first thought is that the you have some life-cycle hooks or counter-cache configuration on the models that's conflicting with your logic. (You may want to show us your model definitions)
If that's not the case, I'd simply skip updating the balance after every transaction and calculate the balance at the end of the transaction-creation loop.
In pseudo:
accounts.each do |account|
80.times do
# create transactions
end
account.reload
account.balance = account.transactions.sum(:adjusted_bal)
account.save
end
Or even faster, build and cache the balance locally during transaction-creation:
accounts.each do |account|
balance = 0
80.times do
# create transaction
balance += transaction.amount
end
account.balance = balance
account.save
end

To make this work, I had to do 2 things:
Change the syntax of update method (last line of seeds.rb) to this : account.update(balance: transaction.adjusted_bal)
Add a ! (bang) to the create method (AcctTransaction.create! do |transaction|)
The create method probably works fine without the bang, but I'm leaving it in because everything works great now. Also, I'm not sure why using update only works with this syntax. I tried other variants and other methods to update this column and none seemed to work. Almost seems like seeds.rb only works with "bare bones" Rails methods. Thanks to the Deeno and all others who've helped me work this out.

Related

Search by availability/dates Ruby On Rails

I've built a RoR app and implemented a simple booking system. The user is able to look for a space and can book it per day or per hour.
Everything works well, but I would now like to make the user able to look for a space depending on its availability.
I want to user to be able to select a start/end date and a start/end time and to show only spaces that don't have any booking included in this period.
I am using pg search at the moment to look for a space by category and location, but I have no idea how to implement a search by date and time, as it uses a different logic.
I've tried to do it by hand by creating an array of bookings for each space so I could compare it with the params, but it sounded very complicated and not clean (and I started being stuck anyway, as making it available for one hour or several hours or several days makes it even more complicated)
Is there a gem that could do this for me? If I have to do it by hand, what's the best way to begin?
Thanks a lot
Just create an instance method available? which tests there are no bookings that overlap the from to range. You can use none? on the relationship.
class Space
has_many :bookings
def available?(from, to)
bookings.where('start_booking <= ? AND end_booking >= ?', to, from).none?
end
end
Taking some inspiration from the answer of SteveTurczyn. The following might give you some inspiration.
class Space < ApplicationRecord
# attributes: id
has_many :bookings
def self.available(period)
bookings = Booking.overlap(period)
where.not(id: bookings.select(:space_id))
end
def available?(period)
if bookings.loaded?
bookings.none? { |booking| booking.overlap?(period) }
else
bookings.overlap(period).none?
end
end
end
class Booking < ApplicationRecord
# attributes: id, space_id, start, end
belongs_to :space
def self.overlap(period)
period = FormatConverters.to_period(period)
# lteq = less than or equal to, gteq = greater than or equal to
# Other methods available on attributes can be found here:
# https://www.rubydoc.info/gems/arel/Arel/Attributes/Attribute
where(arel_table[:start].lteq(period.end).and(arel_table[:end].gteq(period.start)))
end
def overlap?(period)
period = FormatConverters.to_period(period)
self.start <= period.end && self.end >= period.start
end
module FormatConverters
module_function
def to_period(obj)
return obj if obj.respond_to?(:start) && obj.respond_to?(:end)
obj..obj
end
end
end
With the above implemented you can query a single space if it is available during a period:
from = Time.new(2019, 10, 1, 9, 30)
to = Time.new(2019, 10, 5, 17, 30)
period = from..to
space.available?(period) # true/false
You can get all spaces available:
spaces = Space.available(period) # all available spaces during the period
Note that class methods will also be available on the scope chain:
spaces = Space.scope_01.scope_02.available(period)
I've also added the overlap scope and overlap? helper to simplify creating the above helpers.
Since in my version Booking has a start and end attribute (similar to Range) you can also provide it to any methods accepting a period.
booking_01.overlap?(booking_02) # true/false
To retrieve all bookings that that overlap this very moment:
bookings = Booking.overlap(Time.now) # all bookings overlapping the period
Hope this gave you some inspiration. If you'd like to know how the overlap checking works I have to forward you to this question.
Note: This answer assumes that the provided period is valid. A.k.a. start <= end. If you for some reason provide Time.new(2019, 10, 1)..Time.new(2019, 9, 23) the results are going to be skewed.

Summing time by looking at an associated column- an easier way?

I have a Project model that has_many: Tasks each Task has_many :users, through: :userAssociations so one person can work on a task but in the case of a meeting or any collaboration many users can work on the same task.
Tasks have a column: t.integer "time" which allows users to state in minutes the time the task took.
In the Show method of the Project, I'd like to be able to sum the total time that the project has taken.
If not for the fact that a Task can have many users through the userAssociations model, I think this would be very easy: #project.tasks.sum(:time) but that won't double count the time if multiple users were working on the task.
Since originally posting this question I was able to get it working with this helper:
def project_time_spent
#timeSum = 0;
#tasks.each do |tsk|
#userTask = UserTask.where(task_id: tsk.id);
if #userTask.count(:id) > 1
#timeSum += tsk.time * #userTask.count(:id)
else
#timeSum += tsk.time
end
end
return #timeSum
end
But this doesn't feel very "Railsy". Do any Rails gurus recommend a cleaner approach? Am I missing out on an easy method?
You can do it a little easier than that since a task has many users. I think you can also use inject here.
def project_time_spent
#tasks.inject { |sum, tsk| sum + (tsk.time * tsk.users.count) }
end
You can include an instance method in your Project model:
def time_spent
self.tasks.inject { |sum, tsk| sum + (tsk.time * tsk.users.count) }
end
I wouldn't bother checking for count > 1. The savings getting rid of the multiply are minuscule.

Access to old association inside Rails/ActiveRecord callback

In my Rails callback I have something like this:
private
def update_points
if user_id_changed?
user_was = User.find(user_id_was)
user_was.points -= points_was
user_was.save
user.points += points
user.save
end
end
Is that the proper way to do this with user_was? I initially just assumed user_was was already defined (or could be defined on the spot) because user_id_was existed.
It's not clear to me from the context what exactly you're doing (maybe the 2nd points is supposed to be points_was?). For mildly improved clarity, depending on who you ask, and with fewer lines:
...
user_was = User.find(user_id_was)
user_was.update_column :points, user_was.points - points_was
user.update_column :points, user.points + points
...

Why this application controller won't return correct value to controller who called?

Why do I get empty when I execute this? Asumming User's point is 2500. It should return 83
posts_controller
#user = User.find(params[:id])
#user.percentage = count_percentage(#user)
#user.save
application_controller
def count_percentage(user)
if user
if user.point > 2999
percentage = ((user.point / 5000.0) * 100 ).to_i
elsif user.point > 1999
percentage = ((user.point / 3000.0) * 100 ).to_i
end
return percentage
end
end
It looks like there is some lack of basic understanding, so I try to focus on a few points here.
count_percentage belongs to your model. It is a method which does things that are tied to your User records. In your example, the code can only be used with a User record. Therefore it belongs to your User class!
class User < ActiveRecord::Base
def percentage
if self.point > 2999
return ((self.point / 5000.0) * 100 ).to_i
elsif user.point > 1999
return ((self.point / 3000.0) * 100 ).to_i
else
# personally, I'd add a case for points range 0...3000
end
end
As you said, you want to put that value into your "side menu", so this allows to e.g #user.percentage there which gives you the desired percentage.
According to your description (if I understood it the right way) you want to store the calculated value in your user model. And here we go again: Model logic belongs into your model class. If you want to keep your percentage value up to date, you'd add a callback, like:
class User < ActiveRecord::Base
before_save :store_percentage
def precentage
# your code here
end
private
def store_percentage
self.my_percentage_field_from_my_database = self.percentage
end
end
To your actual problem: user.point may be NULL (db) / nil (ruby), so you should convert it to_i before actually calculating with it. Also that's why I've suggested an else block in your condition; To return a value wether or not your user.point is bigger than 1999 (if it even is an integer.. which I doubt in this case).
To answer on this question you should try to examine user.point, percentage value in the count_percentage method.
I strongly recommend pry. Because it's awesome.

calculating royalties based on ranges in rails 3

I've built an application that tracks sales of books and is -- hopefully -- going to calculate author royalties.
Right now, I track sales in orders. Each order has_many :line_items. When a new line item is saved, I calculate the total sales of a given product, so I have a total sales count.
Each author has multiple royalty rules based on their contract. For example, 0 to 5000 copies sold, they get 10 percent. 5001 to 10,000, they get 20 percent. At first, I was calculating the author's share per line item. It was working well, but then I realized that my app is choosing which royalty rule to apply based on the total sales. If I post a big order, it's possible that the author's royalties would be calculated at the high royalty rate for the entire line item when in fact, the royalty should be calculated based on both the lower and high royalty rate (as in, one line item pushes the total sales passed the royalty rule break point).
So my question is how to best go about this. I've explored using ranges but this is all a little new to me and the code is getting a little complex. Here's the admittedly clunky code I'm using to pull all the royalty rules for a given contract into an array:
def royalty_rate
#product = Product.find_by_id(product_id)
#total_sold = #product.total_sold
#rules = Contract.find_by_product_id(#product).royalties
... where next?
end
#rules has :lower and :upper for each royalty rule, so for this product the first :lower would be 0 and the :upper would be 5000, then the second :lower would be 5001 and the second :upper would be 10,000, and so on.
Any help or ideas with this would be appreciated. It's actually the last step for me to get a fully working version up I can play with.
I was using this code below to pick out a specific rule based on the value of total_sold, but again, that has the effect of taking the cumulative sales and choosing the highest royalty rate instead of splitting them.
#rules = #contract.royalties.where("lower <= :total_sold AND upper >= :total_sold", {:total_sold => #total_sold}).limit(1)
Thanks in advance.
It sounds like you need to store the royalty calculation rules separately for each author -- or perhaps you have several schemes and each author is associated with one of them?
For the first case, perhaps something like this:
class Author
has_many :royalty_rules
end
class RoyaltyRule
belongs_to :author
# columns :lower, :upper, :rate
end
So when an Author is added you add rows to the RoyaltyRule model for each tier. Then you need a method to calculate the royalty
class Author
def royalty(product)
product = Product.find_by_id(product.id)
units = product.total_sold
amount = 0
royalty_rules.each do |rule|
case units
when 0
when Range.new(rule.lower,rule.upper)
# reached the last applicable rule -- add the part falling within the tier
amount += (units - rule.lower + 1) * rule.rate
break
else
# add the full amount for the tier
amount += (rule.upper - rule.lower + 1) * rule.rate
end
end
amount
end
end
And some specs to test:
describe Author do
before(:each) do
#author = Author.new
#tier1 = mock('tier1',:lower=>1,:upper=>5000,:rate=>0.10)
#tier2 = mock('tier2',:lower=>5001,:upper=>10000,:rate=>0.20)
#tier3 = mock('tier3',:lower=>10001,:upper=>15000,:rate=>0.30)
#author.stub(:royalty_rules) { [#tier1,#tier2,#tier3] }
end
it "should work for one tier" do
product = mock('product',:total_sold=>1000)
#author.royalty(product).should == 100
end
it "should work for two tiers" do
product = mock('product',:total_sold=>8000)
#author.royalty(product).should == (5000 * 0.10) + (3000 * 0.20)
end
it "should work for three tiers" do
product = mock('product',:total_sold=>14000)
#author.royalty(product).should == (5000 * 0.10) + (5000 * 0.20) + (4000 * 0.30)
end
# edge cases
it "should be zero when units is zero" do
product = mock('product',:total_sold=>0)
#author.royalty(product).should == 0
end
it "should be 500 when units is 5000" do
product = mock('product',:total_sold=>5000)
#author.royalty(product).should == 500
end
it "should be 500.2 when units is 5001" do
product = mock('product',:total_sold=>5001)
#author.royalty(product).should == 500.2
end
end
Notes: Author.royalty_rules needs to return the tiers sorted low to high. Also, the lowest tier starts with 1 instead of 0 for easier calculation.
So, I don't get why you can't just calculate on total quantity sold?
I'll assume they don't need to know at the exact moment of order, so why not calculate, based on quantity sold as of yesterday.
E.g., Run a rake task, in the morning (let's say) that uses the following module called RoyaltyPayments in a file in lib/royalty_payments.rb
Do something like
Module RoyaltyPayments
def royalty_range(total_sold, product_price)
sold_price = total_sold * product_price
leftover = total_sold % 5000
case total_sold
when 0..5000
total_sold * 0.1
when 5001..10000
((sold_price * 0.10)*5000) + ((sold_price * 0.2)*leftover)
else
((sold_price * 0.10)*5000) + (sold_price * 0.20)*5000) + ((sold_price * 0.3)*(total_sold - 10000)
end
end
Then make lib/tasks/royalty_payments.rake
In that file put something like:
include RoyaltyPayments
namespace :royalty_payments
desc "Make royalty calculations"
task :calculate_latest_totals
Product.all.each do |product|
total_sold = product.total_sold
royalty_range(total_sold, product.price)
end
Something like that.
You can do ranges of variables, eg. min..max
$irb
> min = 0
=> 0
> max = 5000
=> 5000
> min..max
=> 0..5000
> (min..max).class
=> Range
% is Numeric.modulo see http://www.ruby-doc.org/core/classes/Numeric.html#M000969 for details.

Resources