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
How's this possible?
Time.now.utc.to_date + 1.month + 15.days #=> Mon, 01 Dec 2014
Time.now.utc.to_date + 15.days + 1.month #=> Sun, 30 Nov 2014
Has anyone seen it?
/edit
I guess I asked the question a wrong way. How do you guys explain this then?
Time.now.utc.to_date + (15.days + 1.month) #=> Mon, 08 Dec 2014
Time.now.utc.to_date + (1.month + 15.days) #=> Tue, 09 Dec 2014
(15.days + 1.month) #=> 3888000
(1.month + 15.days) #=> 3888000
First let see Integer#month, it returns an instance of ActiveSupport::Duration. At the rails console:
~/rails/rfinan (1296000):1 > elapsed = 1.month
=> 2592000
~/rails/rfinan (1296000):1 > elapsed.value
=> 2592000
~/rails/rfinan (1296000):1 > elapsed.parts
=> [[:months,1]]
~/rails/rfinan (1296000):1 > elapsed.is_a? ActiveSupport::Duration
=> true
It's time for the method: ActiveSupport::Duration#+
~/rails/rfinan (1296000):1 > sum1 = 1.month + 15.days
=> 3888000
~/rails/rfinan (1296000):1 > sum2 = 15.days + 1.month
=> 3888000
~/rails/rfinan (1296000):1 > sum1.value
=> 3888000
~/rails/rfinan (1296000):1 > sum1.parts
=> [[:months,1],[:days,15]]
~/rails/rfinan (1296000):1 > sum2.value
=> 3888000
~/rails/rfinan (1296000):1 > sum2.parts
=> [[:days,15],[:months,1]]
~/rails/rfinan (1296000):1 > sum1 == sum2
=> true
~/rails/rfinan (1296000):1 > sum1.value == sum2.value
=> true
~/rails/rfinan (1296000):1 > sum1.parts == sum2.parts
=> false
Now Date#+, the ActiveSupport version.
def plus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
other.since(self)
else
plus_without_duration(other)
end
end
alias_method :plus_without_duration, :+
alias_method :+, :plus_with_duration
That means: if I send :+ to a Date instance with a ActiveSupport::Duration instance as parameter, it calls ActiveSupport::Duration#since, and the last one calls ActiveSupport::Duration#sum, that injects over the date instance, and calls Date#advance on each of the parts of duration instance:
def sum(sign, time = ::Time.current) #:nodoc:
parts.inject(time) do |t,(type,number)|
if t.acts_like?(:time) || t.acts_like?(:date)
if type == :seconds
t.since(sign * number)
else
t.advance(type => sign * number)
end
else
raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
end
end
end
Remmember sum1.parts != sum2.parts?, sum send advance to the date instance orderly. Let see what means Date#advance
def advance(options)
options = options.dup
d = self
d = d >> options.delete(:years) * 12 if options[:years]
d = d >> options.delete(:months) if options[:months]
d = d + options.delete(:weeks) * 7 if options[:weeks]
d = d + options.delete(:days) if options[:days]
d
end
When advance recive month: 1 it calls Date#>> from stdlib, that work diferently of ActiveSupport::Duration#+. At irb:
~ (main) > Date.new(2014,10,31) >> 1
=> #<Date: 2014-11-30 ((2456992j,0s,0n),+0s,2299161j)>
~ (main) > Date.new(2014,10,31) >> 2
=> #<Date: 2014-12-31 ((2457023j,0s,0n),+0s,2299161j)>
~ (main) > Date.new(2014,10,31) >> 3
=> #<Date: 2015-01-31 ((2457054j,0s,0n),+0s,2299161j)>
~ (main) > Date.new(2014,10,31) >> 4
=> #<Date: 2015-02-28 ((2457082j,0s,0n),+0s,2299161j)>
~ (main) > Date.new(2014,10,31) >> 5
=> #<Date: 2015-03-31 ((2457113j,0s,0n),+0s,2299161j)>
~ (main) > Date.new(2014,10,31) >> 12
=> #<Date: 2015-10-31 ((2457327j,0s,0n),+0s,2299161j)>
~ (main) > Date.new(2014,10,31) >> 1200
=> #<Date: 2114-10-31 ((2493486j,0s,0n),+0s,2299161j)>
~ (main) > Date.new(2014,10,31) >> 12000
=> #<Date: 3014-10-31 ((2822204j,0s,0n),+0s,2299161j)>
It's clear that Date#>> don't add days, add months and keep the day number. if the day isn't valid for the target month, it fixes it. Adding a fix number of months doesn't fixes the number of days added, because depend on the start date.
Now we can say that Date#+ is not the same of ActiveSupport::Duration#+, and we know why.
The anwer is Date#+ called with an ActiveSupport::Duration instance (say duration) doesn't care about duration.value, it uses duration.parts, which are different in each case.
October has 31 days, November does not. This means that it depends a little on how you calculate the 31st + 1 Month.
For the first example:
Now + 1 Month = 16-Nov
16-Nov + 15 days = 1-Dec
For the second example:
Now + 15 days = 31-Oct
31-Oct + 1 Month = 30-Nov
October has 31 days. When you add 15 days to Oct 16 you get Oct 31. Adding a month carries you to the same date on the next month - Nov. 31, but there is no Nov. 31 so it takes you to Nov 30.
If instead you add the month first, that carries you to Nov 16. Then adding 15 days carries you to Dec 01.
When you do:
(15.days + 1.month) #=> 3888000
(1.month + 15.days) #=> 3888000
You are not operating dates, you are operating seconds (Rails Numeric < Object). To prove, let's convert it back to days:
> 3888000 / 60 / 60 / 24
=> 45
45 = 30 + 15. So we know that, when operating seconds, or days, the compiler interprets 1.month as 30 days by default when operating Numerics. See numerics reference:
http://api.rubyonrails.org/classes/Numeric.html#method-i-seconds
As you can see in the link above, when you operate Dates with Numerics, rails calls the advance(options) method which is responsible for executing correct Date operations. See advance definition on github:
https://github.com/rails/rails/blob/ffc273577a795bb41068bfc2a1bb575ec51a9712/activesupport/lib/active_support/core_ext/time/calculations.rb#L99
Also, when operating dates using Time.now.utc.to_date + (1.month + 15.days) the + () function will actually call the advance(options) method like this:
(Time.now.utc.to_date.advance(month:1)).advance(days:15) #fistCase
when you use Time.now.utc.to_date + (15.days + 1.month), what will be called is this:
(Time.now.utc.to_date.advance(days:15)).advance(month:1) #secondCase
So, lets test #firstCase:
oct16 = Date.new(2014, 10, 16)
> oct16 + (1.month + 15.days)
=> Mon, 01 Dec 2014
> (oct16.advance(months:1)).advance(days:15)
=> Mon, 01 Dec 2014
The #firstCase conclusion is, it calls advance(month:1) resulting Nov-16, then it calls .advance(days:15) on Nov-16 and goes to Dez-01
Let's check the #secondCase:
> oct16 + (15.days + 1.month)
=> Sun, 30 Nov 2014
> (oct16.advance(days:15)).advance(months:1)
=> Sun, 30 Nov 2014
The #secondCase conclusion is, it calls advance(days:15), which results in Oct-31, than it calls advance(months: 1) on the last result, which would give us Nov-31, but wait! Nov-31 does not exist! So the interpreter is smart enough to understand that, since you were on the last day of the month(Oct-31), when you add 1.month, or advance(months:1), you are asking him to take you to the last day of the next month, in that case Nov-30.
That's the convention.
A 'page' may have_many 'sections'. So I want to retrieve the 'sections.title' for each 'sections' that a page contains. I don't understand why my block Do doesn't work but this works sections.each { |n| p n.id }
2.0.0-p353 :041 > pages = Page.find(52).sections.count
Page Load (0.3ms) SELECT `pages`.* FROM `pages` WHERE `pages`.`id` = 52 LIMIT 1
(0.3ms) SELECT COUNT(*) FROM `sections` WHERE `sections`.`page_id` = 52
=> 2
2.0.0-p353 :042 > pages =Page.find(52).sections
Page Load (0.3ms) SELECT `pages`.* FROM `pages` WHERE `pages`.`id` = 52 LIMIT 1
Section Load (0.2ms) SELECT `sections`.* FROM `sections` WHERE `sections`.`page_id` = 52
=> #<ActiveRecord::Associations::CollectionProxy [#<Section id: 20, title: "La victoire est proche", body: "Guinsly t'e le meilleur Oat cake sweet roll browni...", page_id: 52, created_at: "2014-01-22 01:40:14", updated_at: "2014-01-22 01:40:14">, #<Section id: 36, title: "La victoire est proche", body: "Guinsly t'e le meilleur Oat cake sweet roll browni...", page_id: 52, created_at: "2014-01-22 01:40:15", updated_at: "2014-01-22 01:40:15">]>
2.0.0-p353 :044 > pages.each do |n|
2.0.0-p353 :045 > p.title
2.0.0-p353 :046?> end
NoMethodError: undefined method `title' for nil:NilClass
or
...
2.0.0-p353 :055 > Page.find(52).sections.each { |n| p n.id }
Page Load (0.3ms) SELECT `pages`.* FROM `pages` WHERE `pages`.`id` = 52 LIMIT 1
Section Load (0.3ms) SELECT `sections`.* FROM `sections` WHERE `sections`.`page_id` = 52
20
36
Your each block is looking for you to use n not p...
pages.each do |n|
n.title
end
Though p makes more sense, you might use p instead of n in both places.
Or are you trying to print the value using the p method? Then:
pages.each do |page|
p page.title
end
I can't figure out why this object keeps coming up as nil.
Here is the error:
1) Item Calculate with just Total
Failure/Error: subject.calculate_tax(tax, sub_category)
TypeError:
nil can't be coerced into Fixnum
# ./app/models/item.rb:111:in `+'
# ./app/models/item.rb:111:in `calculate_tax'
# ./spec/models/item_spec.rb:26:in `block (2 levels) in <top (required)>'
Here is the line it applies to - it thinks "self.tax_rate" is nill (second last argument)
self.tax_amount = ((self.total - self.deduction) - ((self.total - self.deduction) / (1 + self.tax_rate))) * self.tax_adjustment
Here is my Test
describe Item do
subject {Item.new(:report_id => 26 , :name => 'Gas' ,:tax_rate => 0.13, :tax_id => 1 , :category_id => 15 , :sub_category_id => 31 , :job_id => 1 , :total => 20 )}
let(:tax) {Tax.where(id: subject.tax_id).first}
let(:sub_category) {SubCategory.where(id: subject.sub_category_id).first}
it 'Calculate with just Total' do
subject.name.should be == 'Gas'
tax = Tax.find_by_id(subject.tax_id)
subject.sub_category_id.should be == 31
subject.set_nil_values
sub_category.should_receive(:taxable).exactly(3).times.and_return(sub_category.taxable)
tax.should_receive(:rate).exactly(4).times.and_return(tax.rate)
sub_category.should_receive(:tax_adjustment).and_return(sub_category.tax_adjustment)
subject.calculate_tax(tax, sub_category)
subject.should_receive(:tax_rate).exactly(2).times.and_return(tax.rate)
subject.calculate_cost
subject.cost.should be_within(0.01).of(17.70)
subject.tax_amount.should be_within(0.01).of(2.30)
subject.save
end
Your taxes table in your test database appears not to have an entry with id equal to 1 whose tax.rate is not nil
I get a sitenum undefined method error. Problem is I am trying to target site_id and student_number and then increment value of student_number by 1 based on site_id. So there will be at least two records with a 2001 value for student_number if there are two site_ids (e.g. site_id 1, site_id 2). And if a value of 2001 exists for that site, then I would like to increment by 1 so next student_number for that site will be 2002, 2003, etc.
Student Model:
:student_number =>
sitenum = self.site_id
count = Student.count_by_sql("SELECT MAX(student_number) FROM students WHERE site_id = #{sitenum}")
if count >= 2001
Student.sitenum(:order => "student_number DESC").student_number + 1
else
2001
end
Any response would be greatly appreciated.
I understood nothing from a description, but suppose, you want this that way:
:student_number =>
sitenum = self.site_id
count = Student.count_by_sql("SELECT MAX(student_number) FROM students WHERE site_id = #{sitenum}")
if count >= 2001
Student(:condition => { :site_id => sitenum },
:order => "student_number DESC").student_number + 1
else
2001
end