Why is Rails setting my attribute to nil on create? - ruby-on-rails

Ruby 2.2.0 on Rails 4.2.2
I'm attempting to write a custom date validator (that is going to be expanded to handle some other cases once I get this part working), but right now it's failing to appropriately validate (and return false) on strings - it doesn't even run. It seems that rails is completely ignoring the date_ended value when it's set to a string. When I try the same test except with an integer it correctly validates and fails that validation. If I don't allow nil values, the validator correctly prevents record creation on a string value, but only because it rejects the nil value. Any and all suggestions are appreciated.
The same exact problem exists for date_started.
Edit: I've confirmed that the validation is failing on the second expectation and not the first validation by double-checking that CommitteeMember.count is 0 before the first expectation.
CommitteeMember:
class CommitteeMember < ActiveRecord::Base
belongs_to :committee
belongs_to :member
validates :committee_id, presence: true
validates :member_id, uniqueness: { scope: :committee_id }, presence: true
validates :date_started, date: true, allow_nil: true
validates :date_ended, date: true, allow_nil: true
end
schema.rb relevant lines:
create_table "committee_members", force: :cascade do |t|
t.integer "committee_id", null: false
t.integer "member_id", null: false
t.date "date_started"
t.date "date_ended"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
DateValidator (custom validator):
(note the printed value in the middle)
class DateValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
puts "Validating #{value}"
unless value.kind_of?(Date)
record.errors[attribute] << "must be of type date"
end
end
end
CommiteeMember relevant spec:
(note the printed value in the middle)
it 'should fail with a string for date_ended' do
expect(CommitteeMember.count).to eq(0)
CommitteeMember.create!(member_id: 1, committee_id: 1, date_ended: "S")
ap CommitteeMember.first
expect(CommitteeMember.count).to eq(0)
end
Spec Output:
$ rspec spec/models/committee_member_spec.rb
......#<CommitteeMember:0x00000007d1f888> {
:id => 1,
:committee_id => 1,
:member_id => 1,
:date_started => nil,
:date_ended => nil,
:created_at => Tue, 11 Aug 2015 19:22:51 UTC +00:00,
:updated_at => Tue, 11 Aug 2015 19:22:51 UTC +00:00
}
F
Failures:
1) CommitteeMember validations should fail with a string for date_ended
Failure/Error: expect(CommitteeMember.count).to eq(0)
expected: 0
got: 1
(compared using ==)
# ./spec/models/committee_member_spec.rb:52:in `block (3 levels) in <top (required)>'
Finished in 0.54864 seconds (files took 2.43 seconds to load)
7 examples, 1 failure
Failed examples:
rspec ./spec/models/committee_member_spec.rb:48 # CommitteeMember validations should fail with a string for date_ended

Since the attributes are date attributes, Rails automatically parses any values that you attempt to assign. If it cannot parse as a date, it will leave the value as nil, e.g. in the console:
c = CommitteeMember.new
c.date_started = 'S'
=> "S"
c.date_started
=> nil
In fact, Rails will actually parse a string into a Date:
c.date_started = '2016-1-1'
=> "2016-1-1"
c.date_started
=> Fri, 01 Jan 2016
c.date_started.class
=> Date
This means that you don't need to validate that your date fields are dates at all, because Rails won't store them otherwise. Instead, just validate that they exist:
validates_presence_of :date_started, :date_ended

Related

postgreSQL date records only persisting up to a certain point in time in Rails

