Reputation: 4730
So I've got an app that when I create a new user, I setup columns in some other databases related to that specific user. I realize this isn't best practice, but for my use-case it's much faster than serializing an array which holds all user info for that table.
What I'm trying to do is setup a rake task which creates the User
, as well as doing the necessary migrations to the tables.
Here's what I have so far:
desc "Adds User and creates correct DB entries."
task add_user: :environment do
username = ENV['username'].to_s
email = ENV['email'].to_s
password = ENV['password'].to_s
initials = ENV['initials'].to_s
if username and email and password and initials
User.create! :username => username, :email => email, :password => password, :password_confirmation => password, :initials => initials
Rake::Task['generate migration AddPay' + initials + 'ToShoppingLists pay' + initials + ':decimal'].invoke
Rake::Task['generate migration AddPay' + initials + 'ToPayments pay' + initials + ':decimal'].invoke
Rake::Task['db:migrate'].invoke
end
end
My issues is that in Rails 5
, I have to run rails g migration
not rake g migration
so I'm unsure how to call rails
commands from inside a rake
task.
Additionally, is there a way to check if a migration has already been created? For example, if I run this in development mode, I don't need to recreate the migration in production mode, just perform db:migrate
.
Upvotes: 2
Views: 1797
Reputation: 2575
You can use Rake
's sh
method and just call the rails
shell commands.
sh "rails g migration AddPay#{initials}ToShoppingLists pay#{initials}:decimal"
sh "rails g migration AddPay#{initials}ToPayments pay#{initials}:decimal"
When using sh
as opposed to ruby's built-in backtick delimiters for shell commands, if the command has an exit status other than 0
it will raise an exception and abort the task.
To see if your migration has already been created, you can just check for the existence of a migration file matching the naming pattern.
files = Dir.glob Rails.root.join('db/migrate/*')
migration_patterns = {
/add_pay_#{initials.downcase}_to_shopping_lists/ => "rails g migration AddPay#{initials}ToShoppingLists pay#{initials}:decimal",
/add_pay_#{initials.downcase}_to_payments/ => "rails g migration AddPay#{initials}ToPayments pay#{initials}:decimal"
}
migration_patterns.each do |file_pattern, migration_command|
if files.none? { |file| file.match? file_pattern }
sh migration_command
end
end
Rake::Task['db:migrate'].invoke
This assumes you won't have any migration naming collisions that raise false positives in none?
. But Rails won't let you have migration naming collisions anyway, so the check might not be necessary. It seems like you are bound to run into this problem eventually given the way you're naming the migrations and columns. What if two users have the same initials?
Might there be a way you can accomplish what you need to by using an additional database table (maybe a polymorphic join table?) instead of adding columns for every user
? Something along these lines could work:
class CreateDisbursements < ActiveRecord::Migration[5.1]
def change
create_table :disbursements do |t|
t.decimal :amount
t.integer :payable_id
t.string :payable_type
t.integer :receivable_id
t.string :receivable_type
t.timestamps
end
add_index :disbursements, [:payable_type, :payable_id]
add_index :disbursements, [:receivable_id, :receivable_type]
end
end
class Disbursement < ApplicationRecord
belongs_to :payable, polymorphic: true
belongs_to :receivable, polymorphic: true
end
class ShoppingList < ApplicationRecord
has_many :disbursements, as: :payable
has_many :users, through: :disbursements, source: :receivable, source_type: 'User'
end
class Payment < ApplicationRecord
has_many :disbursements, as: :payable
has_many :users, through: :disbursements, source: :receivable, source_type: 'User'
end
class User < ApplicationRecord
has_many :disbursements, as: :receivable
has_many :payments, through: :disbursements, source: :payable, source_type: 'Payment'
has_many :shopping_lists, through: :disbursements, source: :payable, source_type: 'ShoppingList'
end
user = User.find params[:user_id]
payment = Payment.find params[:payment_id]
amount = params[:amount]
payment.disbursements.create(amount: amount, receivable: user)
user.disbursements.create(amount: amount, payable: payment)
Disbursement.create(amount: amount, payable: payment, receivable: user)
user.payments
payment.users
Upvotes: 1