rails date_select - ruby-on-rails

Right now I am saving my dates as a string in the format of mm/dd/yyyy, but want to convert to date_select but I keep getting errors for some reason.
Here is the code that I am using
the form
<%= f.date_select :start_date %>
the model
validates :start_date, :presence => true
but I get an error from my controller saying that it doesnt fit the params.

That's because of the way Rails automatically looks at a database column to figure out what type of object is going to be stored there. In this case, Rails is looking for a Datetime column to be used in conjunction with the date_select helper, but instead it's finding a varchar column.
I would run a migration to drop the start_date column, and re-add it as a datetime column, like so
To generate a new migration:
rails generate migration [name of your migration]
In your case something like:
rails generate migration change_start_date_column_to_timestamp
This will generate a file in your RAILS_ROOT/db/migrations folder, which will look something like:
class ChangeStartDateColumnToTimestamp < ActiveRecord::Migration
def self.up
end
def self.down
end
end
And you need to modify it to look like:
class ChangeStartDateColumnToTimestamp < ActiveRecord::Migration
def self.up
remove_column :table_name, :start_date
add_column :table_name, :start_date, :timestamp
end
def self.down
remove_column :table_name, :start_date
add_column :table_name, :start_date, :string
end
end
Then, when rails pulls the data from the database, it'll automatically convert them to Ruby Time objects.
A word of caution... this will destroy the data in the start_date field. So if you have pre-existing information that needs to be preserved, you need to do something more complicated.

Related

Migrate from two string yes/no variable to a single boolean variable in rails?

The question is about rails database migration.
The current database contains two entries for a supposedly boolean variable as in the database scheme as follows:
create_table "table_name", force: :cascade do |t|
...
t.string "yes_boolvar"
t.string "no_boolvar"
...
end
I need to convert it to one single boolean variable as following:
t.boolean "boolvar"
I considered about renaming the 'yes_boolvar', changing its type from string to boolean, and then removing 'no_boolvar' column, based on some readings, like the following:
t.rename :yes_boolvar,
:boolvar
t.change :boolvar,
:boolean
t.remove :no_boolvar
However, this will only consider the truth value of 'yes_*' and not 'no_*' while copying the value of the variable. Is there a way to successfully migrate the var so that the truth (or nil) values of the both the vars are taken into account.
It depends on your app.
If no one can update those values (i.e. it is not a field in a user profile), then you can:
make a database dump
run your migration
execute a code that will fill boolvar
Another solution is to migrate data in 3 steps:
first migration renames column
second migration migrates data
third migration removes no_boolvar column
I guess it is possible to merge the first two actions into a single migration (but I prefer to keep them separated).
I recommends you to handle with 3 migrations.
For first, create a migration adding a boolean: :boolvar
class AddBoolvarToTableName < ActiveRecord::Migration
def up
add_column :table, :boolvar, :boolean
end
def down
remove_column :table, :boolvar
end
end
After, create a new migration to handle data:
class RepopulateBooleanValues < ActiveRecord::Migration[5.0]
def change
YourClass.all.each do |record|
# put the logic here like:
record.boolvar = record.yes_boolvar == 'true'
# or
record.boolvar = record.not_boolvar == 'false'
# I'am not sure whats the content of yes_boolvar and not_boolvar, elaborate the logic here
record.save
end
end
end
To finish it, just create a new migration removing yes_boolvar and no_boolvar.
This is roughly the migration I'd write (I didn't run the code, but it should work):
# This ensures the migration to work
# regardless the customizations on your original model
class TempModel < ActiveRecord::Base
self.table_name = 'table_name'
end
class MyMigration < ActiveRecord::Migration[5.0]
def up
add_column :table_name, :boolvar, :boolean
TempModel.reset_column_information
TempModel.find_each do |record|
# Decide some logic here about how to migrate values from yes_boolvar
# and no_boolvar columns to boolvar column
boolvar_value = record.yes_boolvar || !record.no_boolvar
record.update_column :boolvar, boolvar_value
end
remove_column :table_name, :yes_boolvar
remove_column :table_name, :no_boolvar
end
def down
add_column :table_name, :yes_boolvar, :string
add_column :table_name, :no_boolvar, :string
TempModel.reset_column_information
TempModel.find_each do |record|
# Decide some logic here about how to handle yes_boolvar
# and no_boolvar values
record.update_columns yes_boolvar: record.boolvar,
no_boolvar: !record.boolvar
end
remove_column :table_name, :boolvar
end
end

How to change already added string column to a Reference in Rails

