Rails throwing "Invalid Date" - ruby-on-rails

I'm importing a csv file into pg database, and am getting this error that I never got before upgrading to Rails 5
def self.assign_from_row(row)
member = Member.where(membership_id: row[:membership_id]).first_or_initialize
member.assign_attributes row.to_hash.slice(
:last_name, :first_name, :membership_id, :email
).merge(
:birthday => row[4].nil? ? nil : DateTime.strptime(row[4], "%m/%d/%Y").strftime("%Y/%m/%d")
)
member
end
The exact error is with the birthday line. When I remove from .merge to ), things work. The csv headers look like this:
"Last Name","First Name","Membership Id","E-mail","Birthday"
and the rows look like this:
"Aber","Barbara","00591 2","bab#example.com","07/05/2015"
I have also tried this line
:birthday => DateTime.strptime("%m/%d/%Y").strftime("%Y/%m/%d")
I believe the problem lies in the fact that many of the birthdays are populated with "", or are nil and I don't know how to tell the database to insert nil when it is expecting a date format. Any help most appreciated

You can rescue nil if the date conversion fails.
:birthday => (DateTime.strptime(row[4], "%m/%d/%Y").strftime("%Y/%m/%d") rescue nil)
In line rescue is generally not recommended as it can mask other raised exceptions (for example, if you accidentally typed DateTim instead of DateTime you wouldn't spot the problem) but used with care, in line rescue is a useful tool.

I had to go back to the csv original, open it in Office Calc and change the cells containing the date as a string. I first changed the column to a number, then a date with a format to match the sql format '2015-02-23' and it imported just fine. Thanks everybody!
So it wasn't a Rails issue to begin with, but rather the misidentification of the column. I actually had to go through some hoops in Calc to do it. I had to change the column to a number first, then to a date.

Related

You're trying to create an attribute in Rails 3.2.5

I have a DI routine where I have a large csv I'm importing with known column format. I first set up a column map:
col_map =
{
4 => :name,
6 => :description,
21 => :in_stock,
...
I then read each line in, and then using the column map, attempt to set the attribute:
i = Item.new
col_map.each do |k,v|
i[v] = chunks[k] #chunks is the line read in split by the delimiter
In my item declaration, I declare two attributes, b/c these are not stored in the database, they're used for other logic:
attr_writer :in_stock
attr_writer :end_date
When the code gets to this line:
i[v] = chunks[k]
I get this message:
X.DEPRECATION WARNING: You're trying to create an attribute `in_stock'. Writing arbitrary attributes on a model is deprecated. Please just use `attr_writer`
But I'm not trying to create an attribute, and I am using attr_writer. I suspect this has something to do with the [] I'm using instead of . for the lvalue.
Can anyone see what I'm doing wrong?
Thanks for any help,
Kevin
Admittedly, the deprecation wording is slightly confusing, but you're seeing this warning because the model[attribute_name] = ... style is only supported for ActiveRecord attributes on the model, not non-persisted attributes added with attr_writer.
You can see the code that produces the warning over here.
To address this I'd use send which will work for all attributes e.g.
i.send("#{v}=", chunks[k])

Returning a virtual attribute in a select query

Part of an application I'm building is an API. Recent changes mean that I need to put two different versions of the data into my json feed. I think the best way to do this is to make the necessary changes in the database then create a virtual attribute to concatenate the data.
In my model I have the event_summary virtual attribute which there's no issue with outputting in views using <%= #event.event_summary =>:
def event_summary
"#{title} (#{start_datetime.strftime('%A %d %b, %l:%M%P')})"
end
In my API controller I have a select query which gets the attributes I need for the API response (simplified version here):
respond_to :json
def index
respond_with Event.select('title, event_summary')
end
The problem is that it always returns an undefined column error:
PG::UndefinedColumn: ERROR: column "event_summary" does not exist LINE 1: SELECT title, event_summary FROM "events"
I also tried the following but got the same result:
respond_with Event.select('title', :event_summary)
I'm using Postgres if that makes any difference.
Is there a way to do this?
Thanks!
You can't use virtual attributes in the select method because it will be turned into a SQL statement and that field doesn't exist in your table. You could however, do the concatenation and date formatting in SQL:
Event.select('title, (title || ' ' || to_char(start_datetime, 'HH12:MI:SS')) as event_summary')
That will in effect create a "virtual attribute" but in sql land and it will return events with that attribute present and formatted by the to_char postgres method. The date formatting isn't exactly what you had, so you'll need to tweak it to your needs (by changing the format string 'HH12:MI:SS') as detailed in the docs here: http://www.postgresql.org/docs/8.2/static/functions-formatting.html
Hope that helps.
I think this might do what you want:
def index
render :json => Event.all.to_json :methods => :event_summary
end
[EDIT]
IIRC respond_with doesn't work with to_json, so you have to use render

How to convert a string "560,000" into an integer 560000 when importing from csv into database in Rails

just started learning Rails and have managed to import a csv file into a database, but the price field in the csv has quotes and a comma like this: "560,000"
But if I make the price field as t.integer in the migration file, then add the data, the price gets imported as 560. So, how do I remove the quotes and the comma before importing it? thanks, Adam
edit: here's the rake file:
require 'csv'
task :csv_to_properties => [:environment] do
CSV.foreach("lib/assets/cbmb_sale.csv", :headers => true) do |row|
Property.create!(row.to_hash)
end
end
Try something like:
csvvalue = csvvalue.gsub!(/,/,'').to_i
Cheers!
Thanks for posting your code. I don't do a ton with converting csv's to hashes but something like this will probably work:
Property.create!(row.to_hash.each_pair{|k,v| row.store(k,v.gsub(/,/,'').to_i)})
Pretty ugly but probably pretty close to what you want.
In your code example, assuming the price field is in row element 4:
CSV.foreach("lib/assets/cbmb_sale.csv", :headers => true) do |row|
row[price=4].gsub!(/,/,'')
Property.create!(row.to_hash)
end
The price=4 is just a handy way to document the index value of the price element, it creates a variable called price assigns the value 4 to it, then immediately uses it as the array index.
Since Property.create! is already taking care of the string to integer conversion, we can perform an in-place substitution for the regular expression that contains a comma /,/ for an empty string ''.
Try:
"220,000".scan(/\d+/).join().to_i

Saving values from a time select into a string

I want to have a drop down time select that saves as a string but I keep getting the following exception: 1 error(s) on assignment of multiparameter attributes. Here is the code that I have written:
<%= f.time_select :appointment_time, :minute_step => 5, :ignore_date => true %>
When I submit the form I get the following values in a params hash
"appointment_time(4i)"=>"12", "appointment_time(5i)"=>"00"
Is there any way to easily grab those two values and save the result as "12:00:00"?
Assuming that there are more than just the appointment keys in the params hash:
params[:your_model].select {|k,v| k =~ /^appointment_time/ }.
sort.map {|f| f.last.to_s }.join(':')
Of course, this also assumes that the appointment_time field in your table isn't a DateTime or Date field. You'd have to do this before updating the (assumed) ActiveRecord model instance's attributes. There are simpler ways to do this, but without seeing the code and backtrace it's tough to know whether they'd work.
Edit
processed = params.dup
processed[:your_model][:appointment_time] = params[:your_model].
select {|k,v| k =~ /^appointment_time/ }.sort.map {|f| f.last.to_s}.join(':')
#model.update_params(processed[:your_model])
That's probably more along the lines of what you're looking for.
I solved my problem by creating different columns for each value of the time as strings, and then joining everything together after creation. This is not the most elegant solution, but it works for my application

Ruby on Rails - Currency : commas causing an issue

Looking on SO, I see that the preferred way to currency using RoR is using decimal(8,2) and to output them using number_to_currency();
I can get my numbers out of the DB, but I'm having issues on getting them in.
Inside my update action I have the following line:
if #non_labor_expense.update_attributes(params[:non_labor_expense])
puts YAML::dump(params)
The dump of params shows the correct value. xx,yyy.zz , but what gets stored in the DB is only xx.00
What do I need to do in order to take into account that there may be commas and a user may not enter .zz (the cents). Some regex and for comma? how would you handle the decimal if it were .2 versus .20 .
There has to be a builtin or at least a better way.
My Migration (I don't know if this helps):
class ChangeExpenseToDec < ActiveRecord::Migration
def self.up
change_column :non_labor_expenses, :amount, :decimal, :precision => 8, :scale => 2
end
def self.down
change_column :non_labor_expenses, :amount, :integer
end
end
I tried Daniel's before_validation idea and I just couldn't get it to work. It seemed that the by the time I get to the before_validation the input has already been converted. The solution I went with was to override the method for the column, and strip the commas there:
def profit=(num)
num.gsub!(',','') if num.is_a?(String)
self[:profit] = num
end
It might depend on what DBMS you're using, but as far as I know, decimal fields won't accept commas (at least not as separators; there might be a way to have the database accept a comma as a decimal point rather than a period). What you will have to do is remove the commas from your numbers (in a before_save or before_validation filter, perhaps), and then when you display the number, add the commas back in.
before_validation :strip_commas_from_non_labor_expense
def strip_commas_from_non_labor_expense
self.non_labor_expense = self.non_labor_expense.to_s.gsub(/,/, '').to_f
end
Then use number_to_currency when you want to display the expense amount formatted with comma separated groups and two decimal places, as you mentioned:
<%
non_labor_expense = ... # get value from your model
puts number_to_currency(non_labor_expense, :precision => 2, :separator => ',')
%>
Checkout the delocalize gem:
http://github.com/clemens/delocalize
Here you can find a code snippet that will make any decimal column accept values with the comma as decimal separator:
http://gem-session.com/2010/03/how-to-use-the-comma-as-decimal-separator-in-rails-activerecord-columns-and-text-fields

Resources