ORM one model - multiply tables - ruby

Have a database with multiple tables. All tables have the same structure. I am writing a small web application to work with this database on Ruby / Sinatra. I want to simplify the work with tables using ORM - Active Record or DataMapper (preferred). The manuals for the use of a single model for multiple tables offer something like:
class Table
include DataMapper::Resource
property id, Serial
property item, String
end
class TableA < Table
self.table_name = 'table_a'
end
class TableB < Table
self.table_name = 'table_b'
end
How this can be done for several dozen tables without copypaste?
If possible, the decision should be the possibility to add / remove the table without changing the code / settings and restart the application.
Something like:
# Model declaration
DataMapper.finalize
itemA = Table.new (use_table: 'table_a')
itemB = Table.new (use_table: 'table_b')

One of the ways to achive this is using eval.
class Table
include DataMapper::Resource
property id, Serial
property item, String
end
table_names = {'TableA' => 'table_a', 'TableB' => 'table_b'}
table_names.each do |klass_name, table_name|
eval <<DYNAMIC
class #{klass_name} < Table
self.table_name = '#{table_name}'
end
DYNAMIC
end

Related

ActiveRecord mapping to table name with schema name as prefix

Has anyone experienced this issue on mapping table in ActiveRecord when table name need a schema name as a prefix (oracle)?
Gemfile
gem 'activerecord', '4.2.4'
gem 'activerecord-oracle_enhanced-adapter', '1.6.7'
....
db_helper.rb
class Student < ActiveRecord::Base
self.abstract_class = true
self.table_name_prefix = 'OPT_ABC.'
self.table_name = 'STUDENT'
def self.list_student
puts Student.take(1) #testing
end
end
The actual table name looks like:
SELECT * FROM OPT_ABC.STUDENT;
I am able to connect to the database instance, but when the code gets line:
puts Student.take(1) # SELECT * FROM STUDENT LIMIT 1
I get the following error:
ActiveRecord::StatementInvalid:
table or view does not exist: SELECT "STUDENT".* FROM "STUDENT"
I am looking for solution on how to handle 'OPT_ABC." table prefix. Please share your solution.
It looks like the problem is that you're trying to use both self.table_name_prefix= and self.table_name= together when you should be using one OR the other.
First let's consider how both self.table_name_prefix= and self.table_name= work.
self.table_name_prefix=
According to the documentation, self.table_name_prefix= works by prepending the passed in value to the table name that ActiveRecord automatically generates based off of the name of the class.
So, if the class name is Student and you do self.table_name_prefix = 'OPT_ABC.', your table name will be OPT_ABC.STUDENTS. Note that the generated table name is plural (and ends in an s).
self.table_name=
According to the documentation, self.table_name= sets the table name explicitly. This means that it completely overrides the table name to the value that you pass in.
So, if you do self.table_name = 'OPT_ABC.STUDENT' your table name will be OPT_ABC.STUDENT.
So, given that, to set the table name to OPT_ABC.STUDENT, you should be able to simply pass the value into self.table_name like this:
class Student < ActiveRecord::Base
self.abstract_class = true
self.table_name = 'OPT_ABC.STUDENT'
def self.list_student
puts Student.take(1) #testing
end
end

You can have_one if you're true

