change default value to a column through a migration - ruby-on-rails-4

I have an existing table in which default value of column is already set. This table contains lot of data in it. I don't want to change any of exiting record in table(don't want to change column of exiting record), but from here onwards I want to change the default value of that column. How do I do that?
Rails version: Rails 4.0.13
Ruby version: ruby 2.2.10p489

Consider I want to change default value of a field(is_male) in table(users). My users table contains lot of data in it where I want to change the default value of is_male column which is of type tinyint, from true to false.
I have created a migration using:
rails g migration add_default_false_to_is_male_for_users
made below changes in migration file:
class AddDefaultFalseToColumnNameTableName < ActiveRecord::Migration
def up
change_column_default :users, :is_male, false
end
def down
change_column_default :users, :is_male, true
end
end
It won't change existing data.

Related

Update column with data in rails migration

I have an events table and sessions table. Events has_many sessions, this is the association. Now I want to move the time_zone column from the sessions table to events table only. So how do I do this with help of migrations. How do I move the existing records for time_zone in sessions table to events table?
First, you need to be sure that sessions associated with the same event have the same time zone. You can do this with:
Session.group(:event_id).count(:time_zone)
This will return a hash mapping an event_id to the number of time zones associated with it. This number should always be one.
Second, I recommend that you first add events.time_zone and start using it and remove sessions.time_zone in a separate migration after the new code has been in production for some time and is proved to work.
Third, the migration to add events.time_zone should look like this (I added some comments for clarity):
class AddTimeZoneToEvents < ActiveRecord::Migration
class Event < ActiveRecord::Base; end
class Session < ActiveRecord::Base; end
def up
# Add a NULLable time_zone column to events. Even if the column should be
# non-NULLable, we first allow NULLs and will set the appropriate values
# in the next step.
add_column :events, :time_zone, :string
# Ensure the new column is visible.
Event.reset_column_information
# Iterate over events in batches. Use #update_columns to set the newly
# added time_zone without modifying updated_at. If you want to update
# updated_at you have at least two options:
#
# 1. Set it to the time at which the migration is run. In this case, just
# replace #update_columns with #update!
# 2. Set it to the maximum of `events.updated_at` and
# `sessions.updated_at`.
#
# Also, if your database is huge you may consider a different query to
# perform the update (it also depends on your database).
Event.find_each do |event|
session = Session.where(event_id: event.id).last
event.update_columns(time_zone: session.time_zone)
end
# If events don't always need to have time zone information then
# you can remove the line below.
change_column_null :events, :time_zone, false
end
def down
remove_column :events, :time_zone
end
end
Note that I redefined models in the migration. It's crucial to do so because:
The original model may have callbacks and validations (sure, you can skip them but it's one extra precaution that contributes zero value).
If you remove the models in 6 months down the road the migration will stop working.
Once you're sure your changes work as expected you can remove sessions.time_zone. If something goes awry you can simply roll back the above migration and restore a working version easily.
You can simply use the following migration.
class Test < ActiveRecord::Migration
def change
add_column :events, :time_zone, :string
Event.all.each do |e|
e.update_attributes(time_zone: e.sessions.last.time_zone)
end
remove_column :sessions, :time_zone
end
end

how django knows to update or insert

I'm reading django doc and see how django knows to do the update or insert method when calling save(). The doc says:
If the object’s primary key attribute is set to a value that evaluates to True (i.e. a value other than None or the empty string), Django executes an UPDATE.
If the object’s primary key attribute is not set or if the UPDATE didn’t update anything, Django executes an INSERT link.
But in practice, when I create a new instance of a Model and set its "id" property to a value that already exist in my database records. For example: I have a Model class named "User" and have a propery named "name".Just like below:
class User(model.Model):
name=model.CharField(max_length=100)
Then I create a new User and save it:
user = User(name="xxx")
user.save()
now in my database table, a record like id=1, name="xxx" exists.
Then I create a new User and just set the propery id=1:
newuser = User(id=1)
newuser.save()
not like the doc says.when I had this down.I checked out two records in my database table.One is id = 1 ,another is id=2
So, can anyone explain this to me? I'm confused.Thanks!
Because in newer version of django ( 1.5 > ), django does not check whether the id is in the database or not. So this could depend on the database. If the database report that this is duplicate, then it will update and if the database does not report it then it will insert. Check the doc -
In Django 1.5 and earlier, Django did a SELECT when the primary key
attribute was set. If the SELECT found a row, then Django did an
UPDATE, otherwise it did an INSERT. The old algorithm results in one
more query in the UPDATE case. There are some rare cases where the
database doesn’t report that a row was updated even if the database
contains a row for the object’s primary key value. An example is the
PostgreSQL ON UPDATE trigger which returns NULL. In such cases it is
possible to revert to the old algorithm by setting the select_on_save
option to True.
https://docs.djangoproject.com/en/1.8/ref/models/instances/#how-django-knows-to-update-vs-insert
But if you want this behavior, set select_on_save option to True.
You might wanna try force_update if that is required -
https://docs.djangoproject.com/en/1.8/ref/models/instances/#forcing-an-insert-or-update

How to deal with the required value which does not have a default value in Django model

The version of my Django is 1.7.
I have a model named Booking, it has a Boolean field named is_departure, which is used to describe the booking is departure or arrival.
class Booking(models.Model):
...
is_departure = models.BooleanField()
...
When I migrate my app, it will return me a warning that is_departure does not have a default value.
However, I do not want to add a default value for is_departure. This is a required value and it needs to be filled by user. I do not want to use NullBooleanField neither, because is_departure should not be null.
Is there any good way to remove this warning?
The problem is, what will Django put as a value for all the existing rows that now have a is_departure value that according to you, cannot be null, you can't satisfy this constraint.
If you're still developing, then you can reset the DB and you can indeed use BooleanField without default (since there will be no existing rows violating this)
Otherwise, I'd make the migration put a is_departure value (true or false) on the existing rows, consistent with your business logic

Adding custom column using 'activeadmin-axlsx' gem

I am using activeadmin gem in my application using Rails 4. I need excel download for which I am making use of activeadmin-axlsx gem.
I am able to get all the columns that exists in my database for the particular model in the excel sheet as its columns. However, what I want is, to add a column which does not exists in my database.
This is what I have tried until now
column('start_date') do |date|
date.start_date
end
Here start_date is an attribute in my db and hence I get that column in the excel.
Now when I try to add another column End Date(which is not an attribute in the db), I get a blank column.
I have also tried the below snippet, referring to the Github link for activeadmin-axlsx
config.xlsx_builder.column('end_date') do |emp|
emp.end_time.strftime("%Y-%m-%d")
end
This gives a blank column in the excel
Can anybody help me achieve this?Or suggest any other gems that can be used with activeadmin gem?
Many Thanks!
The solution is to add the attributes that do not exist in your database as an attribute accessor in your respective model.
For the above example,
end_date is not an attribute in the table, so in the model say Employee, add this
class Employee < ActiveRecord::Base
attr_acessor :end_date
end
Here I am modifying end_time which is an attribute in database of type datetime to fetch only the date with the name of the column being End Date
In the app/admin/employee.rb
ActiveAdmin.register User do
xlsx do
column('end_date') do |emp|
emp.end_time.strftime("%Y-%m-%d")
end
end
end
This would give you the desired value.
I think you can just define one like start_date. Did you try:
column(:end_date) do |resource|
resource.end_time.strftime('%Y-%m-%d')
end

django model field: Why default value not working?

When I create a new row in my PG commander
I let small_price empty because I thought I set a default : 0
small_price = models.IntegerField(default=0)
But when I saved It has an alert :
Reason: null value in column "small_price" violates not-null constraint
Why the default not work??
Do I still need to type 0 when create row??
If you are auto-generating your migrations then you need to make a small edit to the migration before you actually apply it. Go into the generated migration and look for the argument: keep_default=False
in the db.add_column call.
You have to change it to: keep_default=True
This way Django will apply your default value to the actual schema + any existing rows.