Dynamically create Ruby class with ActiveRecord connection - ruby-on-rails

I'm attempting to dynamically create a Class and assign one of several database connections to each Class.
I'm working with anywhere between two or three databases which change over time, therefore, I'm hesitant to store each connection string in a separate Class and inherit from it instead of ActiveRecord::Base.
The following throws an error "RuntimeError: Anonymous class is not allowed.", but I'm not sure how to work around it or if there are better alternatives.
class ClassFactory
def self.create_class(new_class, table, db_connection)
c = Class.new(ActiveRecord::Base) do
db = db_connection
self.table_name = table
establish_connection(:adapter => db.database_type, :host => db.host, :database => db.database, :username => db.username, :password => db.password).connection
end
Module.const_set new_class, c
end
end

Per ActiveRecord::ConnectionHandling#establish_connection's source, you can't establish a connection from a class for which name doesn't return a truthy value.
Now, you are assigning the class to a constant using const_set, and that will give it a name. But you need to do that before you call establish_connection:
class ClassFactory
def self.create_class(new_class, table, db_connection)
c = Class.new(ActiveRecord::Base) do
db = db_connection
self.table_name = table
end
Module.const_set new_class, c
c.establish_connection(:adapter => db.database_type, :host => db.host, :database => db.database, :username => db.username, :password => db.password).connection
end
end
Also, do you really want Module.const_set(...)? That will result in a class named Module::Foo. Possibly you just want Object.const_set(...) (which just gives Foo)? Or even const_set(...), so you get ClassFactory::Foo?

You may set establish_connection for model dynamically:
database.yml
development:
adapter: mysql
username: root
password:
database: example_development
oracle_development:
adapter: oracle
username: root
password:
database: example_oracle_development
And in any place of code you may change db connection of your model:
User.establish_connection "oracle_#{RAILS_ENV}".to_sym
Also you can create model classes dynamically:
class ClassFactory
def self.create_class(new_class, table, connection_name)
Object.const_set(new_class, Class.new(ActiveRecord::Base) {})
new_class.constantize.table_name = table
new_class.constantize.establish_connection(connection_name)
end
end
ClassFactory.create_class('NewUser', 'users', :development)
After that NewUser class will be available to use.
This version works in both Rails 5.0 and 3.2.

Related

Does Ruby or Rails have an equivalent of pandas.read_sql?

So in Python, I am used to using something like
pd.read_sql(sql_query, connection_object)
in order to grab data from a remote database. But when forming a similar connection object in Ruby:
require 'pg'
#connect_obj = PG.connect(:host => host, :dbname => db , :user => user , :password => pwd , :port => port )
what can Ruby do in order to run something like pd.read_sql(sql_query, connection_object)?
With Rails, the usual way is to create a model class for your table and then use ActiveRecord methods.
But if you want to run some general queries without using any model classes, you can try it this way:
ActiveRecord::Base.connection.execute('SELECT * FROM users').each { |row| puts row }

Have access to multiple databases [duplicate]

In our program, each customer gets their own database. We e-mail them a link that connects them to their database. The link contains a GUID that lets the program know which database to connect to.
How do I dynamically and programatically connect ActiveRecord to the right db?
You can also do this easily without hardcoding anything and run migrations automatically:
customer = CustomerModel.find(id)
spec = CustomerModel.configurations[RAILS_ENV]
new_spec = spec.clone
new_spec["database"] = customer.database_name
ActiveRecord::Base.establish_connection(new_spec)
ActiveRecord::Migrator.migrate("db/migrate_data/", nil)
I find it useful to re-establish the old connection on a particular model afterwards:
CustomerModel.establish_connection(spec)
you can change the connection to ActiveRecord at any time by calling ActiveRecord::Base.establish_connection(...)
IE:
ActiveRecord::Base.establish_connection({:adapter => "mysql", :database => new_name, :host => "olddev",
:username => "root", :password => "password" })
It's been a while since this question has been created, but I have to say that there is another way too:
conn_config = ActiveRecord::Base.connection_config
conn_config[:database] = new_database
ActiveRecord::Base.establish_connection conn_config
class Database
def self.development!
ActiveRecord::Base.establish_connection(:development)
end
def self.production!
ActiveRecord::Base.establish_connection(ENV['PRODUCTION_DATABASE'])
end
def self.staging!
ActiveRecord::Base.establish_connection(ENV['STAGING_DATABASE'])
end
end
And in .env (with dotenv-rails gem for instance):
PRODUCTION_DATABASE=postgres://...
STAGING_DATABASE=postgres://...
And now you can:
Database.development!
User.count
Database.production!
User.count
Database.staging!
User.count
# etc.

