Rails AR cannot update NULL datetime field - ruby-on-rails

I am using Rails 3.1. I have a database column t.datetime "end_at". If I enter a date at the time of object creation, then I can change the value (update) later. But If I leave it blank (NULL), I found that I cannot update it. I verified that the name and new value of the field are in the params. Why?
def update
begin
model = MyModel.find(params[:id])
model.update_attributes!(params[:my_model])
rescue ActiveRecord::RecordNotFound => e
#something
end
end

You need to make sure two things are true:
your params[:end_at] must contain a nil or a DateTime. Normally params do not come in as datetime.
your column should not be a TIMESTAMP (but this is probably not the case already)
Also investigate multiparameter attributes.

Related

Rails throwing "Invalid Date"

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.

Rails update attribute in Model

I want to update the :position_status in the model based on if :position_date which is equal Date.today, let say I have the :position_date which is Mon, 26 Oct 2017 stored in the database, if the Date.today is on that particular date, then update the :position_status
I have tried to change the position_date to today date to see if it will update or not, but the :position_date was not updated.
attr_accessor :update_position_status
def update_position_status
if Date.today == self.position_date
self.update_attribute(:position_status => 'Filled')
end
end
update_attribute(name, value)
http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update_attribute
update_attribute updates a single attribute and skips validations. update_attributes takes a hash of attributes and performs validations.
So the corrected code would be:
def update_position_status!
update_attribute(:position_status, 'Filled') if self.position_date.today?
end
The name should end with ! since it mutates (changes) the object.
However updating the records one by one is not a particularly scalable solution. Instead you want to select the all the records by date and do a mass update:
# MySQL
Thing.where("DATE(position_date)=CURDATE()")
.update_all(position_status: 'Filled')
# Postgres
Thing.where("date_trunc('day', position_date) = current_date()")
.update_all(position_status: 'Filled')
Yes update_attribute requires two arguments.
The correct syntax is:
self.update_attribute(:position_status, 'Filled')

Rails Save Date Range to Database

I have a form field that posts a date range in the format "05/14/2013 - 05/22/2013". My model has two separate date fields, begin and end for the respective beginning and end dates from the form field. What is the best MVC way to approach getting the date range into the correct fields?
I've been trying to manually deconstruct the date range in the controller's create method, but it looks like the updated params aren't properly seen in the model before the record is created.
EDIT:
The date range is coming in that format because I'm using Keith Wood's datepick, and it outputs the dates in a single input field.
What I've been trying to do currently is this (contract is the name of my model, and dates is the input date range:
beginDate = params[:dates].split("-")[0].strip()
endDate = params[:dates].split("-")[1].strip()
params.delete :dates
params[:contract][:begin] = Date.strptime(beginDate, '%m/%d/%Y')
params[:contract][:end] = Date.strptime(endDate, '%m/%d/%Y')
#contract = Contract.new(params[:contract])
... but these changes to params don't show up by the time the record is created and validated.
Define a setter on your model which takes the field, splits it, and puts each part into the appropriate field.
def date_range=(val)
begin_str, end_str = val.split(' - ')
self.begin_at = Date.parse(begin_str)
self.end_at = Date.parse(end_str)
end
This will work when called directly, or from a mass assignment method such as update_attributes or create. Make sure you add date_range (or the relevant param name) to attr_accessible if you already have this defined in your model.
I've changed the field names in my example, asbegin and end should be avoided as field names, since one is a method and the other is part of the ruby syntax.
You can use virtual attributes to make the conversion from the date range text into individual dates at the model level. Add a setter and getter as below in the model,
def date_range_text
return "#{start_date.to_s} - #{end_date.to_s}"
end
def date_range_text= val
start_date_text,end_date_text = val.split[" - "]
start_date = Time.zone.parse(start_date_text) unless start_date_text.nil?
end_date = Time.zone.parse(end_date_text) unless end_date_text.nil?
end
And use data_range_text in your forms. For me information, check out the railscast below.
http://railscasts.com/episodes/16-virtual-attributes-revised
This is the best way to handle the difference between database structure and the user input forms.

