How to use default sql expression with ActiveRecord? - ruby-on-rails

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)")

Related

Rails and ActiveRecord::Migration, edit migration to ignore table prefix or use explicit table name

I'm retroactively writing ActiveRecord migrations inside a quirky, legacy Rails app that did not follow convention so well.
The models already exist, but were not made with a generator so the tables and everything in the past were set up by hand.
I'm changing that, and I made a migration to start (I'm also switching db behind the app from MS SQL Server to Postgres).
The migration looks like this:
class CreateSuite < ActiveRecord::Migration
def change
create_table :suites do |t|
t.string :name
t.string :owner
t.string :resource_dependencies
t.datetime :requested_time
t.datetime :finished_time
t.string :status
...
end
end
end
The application, which we'll say is called foo is set as an active_record.table_name_prefix in the application.rb:
config.active_record.table_name_prefix = 'foo_'
After the migration, I want a table that is named 'suites' but instead I get 'foo_suites'. I do not want to change the prefix in application.rb since it's there for a reason (the application needs another table called 'foo_suites' but I'm going to tackle that with another migration file.)
How do I make this ActiveRecord::Migration create 'suites' instead of 'foo_suites'?

Creating CRUD interface for Ruby-on-Rails App that uses an Array-type via terminal

I am creating a Ruby on Rails Application where I want to create a CRUD interface that will allow me to store records in a postgresql database. Eventually this will become part of the backend for an iOS game I am developing. I want to store arrays in these records of text and/or integer types respectively, but I don't know how...
So far I can create a CRUD interface via terminal, for example using a 'POST' model with the scaffold command like..
$ bin/rails generate scaffold post first_name:text last_name:text date_of_birth:date
This will produce a database migration file that looks like
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.text :first_name
t.text :last_name
t.date :date_of_birth
t.timestamps null: false
end
end
end
To best illustrate what I want to achieve, I have created the following example desired database migration file.
class CreatePosts < ActiveRecord::Migration
def change
create_table :player do |t|
t.text :player_name, null: false
t.text :player_email, null: false
t.integer :tokens_collected_by_level, array: true, default: []
t.text :items_collected, array: true, default: []
end
end
end
In order to help me understand how to create a CRUD interface for a Ruby on Rails App USING arrays, via Terminal.. What would be the corresponding 'bin/rails generate scaffold post ... ' command to execute, to generate the above code?
These are the accepted column modifiers accepted for Rails edge (> 4.2), that can be passed using curly braces:
limit Sets the maximum size of the string/text/binary/integer fields.
precision Defines the precision for the decimal fields, representing
the total number of digits in the number.
scale Defines the scale for the decimal fields, representing the number of digits after the decimal point.
polymorphic Adds a type column for belongs_to
associations.
null Allows or disallows NULL values in the column.
default Allows to set a default value on the column. Note that if you
are using a dynamic value (such as a date), the default will only be
calculated the first time (i.e. on the date the migration is applied).
index Adds an index for the column.
required Adds required: true for belongs_to associations and null: false to the column in the migration.
array: true is not a valid option for a field type in Rails. You can use serialize to store your array in a :text field and handle the default there.
So in your example:
bin/rails generate model Post player_name:text:required player_email:text:required tokens_collected_by_level:text items_collected:text

rails non-integer primary key

I have a table called Contracts. Its current default primary key is the :id field that Rails automatically generates, which is an integer. I want to have a field called contractId that is a string type and use it as a primary key instead. What I want to know is:
Is this a best practice? Are there any potential issues with doing this?
How I would go about it
Ruby on Rails (RoR) likes to emphasise the concept of convention over configuration. Therefore, it seeks to minimialise the amount of configuration.
So if you want contractId that is a string type then you can add one extra field in your table and use it wherever you want and let the Rails use id as primarykey.
Change PrimaryKey
Generate a new migration file name it "ChangePrimaryKey" (You can give any name).
class ChangePrimaryKey < ActiveRecord::Migration
def up
remove_column :table, :id # remove existing primary key
rename_column :table, :udid, :id # rename existing UDID column
execute "ALTER TABLE table ADD PRIMARY KEY (id);"
end
def down
# Remove the UDID primary key. Note this would differ based on your database
execute "ALTER TABLE table DROP CONSTRAINT table_pkey;"
rename_column :table, :id, :udid
add_column :table, :id, :primary_key
end
end
If you are creating a new table, your migration might look like this:
class AddTableWithDifferentPrimaryKey < ActiveRecord:Migration
def change
create_table :table, id: false do |t|
t.string :id, null: false
# other columns
t.timestamps
execute "ALTER TABLE table ADD PRIMARY KEY (id);"
end
end
end
Notice the id: false options you pass into the table — this asks Rails not to create a primary key column on your behalf.
Changes to Model
In the model, it is essential that you add the following line in order for
Rails to programmatically find the column you intend to use as your primary key.
class Table < ActiveRecord::Base
self.primary_key = :id
# rest of span
end
I hope you can do rest of the things.
Don't change default id if you want to see Rails real Magics :)
As you may know, Rails supports changing the primary id column out of the box:
class Contract < ActiveRecord::Base
self.primary_key = "contractId"
end
Please note that even if the contractId column has a unique index, an index on a string column will always be a bit slower than an index in an integer column.
Furthermore, this is not the Rails way and might confuse other developers that work with this application. Especially building associations or routes are error-prone when your table has a non-standard primary key. IMHO that is a good reason to avoid using this technic as long as possible.

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

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