SQLite Creating Table Dynamically - ruby

I have a table in SQLite and when an entry is made in this table, I would like to create a new table for each entry in the first table. (I want to do this in Ruby On Rails, if that helps)
Here is an example to clarify what I am trying to achieve:
Assume there is a table: Campaigns
Campaigns
Campaign_ID, date, name
So if I make an entry:
01 06/12 FirstCampaign
is there a way to create a new table called: {Campaign_ID}_Page
i.e:
01_Page
(fields for this table go here)

Why do you need it?
I think it would be better to create a table Pages with a foreign key to your table Campaigns.
An example (I use Sequel):
require 'sequel'
DB = Sequel.sqlite
DB.create_table :Campaigns do
primary_key :id
column :campaign_id, :integer
column :date, :date
column :name, :string
end
DB.create_table :Pages do
primary_key :id
foreign_key :campaign_id, :Campaigns
column :text, :string
end
key = DB[:Campaigns].insert(:campaign_id => 01, :date=> Date.new(2012,1,1), :name => 'FirstCampaign')
DB[:Pages].insert(:campaign_id => key, :text => 'text for FirstCampaign')
key = DB[:Campaigns].insert(:campaign_id => 02, :date=> Date.new(2012,1,1), :name => 'SecondCampaign')
DB[:Pages].insert(:campaign_id => key, :text => 'text for SecndCampaign')
#All pages for 1st campaign
p DB[:Pages].filter(
:campaign_id => DB[:Campaigns].filter(:campaign_id => 1).first[:id]
).all
But to answer your question: You could try to use a model hook.
An example with Sequel:
require 'sequel'
DB = Sequel.sqlite
DB.create_table :Campaigns do
primary_key :id
column :campaign_id, :integer
column :date, :date
column :name, :string
end
class Campaign < Sequel::Model
def after_create
tabname = ("%05i_page" % self.campaign_id).to_sym
puts "Create table #{tabname}"
self.db.create_table( tabname ) do
foreign_key :campaign
end
end
end
p DB.table_exists?(:'01_page') #-> false, table does not exist
Campaign.create(:campaign_id => 01, :date=> Date.new(2012,1,1), :name => 'FirstCampaign')
p DB.table_exists?(:'00001_page') #-> true Table created
My example has no test, if the table already exist. If you really want to use it,

Related

create unique constraints per user