I am seeding some simple data in my rails program that is using a postgres database.
Currently, it is only persisting certain dates to the database. Other times, it is showing up as null in my API, which is very odd. I will post a picture of my database, and the seeded data, as well as my JSON API.
Here is my table:
create_table "pickup_deliveries", force: :cascade do |t|
t.date "pickup_date"
t.text "pickup_location"
t.money "rate", scale: 2
t.date "delivery_date"
t.text "delivery_location"
t.boolean "local"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "loaded_miles", default: 0
t.integer "deadhead_miles", default: 0
end
Here is my seeded data:
PickupDelivery.create(:pickup_date => '05-10-2019', :pickup_location => 'Kansas City, MO', :rate => '550.00', :delivery_date => '05-11-2019', :delivery_location => 'Wichita, KS', :local => false, :loaded_miles => '550', :deadhead_miles =>'230');
PickupDelivery.create(:pickup_date => '05-20-2019', :pickup_location => 'Kansas City, MO', :rate => '550.00', :delivery_date => '05-25-2019', :delivery_location => 'Wichita, KS', :local => false, :loaded_miles => '550', :deadhead_miles =>'230');
Here is the persisted data in my JSON API:
[
{
id: 1,
pickup_date: "2019-10-05",
pickup_location: "Kansas City, MO",
rate: "550.0",
delivery_date: "2019-11-05",
delivery_location: "Wichita, KS",
local: false,
created_at: "2019-05-09T16:14:35.312Z",
updated_at: "2019-05-09T16:14:35.312Z",
loaded_miles: 550,
deadhead_miles: 230
},
{
id: 2,
pickup_date: null,
pickup_location: "Kansas City, MO",
rate: "550.0",
delivery_date: null,
delivery_location: "Wichita, KS",
local: false,
created_at: "2019-05-09T16:14:35.319Z",
updated_at: "2019-05-09T16:14:35.319Z",
loaded_miles: 550,
deadhead_miles: 230
}
]
As you can see, the dates were both inputted in the same format, but only one came out as intended while the other came out as null
Thanks for reading.
The reason is that you are passing an invalid format in the second record
# Record 1
:delivery_date => '05-11-2019' # dd-mm-yyyy
# to
delivery_date: "2019-11-05"
# Record 2
:delivery_date => '05-25-2019' # dd-mm-yyyy
# Invalud since there is no month 25
so it becomes nil
This date is being parsed as dd-mm-yyyy
The date you are passing in the second record is invalid due to the month
I suggest you pass the date in this format 'dd-mm-yyyy' - '20-05-2019'
NOTE: Even in your first record the date in being parsed as 05 the day, 11 as the month and 2019 as the year

Searching UNIX timestamp gives differing results

The following queries give different results, the result for both should be two. I'm using timestamp columns in my db (postgres), and am searching for objects where their end_at column is less than or equal to a given UNIX timestamp.
puts object.time_records.where('time_records.end_at <= ?', object.time_records.second.end_at).count #=> 2 (Correct)
puts object.time_records.where('time_records.end_at <= ?', DateTime.strptime(object.time_records.second.end_at.to_i.to_s, '%s')).count # => 1 (Incorrect)
puts object.time_records.where('time_records.end_at <= ?', Time.at(object.time_records.second.end_at.to_i)).count # => 1 (Incorrect)
If I seed some data, the timestamp used in the query might be, for example:
1473024092
Then if I print the timestamps for the object:
puts object.time_records.pluck(:end_at).map(&:to_i)
I get the following results:
1472419292
1473024092
1473628892
1474233692
As can be seen from these, the correct result should be two. If anyone has encountered something similar I'd appreciate a pointer in the right direction.
For what it's worth, this is occurring in specs I'm writing for a gem. I've tried varying combinations of in_time_zone and .utc for parsing and converting to the timestamp, and they all offer the same result. Even converting to a timestamp and straight back to a Time, and testing for equality results in false, when to_s is equal for both.
I ran an example in irb:
2.3.0 :001 > now = Time.now
=> 2016-08-28 21:58:43 +0100
2.3.0 :002 > timestamp = now.to_i
=> 1472417923
2.3.0 :003 > parsed_timestamp = Time.at(timestamp)
=> 2016-08-28 21:58:43 +0100
2.3.0 :004 > now.eql?(parsed_timestamp)
=> false
2.3.0 :005 > now == parsed_timestamp
=> false
2.3.0 :006 > now === parsed_timestamp
=> false
2.3.0 :007 > now.class
=> Time
2.3.0 :008 > parsed_timestamp.class
=> Time
The issue was fractional times. UNIX timestamps are to the second, so when converting to_i, the milliseconds are discarded.
Setting the precision of the timestamp columns resolved this issue:
class CreateTimeRecords < ActiveRecord::Migration
def change
create_table :time_records do |t|
t.belongs_to :object, index: true, null: false
t.datetime :start_at, null: false, index: true, precision: 0
t.datetime :end_at, null: false, index: true, precision: 0
t.timestamps null: false
end
end
end

