Rails decimal field is not saving correctly - ruby-on-rails

I have a price: decimal field on my model and when I say model.price = 10, I am expecting it to be 20.0 but what it saves is 0.02e2. Any idea what is happening?
Migration has this: t.decimal :price
and this is what I do in the rails console:
irb(main):019:0> app.price
=> 0.2e2
irb(main):020:0> app.price = 2000
=> 2000
irb(main):021:0> app.save!
(0.3ms) BEGIN
MyModel Update (0.5ms) UPDATE "my_model" SET "price" = $1, "updated_at" = $2 WHERE "my_models"."id" = $3 [["price", "2000.0"], ["updated_at", "2021-02-10 08:28:50.075958"], ["id", "44e7448f-0504-49cb-be76-55627fe57f1e"]]
(33.0ms) COMMIT
=> true
irb(main):022:0> app.price
=> 0.2e4

The e here stands for exponent (although some people may more commonly know it as power).
As it's a decimal column, we're working in tens.
102 = 100
0.2 x 100 = 20.
So it is storing 20. It's just not immediately obvious if you're unfamiliar with this kind of thing.
The object being returned is a BigDecimal. In a Rails Console, you can prove it:
BigDecimal("20")
=> 0.2e2
BigDecimal("20").to_f
=> 20.0
BigDecimal("20").to_i
=> 20

Related

where.not with where in rails/activerecord excluding all records?

