Rails set default DateTime to now - ruby-on-rails

In my app I have teams and each team has a game time every week. I want the game times to be set to 'now' as a default. My table is set up like so
create_table "teams", force: true do |t|
t.datetime "wk1_time"
end
I created a migration and it looks like this:
class ChangeDateTimeDefault < ActiveRecord::Migration
def change
change_column :teams, :wk1_time, :default => DateTime.now
end
end
When I run rake db:migrate I get an error. Is my syntax wrong or am I missing something else?

Since Rails 5 you can make a migration like this:
change_column_default :users, :wk1_time, -> { 'CURRENT_TIMESTAMP' }
In my mind this is the best option because it not database specific answer.

Yes, you are missing the type :
class ChangeDateTimeDefault < ActiveRecord::Migration
def change
change_column :teams, :wk1_time, :datetime, :default => DateTime.now
end
end
But, you need the below not the above one, because you just want to change the default.
class ChangeDateTimeDefault < ActiveRecord::Migration
def change
change_column_default :teams, :wk1_time, DateTime.now
end
end
But none of these are correct approach for your task. The reason is DateTime.now will be evaluated based upon when you ran the migration, instead when the record is created. You need look to into this answer to know how to set the default time.

EDIT: For Rails 5+ there are better answers, like this one: https://stackoverflow.com/a/55357711/252799, though the below still works.
The way I found, was to do a migration on an existing datetime column, like this:
#migration
execute("ALTER TABLE teams ALTER COLUMN wk1_time SET DEFAULT CURRENT_TIMESTAMP")
that produces a schema.rb entry shown like this:
#schema.rb
t.datetime "wk1_time", default: "now()", null: false
The "now()" is a string sent to postgresql and evaluated at runtime, upon each insert.

You're going to run into problems settings the default date time in the migration. This is because DateTime.now will be evaluated based upon when the migrate runs, not when the record is created!
To fix that you'll need to create an ActiveRecord callback in order to set wk1_time like so:
before_create :set_default_wk1_datetime
def set_default_wk1_datetime
self.wk1_time = DateTime.now
end

for Postgresql :
add_column :users, :msgs_seen_at, 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP'
but you'll have to use user.reload after user = User.create in order to "see" msgs_seen_at

Related

How to make migration to update table RAILS

I am stuck with making migration which will update my table Users. I need to set country_code with 1 everywhere where i have "" or NULL for that column.
Thanks
class UpdateCountryCodeColumnUsers < ActiveRecord::Migration
def up
execute %Q(
UPDATE users
SET country_code = 1
WHERE country_code IS NULL OR country_code = ""
)
end
end
You probably shouldn't alter the data in a migration and only use it to alter the schema.
A lot of devs use rake db:reset which won't run this migration.
A better solution is to create a rake or thor task as a one off or simply just execute the SQL.
Sergei's answer is your best bet and will update the data (which would be needed first - and is very important) and it lets the database do the work. If you also need to set the default for going forward (after the update Sergei proposed) you can make a separate migration (to separate activities) and include the below...
You can also use the rails migration helper method change_column_default
change_column_default :users, :country_code, from: nil, to: 1
If you want to make it reversible just use change_column...
def up
change_column :users, :country_code, :string, default: 1
end
def down
change_column :users, :country_code, :string, default: nil
end
def UpdateCountryCodeForUsers < ActiveRecord::Migration
def up
Users.where("country_code = '' or country_code = NULL")
.update_attributes({country_code: 1})
end
end
On rails 5.2 , I needed to update some text attributes in a column & had to use update_all instead of update_attributes.
class UpdateTableColumn < ActiveRecord::Migration[5.2]
def up
TableName.where("my_column = 'my column old value'")
.update_all({keywords: 'my column new value'})
end
end

Rails - using `rails generate model` to specify non-nullable field type