Why Rails data loaded from Fixtures are broken?

I got two fixture files for Locales and Translations.
Locales are loaded fine, but Translations are broken:
Fixture
translation_05064:
id: 5064
key: control.base_search_users.panel.title
value: Поиск пользователей
interpolations:
locale: ru
locale_id: 16
is_proc: false
Becomes the record:
#<Translation id: 5064,
key: "control.base_search_users.panel.title",
value: "Поиск пользователей",
interpolations: nil,
locale: nil,
locale_id: 1019186233,
is_proc: false>
For some reason locale instead of 'ru' becomes nil, while locale_ib instead of 16 becomes 1019186233 for every fixture in a file.
I load fixtures such way:
require 'active_record/fixtures'
ActiveRecord::Fixtures.reset_cache
fixtures_folder = File.join(Rails.root, 'test', 'fixtures')
fixtures = Dir[File.join(fixtures_folder, '*.yml')].map {|f| File.basename(f, '.yml') }
ActiveRecord::Fixtures.create_fixtures(fixtures_folder, fixtures)
Translation model
class Translation < ActiveRecord::Base
validates :key, :uniqueness => {:scope => :locale_id}
validates :key, :locale, :locale_id, :value, :presence => true
belongs_to :locale
attr_accessible :key, :value, :locale_id, :locale
end
The migration
class CreateTranslations < ActiveRecord::Migration
def change
create_table :translations do |t|
t.string :key
t.text :value
t.text :interpolations
t.string :locale
t.integer :locale_id
t.boolean :is_proc, :default => false
end
add_index :translations, [:key, :locale]
end
end
I see in a test.log that inserts to DB contain broken data. When I load the fixture file in rails concole with YAML.load_file 'test/fixtures/translations.yml' I get correct Hash data.
Why that happens? How to fix that?
Rails-2.3.8, PostgreSql-8.4
UPDATE:
Tried named fixtures. In locales.yml:
locale_00016:
id: 16
code: ru
name: Русский
and in translations.yml all locale key values set to locale_00016
translation_05064:
id: 5064
key: control.base_search_users.panel.title
value: Поиск пользователей
locale: locale_00016
is_proc: false
YES, that works!
Translation id referred to existing and correct Locale record, but locale was still nil, to fix it I ran Locale.find_by_code('ru').translations.update_all(:locale => 'ru')
If locale_id is set, it seems ok; locale will be filled by Rails when you need it (the first time you will request it). 1019186233 is the id generated by rais when the fixtures are created.
Most of the time, you do not need to specify ids in fixtures, rails generate them for you, so fixtures like below should be fine (you should not define both localeand locale_id in the Translation fixture):
locales.yml:
ru:
what_ever_attr: value
...
translations.yml:
ru_title_translation:
key: control.base_search_users.panel.title
value: Поиск пользователей
interpolations:
locale: ru
is_proc: false

Rails is not inputting user data to postgresql