I am building a small app and at this point I am creating the database schema. I use PostgreSQL with Sequel and I have the two migrations:
Sequel.migration do
change do
Sequel::Model.db.run 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'
create_table :user do
String :id, :type => :uuid, :primary_key => true, :default => Sequel.function(:uuid_generate_v4)
DateTime :created_at
DateTime :updated_at
index :id, :unique => true
end
end
end
Sequel.migration do
change do
Sequel::Model.db.run 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'
create_table :location do
String :id, :type => :uuid, :primary_key => true, :default => Sequel.function(:uuid_generate_v4)
foreign_key :user_id, :user, :type => :uuid
String :name, :unique => true, :null => false # TODO: unique, per user
Float :latitude, :null => false
Float :longitude, :null => false
DateTime :created_at
DateTime :updated_at
index :id, :unique => true
full_text_index :name, :index_type => :gist
end
end
end
As you can see the name column on the location table is unique, but I am in doubt about it. Because I want to establish a unique constraint on the name, but per user, so a user can have only one location with the same name but in the entire table many users can have locations with the same name (the relation between user and location is a one to many).
I suppose my unique constraint added on the column will make column generally unique, so amongst all users the location name must be unique. If so, how do I create a constraint that makes the name unique on a per user basis?
Just create the unique constraint over both columns:
UNIQUE (user_id, name)
Per documentation:
This specifies that the combination of values in the indicated columns
is unique across the whole table, though any one of the columns need
not be (and ordinarily isn't) unique.
But from the looks of it, you really want another table user_location than implements an n:m relation between locations and users - with a primary key on (user_id, location_id).
And don't call the first table "user", that's a reserved word in standard SQL and in Postgres and shouldn't be used as identifier.

create unique constraints per user in tables with n:m relation

I have asked a question a few moments ago, create unique constraints per user and the answer was very simple.
For creating a unique index on a column, but on a per user basis, all I have to do is:
unique [:user_id, :name] # SQL syntax: UNIQUE (user_id, name)
But the relation between the user table and the table that references the user_id is a 1:n (user to location), so I have a foreign_key inside the location table which references user_id. This question is about a n:m relation between two tables and adding a unique constraint on one of the tables.
Sequel.migration do
change do
Sequel::Model.db.run 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'
create_table :customer do
String :id, :type => :uuid, :primary_key => true, :default => Sequel.function(:uuid_generate_v4)
DateTime :created_at
DateTime :updated_at
index :id, :unique => true
end
end
end
Sequel.migration do
change do
Sequel::Model.db.run 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'
create_table :role do
String :id, :type => :uuid, :primary_key => true, :default => Sequel.function(:uuid_generate_v4)
String :name, :unique => true, :null => false # TODO: unique, per customer
DateTime :created_at
DateTime :updated_at
unique [:customer_id, :name]
index :id, :unique => true
full_text_index :name, :index_type => :gist
end
end
end
The above code illustrates the two tables I mentioned have a n:m relation (customer to role), the following table illustrates the join table with the foreign keys for both the tables:
Sequel.migration do
change do
Sequel::Model.db.run 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'
create_table :customer_role do
String :id, :type => :uuid, :primary_key => true, :default => Sequel.function(:uuid_generate_v4)
foreign_key :customer_id, :customer, :type => :uuid
foreign_key :role_id, :role, :type => :uuid
index :id, :unique => true
end
end
end
What I would like to do is to declare a unique constraint such as UNIQUE (customer_id, :name) on the role table. But I cannot do that, so how do I achieve that in another way?
What I would like to do is to declare a unique constraint such as
UNIQUE (customer_id, :name) on the role table. But I cannot do that,
so how do I achieve that in another way?
The essence of your problem seems to be that the column role.customer_id doesn't exist. In fact, I'm pretty sure that column shouldn't exist, so that part's actually good.
At the very least, you need
unique [:customer_id, :role_id]
in "customer_role".

How to save newly-created associated models using Sequel?

Assuming I have something like this:
# Schema:
DB.create_table :transactions do
primary_key :id
foreign_key :card_id, :cards
Integer :amount
end
DB.create_table :cards do
primary_key :id
foreign_key :transaction_id, :transactions
Intger :number
end
# Models:
class Transaction < Sequel::Model
one_to_one :card
end
class Card < Sequel::Model
one_to_one :transaction
end
How do I make this work, such that it saves both trans, card, and their respective associations?
trans = Transaction.new(:amount => 100)
card = Card.new(:number => 4000500060007000)
trans.card = card
trans.save
As it stands, this doesn't work because card isn't saved first, and Sequel throws a "no primary key" error. If I save the card first, it won't get the transaction's id.
Basically, I'm trying to avoid this:
# Save without associations first, but this will assign primary keys
trans.save
card.save
# Now, manually create associations
trans.card = card
card.trans = trans
# Re-save again, this time with associations
trans.save
card.save
You may want to try and change the association type to something more like:
# Schema:
DB.create_table :transactions do
primary_key :id
Integer :amount
end
DB.create_table :cards do
primary_key :id
foreign_key :transaction_id, :transactions
Integer :number
end
# Models:
class Transaction < Sequel::Model
one_to_many :card
end
class Card < Sequel::Model
one_to_one :transaction
end
Now you create as:
trans = Transaction.new(:amount => 100)
trans.save
trans.add_card(:number => 4000500060007000)
This will allow for all the same options as well as permitting (but certainly not requiring) a transaction to be split across multiple cards.

Sequel accessing many_to_many join table when adding association

I'm building a wishlist system using Sequel. I have a wishlists and items table and an items_wishlists join table (that name is what sequel chose). The items_wishlists table also has an extra column for a facebook id (so I can store opengraph actions), which is a NOT NULL column.
I also have Wishlist and Item models with the sequel many_to_many association set up. The Wishlist class also has the :select option of the many_to_many association set to select: [:items.*, :items_wishlists__facebook_action_id].
Is there a way that I can add in extra data when creating the association, like wishlist.add_item my_item, facebook_action_id: 'xxxx' or something? I can't do it after I create the association as the facebook id is has NOT NULL on the column.
Thanks for any help
The recommended way to do this is to add a model for the join table. However, if you don't want to do that, you can do:
class Wishlist
def _add_item(item, hash={})
model.db[:items_wishlists].insert(hash.merge(:item_id=>item.id, :wishlist_id=>id))
end
end
I think there is also another possibility.
First a MWE (minimal working example) of your question:
require 'sequel'
Sequel.extension :pretty_table #Sequel::PrettyTable.print()/Sequel::PrettyTable.string()
DB = Sequel.sqlite
DB.create_table(:wishlists){
primary_key :id
String :listname
}
DB.create_table(:items){
primary_key :id
String :descr
}
DB.create_table(:items_wishlists){
primary_key :id
foreign_key :wishlist_id, :wishlists
foreign_key :item_id, :items
add_column :facebook_id, type: :nvarchar
}
class Item < Sequel::Model
many_to_many :wishlists
end
class Wishlist < Sequel::Model
many_to_many :items
end
w1 = Wishlist.create(listname: 'list1')
w1.add_item(Item.create(descr: 'item 1'))
#w1.add_item(Item.create(descr: 'item 2'), facebook_id: 'fb2') ##<- This does not work
#Sequel::PrettyTable.print(Wishlist)
#Sequel::PrettyTable.print(Item)
Sequel::PrettyTable.print(DB[:items_wishlists])
To allow ad_itemwith a parameter (Wishlist#add_item(Item.create(descr: 'item 2'), facebook_id: 'fb2')) you must define an adder as in this example:
require 'sequel'
Sequel.extension :pretty_table
Sequel::PrettyTable.print()/Sequel::PrettyTable.string()
DB = Sequel.sqlite
DB.create_table(:wishlists){
primary_key :id
String :listname
}
DB.create_table(:items){
primary_key :id
String :descr
}
DB.create_table(:items_wishlists){
primary_key :id
foreign_key :wishlist_id, :wishlists
foreign_key :item_id, :items
add_column :facebook_id, type: :nvarchar
}
class Item < Sequel::Model
#~ many_to_many :wishlists
end
class Wishlist < Sequel::Model
many_to_many :items,
join_table: :items_wishlists, class: Item,
left_key: :wishlist_id, right_key: :item_id,
adder: (lambda do |item, facebook_id: nil|
self.db[:items_wishlists].insert(wishlist_id: self.id, item_id: item.id, facebook_id: facebook_id)
end)
end
w1 = Wishlist.create(listname: 'list1')
w1.add_item(Item.create(descr: 'item 1'))
w1.add_item(Item.create(descr: 'item 2'), facebook_id: 'fb2')
Sequel::PrettyTable.print(DB[:items_wishlists])
The result:
+-----------+--+-------+-----------+
|facebook_id|id|item_id|wishlist_id|
+-----------+--+-------+-----------+
| | 1| 1| 1|
|fb2 | 2| 2| 1|
+-----------+--+-------+-----------+
But the next problem will come later:
With w1.items you get the list of items, but you have no access to the parameters. (at least I found no way up to now. I'm still researching, but I expect, that I need a model of the join table for this (see Jeremys recommendation))

ActiveRecord/sqlite3 column type lost in table view?

I have the following ActiveRecord testcase that mimics my problem. I have a People table with one attribute being a date. I create a view over that table adding one column which is just that date plus 20 minutes:
#!/usr/bin/env ruby
%w|pp rubygems active_record irb active_support date|.each {|lib| require lib}
ActiveRecord::Base.establish_connection(
:adapter => "sqlite3",
:database => "test.db"
)
ActiveRecord::Schema.define do
create_table :people, :force => true do |t|
t.column :name, :string
t.column :born_at, :datetime
end
execute "create view clowns as select p.name, p.born_at, datetime(p.born_at, '+' || '20' || ' minutes') as twenty_after_born_at from people p;"
end
class Person < ActiveRecord::Base
validates_presence_of :name
end
class Clown < ActiveRecord::Base
end
Person.create(:name => "John", :born_at => DateTime.now)
pp Person.all.first.born_at.class
pp Clown.all.first.born_at.class
pp Clown.all.first.twenty_after_born_at.class
The problem is, the output is
Time
Time
String
When I expect the new datetime attribute of the view to be also a Time or DateTime in the ruby world. Any ideas?
I also tried:
create view clowns as select p.name, p.born_at, CAST(datetime(p.born_at, '+' || '20' || ' minutes') as datetime) as twenty_after_born_at from people p;
With the same result.
Well, after more investigation, I found that:
MySQL works:
%w|pp rubygems active_record irb active_support date|.each {|lib| require lib}
ActiveRecord::Base.establish_connection(
:adapter => "mysql",
:username => "root",
:database => "test2"
)
ActiveRecord::Schema.define do
create_table :people, :force => true do |t|
t.column :name, :string
t.column :born_at, :datetime
end
execute "create view clowns as select p.name, p.born_at, (p.born_at + INTERVAL 20 MINUTE) as twenty_after_born_at from people p;"
end
class Person < ActiveRecord::Base
validates_presence_of :name
end
class Clown < ActiveRecord::Base
end
Person.create(:name => "John", :born_at => DateTime.now)
pp Person.all.first.born_at.class
pp Clown.all.first.born_at.class
pp Clown.all.first.twenty_after_born_at.class
Produces:
Time
Time
Time
Reading the sqlite3 adapter source code, I found out that it uses PRAGMA table_info(table_name) to get the type information, and that does not return the types for views:
sqlite> pragma table_info('people');
0|id|INTEGER|1||1
1|name|varchar(255)|0||0
2|born_at|datetime|0||0
sqlite> pragma table_info('clowns');
0|name|varchar(255)|0||0
1|born_at|datetime|0||0
2|twenty_after_born_at||0||0
Therefore it may be a limitation of the adapter or just a sqlite3's views limitation. I have opened a ticket for ActiveRecord:
Also, quoting this mail in sqlite-users:
RoR should be using the
sqlite3_column_type() API to determine
the type of the values returned from a
query. Other APIs like
sqlite3_column_decltype() and pragma
table_info are returning other
information, not the type of the
result value.
Well, basically there is no datatime type in SQLite as opposed to MySQL. In your example you explicitly define types for the table but do not specify types for the view. That might be the problem. Can not check it since I have never touched ruby.

Resources