How to merge columns in a rails table? - ruby-on-rails

I have a rails table of users, with columns first_name and last_name etc.
How do i merge these two together? Or how do i create a new column called name and add data from these two columns?
Basically i need a column called name which is the concatenation of the first_name and last_name.

As Richard Brown has answered, you should create a migration if you want to save the concatenated string to the database.
rails g migration add_fullname_to_users fullname
then run an sql inside the generated migration to update all records
# mysql
User.update_all('fullname = CONCAT(first_name, " ", last_name)')
# postgre
User.update_all("fullname = (first_name || ' ' || last_name)")
But I'd suggest you just keep your current setup and just create a method called fullname in your model
# user.rb
def fullname
"{first_name} #{last_name}"
end
which is better since you have access to first_name and last_name

If you want to make sure you can rollback, you can do something like this in your migration file.
def up
add_column :your_table, :name, :string
YourClass.all.each do |person|
person.update_attributes! :name => person.first_name + " " + person.last_name
end
remove_column :your_table, :first_name
remove_column :your_table, :last_name
end
And your down method for the roll back:
def down
add_column :your_table, :first_name, :string
add_column :your_table, :last_name, :string
YourClass.all.each do |person|
person.update_attributes! :first_name => person.name.match(/\w+/)[0]
person.update_attributes! :last_name => person.name.match(/\w+/)[1]
end
remove_column :your_table, :name
end

Create a migration to add the new field:
rails g migration AddNameToTableName name
You can write a rake task to do the join.
TableName.all.each do { |row| row.update_attributes(name: "#{row.first_name} #{row.last_name}") }
You can execute that from the rails console, but I like repeatable database updates so I prefer a rake task.

The \w regex matcher and concatenating with spaces as in the above examples were problematic for me, although I had the opposite problem, splitting a name into first and last names. Instead, consider using what I ended up with. Back up your databases first, and test this in development!
def up # Split name into first and last
add_column :people, :first_name, :string
add_column :people, :last_name, :string
Person.all.each do |person|
person.update_column(:first_name, person.attributes["name"].rpartition(" ").first)
person.update_column(:last_name, person.attributes["name"].rpartition(" ").last)
person.save(:validate => false)
end
remove_column :people, :name
end
def down # Merge first and last into name
add_column :people, :name, :string
Person.all.each do |person|
person.update_column(:name, [person.attributes["first_name"].to_s, person.attributes["last_name"].to_s].reject(&:blank?).join(" "))
person.save(:validate => false)
end
remove_column :people, :first_name
remove_column :people, :last_name
end
This avoids inserting stray spaces in case one of the names is nil, and also splits a fullname more effectively into first and last names; specifically, by splitting on the last space in the name, since "First Middle" "Last" is more of a human-compatible split than "First" "Middle Last". See examples:
# Before split
> puts p.name.inspect
"Prince"
"Test User"
"Jean Luc Picard"
# After split
> puts p.first_name.inspect + " " + p.last_name.inspect
"" "Prince"
"Test" "User"
"Jean Luc" "Picard"
My main complaint is that "Prince" should probably be a first name not a last name, but then again depending on your database constraints it might need to go into the last_name field anyway.

In my rails 4 app with a User model I generated my migration file
rails g migration RemoveFirstLastNameColumnsFromUser
Then I put in
class RemoveFirstLastNameColumnsFromUser < ActiveRecord::Migration
def up
add_column :users, :name, :string
User.all.each do |u|
u.update! name: "#{u.first_name} #{u.last_name}"
end
remove_column :users, :first_name
remove_column :users, :last_name
end
def down
add_column :users, :first_name, :string
add_column :users, :last_name, :string
User.all.each do |u|
n = u.name
u.update! last_name: n.slice!(/\w+\z/)
u.update! first_name: n.strip
end
remove_column :users, :name, :string
end
end
This #down method will account for names with more than one space.

Related

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

Remove boolean and add list in ruby on rails

I would like to modify gender field,
Initially i have declared gender field as boolean true or false. but now i want it to be changed as list (Male, Female, Other).
class AddExtraFieldsToUser < ActiveRecord::Migration
def change
add_column :users, :phone_number, :string
add_column :users, :date_of_birth, :datetime
add_column :users, :gender, :boolean, default: false
add_column :users, :live_in, :string
add_column :users, :description, :text
end
end
Can i modify as following.... please let me know the correct way...
i thought of doing rails g migration RemovegenderFromUsers gender:boolean
then rake db:migrate followed by creating new one
rails g migration AddGenderToUsers gender:select
user.rb
GENDER_TYPES = ["Male", "Female", "Other"]
html
<%= f.select :gender, User::GENDER_TYPES %>
Is above mentioned process correct or any other way ?
The answer by Ahmad Hussain is correct . List is not a database field type .
You should generate a migration to change the column type :
**change_column :table_name, :column_name, :type**
Select is not a database field type if you want to do it then do it like this
rails g migration AddGenderToUsers gender:integer
In migration file change it to like this:
change_column :users, :gender, :integer, default: 0
For form page do this:
<%= f.select :gender, User::GENDER_TYPES.each_with_index.map { |gender, index| [gender, index] } %>
And in user model you can define function to get gender name to display
def gender_name
GENDER_TYPES[gender]
end

