comparison of String with ActiveSupport::TimeWithZone failed - ruby-on-rails

My application gives users the opportunity to create events with a start and end time:
>> Event.where('end_time < ?', 1.days.ago).first
Event Load (0.3ms) SELECT "events".* FROM "events" WHERE (end_time < '2017-12-21 13:30:36.411209') ORDER BY "events"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<Event id: 1, user_id: 1, place: "london", start_time: "2017-08-26 20:00", end_time: "2017-08-26 22:00", note: "Note", note_id: 355, picture: "dwarf_hammering.jpg", created_at: "2017-08-14 11:27:16", updated_at: "2017-08-14 11:27:16">
As you can see the start_time and end_time are String with a pre-defined format, and Active Record is able to compare them against a Time instance.
I created a Sidekiq job to delete all events that are older than one day:
class EventsCleanerWorker
include Sidekiq::Worker
def perform
events = Event.where('end_time < ?', 1.days.ago)
if events.any?
events.delete_all
end
end
end
Inside test/fixtures/events.yml I created 6 events: two of them have an end_time older than one day, as the event below:
event3:
user: John
place: "London"
start_time: 3.days.ago.strftime("%Y-%m-%d %H:%M")
end_time: 2.days.ago.strftime("%Y-%m-%d %H:%M")
note: "A thrilling adventure"
Then I created the following test:
def test_events_cleaner_worker_inline_mode
assert Event.count == 6
assert events(:event3).end_time < 1.days.ago
EventsCleanerWorker.perform_async
assert Event.all.reload.count == 4
assert_equal 0, EventsCleanerWorker.jobs.size
end
However, when I run this test I receive the following error:
ArgumentError: comparison of String with ActiveSupport::TimeWithZone failed
This error is referred to assert events(:event3).end_time < 1.days.ago
If I remove this line the test fails because the test expects Event.all.reload.count to be 6 instead of 4. I have no idea why the string generated by end_time inside the events fixtures cannot be compared against Time, while this is possible in the dev environment.

start_time and end_time in the fixture should be embed in ERB as follows:
event3:
user: John
place: "London"
start_time: <%= 3.days.ago.strftime("%Y-%m-%d %H:%M") %>
end_time: <%= 2.days.ago.strftime("%Y-%m-%d %H:%M") %>
note: "A thrilling adventure"

Related

ActionView::Template::Error (PG::UndefinedColumn: ERROR: column does not exist)

model
Attempt 1
def challenge_date
deadline || date_started
end
Attempt 2
def challenge_date
if deadline != date_started
[deadline, date_started].reject(&:blank?).join()
else
date_started
end
end
Both attempts gave the error. I need the method to iterate through next_user_challenge
def next_user_challenge
user.challenges.where('challenge_date > ?', challenge_date).order('challenge_date ASC').first
end
rails c
# some challenges will just have a deadline
id: 1,
name: "Publish a Novel",
deadline: Sat, 26 Nov 2016,
date_started: nil,
user_id: 117,
# some challenges will just have a date_started
id: 2,
name: "Write a Chp",
deadline: nil,
date_started: Thu, 20 Oct 2016,
user_id: 117,
# and some challenges will have both
id: 3,
name: "Run a Mile",
deadline: Thu, 26 Sep 2016,
date_started: Thu, 26 Sep 2016, # If challenge has both deadline and date_started then date_started will be the same date as the deadline
user_id: 117,
Not a postgres query but you can achieve it rails way
def next_user_challenge
user.challenges.select{ |ch| ch.challenge_date > challenge_date }.sort_by(&:challenge_date).first
end
NOTE: This will fetch all challenges from db and then select the valid one.
I will suggest creating a new column in challenges table and using ActiveModal callbacks to update it.
You're using not database model attribute to query the data and of course it will not work.
To solve this problem, there are 2 ways:
Use Deepak way
Use raw SQL query, in this way, you should prepare challenge_date and join with self table to process other query.

Why is this Rails 4 model test failing?

I have the following model test in Rails 4:
require 'test_helper'
class AwardTest < ActiveSupport::TestCase
def test_validity_of_year
# test for rejection of missing year
award = Award.new({name: "Test award, student_id: 1"})
assert !award.valid?
# test under lower boundary
award.year = 1979
assert !award.valid?
# lower boundary
award.year = 1980
assert award.valid?
# top boundary
award.year = Date.today.year
assert award.valid?
# top boundary case, award isn't valid for next year
award.year = Date.today.year + 1
assert !award.valid?
end
end
Here is the award model:
class Award < ApplicationRecord
belongs_to :student
validates_presence_of :name, :year
validates_inclusion_of :year, in: (1980..Date.today.year)
end
Here is the output of my award_test.rb
bundle exec rake test TEST=test/models/award_test.rb
DEPRECATION WARNING: alias_method_chain is deprecated. Please, use Module#prepend instead. From module, you can access the original method using super. (called from included at .rvm/gems/ruby-2.2.2#rails/gems/turbolinks-2.5.3/lib/turbolinks/xhr_url_for.rb:7)
Run options: --seed 53138
# Running:
F
Failure:
AwardTest#test_validity_of_year [/Users/marklocklear/sandbox/students03/test/models/award_test.rb:16]:
Failed assertion, no message given.
bin/rails test test/models/award_test.rb:4
Finished in 0.042099s, 23.7538 runs/s, 71.2614 assertions/s.
1 runs, 3 assertions, 1 failures, 0 errors, 0 skips
If I run this manually in the console I get...
2.2.2 :037 > award = Award.new({name: "Test award", student_id: 1})
=> #<Award id: nil, name: "Test award", year: nil, student_id: 1, created_at: nil, updated_at: nil>
2.2.2 :038 > award.valid?
Student Load (0.1ms) SELECT "students".* FROM "students" WHERE "students"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
=> false
2.2.2 :039 > award.year = 1979
=> 1979
2.2.2 :040 > award.valid?
=> false
2.2.2 :041 > award.year = 1980
=> 1980
2.2.2 :042 > award.valid?
=> true