I am having an issue with Rails not inputting values to postgresql. The database itself is connected. When I run db:create:all (snippet from database.yml)
development:
adapter: postgresql
encoding: unicode
database: website_development
username: postgres
password: *******
host: 127.0.0.1
port: 9435
(test: is the same but with database: website_test instead of website_development) all the databases are created for test and development. When I run my db:migration the user table is also created e.g. snippet from migration file "date"_create_user.rb
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :username
t.string :email
t.timestamps
end
end
def self.down
drop_table :users
end
end
(I have checked in pgAdmin and found the tables that where created)But when I try to insert data from the console e.g.(this was run in sandbox)
irb(main):001:0> User.create!(:username => "John", :email => "john#example.com)
=> #<User id: 1, username: nil, email: nil, created_at: "2011-04-26 22:00:28", u
pdated_at: "2011-04-26 22:00:28">
here is the sql produced on a different create! I had run
[1m[35mSQL (2.0ms)[0m INSERT INTO "users" ("username", "email", "created_at", "updated_at") VALUES (NULL, NULL, '2011-04-26 20:53:43.363908', '2011-04-26 20:53:43.363908') RETURNING "id"
Any help as to why rails is creating the databases and tables fine but can't find the proper username and email to enter into sql.
P.S. I am running Rspec for my tests and have made several tests regarding the values of username and email not being nil to which all succeed.
......................
Finished in 1.62 seconds
22 examples, 0 failures
Notification failed: 201 - The destination server was not reachable
Notification failed: 201 - The destination server was not reachable
As you can see all Rspec tests are green but it to is having trouble connecting to the postgres server
Thank you in advance for any advice.
Update: added user model snippet
class User < ActiveRecord::Base
attr_accessor :username, :email
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
username_regex = /\A[\w\d]+\z/i
validates :username, :presence => true,
:format => { :with => username_regex },
:length => { :maximum => 30},
:uniqueness => { :case_sensitive => false }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
end
==Answer==
These were my mistakes:
Part 1: By changing attr_accessor to attr_accessible all my tests worked properly, and everything that needed to went to red, this also allowed me to add :email details but not :username details which leads to part 2.
Part 2: For some reason rails didn't like the fact that my table was named :user and my column was named :username. So I tried changing :username to :loginname which fixed the problem entirely.
Thank you everyone for all your help.
To isolate this you may want to construct a unit test to replicate the problem, then repair it as required. At first I suspected it would be a case of protected attributes, but it appears you have made them accessible, which is the correct thing to do.
Calling create! directly is somewhat hazardous as you are not easily able to capture the object that is half-created in the event of an exception. This is because although the exception contains a reference to a model, it is not clear if the User model or some other model caused the exception in the first place without additional digging.
A more reliable approach is this:
def test_create_example
user = User.new(:username => "John", :email => "john#example.com")
assert_equal 'John', user.username
assert_equal 'john#example.com', email
user.save
assert_equal [ ], user.errors.full_messages
assert_equal false, user.new_record?
end
If an error occurs in the validation stream you will see the error listed alongside what should be an empty array. It also checks that the record has been saved by testing that it is no longer a new record as records can be valid but fail to save if a before_save or before_create filter returns false, something that happens by accident quite often.
If you call new and then save you have an opportunity to inspect the newly prepared object before it is saved, as well as after.

Why does RSpec fail on that validation?

I have these validations :
it "should have 100 adventure points" do
user = User.new
user.adventure_points.should == 100
end
it "should be level 1" do
user = User.new
user.level.should == 1
end
it "should have 10 sapphires" do
user = User.new
user.sapphires.should == 10
end
Then, i also have in my migration :
t.string :name, :limit => 20
t.integer :strength_points, :default => 0
t.integer :dexterity_points, :default => 0
t.integer :magic_points, :default => 0
t.integer :accuracy_points, :default => 0
t.integer :health_points, :default => 0
t.integer :level, :default => 1
t.integer :adventure_points, :default => 100
t.integer :sapphires, :default => 10
t.integer :duel_wins, :default => 0
t.integer :duel_losses, :default => 0
t.string :image_url
t.integer :strength
When i run rspec i get :
A new User
is not valid without a name
is not valid without a first class
is not valid without a password
should have 100 adventure points
should be level 1 (FAILED - 1)
should have 10 sapphires (FAILED - 2)
Failures:
1) A new User should be level 1
Failure/Error: user.level.should == 1
expected: 1
got: nil (using ==)
# ./spec/models/user_spec.rb:28
2) A new User should have 10 sapphires
Failure/Error: user.sapphires.should == 10
expected: 10
got: nil (using ==)
# ./spec/models/user_spec.rb:33
Why does it present these errors on level, sapphires, but it works ok for adventure_points ? Moreover, if i open a console and do User.new, i get the default values as expected. What is hapening here ?
The database defaults are not set until the model is saved. Try:
it "should have 100 adventure points" do
user = User.create
user.adventure_points.should == 100
end
Found the solution myself, pretty sneaky one. I had to run rake:test:prepare because the test database did not have fixtures data and was not prepared :P

Resources