According to the rails documentation
http://guides.rubyonrails.org/migrations.html
2.3 Supported Type Modifiers says it should be possible to modify fields to allow or disallow NULL in the column, and that it's possible to do this on the terminal
This is what I want to appear in the migration file
class CreateTestModels < ActiveRecord::Migration
def change
create_table :test_models do |t|
t.string:non_nullable, :null => false
t.timestamps
end
end
end
On the terminal, I've tried
rails generate model TestModel non_nullable:string{null}
rails generate model TestModel 'non_nullable:string{null: false}'
I can't think of any other way to express it
Note: I already know you can go into the migration file and manually add it. That's not what I'm looking for.
The docs mention that
Some commonly used type modifiers can be passed directly on the command line. They are enclosed by curly braces and follow the field type
but they don't give details about which "commonly used" modifiers willl work.
As pointed out by mr rogers
there are only three supported options:
length for string/text/binary/integer (name:string{255})
precision,scale for decimal (dollar_fragment:decimal{3,2})
polymorphic for references/belongs_to (agent:references{polymorphic})
As mentioned by user2903934
it may be possible to make this work from the command line as a hack.
NOTE: this is a hack. i wouldn't recommend doing this but it does answer your question.
rails generate model TestModel 'non_nullable, null => false:string'
It looks like it splits on the first colon, so we can use a hashrocket syntax to sneak options in there. This yields:
class CreateTestModels < ActiveRecord::Migration
def change
create_table :test_models do |t|
t.string :non_nullable, null => false
t.timestamps
end
end
end
That obviously isn't officially supported, it just happens to work.
The closest I can get to your solution is something like this:
rails generate model TestModel non_nullable,null:string
I couldn't work out what comes after the , but that should give you a start
You can open editor by utilising https://github.com/rails/rails/pull/38870 (available for Rails versions > 6.1.0)
To create migration with null: false from command line, first you need to enable EDITOR_FOR_GENERATOR
# config/application.rb
# https://github.com/rails/rails/pull/38870#issuecomment-609018444
config.generators.after_generate do |files|
if ENV["EDITOR_FOR_GENERATOR"]
files.each do |file|
system("#{ENV["EDITOR_FOR_GENERATOR"]} #{file}")
end
end
end
Than use sed to append to specific columns.
For example that you want to create a model with jti and exp columns with not null constrains and add index to them (index is supported on command line using :index).
We need to match line t.string :jti and append to it so end result is t.string :jti, null: false
Here is command I use:
# rails g model CreateJwtDenylist jti:index exp:datetime:index
# replace jti and exp with your column names
EDITOR_FOR_GENERATOR='sed -i "" -r -e "/^[[:space:]]*t.*(jti|exp)$/ s/$/, null: false/"' rails g model CreateJwtDenylist jti:index exp:datetime:index
This works both for rails g migration and rails g model.
Resulting migration is
# db/migrate/20230121091319_create_jwt_denylist.rb
class CreateJwtDenylist < ActiveRecord::Migration[7.0]
def change
create_table :jwt_denylists do |t|
t.string :jti, null: false
t.datetime :exp, null: false
t.timestamps
end
add_index :jwt_denylists, :jti
add_index :jwt_denylists, :exp
end
end
You can do it in your model class like this-
class TestModel < ActiveRecord::Base
validates_presence_of :non_nullable
end

Change a Date-fields data before formated by model?