How can I load different model with different language?

I have a tables with content in two languages. Then I have a column called 'language' to distinguish.Now I use I18n to do multi languages website.So, how can I get content in database with current language?
I think that if I can set the default_scope of model accoding to the language, or load different models the same name.
Thank you!
You can use a URL parameter to set the application locale for a request, for example:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_locale
 
  def set_locale
    I18n.locale = params[:locale] || I18n.default_locale
  end
end
Find the model by current locale:
YourContent.where(name: some_name, language: I18n.locale)
and use the t method in views with your strings files since you have the application locale set.
The docs have a few other examples.
Yes, you can use default_scope { language: I18n.locale } to omit the extra where, but be aware of the caveats of default_scope.
If you want to switch to different table conditionally for same model, you can use ActiveRecord::Base.table_name class method.
Example:
cTable4 ❯ rails c -e production
Running via Spring preloader in process 60375
Loading production environment (Rails 4.2.4)
irb(main):001:0> Book.first
Book Load (0.1ms) SELECT "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT 1
=> #<Book id: 1, name: "ELoquent Ruby", author: "Russ", year: "2011", created_at: "2016-01-07 08:40:47", updated_at: "2016-01-07 08:40:47">
irb(main):002:0> Book.table_name = 'titles'
=> "titles"
irb(main):003:0> Book.first
Book Load (0.2ms) SELECT "titles".* FROM "titles" ORDER BY "titles"."id" ASC LIMIT 1
=> #<Book id: 1, title: "Practical OOD Ruby", author: "Sandi Metz">
irb(main):004:0>

RSpec test custom validator

