Changing table name at query run time in a Rails application
The method
def self.for_tenant(tid)
self.table_name = "products_" + tid.to_s
self
end
makes sense, however, it has a side effect: it changes table name for Product
class. When this class is used later in the same request, for example, in this way:
Product.where(name: "My other product") ...
the table name won't be products
as you may expect; it will stay the same as changed by for_tenant
method previously.
To avoid this ambiguity and keep code clean you can use another strategy:
1) Define a module which holds all logic of work with tenant partitions:
# app/models/concerns/partitionable.rb
module Partitionable
def self.included(base)
base.class_eval do
def self.tenant_model(tid)
partition_suffix = "_#{tid}"
table = "#{table_name}#{partition_suffix}"
exists = connection.select_one("SELECT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = '#{table}')")
unless exists['exists'] == 't' || exists['exists'] == true # different versions of pg gem give different answers
return self # returning original model class
end
class_name = "#{name}#{partition_suffix}"
model_class = Class.new(self)
model_class.define_singleton_method(:table_name) do
table
end
model_class.define_singleton_method(:name) do
class_name
end
model_class
end
end
end
end
2) Include this module in your model class:
class Product < PostgresDatabase
include Partitionable
...
end
3) Use it the same way as you intended:
Product.tenant_model(TENANT_ID).where(name: "My product")...
What's happened there:
Method tenant_model(TENANT_ID)
creates another model class for the tenant with ID TENANT_ID
. This class has name Product_<TENANT_ID>
, works with table products_<TENANT_ID>
and inherits all methods of Product
class. So it could be used like a regular model. And class Product
itself remains untouched: its table_name
is still products
.
How to change ActiveRecord's table name during runtime
I found the explanation for the described behavior. Although reset_table_name
resets the table name computed from the prefix, suffix (and maybe other things too), the table is initialized when a model is used and a query is generated. ActiveRecord
works "on the top of" Arel
, a relational algebra gem. When an ActiveRecord
model is used a table is created and filled @arel_table
instance variable. This caching is for performance purposes. If one wants to recreate the arel table, it can be reset by calling reset_column_information
. I needed to have both reset_table_name
and reset_column_information
in order to get a new table for the new table name. Probably I will have to worry about the performance, if I reset the table often.
Rails 3/ActiveRecord: How to switch/change table name during request dynamically?
I would use class variables:
class SampleData < ActiveRecord::Base
class << self
@@model_class = {}
# Create derived class for id
@@model_class[id] = Class.new(SampleData)
@@model_class[id].set_table_name "sample_data_#{id}"
# Repeat for other IDs
end
end
Now you can use the derived classes over and over, without causing memory leaks.
Depending on your actual situation (eg you do not know the IDs on beforehand), you could check whether the id is already present in the Hash dynamically and add it if not.
How to dynamically set model's table_name?
What I realized is, that I could just use superclass
without using globals, which I ended up using. This does not pose a race condition issue as with globals.
class ListenLocB < ListenLoc
superclass.table_name = 'LISTEN_ADDR_B' # or ListenLoc.table_name
self.sequence_name = :autogenerated
belongs_to :category, class_name: 'ListenLocCatB', foreign_key: 'cat_id'
belongs_to :country, class_name: 'Country', foreign_key: 'country_id'
end
Renaming table in rails
You would typically do this sort of thing in a migration:
class RenameFoo < ActiveRecord::Migration
def self.up
rename_table :foo, :bar
end
def self.down
rename_table :bar, :foo
end
end
How to re-name a ActiveRecord Model which can automatically change the table name in DB?
I would recommend the following:
Change manually the Active Record model class into Train
Make migration to change the database table name from cars to trains
Make good search to change references from Car to Train.
If you have constantly the need to change database table names, you might want to reconsider naming the tables more abstact way. Like in this case you could have table called vehicles and have the "type" field specifying the type (for instance: car or train).
How can I rename a database column in a Ruby on Rails migration?
rename_column :table, :old_column, :new_column
You'll probably want to create a separate migration to do this. (Rename FixColumnName
as you will.):
bin/rails generate migration FixColumnName
# creates db/migrate/xxxxxxxxxx_fix_column_name.rb
Then edit the migration to do your will:
# db/migrate/xxxxxxxxxx_fix_column_name.rb
class FixColumnName < ActiveRecord::Migration
def self.up
rename_column :table_name, :old_column, :new_column
end
def self.down
# rename back if you need or do something else or do nothing
end
end
For Rails 3.1 use:
While, the up
and down
methods still apply, Rails 3.1 receives a change
method that "knows how to migrate your database and reverse it when the migration is rolled back without the need to write a separate down method".
See "Active Record Migrations" for more information.
rails g migration FixColumnName
class FixColumnName < ActiveRecord::Migration
def change
rename_column :table_name, :old_column, :new_column
end
end
If you happen to have a whole bunch of columns to rename, or something that would have required repeating the table name over and over again:
rename_column :table_name, :old_column1, :new_column1
rename_column :table_name, :old_column2, :new_column2
...
You could use change_table
to keep things a little neater:
class FixColumnNames < ActiveRecord::Migration
def change
change_table :table_name do |t|
t.rename :old_column1, :new_column1
t.rename :old_column2, :new_column2
...
end
end
end
Then just db:migrate
as usual or however you go about your business.
For Rails 4:
While creating a Migration
for renaming a column, Rails 4 generates a change
method instead of up
and down
as mentioned in the above section. The generated change
method is:
$ > rails g migration ChangeColumnName
which will create a migration file similar to:
class ChangeColumnName < ActiveRecord::Migration
def change
rename_column :table_name, :old_column, :new_column
end
end
ActiveRecord wrong table_name
Media is a already a plural (it's the plural of medium), so active record pluralizes no further, even if people say "medias" often enough.
You can check how rails pluralizes things from the rails console
"medium".pluralize #=> "media"
"media".pluralize #=> "media"
If you disagree strongly and don't want to rename your table / model, you can either force active record to use your table name
class Media < ActiveRecord::Base
self.table_name = "medias"
end
or customise the inflections. Your app should have a config/initializers/inflections.rb which sets out how to do this.
Related Topics
Why Does My Ruby 'Ri' Tool Not Return Results in Command Prompt
Finding Out Current Index in Each Loop (Ruby)
How to Configure Ruby on Rails with Oracle
How to Know When to "Refresh" My Model Object in Rails
Get Single Char from Console Immediately
How to Set Http_Referer When Testing in Rails
How to Iterate Activerecord Attributes, Including Attr_Accessor Methods
In 'Require': No Such File to Load -- Iconv (Loaderror)
Ruby SQLite3 Installation SQLite3_Libversion_Number() MACos Sierra
How to Know What Is Not Thread-Safe in Ruby
How to Check If a Given Directory Exists in Ruby
How to Get Readline Support in Irb Using Rvm on Ubuntu 11.10
Difference Between Truncation, Transaction and Deletion Database Strategies
Install Rails 3 on Osx with Rvm
Spinning Background Tasks in Rails