Rails. ActiveRecord::Migration. Reversible

I read RoR guide and I don't understand the next line:
If you wish for a migration to do something that Active Record doesn't know how to reverse, you can use reversible
What does it mean "doesn't know how to reverse"? How to distinguish what Active Record can reverse and what cannot?
It's simply, there are two kinds of operations in AR migrations:
1) Rails automatically knows how to rollback (revert) those operations in migrations, for example:
def change
add_column :users, :age, :integer
end
migration means add column age, rollback means remove column age. Or create_table, reverse operation is drop_table. This operations you can put into change method in migration and rails knows what to do when rollback / reverse those migrations.
2) Rails needs to help how to process migration and rollback process, usually it is operations which somehow modifies data.
def self.up
add_column :users, :name, :string
add_column :users, :surname, :string
say_with_time 'Split username into name and surname' do
Users.select(:username).all.each do |user|
user.name = user.username.split(/ /)[0]
user.surname = user.username.split(/ /)[1]
user.save!
end
end
remove_column :users, :username
end
As you can see, this operation is quite complicated. Rails does not know how to reverse this operation, so you have to write reverse operation code in self.down:
def self.down
add_column :users, :username, :string
say_with_time 'Join name and surname into username' do
User.all.each do |user|
user.update_attributes(username: "#{user.name} #{user.surname}")
end
end
remove_column :users, :name
remove_column :users, :surname
end
and thats all...

How to write conditional migrations in rails?

I am looking for ways to write migrations in rails that can be executed against the database many times without failing.
For instance let say I have this migration:
class AddUrlToProfile < ActiveRecord::Migration
def self.up
add_column :profile, :url, :string
end
def self.down
remove_column :profile, :url
end
end
If the url column already exists in the Profile table (if the schema.rb has been modified unexpectedly for instance), my migration will fail saying that it's a duplicate!
So how to execute this migration only if it has to?
Thanks
You can do something like this:
class AddUrlToProfile < ActiveRecord::Migration
def self.up
Profile.reset_column_information
add_column(:profile, :url, :string) unless Profile.column_names.include?('url')
end
def self.down
Profile.reset_column_information
remove_column(:profile, :url) if Profile.column_names.include?('url')
end
end
This will reset the column information before it begins - making sure that the Profile model has the up-to-date column information from the actual table. It will then only add the column if it doesn't exist. The same thing happens for the down function, but it only removes the column if it exists.
If you have multiple use cases for this you could factor the code out into a function and re-use that in your migrations.
For Rails 3.X, there's the column_exists?(:table_name, :column_name) method.
For Rails 2.X, you can check the existence of columns with the following:
columns("<table name>").index {|col| col.name == "<column name>"}
...or if you're not in a migration file:
ActiveRecord::Base.connection.columns("<table name>").index {|col| col.name == "<column name>"}
If it returns nil, no such column exists. If it returns a Fixnum, then the column does exist. Naturally, you can put more selective parameters between the {...} if you want to identify a column by more than just its name, for example:
{ |col| col.name == "foo" and col.sql_type == "tinyint(1)" and col.primary == nil }
This should work
def self.table_exists?(name)
ActiveRecord::Base.connection.tables.include?(name)
end
if table_exists?(:profile) && !Profile.column_names.include?("url")
add_column :profile, :url, :string
end
Wrapping my migration in a conditional worked for me.
Rails 4.X
class AddUrlToProfile < ActiveRecord::Migration
unless Profile.column_names.include?("url")
def self.up
add_column :profile, :url, :string
end
def self.down
remove_column :profile, :url
end
end
end

Create a set column with database migration in rails

I need to add a new column to my users table in the database. I want the type of the column to be set. The column represents the users gender. There should be two options to the set. One form Male "m" and the other for Female "f".
But I haven't found any documentation for adding a column with the set type.
How can I do this?
What db is used? mysql? If you're want to use SET datatype, you'll have to do it manually, as rails doesn't support it. However, I'd do just
t.string :gender, :limit => 1
for the sake of convenience.
In your Users model, you should add the following line to require M/F answers.
validates_inclusion_of :gender, :in => %w( m f M F)
I think you want to add the gender column with a default datatype (correct me if I'm wrong), If so there would be the step
here I'm assuming 'M' is for male and "F" is for female (you can use integers also if you wish)
create a migration
ruby script/generate migration add_gender_column_to_users
This will create a migration for you and as the name implies it will add a gender column to your users table
in your migrations self.up action add this
add_column :users, :gender, :string, :default => 'm'
here it says we are adding a gender column of string type and its default values is 'm'
and add this to self.down events
remove_column :users, :gender
so your final migration will look something like this
class AddGenderColumnToUsers < ActiveRecord::Migration
def self.up
add_column :users, :gender, :string, :default => 'm'
end
def self.down
remove_column :users, :gender
end
end
and do a
rake db:migrate
thats it, hope this helps

Resources