I am working on an Events Model with start datetime & end datetime. It should fill in the start datetime as the end datetime if start is blank, as stated in the test. I set up validations to require start/end presence to be true but this is obviously not what the test is asking for as it fails. I am pretty new to this and not familiar with creating/fixing tests so the answer may be right in front of me.
sounds as if you need a before_save callback.
class Event < ApplicationRecord
before_save :ensure_start_time
def ensure_start_time
start_time ||= end_time
# i.e. set start_time = end_time if start_time is nil
end
end
Related
I'm trying to have a record with ends_at column which is suppose to have the created_at value with 5 minutes added to it for instance:
Class Post < ApplicationRecord
after_create :set_end_time
private
def set_end_time
self.ends_at = self.created_at + 5.minutes
end
end
this saves the ends_at column as a null in the database
You fire after_create hook. It gets triggered after Rails calls SQL INSERT command. So you setup ends_at already when object was saved to the database and it will not be saved again. Object with initialized ends_at not going anywhere and then just get cleared from the memory.
Replace your hook with before_create and it should do the trick for you.
You forgot to save the record
def set_end_time
self.ends_at = self.created_at + 5.minutes
save
end
One hard way to do it would be to set a default value when a row is created if the migration
for exemple
change_column_default :posts, :end_time, "current_timestamp + (5 ||' minutes')::interval" if you use postgres for exemple.
But only do that if you are sure that this is not going to change soon.
If it is, then setting a before_create hook with a custom setter is the way to go (Just be sure that your field is of datetime type)
I'm trying to put a validation on a record. The validation will check that the record can't be created if the ip_address and post_id are the same. This works good.
I am trying to add another condition that will allow this duplication only if it is after a 24 hour period, if so, allow it to save, but all future saves will be disabled again until the 24 period is over.
Here has been my best attempt so far:
validates_uniqueness_of :ip_address, scope: :impressionable_id,
conditions: -> {where('created_at < ?', Time.now - 24.hours)}
So the validation should somehow only check the "latest" record of the group it finds to do the validation to be accurate
Thank you!
It might be easier to just make an explicit validator method for this:
class Foo < ActiveRecord::Base
validate :ip_address_uniqueness, on: :create
# ...
def ip_address_uniqueness
existing = Foo.where(impressionable_id: self.impressionable_id)
.where(ip_address: self.ip_address)
.where(created_at: Time.current.all_day)
errors.add(:ip_address, 'cannot be used again today') if existing
end
end
I have a model Booking with attr_acessor :date and time:
class Booking < ActiveRecord::Base
# ...
before_save :convert_date_and_time
attr_accessor :date, :time
def convert_date_and_time
self.date_and_time = DateTime.parse("#{date.to_s} #{time.to_s}")
end
end
I am trying to define getter methods for date and time:
def date
date_and_time.to_date if self.id.present?
end
def time
date_and_time.to_time if self.id.present?
end
but I think this is not quite the way to do it. I need self.id.present? because when I am trying to create a new record, obviously date_and_time still has no value and the getters will yield errors.
If that is the case, how should the getters look like so that I can handle new records that are not yet saved? Should I leave them like how they are now?
Thanks!
To detect new record you can use new_record?, but in your case you can use try :
date_and_time.try(:to_date)
date_and_time.try(:to_time)
I have a simple Rails 3 model, with an attr_accessor that doesn't have a field in the database, and I need to set it up using fixtures, because of my initialization setup.
But when I try it, I get an error about the unknown column.
Is there another way to do this?
My model:
class Timeslot < ActiveRecord::Base
attr_accessor :interval
after_initialize :init
def init
self.interval ||= 15
self.start_time ||= Time.local(0, 1, 1)
self.end_time = self.start_time.advance :minutes => self.interval
end
end
Fixtures are end state. Meaning, what's in your database is output after you've called attr_accessor. So end_time would always be calculated for you if you were using it in a test like #timeslot = timeslots(:your_name_of_fixture). You don't need to worry about them in the Fixture, except for when you are setting it up.
Now, I've not seen an after_initialize before, but i see it's a callback function. To me, you are setting an interval in a view. Probably not programmatically (unless it's that default 15 minutes there).
Switching to a before_save callback (if you want your people to be able to change intervals on edit too) like:
class Timeslot < ActiveRecord::Base
attr_accessor :interval
before_save :set_times
def set_times
self.interval ||= 15
self.start_time ||= Time.local(0, 1, 1)
self.end_time = self.start_time.advance minutes: self.interval
end
end
Fixtures add data directly into the database. If you want to pass data to a model instead, consider using factories (Factory Girl is a good library).
You may already have a big investment in fixtures, but factories are well worth a look.
My model has two fields that i want to compare to each other as part of the validation. I want to be sure end_time is after start_time. I have written a validation method to compare them, but I must be doing something wrong, because the values are always nil. Can someone help?
class LogEntry < ActiveRecord::Base
validates :start_time, :presence => { :message => "must be a valid date/time" }
validates :end_time, :presence => {:message => "must be a valid date/time"}
validate :start_must_be_before_end_time
def start_must_be_before_end_time
errors.add(:start_time, "must be before end time") unless
start_time > end_time
end
end
gets the error
undefined method `>' for nil:NilClass
So, start_time and/or end_time are nil. I thought I was following the many examples I found, but apparently not. What am I missing?
Thanks.
My best guess is you need your method to look like this:
private
def start_must_be_before_end_time
errors.add(:start_time, "must be before end time") unless
start_time < end_time
end
(Also, notice the < rather than > (or change to if and >=)
If this doesn't work then you should also check that start_time and end_time are being defined correctly in the controller as there can be funny things happen if the time is created across more than one form element.
With Rails 7 ComparisonValidator
Rails 7 has added the ComparisonValidator which allows you to use the convenience method validates_comparison_of like so:
class LogEntry < ActiveRecord::Base
validates_comparison_of :start_time, less_than: :end_time
# OR
validates_comparison_of :end_time, greater_than: :start_time
end
Find out more here: https://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html#method-i-validates_comparison_of
You need to check for presence yourself (and skip the validation step if not present).
def start_must_be_before_end_time
return unless start_time and end_time
errors.add(:start_time, "must be before end time") unless start_time < end_time
end
Prints either "must be a valid date/time" OR "start time must be before end time".
Alternative
def start_must_be_before_end_time
valid = start_time && end_time && start_time < end_time
errors.add(:start_time, "must be before end time") unless valid
end
Prints "start time must be a valid date/time" AND "start time must be before end time" if start_time or end_time isn't set.
Personal preference for the first, since it only shows what the user did wrong. The latter is like many websites which just load off 20 lines of error text onto the user just because the programmer thought it would be nice to see every validation result. Bad UX.
Clean and Clear (and under control?)
I find this to be the clearest to read:
In Your Model
# ...
validates_presence_of :start_time, :end_time
validate :end_time_is_after_start_time
# ...
#######
private
#######
def end_time_is_after_start_time
return if end_time.blank? || start_time.blank?
if end_time < start_time
errors.add(:end_time, "cannot be before the start time")
end
end
Ruby on Rails 7.0 supports validates_comparison_of like this
validates :start_time, comparison: { less_than: :end_date }
You can use validates_timeliness gem https://github.com/adzap/validates_timeliness
start_time.to_i < end_time.to_ishould fix it. You are trying to compare datetime but for some reason it can't, so convert them to int before comparing.