I have a model (pairings) that has a length and date attribute (among others). I am trying to query all pairings that do NOT touch a specific date or date range while matching other properties of the pairing. The code for all other queries working fine, but my where.not is excluding all records. This is because (I think) of the way I am building the relation/query.
I need something that excludes selected date range AND matches the length. This needs to be stacked on other scoped queries.
Any ideas on how to accomplish this?
Thanks in advance!
Controller code:
rel = rel.other scoped queries
#all_trip_len.each do |len|
rel = rel.date_selector(#date_sel_1, #date_start_1, #date_end_1, len)
end
rel = rel.more scoped queries
Model Scope:
def self.date_selector(sel, start_d, end_d, length)
# Need to get all possible start dates that could touch avoid days for each length of trip
rel = self
start = Date.parse(start_d) - (length.to_i - 1)
rel = rel.where.not(date: start.to_s..Date.parse(end_d).to_s).where(length: length)
return rel
end
I am getting a query that is:
SELECT "pairings".*
FROM "pairings"
WHERE "pairings"."bid_month_id" = $1
AND NOT ("pairings"."date" BETWEEN $2 AND $3)
AND "pairings"."length" = $4
AND NOT ("pairings"."date" BETWEEN $5 AND $6)
AND "pairings"."length" = $7 [["bid_month_id", 8], ["date", "2020-04-08"], ["date", "2020-04-08"], ["length", 1], ["date", "2020-04-07"], ["date", "2020-04-08"], ["length", 2]
And I need something more like:
SELECT "pairings".*
FROM "pairings"
WHERE "pairings"."bid_month_id" = $1
AND NOT (("pairings"."date" BETWEEN $2 AND $3)
AND "pairings"."length" = $4)
AND NOT (("pairings"."date" BETWEEN $5 AND $6)
AND "pairings"."length" = $7) [["bid_month_id", 8], ["date", "2020-04-08"], ["date", "2020-04-08"], ["length", 1], ["date", "2020-04-07"], ["date", "2020-04-08"], ["length", 2]
Edit:
With the help of MurifoX I got a little farther. The query is built almost correctly but I need OR in between the pairing.date grouping.
What I have now:
SELECT "pairings".*
FROM "pairings"
WHERE "pairings"."bid_month_id" = $3
AND (((date <= '2020-04-06' AND date >= '2020-04-07') AND length = 1))
AND (((date <= '2020-04-05' AND date >= '2020-04-07') AND length = 2))
What I need:
SELECT "pairings".*
FROM "pairings"
WHERE "pairings"."bid_month_id" = $3
AND (((date <= '2020-04-06' AND date >= '2020-04-07') AND length = 1)
OR ((date <= '2020-04-05' AND date >= '2020-04-07') AND length = 2))
I have tried using the rails 5 or (rel.or(Pairing.date_selector(xxx)) but that does not work because it turns all the where AND into OR and I just need the OR between the pairing/date groupings. Also need the parens around the date groupings.
The construction of semi-complex queries with ActiveRecord methods can be tricky sometimes, so in this particular cases i always tell people to do it by hand:
rel = self
start = Date.parse(start_d) - (length.to_i - 1)
rel = rel.where("((date <= ? AND date >= ?) AND length = ?)", start.to_s, Date.parse(end_d).to_s, length)
return rel
I ended up having to build this as a string to get it to do exactly what I want. the or relation in rails 5 created a statement that OR'd ALL of my previous scope clauses with the new one (all AND -> OR) and I needed all my previous scoped queries to stand and just the new dates to be OR'd.
def exclude_dates(rel, date_start_1, date_end_1, all_trip_len)
dates_query = String.new
count = 0
all_trip_len.each do |len|
start_d = Date.parse(date_start_1) - (len.to_i - 1)
end_d = Date.parse(date_end_1)
count += 1
dates_query += " OR " if count > 1
dates_query += "((date < '#{start_d.to_s}' OR date > '#{end_d.to_s}') AND length = '#{len}')"
end
rel = rel.where(dates_query)
end
This created the query I needed. Wish there was a more 'rails' way to do it...
SELECT COUNT(*) FROM "pairings" WHERE "pairings"."bid_month_id" = $1 AND (((date < '2020-04-02' OR date > '2020-04-02') AND length = '1') OR ((date < '2020-04-01' OR date > '2020-04-02') AND length = '2') OR ((date < '2020-03-31' OR date > '2020-04-02') AND length = '3') OR ((date < '2020-03-30' OR date > '2020-04-02') AND length = '4')) AND (((date < '2020-04-14' OR date > '2020-04-17') AND length = '1') OR ((date < '2020-04-13' OR date > '2020-04-17') AND length = '2') OR ((date < '2020-04-12' OR date > '2020-04-17') AND length = '3') OR ((date < '2020-04-11' OR date > '2020-04-17') AND length = '4')) [["bid_month_id", 8]]
Thanks to #MurifoX for getting me going in the right direction with the dates stuff!

Rails sum on AssociationRelation attribute is incorrect if association has limit clause

I have a method that computes stats (mainly sums) on a number of float attributes in a model.
The models
class GroupPlayer < ActiveRecord::Base
belongs_to :group
has_many :scored_rounds
has_many :rounds, dependent: :destroy
end
class Round < ActiveRecord::Base
belongs_to :group_player
end
class ScoredRound < Round
# STI
end
The method that provides stats on up to 4 float attributes that is called from a other methods, depending if I'm getting stats for one player or a group of players. An initial filter on ScoredRound is passed to the method (sr)
def method_stats(method,sr,grp)
rounds = sr.where.not(method => nil)
number_rounds = rounds.count
won = rounds.sum(method).round(2)
if method == :quality
dues = grp.options[:dues] * number_rounds
else
dues = grp.options["#{method.to_s}_dues"] * number_rounds
end
balance = (won - dues).round(2)
perc = dues > 0 ? (won / dues).round(3) : 0.0
[self.full_name,number_rounds,won,dues,balance,perc]
end
3 of the 4 attributes I am summing in ScoredRounds may not be set (nil) if the player did not win that game so the rounds are filtered.
Everything worked fine until I decided to add a limit on how many rounds to use. For instance if I only wanted status for the last 25 rounds in the query passed to method_stats I'd call:
def money_stats(grp,method,limit=100)
sr = self.scored_rounds.where.not(method => nil).order(:date).reverse_order.limit(limit)
method_stats(method,sr,grp)
end
Again, I just added the limit and order clause to the query. Worked fine for all records.
If I simulate the procedure in the console with out using the above methods (or using them!) I'll get an erroneous sum
gp = GroupPlayer.find(123)
GroupPlayer Load (2.1ms) SELECT "group_players".* FROM "group_players" WHERE "group_players"."id" = $1 LIMIT $2 [["id", 123], ["LIMIT", 1]]
=> valid group player
sr = gp.scored_rounds.where.not(:quality => nil)
ScoredRound Load (1.7ms) SELECT "rounds".* FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL) [["group_player_id", 123]]
=> #<ActiveRecord::AssociationRelation [#<ScoredRound id: 5706, player_id: 123, group_player_id: 123, event_id: 12, type: "ScoredRound", date: "2016-11-04", team: 3, tee: "White", quota: 32, front: 15, back: 15, total: 30, created_at: "2016-11-04 14:18:27", updated_at: "2016-11-04 19:12:47", quality: 0.0, skins: nil, par3: nil, other: nil>,...]
sr.count
(1.5ms) SELECT COUNT(*) FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL) [["group_player_id", 123]]
=> 44
sr.sum(:quality)
(1.0ms) SELECT SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL) [["group_player_id", 123]]
=> 354.166666666667
# Now if I add the order and limit clause
sr = gp.scored_rounds.where.not(:quality => nil).order(:date).reverse_order.limit(25)
ScoredRound Load (1.6ms) SELECT "rounds".* FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL) ORDER BY "rounds"."date" DESC LIMIT $2 [["group_player_id", 123], ["LIMIT", 25]]
=> => #<ActiveRecord::AssociationRelation [...]
sr.count
(1.1ms) SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL) LIMIT $2) subquery_for_count [["group_player_id", 123], ["LIMIT", 25]]
=> 25
sr.sum(:quality)
(1.8ms) SELECT SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL) LIMIT $2 [["group_player_id", 123], ["LIMIT", 25]]
=> 354.166666666667
### This is the error, it return the sum off all records,
# not the limited???? if I use pluck and sum
sr.pluck(:quality)
=> [10.0, 11.3333333333333, 10.0, 34.0, 0.0, 7.33333333333333, 0.0, 0.0, 31.5, 0.0, 21.3333333333333, 0.0, 19.0, 0.0, 0.0, 7.5, 0.0, 20.0, 10.0, 28.0, 8.0, 9.5, 0.0, 3.0, 24.0]
sr.pluck(:quality).sum
=> 254.49999999999994
Don't know if I found a bug in AREL or I'm doing something wrong. I tried it with just Round instead of the STI ScoredRound with the same results.
Any ideas?
If you notice, the SUM results for both, with and without LIMIT, are the same:
sr = gp.scored_rounds.where.not(:quality => nil)
sr.sum(:quality)
(1.0ms) SELECT SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL) [["group_player_id", 123]]
=> 354.166666666667
sr = gp.scored_rounds.where.not(:quality => nil).order(:date).reverse_order.limit(25)
sr.sum(:quality)
(1.8ms) SELECT SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = $1 AND ("rounds"."quality" IS NOT NULL) LIMIT $2 [["group_player_id", 123], ["LIMIT", 25]]
=> 354.166666666667
That's because LIMIT affects the number of rows returned by the query and SUM returns just one, so the function is applied for all the 44 records, not the 25 given to LIMIT. That's not what happens with sr.pluck(:quality).sum which applies only to the 25 records returned by the query.
Don't know if I found a bug in AREL or I'm doing something wrong
Sadly, 99.9% of times is not a bug but our fault :(
# File activerecord/lib/active_record/relation/calculations.rb, line 75
def sum(column_name = nil)
return super() if block_given?
calculate(:sum, column_name)
end
if you call sr.sum(:quality) then sum take quality as a column name and Calculates the sum of values on a given column.

Rails iterating through model and changing values doesn't work

I have a PiggyBank model where one of the attributes is interest_modifier(format: decimal). I wrote the following method to change the interest_modifier to a random number:
def set_random_interest_modifier(min = 0.5, max = 1)
self.interest_modifier = rand(min..max).round(2)
self.save!
end
It works. Now, I want a method which iterates over all PiggyBanks and changes the interest_modifier, so i wrote this:
def self.change_interest_modifiers
find_each do |pb|
pb.set_random_interest_modifier
end
end
It iterates over all records but doesn't change the values. Heres the log
[12] pry(main)> PiggyBank.change_interest_modifiers
PiggyBank Load (1.4ms) SELECT "piggy_banks".* FROM "piggy_banks" ORDER BY "piggy_banks"."id" ASC LIMIT 1000
(0.3ms) BEGIN
SQL (1.0ms) UPDATE "piggy_banks" SET "interest_modifier" = $1, "updated_at" = $2 WHERE "piggy_banks"."id" = 7 [["interest_modifier", "0.58"], ["updated_at", "2015-06-02 11:53:33.981262"]]
(3.1ms) COMMIT
(0.4ms) BEGIN
SQL (0.7ms) UPDATE "piggy_banks" SET "interest_modifier" = $1, "updated_at" = $2 WHERE "piggy_banks"."id" = 8 [["interest_modifier", "0.53"], ["updated_at", "2015-06-02 11:53:33.990404"]]
(0.4ms) COMMIT
(0.2ms) BEGIN
SQL (0.7ms) UPDATE "piggy_banks" SET "interest_modifier" = $1, "updated_at" = $2 WHERE "piggy_banks"."id" = 9 [["interest_modifier", "0.78"], ["updated_at", "2015-06-02 11:53:33.994231"]]
(0.4ms) COMMIT
(0.2ms) BEGIN
SQL (0.7ms) UPDATE "piggy_banks" SET "interest_modifier" = $1, "updated_at" = $2 WHERE "piggy_banks"."id" = 10 [["interest_modifier", "0.68"], ["updated_at", "2015-06-02 11:53:33.997966"]]
(0.5ms) COMMIT
(0.2ms) BEGIN
SQL (0.7ms) UPDATE "piggy_banks" SET "interest_modifier" = $1, "updated_at" = $2 WHERE "piggy_banks"."id" = 11 [["interest_modifier", "0.71"], ["updated_at", "2015-06-02 11:53:34.002414"]]
(0.5ms) COMMIT
(0.1ms) BEGIN
(0.1ms) COMMIT
(0.1ms) BEGIN
SQL (0.5ms) UPDATE "piggy_banks" SET "interest_modifier" = $1, "updated_at" = $2 WHERE "piggy_banks"."id" = 13 [["interest_modifier", "0.91"], ["updated_at", "2015-06-02 11:53:34.007100"]]
(0.5ms) COMMIT
(0.1ms) BEGIN
SQL (0.6ms) UPDATE "piggy_banks" SET "interest_modifier" = $1, "updated_at" = $2 WHERE "piggy_banks"."id" = 14 [["interest_modifier", "0.96"], ["updated_at", "2015-06-02 11:53:34.010488"]]
(0.4ms) COMMIT
(0.2ms) BEGIN
SQL (0.6ms) UPDATE "piggy_banks" SET "interest_modifier" = $1, "updated_at" = $2 WHERE "piggy_banks"."id" = 15 [["interest_modifier", "0.8"], ["updated_at", "2015-06-02 11:53:34.014190"]]
(0.5ms) COMMIT
(0.3ms) BEGIN
SQL (0.6ms) UPDATE "piggy_banks" SET "interest_modifier" = $1, "updated_at" = $2 WHERE "piggy_banks"."id" = 16 [["interest_modifier", "0.71"], ["updated_at", "2015-06-02 11:53:34.018658"]]
(0.5ms) COMMIT
(0.2ms) BEGIN
SQL (0.6ms) UPDATE "piggy_banks" SET "interest_modifier" = $1, "updated_at" = $2 WHERE "piggy_banks"."id" = 18 [["interest_modifier", "0.67"], ["updated_at", "2015-06-02 11:53:34.023011"]]
(0.5ms) COMMIT
=> nil
Now, I have no idea why this happens. No errors are raised. Please, help:)
After a few experiments with reloading in console:
p = PiggyBank.first
p.interest_modifier.to_s => "0.69"
PiggyBank.change_interest_modifiers
p.interest_modifier.to_s => "0.69"
reload!
p.interest_modifier.to_s => "0.69"
But, if i do the following, it works:
p = PiggyBank.first
p.interest_modifier.to_s => "0.69"
PiggyBank.change_interest_modifiers
p.interest_modifier.to_s => "0.69"
p.reload
p.interest_modifier.to_s => "0.57"
Why would that be?
This is what's happening:
p = PiggyBank.first
p.interest_modifier.to_s => "0.69"
so we've got an object in memory, p, which we've created and in the act of creation, loaded it's data out of the database.
PiggyBank.change_interest_modifiers
This will iterate through all of the PiggyBank records, creating an object for each. These objects are all different objects to the one that you made already. So, effectively, there have been two different objects made from the same record. The object inside the class method will save the new data to the database, behind p's back if you like.
These objects do not have a "live" connection to the database - that is to say, if the database is changed by a different method, the object in memory does not magically update itself with the new data.
So, while you have p sitting there, something else has changed the data in the database. When you say p.interest_modifier, you're just accessing an instance variable inside the object, you're not accessing the database.
When you then go on to say
p.reload
what you are actually doing is something like this:
p = PiggyBank.find(p.id)
ie, re-setting the contents of the p pointer to point to a new object, created out of the database record. This object now has the up to date data from the database.
Well, the method worked from the start, just forgot to reload the resource :O

