Syntax error fixing devise to not use email - ruby-on-rails

class UpdateIndexOnUsers < ActiveRecord::Migration
def up
sql = 'DROP INDEX index_users_on_email'
sql << ' ON users' if Rails.env == 'production' # Heroku pg
ActiveRecord::Base.connection.execute(sql)
end
end
Getting a syntax error when trying to rake db:migrate on heroku. Any help would be greatly appreciated. Using Postgresql. I should mention it works fine locally.
Edit: The error
PG::SyntaxError: Error: syntax error at or near "ON"
Line 1: DROP INDEX index_users_on_email ON users
^

The easy thing would be to use rails' helpers and let them take care of datebase specifics:
class UpdateIndexOnUsers < ActiveRecord::Migration
def up
remove_index :users, :email
end
end
If the index was initially created with a name other than the one that rails would use by default you could do
class UpdateIndexOnUsers < ActiveRecord::Migration
def up
remove_index :users, :email, :name => 'name_of_index'
end
end

As far as I know, Postgresql doesn't support (or need) ON xxxx when dropping an index. The docs don't mention it either:
http://www.postgresql.org/docs/9.2/static/sql-dropindex.html
Just specifying the index name should be fine, though you can qualify it with the schema name which you probably don't need to do if you've got a standard setup.
If you're also using postgresql locally, then presumably it's working because ON users is only added when rails env is production whereas you're presumably running in development locally.

Related

Specs for ActiveRecord::Migration helper method

I have added monetize and demonetize helpers inside ActiveRecord::Migration, ActiveRecord::ConnectionAdapters::TableDefinition and ActiveRecord::ConnectionAdapters::Table by that pull request.
That file shows usage examples. So you will understand invented changes at glance. (It works)
But I have no idea how to test my helpers. What way can I write specs for them? All my attempts of writing migrations in spec files and running them manually failed. Migration manual run did not change table (or I was unable to detect changes) and did not throw any exception.
Example of my attempt:
describe 'monetize' do
class MonetizeMigration < ActiveRecord::Migration
def change
create_table :items
monetize :items, :price
end
end
class Item < ActiveRecord::Base; end
it 'should monetize items' do
MonetizeMigration.up #=> nil
Item #=> Item(has no table)
end
end
This worked for me in the console:
[4667]foo#bar:~/dev/ror/foo$ rails c
Loading development environment (Rails 3.2.9)
irb(main):001:0> class MyMigration def change
irb(main):003:2> create_table :foo
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> MyMigration.new.change
-- create_table(:foo)
(4.5ms) select sqlite_version(*)
(133.2ms) CREATE TABLE "foo" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)
-> 0.2362s
=> []
You can execute any migration helper methods right on your database connection:
ActiveRecord::Base.connection.create_table :items
Thanks to #happy_user for showing my mistake in first attempt. I think someone may use my latest solution in the future, so I'll leave it here.

Migrating DATA - not just schema, Rails

