Mechanize gem string to decimal over 1000 issue - ruby-on-rails

I'm trying to use mechanize to pull some prices and I'm to the point where I can handle anything priced under $1,000 but as soon as anything hits above $1,000, the result is that I lose any integers after the thousands integer. For instance, $1,234.99 becomes $1.00.
This is what I have in my controller:
product_price = page.search(store.price_selector).first.text.match(/\b\d[\d,.]*\b/)
product = Product.create!(
price: product_price
...
)
Price is a decimal i.e.
:price, :decimal, :precision => 8, :scale => 2
Displaying in my Product view as such:
<h4><%= number_to_currency(product.price, :unit => "$", :separator => ".") %></h4>
Here is what I tried to use as a fix in the controller - converting to a string first:
product_price_str = page.search(store.price_selector).first.text.match(/\b\d[\d,.]*\b/).to_s
product_price_str = product_price_str.split(".")[0]
product_price = product_price_str.scan(/\d/).join('')
This solved the problem, only to create another - Now any price with a number other than zero for the decimal is rounded down to zero no matter what. For instance, 1,800.99 is displaying as 1,800.00
I know it's an easy answer (at least I hope), but I'm so new to this whole specific combination and rails in general so any help you've got would be much appreciated.

I'm not sure I understand your issue completely, but why not tweak your regex on data import so you have the price and only the price in a format suitable for inserting into your database?
> s = "or instance, $1,234.99 beco"
=> "or instance, $1,234.99 beco"
> s.match(/\$\d[\d,.]+/).to_s.gsub(/[^\d.]/, '')
=> "1234.99"

Here is what I did in the controller to solve the issue, at least for now:
product_price_str = page.search(store.price_selector).first.text.match(/\b\d[\d,.]*\b/).to_s
product_price_str = product_price_str.split(".")[0]
product_price = product_price_str.scan(/\d/).join('')
Changed to
product_price_str_decimal = "00"
product_price_str_decimal = product_price_str.split(".")[1] if product_price_str.split(".")[1]
product_price_str = product_price_str.split(".")[0]
product_price = product_price_str + "." + product_price_str_decimal

Related

Function return error in clause having ruby on rails

Good afternoon!
I have a function where she does the bank reconciliation with a .txt of the bank with the system, where she shows the number of the launch whose value is exactly equal to the one launched in the system, the problem is that there is 1 debit in the .txt that corresponds to ha several launches in the system of the same client id, in .txt I do not have the client id but the sum of the values ​​launched corresponds exactly to the debit, is there any way to do this query? ... I am trying to use the in the variable sum_lancamentos having for me returns the ids but is generating synthase error. and I believe that all this function could be improved, I just don’t know how, I’m a new RoR and I’m still getting used to good practices.
Any help is very life and thank you in advance!
def conciliacao
#conciliacao = session[:conciliacao_file]
comparacao = {}
#conciliacao.each do |key, line|
data = line[:data]
valor = line[:valor].to_f.round(2)
if line[:mov] == "D"
despesa = true
else
despesa = false
end
lancamentos = Lancamento.ativos.pagos.where(conta_id: params[:conta_id], despesa: despesa).where("lancamentos.data_pagamento BETWEEN '#{data.to_date.beginning_of_day.to_s(:db)}' AND '#{data.to_date.end_of_day.to_s(:db)}'").where(["cast(lancamentos.valor AS NUMERIC(15,2) ) = :value or
cast(lancamentos.valor_pago AS NUMERIC(15,2)) = :value ",
{ value: line[:valor] }])
unless lancamentos.blank?
lancamentos.each do |lancamento|
#puts line
#conciliacao[key][:lancamentos] = "#{lancamento.id}"
#conciliacao[key][:status] = 1
#conciliacao[key][:color] = "green lighten-4"
end
else
sum_lancamentos = Lancamento.ativos.pagos.group(:cliente_id).where(conta_id: params[:conta_id], despesa: despesa).where("lancamentos.data_pagamento BETWEEN '#{data.to_date.beginning_of_day.to_s(:db)}' AND '#{data.to_date.end_of_day.to_s(:db)}'").having(["sum(cast(lancamentos.valor AS NUMERIC(15,2) )) = :value or sum(cast(lancamentos.valor_pago AS NUMERIC(15,2))) = :value ", { value: line[:valor] }])
unless sumlancamentos.blank?
#conciliacao[key][:lancamentos] = "#{sum_lancamentos.ids}"
#conciliacao[key][:status] = 1
#conciliacao[key][:color] = "green lighten-4"
end
end
end
session.delete(:conciliacao_file)
end