In RoR, how do I initialize fields in my model based on a field in my model that has no underlying database column?

I’m using Rails 4.2.7. I have an attribute in my model that doesn’t have a database field underneath it
attr_accessor :division
This gets initialized when I create a new object.
my_object = MyObject.new(:name => name,
:age => get_age(data_hash),
:overall_rank => overall_rank,
:city => city,
:state => state,
:country => country,
:age_group_rank => age_group_rank,
:gender_rank => gender_rank,
:division => division)
What I would like is when this field gets set (if it is not nil), for two other fields that do have mappings in the database to get set. The other fields would be substrings of the “division” field. Where do I put that logic?
I'd probably drop the attr_accessor :division and do it by hand with:
def division=(d)
# Break up `d` as needed and assign the parts to the
# desired real attributes.
end
def division
# Combine the broken out attributes as needed and
# return the combined string.
end
With those two methods in place, the following will all call division=:
MyObject.new(:division => '...')
MyObject.create(:division => '...')
o = MyObject.find(...); o.update(:division => '...')
o = MyObject.find(...); o.division = '...'
so the division and the broken out attributes will always agree with each other.
If you try to use one of the lifecycle hooks (such as after_initialize) then things can get out of sync. Suppose division has the form 'a.b' and the broken out attributes are a and b and suppose that you're using one of the ActiveRecord hooks to break up division. Then saying:
o.division = 'x.y'
should give you o.a == 'x' but it won't because the hook won't have executed yet. Similarly, if you start with o.division == 'a.b' then
o.a = 'x'
won't give you o.division == 'x.b' so the attributes will have fallen out of sync again.
I see couple of options here
You can add it in your controller as follows
def create
if params[:example][:division]
# Set those params here
end
end
Or you can use before_save In your model
before_save :do_something
def do_something
if division
# Here!
end
end

Overriding id on create in ActiveRecord