I've already created a model in Rails to collect some user information
I created the columns as :string initially but I've since changed the way this data is looked up and entered by using separate populated models.
Now instead of entering into these fields as string - i want these columns to be "references" instead.
Is there an easy way to change from the string to reference without having to create a new model entirely?
*do not need to save the existing data
Is there any data in the strings you would like to save?
Or is it just because it has the same name?
You don't have to create a new model.
You could create a simple migration
remove_column :table, :your_column_name, :string
add_column :table, :your_column_name, :integer, references: :your_parent_model
You can add a temporary string column to save the string column first:
rails g migration add_temporary_string_column_to_model temporary_string_column:string
And run rails console:
SomeModel.all.each do |some_model|
some_mode.temporary_string_column = some_mode.string_column
some_mode.save
end
And now you can change your original string column's type to references which is an int(4) column in MySQL, migration like this:
class ChangeFormatInSomeTable < ActiveRecord::Migration
def change
change_column :some_table, :string_column, :references
end
end
Finally, you can run rails console again to convert the string data to integer like this:
SomeModel.all.each do |some_model|
some_mode.string_column = some_mode.temporary_string_column.to_i
some_mode.save
end
And at last, remove the temporary string column:
rails g migration remove_temporary_string_column_from_model temporary_string_column
Here is another solution, without dropping the column itself (not exactly in my case). I'm not sure though if this is the best solution.
In my case, I have a tickets table that holds purchase_uid in itself. I decided to keep purchases in another table after making the necessary improvements in our backend. Purchases table has uuid as the primary key. Given this background, here is my migration to change my column into a reference.
class AddPurchaseRelationToTickets < ActiveRecord::Migration[5.2]
def up
change_column :tickets, :purchase_uid, :uuid, references: :purchase, foreign_key: true, using: 'purchase_uid::uuid'
end
def down
change_column :tickets, :purchase_uid, :string
end
end
In my case, since string doesn't automatically cast into uuid, purchase_uid were dropped and recreated as well. However, if you decide to keep the column type same, I don't think it will be a problem.
You can create migrations to serve the exact purpose.
rails generate migration AddAddressToUsers address:references
This will create a migration file in db/migrate directory.
Then run: rails db:migrate to run migration and make changes in your database.
Don't forget to create associations in your models (belongs_to, has_many, etc.) depending on your system structure.
Wanted to add a simpler alternative to the accepted answer that preserves data:
class ChangeStringToInt < ActiveRecord::Migration[5.1]
def up
change_column :table_name, :field_name, :integer, null: false, references: :table_referenced, using: 'field_name::integer'
add_index :chapter_actions, :field_name
end
def down
change_column :table_name, :field_name, :string, null: false, using: 'field_name::character varying'
remove_index :table_name, :field_name
end
end

Rails: rename globalize column

The rails globalize gem docs are great, but I can't find a solution, what I have to do, when I want to rename a column.
Last Year I did that, to add the translation fields.
def up
remove_column :news, :name
News.add_translation_fields! name: :string
end
def down
add_column :news, :name, :string, default: nil
remove_column :news_translations, :name
end
Now I want to rename the column "name" to "title", without loosing my data and translations. How do I have to write the migration file?
Alter the column on the news translations table directly:
def change
rename_column :news_translations, :name, :title
end

Cast old string values to datetime with migration in Rails PostgreSQL

I had a couple of date fields in a database table, however they are firstly initiated as string, not datetime. Therefore, I wanted to change those value types to datetype with a migration,
class ChangeDateColumnsToDateTime < ActiveRecord::Migration
def change
change_column :users, :flight_date_departure, :datetime
change_column :users, :flight_date, :datetime
change_column :users, :appointment_date, :datetime
end
end
however it can not cast old string values to datetimes that exists in database currently, saying that
PG::DatatypeMismatch: ERROR: column "flight_date_departure" cannot be cast automatically to type timestamp without time zone. HINT: You might need to specify "USING flight_date_departure::timestamp without time zone". We've done it without problem in a SQLite database, however there is this problem for PostgreSQL. How can I modify my migration so that I do not lose old values and properly convert them to datetime?
I've tried the way bellow and it worked like a charm:
change_column :table_name, :column_name, 'timestamp USING CAST(column_name AS timestamp)'
try like this:
change_column :table_name, :column_name, 'datetime USING CAST(column_name AS datetime)'
here is my working code
class AddSrCreateDateToCart < ActiveRecord::Migration[5.2]
def change
add_column :carts, :sr_create_date, :datetime
change_column :carts, :sr_create_date, 'datetime USING CAST(sr_create_date AS timestamp)'
end
end
postgres is now downloading the data
It would be better to make this a reversible migration.
def up
change_column :table_name, :column_name, 'timestamp USING CAST(column_name AS timestamp)'
end
def down
change_column :table_name, :column_name, 'date USING CAST(column_name AS date)'
end

Adding a Model Reference to existing Rails model

I'd like to know the "proper" way to approach adding a relation between two existing classes in Rails 3.
Given existing models: Clown & Rabbit
I'd like to add a reference (belongs_to) from Rabbit to Clown. I start by trying to generate a migration:
rails g migration AddClownToRabbits clown:reference
which gives me a migration that looks like:
class AddClownToRabbits < ActiveRecord::Migration
def self.up
add_column :rabbits, :clown, :reference
end
def self.down
remove_column :rabbits, :clown
end
end
After rake db:migrate on this migration I examine SQLite3's development.db and see a new column: "clown" reference
I guess I was expecting a "clown_id" integer column and a migration that looked like:
class AddClownToRabbits < ActiveRecord::Migration
def self.up
add_column :rabbits, :clown_id
end
def self.down
remove_column :rabbits, :clown_id
end
end
I'm sure :reference is supposed to be equivalent to "t.references :clown" but I can't find the documentation (big surprise). API says add_column: Instantiates a new column for the table. The type parameter is normally one of the migrations native types, which is one of the following: :primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean.
...with no reference to :reference.
If you are using edge rails (4.0) you can use:
rails generate migration AddAddressRefToContacts address:references
As you can see by the docs.
After you set belongs_to in Rabbit, and has_many in Clown, you can do a migration with:
add_column :rabbit, :clown_id, :integer
EDIT: See Paulo's answer below for a more updated answer (Rails 4+)
I'm not sure where you got this idea, but there is no (and never has been) such syntax to do what you want with add_column. To get the behavior you want, you'd have to do t.refences :clown, as you stated. In the background this will call: #base.add_column(#table_name, "#{col}_id", :integer, options).
See here.
EDIT:
I think I can see the source of your confusion. You saw the method call t.reference and assumed it was a datatype because calls such as t.integer and t.string exist, and those are datatypes. That's wrong. Reference isn't a datatype, it's just simply the name of a method, similar to t.rename is.

Resources