`new': invalid date ( ArgumentError)

Ok i somehow get this error for an function with an date:
C:/geburtstag/app/config/initializers/update_year.rb:11:in `new': invalid date (
ArgumentError)
from C:/geburtstag/app/config/initializers/update_year.rb:11:in `block i
n <top (required)>'
The code looks like this:
Patient.all.each do |f|
if (f.birthday.get_birthday + 1) != f.year
f.update_attribute :year, f.birthday.get_birthday + 1
end
if f.thisyear.blank?
f.update_attribute :thisyear, Date.new(Date.today.year, f.birthday.month, f.birthday.mday )
end
if f.thisyear.year != Date.today.year
f.update_attribute :thisyear, Date.new(Date.today.year, f.birthday.month,f.birthday.mday)
end
end
Line 11:
f.update_attribute :thisyear, Date.new(Date.today.year, f.birthday.month, f.birthday.mday )
I dont get why i get this error!! SO i made some test in the console, that all worked fine! I hope you can help me! Thanks
irb(main):005:0> c = Patient.find(24367)
←[1m←[36mPatient Load (0.0ms)←[0m ←[1mSELECT "patients".* FROM "patients" WHE
RE "patients"."id" = ? LIMIT 1←[0m [["id", 24367]]
=> #<Patient id: 24367, vorname: "Hanz", nachname: "Schnitzel", birthday: "1961-06
-29", geschlecht: "1", drucken: nil, extraanrede: nil, extratext: nil, year: nil
, rund: nil, thisyear: nil, zuletzt: nil, strasse: "Lsu 15", ort: "W÷rth", pl
z: "93386", created_at: "2013-09-19 16:37:28", updated_at: "2013-09-19 16:37:28"
>
irb(main):006:0> b = Patient.birthday
irb(main):007:0> d = c.birthday
=> Thu, 29 Jun 1961
irb(main):009:0> month = d.month
=> 6
irb(main):010:0> day = d.mday
=> 29
irb(main):011:0> year = Date.today.year
=> 2013
irb(main):012:0> neu = Date.new(year,month,day)
=> Sat, 29 Jun 2013
You are trying to instantiate a Date object with invalid parameters, e.g.
Date.new(2013,99,1)
# => ArgumentError: invalid date
You problem is most likely not the code presented above, but the data you are inserting. Check your database for records that have invalid dates.
You can prevent such errors by presenting an error to the user upon submission when the date is invalid. In order to acheive that, you could add a validation rule to your model e.g.
class Patient < ActiveRecord::Base
validate :birthday, if:->(d){ Date.valid_date?(d) }
end

Rake Tagging from CSV task to much for SQLite?

I have a rake task that foreach through a CSV file of IDs. I then query the database to see if that id exists in my database. if it exists i use act_as_taggable to add a tag. the script runs fine until it gets to the first match and attempts to write a tag to my SQLite DB. and i get a database locked error. I am wondering if i am hitting the I/O limit of SQLite and need to switch over to a fullblown MySQL db.
if defined?(Rails) && (Rails.env == 'development')
Rails.logger = Logger.new(STDOUT)
end
require 'csv'
desc "Tag Voters that early voted from the Secretary of State Website"
task :tag_early => [:environment] do
file ="db/AllAbsentees.csv"
CSV.foreach(file, :headers=> true) do |row|
#voter_id = row[1]
puts "Working on Row" + row[1]
#voter = Voter.where(:state_id => #voter_id).first()
unless #voter.blank?
#voter.tag_list = "2012_GEN_EARLY_VOTER"
#voter.save()
puts "Voter #" + #voter_id + "tagged"
end
end
end
Voter Load (1.2ms) SELECT "voters".* FROM "voters" WHERE "voters"."state_id" = '00008030' LIMIT 1
ActsAsTaggableOn::Tag Load (0.2ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = 11944 AND "taggings"."taggable_type" = 'Voter' AND (taggings.context = 'tags' AND taggings.tagger_id IS NULL)
(0.1ms) begin transaction
(0.4ms) UPDATE "voters" SET "updated_at" = '2012-11-23 00:02:33.438114' WHERE "voters"."id" = 11944
ActsAsTaggableOn::Tag Load (0.1ms) SELECT "tags".* FROM "tags" WHERE (lower(name) = '2012_gen_early_voter')
ActsAsTaggableOn::Tag Load (0.2ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = 11944 AND "taggings"."taggable_type" = 'Voter' AND (taggings.context = 'tags' AND taggings.tagger_id IS NULL)
ActsAsTaggableOn::Tagging Exists (0.1ms) SELECT 1 AS one FROM "taggings" WHERE ("taggings"."tag_id" = 19 AND "taggings"."taggable_type" = 'Voter' AND "taggings"."taggable_id" = 11944 AND "taggings"."context" = 'tags' AND "taggings"."tagger_id" IS NULL AND "taggings"."tagger_type" IS NULL) LIMIT 1
SQL (1.4ms) INSERT INTO "taggings" ("context", "created_at", "tag_id", "taggable_id", "taggable_type", "tagger_id", "tagger_type") VALUES (?, ?, ?, ?, ?, ?, ?) [["context", "tags"], ["created_at", Fri, 23 Nov 2012 00:02:33 UTC +00:00], ["tag_id", 19], ["taggable_id", 11944], ["taggable_type", "Voter"], ["tagger_id", nil], ["tagger_type", nil]]
(5053.1ms) commit transaction
SQLite3::BusyException: database is locked: commit transaction
(99.7ms) rollback transaction
rake aborted!
SQLite3::BusyException: database is locked: commit transaction
SQLite has not much concurrency; for a transaction to write to the DB, there must be no other reading or writing connections.
Ensure that no other program is reading from or writing to the DB at the same time, and that all DB accesses in your own program use the same database connection.

Resources