In my website (written with sinatra) I am trying to set up a database. I have 2 tables, here referred to as Table1 and Table2.
models.rb
class Table1 < ActiveRecord::Base
Table1.where(bool:true) has_one :table2 # PSUDO-CODE
# So that every record where bool:true has the relationship
# but every record where bool:false or bool:nil doesn't
end
class Table2 < ActiveRecord::Base
belongs_to :table1
end
I am trying to find a way to make the section labeled PSUDO-CODE into actual code. How can I do that?
You can't do this directly: a class either has a relationship or it doesn't (although of course there may be no associated record)
You can set conditions on an association, but to the best of my knowledge you can only really set conditions on the associated collection (i.e. table 2 in this case)
You can however override the generated method, so for example
class Table1 < ActiveRecord::Base
has_one :table2
def table2(*args)
bool ? super : nil
end
end
This works with current versions of activerecord - not how far back this is supported (older version defined the association methods directly on the class so you couldn't call super)

Make friendly_id scope play nice with subclassed ActiveRecord model

I have a subclassed ActiveRecord model which uses a separate table to store records and friendly_id (4.1.0.beta.1) to generate slugs. Problem is friendly_id is using the parent class's table to check for existing slugs, instead of using the child table. Basically I'd like friendly_id to scope its checks to the right table.
Example:
class Parent
friendly_id :name, :use => :slugged
end
class Child < Parent
self.table_name = 'children'
end
Parent.create(name: 'hello').slug
> 'hello'
Child.create(name: 'hello').slug
> 'hello--2'
I want friendly_id to generate the 'hello' slug for the second create, because there are no records in the children table with that slug. Is there a way to configure or monkey patch the class friendly id uses for its queries?
EDIT: added friendly_id version for future reference
I'm posting my own solution to this problem, just in case someone is having the same problem. I should reiterate that this problem was found on version 4.1.0.beta.1 of the friendly_id gem (which at the time was the most recent version), so this issue may not occur any more.
To solve this problem, I basically configured slug_generator_class to use my own class, so I could monkey patch the culprit method.
In my model:
friendly_id do |config|
config.slug_generator_class = SubclassScopableSlugGenerator
end
In an initializer, I overrode the FriendlyId::SlugGenerator.conflicts method so I could access the sluggable_class var:
# Lets a non-STI subclass of a FriendlyId parent (i.e. a subclass with its
# own dedicated table) have independent slug uniqueness.
class SubclassScopableSlugGenerator < FriendlyId::SlugGenerator
private
def conflicts
# this is the only line we're actually changing
sluggable_class = friendly_id_config.model_class
pkey = sluggable_class.primary_key
value = sluggable.send pkey
base = "#{column} = ? OR #{column} LIKE ?"
# Awful hack for SQLite3, which does not pick up '\' as the escape character without this.
base << "ESCAPE '\\'" if sluggable.connection.adapter_name =~ /sqlite/i
scope = sluggable_class.unscoped.where(base, normalized, wildcard)
scope = scope.where("#{pkey} <> ?", value) unless sluggable.new_record?
length_command = "LENGTH"
length_command = "LEN" if sluggable.connection.adapter_name =~ /sqlserver/i
scope = scope.order("#{length_command}(#{column}) DESC, #{column} DESC")
end
end

Specify column for model attributes in Rails3?

Is it possible to specify the column for an attribute? I have something like:
NAME, COUNTRY
The database is quite large and I have over a thousand columns which are capitalized like this. I want to refer to them as so:
attr_accessible :name, :country
Where :name = column NAME. I'd prefer Model.name rather than Model.NAME. It isn't possible to downcase every column name in the structure file.
Here is an idea to do the way you preferred.
Command to generate migration: (In my example, im applying this on Posts table. Change according to your table name)
rails g migrate RenameColumnsOfPosts
Below is the migration up method. Here taking all the column names and for each one I'm applying rename_column to make it downcase.
class RenameColumnsOfPosts < ActiveRecord::Migration
def up
Post.columns.map(&:name).each do |column_name|
rename_column(:posts, column_name, column_name.downcase)
end
end
def down
#You can do the opposite operation here. Leaving on you
end
end
As i didnt run it personally, it might need some changes. So start with it and let me know if facing any problem.
Please write code inside of model ,
it's just for demostration code,after you get it and update as per logic :
This Code inside of model :
...
attr_accessor :name,:country
before_save :fill_save
..
#assign variable like ...
def fill_save
self.NAME= self.name
self.COUNTRY= self.country
end
....

Sequel model over two joined tables

I have a legacy PostgreSQL database, which has a single model split into two tables, with one-to-one mapping between them.
CREATE TABLE auth_user (
id SERIAL,
username VARCHAR(30),
email VARCHAR(75),
password VARCHAR(64),
first_name VARCHAR(75),
last_name VARCHAR(75)
)
CREATE TABLE user_profile (
user_id INTEGER REFERENCES auth_User.id,
phone VARCHAR(32)
)
Unfortunately, I'm unable to change database structure.
I want to use this as a single Sequel model. Retreiving data from database works as expected:
class User < Sequel::Model
end
# Variant 1: using LEFT JOIN
#User.set_dataset DB[:auth_user].left_join(:user_profile, :user_id => :id)
# Variant 2: using two FROM tables
User.set_dataset DB[:auth_user, :user_profile]\
.where(:auth_user__id => :user_profile__user_id)
user = User[:username => "root"] # This works.
However, saving the model fails:
user.set :first_name => "John"
user.save # This fails.
If I use first variant of the dataset (with left_join) I get a "Need multiple FROM tables if updating/deleting a dataset with JOINs" error. If I use second variant, it still fails: "PG::Error: ERROR: column "phone" of relation "auth_user" does not exist LINE 1: ..."email" = 'nobody#example.org', "password" = '!', "phone"..."
Is there a way I could make Sequel seamlessly issue two UPDATE statements? (Same question holds for INSERTs, too).
You can have a Sequel model that uses a joined dataset, but there's no easy way to save such a model.
Personally, I would use a many_to_one relationship, nested attributes, and hooks for what you want:
class UserProfile < Sequel::Model(:user_profile)
end
class User < Sequel::Model(:auth_user)
many_to_one :user_profile, :key=>:id, :primary_key=>:user_id
plugin :nested_attributes
nested_attributes :user_profile
def phone
user_profile.phone
end
def phone=(v)
user_profile.phone = v
end
def user_profile
if s = super
s
else
self.user_profile_attributes = {}
super
end
end
def before_destroy
user_profile.destroy
super
end
def before_create
user_profile
super
end
def after_update
super
user_profile.save
end
end
I haven't tested that, but something like it should work. If you have problems with it, you should probably post on the sequel-talk Google Group.

Resources