Is there any way of overriding a model's id value on create? Something like:
Post.create(:id => 10, :title => 'Test')
would be ideal, but obviously won't work.
id is just attr_protected, which is why you can't use mass-assignment to set it. However, when setting it manually, it just works:
o = SomeObject.new
o.id = 8888
o.save!
o.reload.id # => 8888
I'm not sure what the original motivation was, but I do this when converting ActiveHash models to ActiveRecord. ActiveHash allows you to use the same belongs_to semantics in ActiveRecord, but instead of having a migration and creating a table, and incurring the overhead of the database on every call, you just store your data in yml files. The foreign keys in the database reference the in-memory ids in the yml.
ActiveHash is great for picklists and small tables that change infrequently and only change by developers. So when going from ActiveHash to ActiveRecord, it's easiest to just keep all of the foreign key references the same.
You could also use something like this:
Post.create({:id => 10, :title => 'Test'}, :without_protection => true)
Although as stated in the docs, this will bypass mass-assignment security.
Try
a_post = Post.new do |p|
p.id = 10
p.title = 'Test'
p.save
end
that should give you what you're looking for.
For Rails 4:
Post.create(:title => 'Test').update_column(:id, 10)
Other Rails 4 answers did not work for me. Many of them appeared to change when checking using the Rails Console, but when I checked the values in MySQL database, they remained unchanged. Other answers only worked sometimes.
For MySQL at least, assigning an id below the auto increment id number does not work unless you use update_column. For example,
p = Post.create(:title => 'Test')
p.id
=> 20 # 20 was the id the auto increment gave it
p2 = Post.create(:id => 40, :title => 'Test')
p2.id
=> 40 # 40 > the next auto increment id (21) so allow it
p3 = Post.create(:id => 10, :title => 'Test')
p3.id
=> 10 # Go check your database, it may say 41.
# Assigning an id to a number below the next auto generated id will not update the db
If you change create to use new + save you will still have this problem. Manually changing the id like p.id = 10 also produces this problem.
In general, I would use update_column to change the id even though it costs an extra database query because it will work all the time. This is an error that might not show up in your development environment, but can quietly corrupt your production database all the while saying it is working.
we can override attributes_protected_by_default
class Example < ActiveRecord::Base
def self.attributes_protected_by_default
# default is ["id", "type"]
["type"]
end
end
e = Example.new(:id => 10000)
Actually, it turns out that doing the following works:
p = Post.new(:id => 10, :title => 'Test')
p.save(false)
As Jeff points out, id behaves as if is attr_protected. To prevent that, you need to override the list of default protected attributes. Be careful doing this anywhere that attribute information can come from the outside. The id field is default protected for a reason.
class Post < ActiveRecord::Base
private
def attributes_protected_by_default
[]
end
end
(Tested with ActiveRecord 2.3.5)
Post.create!(:title => "Test") { |t| t.id = 10 }
This doesn't strike me as the sort of thing that you would normally want to do, but it works quite well if you need to populate a table with a fixed set of ids (for example when creating defaults using a rake task) and you want to override auto-incrementing (so that each time you run the task the table is populate with the same ids):
post_types.each_with_index do |post_type|
PostType.create!(:name => post_type) { |t| t.id = i + 1 }
end
Put this create_with_id function at the top of your seeds.rb and then use it to do your object creation where explicit ids are desired.
def create_with_id(clazz, params)
obj = clazz.send(:new, params)
obj.id = params[:id]
obj.save!
obj
end
and use it like this
create_with_id( Foo, {id:1,name:"My Foo",prop:"My other property"})
instead of using
Foo.create({id:1,name:"My Foo",prop:"My other property"})
This case is a similar issue that was necessary overwrite the id with a kind of custom date :
# in app/models/calendar_block_group.rb
class CalendarBlockGroup < ActiveRecord::Base
...
before_validation :parse_id
def parse_id
self.id = self.date.strftime('%d%m%Y')
end
...
end
And then :
CalendarBlockGroup.create!(:date => Date.today)
# => #<CalendarBlockGroup id: 27072014, date: "2014-07-27", created_at: "2014-07-27 20:41:49", updated_at: "2014-07-27 20:41:49">
Callbacks works fine.
Good Luck!.
For Rails 3, the simplest way to do this is to use new with the without_protection refinement, and then save:
Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save
For seed data, it may make sense to bypass validation which you can do like this:
Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save(validate: false)
We've actually added a helper method to ActiveRecord::Base that is declared immediately prior to executing seed files:
class ActiveRecord::Base
def self.seed_create(attributes)
new(attributes, without_protection: true).save(validate: false)
end
end
And now:
Post.seed_create(:id => 10, :title => 'Test')
For Rails 4, you should be using StrongParams instead of protected attributes. If this is the case, you'll simply be able to assign and save without passing any flags to new:
Post.new(id: 10, title: 'Test').save # optionally pass `{validate: false}`
In Rails 4.2.1 with Postgresql 9.5.3, Post.create(:id => 10, :title => 'Test') works as long as there isn't a row with id = 10 already.
you can insert id by sql:
arr = record_line.strip.split(",")
sql = "insert into records(id, created_at, updated_at, count, type_id, cycle, date) values(#{arr[0]},#{arr[1]},#{arr[2]},#{arr[3]},#{arr[4]},#{arr[5]},#{arr[6]})"
ActiveRecord::Base.connection.execute sql

How can I dynamically change the Active Record database for all models in Ruby on Rails?

In our program, each customer gets their own database. We e-mail them a link that connects them to their database. The link contains a GUID that lets the program know which database to connect to.
How do I dynamically and programatically connect ActiveRecord to the right db?
You can also do this easily without hardcoding anything and run migrations automatically:
customer = CustomerModel.find(id)
spec = CustomerModel.configurations[RAILS_ENV]
new_spec = spec.clone
new_spec["database"] = customer.database_name
ActiveRecord::Base.establish_connection(new_spec)
ActiveRecord::Migrator.migrate("db/migrate_data/", nil)
I find it useful to re-establish the old connection on a particular model afterwards:
CustomerModel.establish_connection(spec)
you can change the connection to ActiveRecord at any time by calling ActiveRecord::Base.establish_connection(...)
IE:
ActiveRecord::Base.establish_connection({:adapter => "mysql", :database => new_name, :host => "olddev",
:username => "root", :password => "password" })
It's been a while since this question has been created, but I have to say that there is another way too:
conn_config = ActiveRecord::Base.connection_config
conn_config[:database] = new_database
ActiveRecord::Base.establish_connection conn_config
class Database
def self.development!
ActiveRecord::Base.establish_connection(:development)
end
def self.production!
ActiveRecord::Base.establish_connection(ENV['PRODUCTION_DATABASE'])
end
def self.staging!
ActiveRecord::Base.establish_connection(ENV['STAGING_DATABASE'])
end
end
And in .env (with dotenv-rails gem for instance):
PRODUCTION_DATABASE=postgres://...
STAGING_DATABASE=postgres://...
And now you can:
Database.development!
User.count
Database.production!
User.count
Database.staging!
User.count
# etc.

Resources