PRAWN - Two variables in same column

I wish to have a Prawn PDF which will contain a column containing two variables. Here is my table definition:
def transactions_table
grid([3,0], [14,3]).bounding_box do
data = [%w(Date Description Amount)]
data += #rows.map{|r| [r.value_date, r.description, r.amount, r.credit]}
options = { header: true, width: 520,
column_widths: {0 => 100, 2 => 200},
row_colors: ['EEEEEE', 'FFFFFF']}
table(data, options) do
cells.padding = 5
cells.border_width = 0.5
cells.border_color = BLACK
row(0).font_weight = 'bold'
row(0).border_color = BLACK
column(2).align = :right
end
end
end
As you can see there are four columns value_date, description, amount and credit. I want to have amount and credit in the same column, however, I'm not sure how to do this. Simply removing the comma does not work. Is there a joiner could someone fill me in. Thanks.
You can combine the two values in one string, for example with string interpolation.
data += #rows.map{|r|
[r.value_date, r.description, "#{r.amount} #{r.credit}"]
}
You can join two strings with the + operator. I'm not sure if the values you want to join are strings, but you can make sure they are with the to_s method:
data += #rows.map{|r| [r.value_date, r.description, r.amount.to_s + ' ' + r.credit.to_s]}
The best solution I could come up with is was to remove borders:
column(3).borders = [:top, :right, :bottom]
column(2).borders = [:top, :left, :bottom]

Surrounding cents in <sup> tag using Rails number_to_currency

How can I isolate cents and place them inside their own element? The output I'm looking for is this:
<sup>$</sup>20<sup>99</sup>
Notice there is no delimiter to separe the decimal units, and they're contained in their own sup tag. I know how to get <sup>$</sup>20.99 when using format: '<sup>%u</sup>%n', but this does not give me a way to isolate cents.
Any ideas?
You are going to have to do it with substitution regex or something similar.
20.99.number_to_currency.sub(/\^([^\d]+)(\d+)([^\d]+)(\d+)/,
'\1<sup>\2</sup>\3<sup>\4</sup>')
I personally use this method, it allow me to support I18n properly but also to only use the <sub> container when I want the number displayed in HTML.
def formated_price(price, currency, options = {})
html = options[:html].nil? ? false : options[:html]
money = number_to_currency(price, unit: currency) || h('')
if html
separator = I18n.t('number.currency.format.separator')
tmp = money.split(separator)
tmp[1] = tmp[1].sub(/\A(\d+)(.*)\z/, content_tag(:sup, separator + '\1') + '\2') if tmp[1]
money = tmp.join.html_safe
end
money
end
if you like your currency unit to be in <sup> as well when using HTML, you could use this instead:
def formated_price(price, currency, options = {})
html = options[:html].nil? ? false : options[:html]
if html
money = number_to_currency(price, unit: content_tag(:sup, currency)) || h('')
separator = I18n.t('number.currency.format.separator')
tmp = money.split(separator)
tmp[1] = tmp[1].sub(/\A(\d+)(.*)\z/, content_tag(:sup, separator + '\1') + '\2') if tmp[1]
money = tmp.join.html_safe
else
number_to_currency(price, unit: currency) || h('')
end
end
If you find any issue, please let me know.

Using scope to return results within multiple DateTime ranges in ActiveRecord