I have a model, lets call it Posts.
Posts has a Date-field referring to a date other that created_on, updated_on e tc,
Lets call this field custom_date.
When a user creates a post and inputs the custom_date field the POST will look like
params[:post][:custom_date] = "04/12/2013 01:01"
this is referring to day/month/year hour:minute.
When I call
#post= Post.new(post_params)
And then inspect #post the custom_date have been reformated to 2013-12-04 , without the hour and minute. I guess the format is the datebase (correct me if im wrong) way of storing the Date.
But, I want to get the hour and minute in here. So I figured that I could make this happen in a clean way in the model file:
private
def fix_dates_from_input
self.start_date = DateTime.parse(self.custom_date) unless start_date.custom_date?
render text: self.inspect
end
And call this before validation:
before_validation :fix_dates_from_input
How ever, it seems the date is formated when it gets to the model, so that I can't change it there (becouse I dont have the minute or hour data).
I was hoping that I could do this outside of the controller,
Is there another way?
It is likely that your database is only storing the date and not the datetime.
Check the db/schema.rb file to see if this is the problem. If this is the issue, it will say something like:
create_table :posts do |t|
...
t.date :custom_date
...
end
When it should look like this:
create_table :posts do |t|
...
t.datetime :custom_date
...
end
To fix this you will either want to go back and fix the original migration (if it is a new project), or if you do not have the luxury of rebuilding your database from scratch, create a new migration to fix the problem:
From command line:
rails g migration change_custom_date_type
Then edit that file:
class ChangeCustomDateType < ActiveRecord::Migration
def change
change_column :posts, :custom_date, :datetime
end
end

How to use default sql expression with ActiveRecord?

I'm trying to use default values for a model in rails. The best documented way of doing this that I have seen is using the :default parameter in a migration. However, it looks like this only works for values. I am trying to set a default expression, specifically currval('customers_id_seq'::regclass). I can add the default expression by hand, but I can't figure out how to tell rails to use a default value. How can I use this default when creating model objects?
EDIT
Here is a more specific example:
class CreateTestmodels < ActiveRecord::Migration
def up
create_table :testmodels do |t|
t.integer :idxfield, :null=>false
t.string :datafield
t.timestamps
end
execute("ALTER TABLE testmodels ALTER COLUMN idxfield SET DEFAULT currval('testmodels_id_seq'::regclass)")
end
def down
drop_table :testmodels
end
end
With this migration, I can run insert into testmodels (datafield) VALUES ('sometestdata');, which will add a row to the table where idxfield defaults to id.
However, if I run Testmodel.create(:datafield=>"testdata") from the rails console, it substitutes null for idxfield and throws an exception.
For anyone coming across this now, you can use an expression in a default value migration by wrapping it in a lambda: https://github.com/rails/rails/pull/20005
e.g.
create_table :posts do |t|
t.datetime :published_at, default: -> { 'NOW()' }
end
Sounds like you're trying to use Postgres sequences, which rails will use in all primary key definitions in migrations.
If you need to add it to non-primary key column then you will need to run an ALTER TABLE outside of the create_table and add it yourself, like
execute("ALTER TABLE users ADD COLUMN foo INTEGER default nextval('users_id_seq'::regclass)")

activerecord/pg: support automatic timestamps in DDL

Updated (to show code)
I'd like to mimic ActiveRecord's automatic timestamps directly in the database, but without explicitly adding this logic after every migration's create_table() call.
Here's what I do now:
class StatusQuo < My::Migration::Subclass
def self.up
create_table :tbl do |t|
... some columns ...
t.timestamps
end
add_default_now(:tbl, :created_at) # ALTER COLUMN ... DEFAULT NOW()
add_default_now(:tbl, :updated_at) # ALTER COLUMN ... DEFAULT NOW()
add_updated_at_trigger(:tbl) # BEFORE UPDATE ON ... trg_updated_at()
end
end
By contrast, here's what I'd like to do:
class Druthers < My::Migration::Subclass
def self.up
create_table :tbl do |t|
... some columns ...
t.timestamps
end
end
end
Is there an easy or recommended way to accomplish this? Using activerecord 3, postgresql 8.4.
Here's the best I could come up with so far, full source here:
In config/environment.rb check to see if our connection adapter is PostgreSQL. If so, require a file that does the following:
Wrap ColumnDefinition#to_sql to force defaults
Force "created_at" and "updated_at" to have DEFAULT CURRENT_TIMESTAMP
Wrap create_table to conditionally apply the trigger
If the newly created table has an "updated_at" column, install a trigger referencing a database FUNCTION assumed to exist.
Not pretty (need to have maintain a FUNCTION definition outside of this code) and not complete (change_table won't handle introducing timestamps properly), but good enough.

Resources