I have the following validator in my model:
class ContinuumValidator < ActiveModel::Validator
def validate(record)
if !record.end_time.nil? and record.end_time < record.start_time
record.errors[:base] << "An event can not be finished if it did not start yet..."
end
end
end
class Hrm::TimeEvent < ActiveRecord::Base
validates_with ContinuumValidator
end
How can I test it using Rspec?
Here is what I have tried so far: (thanks to zetetic)
describe "validation error" do
before do
#time_event = Hrm::TimeEvent.new(start_time: "2012-10-05 10:00:00", end_time: "2012-10-05 09:00:00", event_type: 2)
end
it "should not be valid if end time is lower than start time" do
#time_event.should_not be_valid
end
it "raises an error if end time is lower than start time" do
#time_event.errors.should include("An event can not be finished if it did not start yet...")
end
end
But I get the following errors:
1) Hrm::TimeEvent validation error raises an error if end time is lower than start time
Failure/Error: #time_event.errors.should include("An event can not be finished if it did not start yet...")
expected #<ActiveModel::Errors:0x007fd1d8e02c50 #base=#<Hrm::TimeEvent id: nil, start_time: "2012-10-05 08:00:00", end_time: "2012-10-05 07:00:00", event_type: 2, employee_id: nil, created_at: nil, updated_at: nil, not_punched: false, validated: false, replace_id: nil>, #messages={}> to include "An event can not be finished if it did not start yet..."
Diff:
## -1,2 +1,5 ##
-["An event can not be finished if it did not start yet..."]
+#<ActiveModel::Errors:0x007fd1d8e02c50
+ #base=
+ #<Hrm::TimeEvent id: nil, start_time: "2012-10-05 08:00:00", end_time: "2012-10-05 07:00:00", event_type: 2, employee_id: nil, created_at: nil, updated_at: nil, not_punched: false, validated: false, replace_id: nil>,
+ #messages={}>
What am I doing wrong? And how can I achieve my goal?
Any help or suggestion would be appreciated.
Thanks.
The problem is that you're expecting #time_event.errors to behave like an array of strings. It doesn't, it returns ActiveModel::Errors. As others pointed out, you also need to trigger the validations with a call to valid?:
it "raises an error if end time is lower than start time" do
#time_event.valid?
#time_event.errors.full_messages.should include("An event can not be finished if it did not start yet...")
end
This solution works for me (using Mongoid):
The model
class OpLog
...
field :from_status, type: String
field :to_status, type: String
...
validate :states_must_differ
def states_must_differ
if self.from_status == self.to_status
errors.add(:from_status, "must differ from 'to_status'")
errors.add(:to_status, "must differ from 'from_status'")
end
end
...
end
The test:
it 'is expected to have different states' do
expect { create(:oplog, from_status: 'created', to_status: 'created').to raise_error(Mongoid::Errors::Validations) }
end
So in your case I'd write a test like this (if using ActiveRecord):
it 'raises an error if end time is lower than start time' do
expect { create(Hrm::TimeEvent.new(start_time: "2012-10-05 10:00:00", end_time: "2012-10-05 09:00:00", event_type: 2)) }.to raise_error(ActiveRecord::Errors)
end
There are no errors because you haven't called an event that triggers the errors. This happens normally when a record is created or saved. You may not want to hit the database in your test though and then you can use the method valid? like this:
it "raises an error if end time is lower than start time" do
#time_event.valid?
#time_event.errors.should include("An event can not be finished if it did not start yet...")
end
Me personally would put these two tests into one since valid? is called in the first case.
Also a minor: if record.end_time is better than if !record.end_time.nil?. (In my opinion at least.... :-) )
I think the record wasnt validated therefore the validatior didn't run and no error was aded. You can see this in the code output. "validated: false"
try:
it "raises an error if end time is lower than start time" do
#time_event.valid?
#time_event.errors.should include("An event can not be finished if it did not start yet...")
end
You have not tested the validation actually, plus i would suggest you to make a single spec.
describe "validation error" do
before { #time_event = Hrm::TimeEvent.new(start_time: "2012-10-05 10:00:00", end_time: "2012-10-05 09:00:00", event_type: 2) }
it "raises an error if end time is lower than start time" do
#time_event.valid?
#time_event.errors.should include("An event can not be finished if it did not start yet...")
end
end
class ContinuumValidator < ActiveModel::Validator
def validate(record)
if record.end_time and record.end_time < record.start_time
record.error.add_to_base << "An event can not be finished if it did not start yet..."
end
end
end

Rails: Some seeds don't appear to be saving

It appears some of my seeds are not saving. For starters, I will show a console session so you can see that the 'instructor_id' is indeed being set in the console, but not when I seed.
ruby-1.9.2-p180 :015 > c = Course.find 2
Course Load (1.6ms) SELECT "courses".* FROM "courses" WHERE "courses"."id" = $1 LIMIT 1 [["id", 2]]
=> #<Course id: 2, name: "Microcomputers II Lab", course_code: "CE-420L", instructor_id: nil, school_id: nil, created_at: "2011-06-04 19:40:32", updated_at: "2011-06-04 19:40:32">
ruby-1.9.2-p180 :016 > c.instructor = Instructor.first
Instructor Load (0.6ms) SELECT "instructors".* FROM "instructors" LIMIT 1
=> #<Instructor id: 1, name: "Instructor Name", created_at: "2011-06-04 19:40:32", updated_at: "2011-06-04 19:40:32">
ruby-1.9.2-p180 :017 > c
=> #<Course id: 2, name: "Microcomputers II Lab", course_code: "CE-420L", instructor_id: 1, school_id: nil, created_at: "2011-06-04 19:40:32", updated_at: "2011-06-04 19:40:32">
From looking at the console, you can see that when I call c.instructor = Instructor.first, it is correctly setting my instructor_id.
Now, in the seeds file I have variables. (This is just a snippet)
### Instructors ###
puts "Creating Instructors"
instructor_1 = Instructor.find_or_create_by_name("Instructor Name")
### Courses ###
puts "Creating Courses"
ce420L = Course.find_or_create_by_name("Microcomputers II Lab", :course_code => "CE-420L")
### Add the Instructor to the Course ###
puts "Adding an Instructor to the Courses"
ce420L.instructor = instructor_1
But when I run the seeds using 'rake db:seed', it is correctly creating all of my models, and most of my relationships. But it is not setting the instructor properly.
Thoughts?
EDIT:
Just tried:
ce420 = Course.find_or_initialize_by_name("Microcomputers II")
ce420.instructor_id = instructor_1.id
ce420.save!
And it did not save my instructor.
Here are my models.
class Instructor < ActiveRecord::Base
### ASSOCIATIONS ###
has_many :courses
end
class Course < ActiveRecord::Base
belongs_to :instructor
end
Did you run...
ce420L.save!
... after assigning the instructor?
Much faster to do this:
### Courses ###
puts "Creating Courses belonging to Instructor 1"
ce420L = Course.find_or_initialize_by_name("Microcomputers II Lab") :course_code => "CE-420L")
ce420L.instructor_id = instructor_1.id
ce420L.save
Note the following:
You had an errant comma after your find_or_create on ce420L.
Doing the assignment together with the course creation prevents the system from validating and saving ce420L twice.
You can try assigning the specific ID as I did, i.e. ce420L.instructor_id = ...
If this doesn't work, check your Instructor Model to ensure you don't have any callbacks getting in the way.

Resources