Sometimes, data migrations are required. As time passes, code changes and migrations using your domain model are no longer valid and migrations fail. What are the best practices for migrating data?
I tried make an example to clarify the problem:
Consider this. You have a migration
class ChangeFromPartnerAppliedToAppliedAt < ActiveRecord::Migration
def up
User.all.each do |user|
user.applied_at = user.partner_application_at
user.save
end
end
this runs perfectly fine, of course. Later, you need a schema change
class AddAcceptanceConfirmedAt < ActiveRecord::Migration
def change
add_column :users, :acceptance_confirmed_at, :datetime
end
end
class User < ActiveRecord::Base
before_save :do_something_with_acceptance_confirmed_at
end
For you, no problem. It runs perfectly. But if your coworker pulls both these today, not having run the first migration yet, he'll get this error on running the first migration:
rake aborted!
An error has occurred, this and all later migrations canceled:
undefined method `acceptance_confirmed_at=' for #<User:0x007f85902346d8>
That's not being a team player, he'll be fixing the bug you introduced. What should you have done?
This is a perfect example of the Using Models in Your Migrations
class ChangeFromPartnerAppliedToAppliedAt < ActiveRecord::Migration
class User < ActiveRecord::Base
end
def up
User.all.each do |user|
user.applied_at = user.partner_application_at
user.save
end
end
Edited after Mischa's comment
class ChangeFromPartnerAppliedToAppliedAt < ActiveRecord::Migration
class User < ActiveRecord::Base
end
def up
User.update_all('applied_at = partner_application_at')
end
end
Best practice is: don't use models in migrations. Migrations change the way AR maps, so do not use them at all. Do it all with SQL. This way it will always work.
This:
User.all.each do |user|
user.applied_at = user.partner_application_at
user.save
end
I would do like this
update "UPDATE users SET applied_at=partner_application_at"
Some times 'migrating data' could not be performed as a part of schema migration, like discussed above. Sometimes 'migrating data' means 'fix historical data inconstancies' or 'update your Solr/Elasticsearch' index, so its a complex task. For these kind of tasks, check out this gem https://github.com/OffgridElectric/rails-data-migrations
This gem was designed to decouple Rails schema migrations from data migrations, so it wont cause downtimes at deploy time and make it easy to manage in overall

Rails migration and column change

working with sqlite3 for local dev. Prod DB is MySql.
Have a migration file for a column change.
class ChangeDateToOrders < ActiveRecord::Migration
def self.up
change_column(:orders, :closed_date, :datetime)
end
def self.down
change_column(:orders, :closed_date, :date)
end
end
Errors out saying index name 'temp_index_altered_orders_on_closed_location_id_and_parent_company_id' on table 'altered_orders' is too long; the limit is 64 characters
Know there is a limitation on index name with sqlite, but is there a workaround for this?
EDIT
Workaround I used.
class ChangeDateToOrders < ActiveRecord::Migration
def self.up
remove_index(:orders, [:closed_location_id, :parent_company_id])
change_column(:orders, :closed_date, :datetime)
add_index(:orders, [:closed_location_id, :parent_company_id], :name => "add_index_to_orders_cli_pci")
end
def self.down
remove_index(:orders, :name => "add_index_to_orders_cli_pci")
change_column(:orders, :closed_date, :date)
add_index(:orders, [:closed_location_id, :parent_company_id])
end
end
Personally, I like my production and development environments to match as much as possible. Its helps avoid gotchas. If I were deploying MySQL I would run my development environment with MySQL too. Besides, I am also not super familiar with SQLite so this approach appeals to my lazy side - I only need to know the ins and outs of one db.
You could hack your copy of active record; Add the following
opts[:name] = opts[:name][0..63] # can't be more than 64 chars long
Around line 535 (in version 3.2.9) of $GEM_HOME/gems/activerecord-3.2.9/lib/active_record/connection_adapters/sqlite_adapter.rb
It's a hack but it might get you past a hurdle. If I had more time, I'd look in to writing a test and sending a pull request to rails core team.

How do I check the Database type in a Rails Migration?

I have the following migration and I want to be able to check if the current database related to the environment is a mysql database. If it's mysql then I want to execute the SQL that is specific to the database.
How do I go about this?
class AddUsersFb < ActiveRecord::Migration
def self.up
add_column :users, :fb_user_id, :integer
add_column :users, :email_hash, :string
#if mysql
#execute("alter table users modify fb_user_id bigint")
end
def self.down
remove_column :users, :fb_user_id
remove_column :users, :email_hash
end
end
Even more shorter call
ActiveRecord::Base.connection.adapter_name == 'MySQL'
ActiveRecord::Base.connection will provide you with everything you ever wanted to know about the database connection established by boot.rb and environment.rb
ActiveRecord::Base.connection returns a lot of information. So you've got to know exactly what you're looking for.
As Marcel points out:
ActiveRecord::Base.connection.instance_of?
ActiveRecord::ConnectionAdapters::MysqlAdapter
is probably the best method of determining if your database MySQL.
Despite relying on internal information that could change between ActiveRecord release, I prefer doing it this way:
ActiveRecord::Base.connection.instance_values["config"][:adapter] == "mysql"
There is an adapter_name in AbstractAdapter and that is there since Rails2.
So it's easier to use in the migration like this:
adapter_type = connection.adapter_name.downcase.to_sym
case adapter_type
when :mysql, :mysql2
# do the MySQL part
when :sqlite
# do the SQLite3 part
when :postgresql
# etc.
else
raise NotImplementedError, "Unknown adapter type '#{adapter_type}'"
end
In Rails 3, (maybe earlier, but I'm using Rails 3 currently) using ActiveRecord::ConnectionAdapters::MysqlAdapter is a poor way to go about it, as it's only initialized if the database adapter in use is MySQL. Even if you have the MySQL gem installed, if it's not your connection type, that call wil fail:
Loading development environment (Rails 3.0.3)
>> ActiveRecord::Base.connection.instance_of? ActiveRecord::ConnectionAdapters::MysqlAdapter
NameError: uninitialized constant ActiveRecord::ConnectionAdapters::MysqlAdapter
from (irb):1
So, I'd recommend stasl's answer and use the adapter_name property of the connection.
This might help:
execute 'alter table users modify fb_user_id bigint WHERE USER() = "mysqluser";'

Add Rows on Migrations

I'd like to know which is the preferred way to add records to a database table in a Rails Migration. I've read on Ola Bini's book (Jruby on Rails) that he does something like this:
class CreateProductCategories < ActiveRecord::Migration
#defines the AR class
class ProductType < ActiveRecord::Base; end
def self.up
#CREATE THE TABLES...
load_data
end
def self.load_data
#Use AR object to create default data
ProductType.create(:name => "type")
end
end
This is nice and clean but for some reason, doesn't work on the lasts versions of rails...
The question is, how do you populate the database with default data (like users or something)?
Thanks!
The Rails API documentation for migrations shows a simpler way to achieve this.
http://api.rubyonrails.org/classes/ActiveRecord/Migration.html
class CreateProductCategories < ActiveRecord::Migration
def self.up
create_table "product_categories" do |t|
t.string name
# etc.
end
# Now populate the category list with default data
ProductCategory.create :name => 'Books', ...
ProductCategory.create :name => 'Games', ... # Etc.
# The "down" method takes care of the data because it
# drops the whole table.
end
def self.down
drop_table "product_categories"
end
end
Tested on Rails 2.3.0, but this should work for many earlier versions too.
You could use fixtures for that. It means having a yaml file somewhere with the data you want to insert.
Here is a changeset I committed for this in one of my app:
db/migrate/004_load_profiles.rb
require 'active_record/fixtures'
class LoadProfiles < ActiveRecord::Migration
def self.up
down()
directory = File.join(File.dirname(__FILE__), "init_data")
Fixtures.create_fixtures(directory, "profiles")
end
def self.down
Profile.delete_all
end
end
db/migrate/init_data/profiles.yaml
admin:
name: Admin
value: 1
normal:
name: Normal user
value: 2
You could also define in your seeds.rb file, for instance:
Grid.create :ref_code => 'one' , :name => 'Grade Única'
and after run:
rake db:seed
your migrations have access to all your models, so you shouldn't be creating a class inside the migration.
I am using the latest rails, and I can confirm that the example you posted definitely OUGHT to work.
However, migrations are a special beast. As long as you are clear, I don't see anything wrong with an ActiveRecord::Base.connection.execute("INSERT INTO product_types (name) VALUES ('type1'), ('type2')").
The advantage to this is, you can easily generate it by using some kind of GUI or web front-end to populate your starting data, and then doing a mysqldump -uroot database_name.product_types.
Whatever makes things easiest for the kind of person who's going to be executing your migrations and maintaining the product.
You should really not use
ProductType.create
in your migrations.
I have done similar but in the long run they are not guaranteed to work.
When you run the migration the model class you are using is the one at the time you run the migration, not the one at the time you created the migration. You will have to be sure you never change your model in such a way to stop you migration from running.
You are much better off running SQL for example:
[{name: 'Type', ..}, .. ].each do |type|
execute("INSERT INTO product_types (name) VALUES ('#{type[:name]} .. )
end

Resources