I've got a Session model that has a :created_at date and a :start_time date, both stored in the database as :time. I'm currently spitting out a bunch of results on an enormous table and allowing users to filter results by a single date and an optional range of time using scopes, like so:
class Session < ActiveRecord::Base
...
scope :filter_by_date, lambda { |date|
date = date.split(",")[0]
where(:created_at =>
DateTime.strptime(date, '%m/%d/%Y')..DateTime.strptime(date, '%m/%d/%Y').end_of_day
)
}
scope :filter_by_time, lambda { |date, time|
to = time[:to]
from = time[:from]
where(:start_time =>
DateTime.strptime("#{date} #{from[:digits]} #{from[:meridian]}", '%m/%d/%Y %r')..
DateTime.strptime("#{date} #{to[:digits]} #{to[:meridian]}", '%m/%d/%Y %r')
)
}
end
The controller looks more or less like this:
class SessionController < ApplicationController
def index
if params.include?(:date) ||
params.include?(:time) &&
( params[:time][:from][:digits].present? && params[:time][:to][:digits].present? )
i = Session.scoped
i = i.filter_by_date(params[:date]) unless params[:date].blank?
i = i.filter_by_time(params[:date], params[:time]) unless params[:time].blank? || params[:time][:from][:digits].blank? || params[:time][:to][:digits].blank?
#items = i
#items.sort_by! &params[:sort].to_sym if params[:sort].present?
else
#items = Session.find(:all, :order => :created_at)
end
end
end
I need to allow users to filter results using multiple dates. I'm receiving the params as a comma-separated list in string format, e.g. "07/12/2012,07/13/2012,07/17/2012", and need to be able to query the database for several different date ranges, and time ranges within those date ranges, and merge those results, so for example all of the sessions on 7/12, 7/13 and 7/17 between 6:30 pm and 7:30 pm.
I have been looking everywhere and have tried several different things but I can't figure out how to actually do this. Is this possible using scopes? If not what's the best way to do this?
My closest guess looks like this but it's not returning anything so I know it's wrong.
scope :filter_by_date, lambda { |date|
date = date.split(",")
date.each do |i|
where(:created_at =>
DateTime.strptime(i, '%m/%d/%Y')..DateTime.strptime(i, '%m/%d/%Y').end_of_day
)
end
}
scope :filter_by_time, lambda { |date, time|
date = date.split(",")
to = time[:to]
from = time[:from]
date.each do |i|
where(:start_time =>
DateTime.strptime("#{i} #{from[:digits]} #{from[:meridian]}", '%m/%d/%Y %r')..
DateTime.strptime("#{i} #{to[:digits]} #{to[:meridian]}", '%m/%d/%Y %r')
)
end
}
Another complication is that the start times are all stored as DateTime objects so they already include a fixed date, so if I want to return all sessions started between 6:30 pm and 7:30 pm on any date I need to figure something else out too. A third party is responsible for the data so I can't change how it's structured or stored, I just need to figure out how to do all these complex queries. Please help!
EDIT:
Here's the solution I've come up with by combining the advice of Kenichi and Chuck Vose below:
scope :filter_by_date, lambda { |dates|
clauses = []
args = []
dates.split(',').each do |date|
m, d, y = date.split '/'
b = "#{y}-#{m}-#{d} 00:00:00"
e = "#{y}-#{m}-#{d} 23:59:59"
clauses << '(created_at >= ? AND created_at <= ?)'
args.push b, e
end
where clauses.join(' OR '), *args
}
scope :filter_by_time, lambda { |times|
args = []
[times[:from], times[:to]].each do |time|
h, m, s = time[:digits].split(':')
h = (h.to_i + 12).to_s if time[:meridian] == 'pm'
h = '0' + h if h.length == 1
s = '00' if s.nil?
args.push "#{h}:#{m}:#{s}"
end
where("CAST(start_time AS TIME) >= ? AND
CAST(start_time AS TIME) <= ?", *args)
}
This solution allows me to return sessions from multiple non-consecutive dates OR return any sessions within a range of time without relying on dates at all, OR combine the two scopes to filter by non-consecutive dates and times within those dates. Yay!
An important point I overlooked is that the where statement must come last -- keeping it inside of an each loop returns nothing. Thanks to both of you for all your help! I feel smarter now.
something like:
scope :filter_by_date, lambda { |dates|
clauses = []
args = []
dates.split(',').each do |date|
m, d, y = date.split '/'
b = "#{y}-#{m}-#{d} 00:00:00"
e = "#{y}-#{m}-#{d} 23:59:59"
clauses << '(start_time >= ? AND start_time <= ?)'
args.push b, e
end
where clauses.join(' OR '), *args
}
and
scope :filter_by_time, lambda { |dates, time|
clauses = []
args = []
dates.split(',').each do |date|
m, d, y = date.split '/'
f = time[:from] # convert to '%H:%M:%S'
t = time[:to] # again, same
b = "#{y}-#{m}-#{d} #{f}"
e = "#{y}-#{m}-#{d} #{t}"
clauses << '(start_time >= ? AND start_time <= ?)'
args.push b, e
end
where clauses.join(' OR '), *args
}
So, the easy part of the question is what to do about datetimes. The nice thing about DateTimes is that they can be cast to times really easily with this:
CAST(datetime_col AS TIME)
So you can do things like:
i.where("CAST(start_time AS TIME) IN(?)", times.join(", "))
Now, the harder part, why aren't you getting any results. The first thing to try is to use i.to_sql to decide whether the scoped query looks reasonable. My guess is that when you print it out you'll find that all those where are chaining together with AND. So you're asking for objects with a date that is on 7/12, 7/13, and 7/21.
The last part here is that you've got a couple things that are concerning: sql injections and some overeager strptimes.
When you do a where you should never use #{} in the query. Even if you know where that input is coming from your coworkers may not. So make sure you're using ? like in the where I did above.
Secondly, strptime is extremely expensive in every language. You shouldn't know this, but it is. If at all possible avoid parsing dates, in this case you can probably just gsub / into - in that date and everything will be happy. MySQL expects dates in m/d/y form anyways. If you're still having trouble with it though and you really need a DateTime object you can just as easily do: Date.new(2001,2,3) without eating your cpu.

Split #blogs into three divs using size of description field as weight

I have a collection of Blog items.
#blogs = Blog.find(:all)
Each blog has a description textfield with some text. What I would like to do is splitting the #blogs objects into 3 divs, but with roughly the same characters in each column.
<div id="left">
#blog1 (653 characters)
</div>
<div id="center">
#blog2 (200 characters)
#blog5 (451 characters)
</div>
<div id="right">
#blog3 (157 characters)
#blog4 (358 characters)
#blog6 (155 characters)
</div>
I can't figure out how to do that without getting really complicated and probably inefficient.
So far I have thought about converting the description field (size) to % of total characters in the #blogs collection, but how do I match/split the elements, so that I get closest to 33% in each column - like a super simple tetris game :)
Any thoughts?
Here's a quick hack that isn't perfect, but might get you pretty close. The algorithm is simple:
Sort items by size.
Partition items into N bins.
Resort each bin by date (or other field, per your desired presentation order)
Here's a quick proof of concept:
#!/usr/bin/env ruby
# mock out some simple Blog class for this example
class Blog
attr_accessor :size, :date
def initialize
#size = rand(700) + 100
#date = Time.now + rand(1000)
end
end
# create some mocked data for this example
#blogs = Array.new(10) { Blog.new }
# sort by size
sorted = #blogs.sort_by { |b| b.size }
# bin into NumBins
NumBins = 3
bins = Array.new(NumBins) { Array.new }
#blogs.each_slice(NumBins) do |b|
b.each_with_index { |x,i| bins[i] << x }
end
# sort each bin by date
bins.each do |bloglist|
bloglist.sort_by! { |b| b.date }
end
# output
bins.each_with_index do |bloglist,column|
puts
puts "Column Number: #{column+1}"
bloglist.each do |b|
puts "Blog: Size = #{b.size}, Date = #{b.date}"
end
total = bloglist.inject(0) { |sum,b| sum + b.size }
puts "TOTAL SIZE: #{total}"
end
For more ideas, look up the multiprocessor scheduling problem.

Resources