how to remove a field from dynamic attributes table if value is blank means?

i am using gem dynamic attributes for dynamically creating header and values for one table
additional_fields is a model where i am using dynamic attributes
class AdditionalField < ActiveRecord::Base
has_dynamic_attributes :destroy_dynamic_attribute_for_nil => true
belongs_to :user
end
in additional field table in dynamic_attributes column all the data will be stored in hash like
{"field_blood_group" => "B positive","field_age" => "22"}
while creating if value is blank for any field means it wont get added to table
but the problem is while editing the user and updating if i remove the value for those fields means it is not getting removed from the table and it is saved in database like
{"field_blood_group" => "B positive","field_age" => ""}
instead i want to remove the field having null value and it should be stored as
{"field_blood_group" => "B positive"}
i don't know how to do this i got struck in this
can any one help me on this
i find a solution for this
before updating the table now i am checking whether the value is blank or not if the value is blank means i am replacing the value by nil and its getting removed from the database
params[:additional_field] = {"field_blood_group" => "B positive","field_age" => ""}
for field in params[:additional_field]
field[1] = field[1].blank? ? nil : field[1]
#user.additonal_field.update_attribute(field[0],field[1])
end
but still i am confused about this working principle bcoz while creating i am not checking whether its value is blank or not
while creating it is not getting created in database if value is null means.after creating if we remove the value means its not removing from database so i am manually giving null for the value now its getting removed
it will be useful
Thank you
You are probably getting an empty string from your interface and since that is != nil the gem will keep it. You could try to do something like:
after_validation do |record|
dynamic = record.methods.each do |m|
if has_dynamic_attribute?( m ) and record.read_attribute( m ) == ''
record.update_attribute m, nil
end
end
end
do it in the after_validation callback since the gem is doing its magic with nil attributes in a before_save hook. Read more about ActiveRecord callbacks.
EDIT: more generic after feedback from questioner.

Rails: Validate that all fields in a date_select were selected but still set :prompt to true

When using Rails date_select with :prompt => true I see some very strange behavior when submitting the form without all fields selected. Eg.
Submitting the form with January selected but the day and year fields left at the default prompt results in January 1st 0001 getting passed to the model validation. If validation fails and the form is rendered again January is still selected (correctly) but the day is set to 1 (incorrectly). If the form is submitted with just the year selected, both month and day get set to 1.
This is very strange behavior - can anyone give me a workaround?
The problem has to do with multiparameter assignment. Basically you want to store three values into one attribute (ie. written_at). The date_select sends this as { 'written_at(1)' => '2009', 'written_at(2)' => '5', 'written_at(3)' => '27' } to the controller. Active record packs these three values into a string and initializes a new Date object with it.
The problem starts with the fact that Date raises an exception when you try to instantiate it with an invalid date, Date.new(2009, 0, 1) for instance. Rails catches that error and instantiates a Time object instead. The Time class with timezone support in Rails has all kinds of magic to make it not raise with invalid data. This makes your day turn to 1.
During this process active record looses the original value hash because it packed the written_at stuff into an array and tried to create a Date or Time object out of it. This is why the form can't access it anymore using the written_at_before_time_cast method.
The workaround would be to add six methods to your model: written_at_year, written_at_year=, and written_at_year_before_type_cast (for year, month and day). A before_validation filter can reconstruct the date and write it to written_at.
class Event < ActiveRecord::Base
before_validation :reconstruct_written_at
def written_at_year=(year)
#written_at_year_before_type_cast = year
end
def written_at_year
written_at_year_before_type_cast || written_at.year
end
def written_at_year_before_type_cast
#written_at_year_before_type_cast
end
private
def reconstruct_written_at
written_at = Date.new(written_at_year, written_at_month, written_at_day)
rescue ArgumentError
written_at = nil
end
end

Resources