create unique constraints per user - ruby

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.

Related

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".

SQLite Creating Table Dynamically

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,

Bug with ActiveRecords

I can't delete information about tables and columns from ActiveRecord's cache.
I'm using ActiveRecord for Ruby without Rails.
require 'active_record'
ActiveRecord::Base.establish_connection(
:adapter => "mysql2",
:database => #
:password => #
)
class Person < ActiveRecord::Base
belongs_to :peoples
end
enter code here
class Persons < ActiveRecord::Base
belongs_to :peoples
end
class People < ActiveRecord::Base
has_many :persons
end
Person.new
ActiveRecord::StatementInvalid: Mysql2::Error: Table 'vkusno.people' doesn't exist: SHOW FULL FIELDS FROM `people`'
Persons.new
ActiveRecord::StatementInvalid: Mysql2::Error: Table 'vkusno.persons' doesn't exist: SHOW FULL FIELDS FROM `persons`'
But I try to connect to a Database without some table and columns.
Before I had a table in the database, "People, Peoples, Person, Persons", but I drop all my tables and restarted my server a few times.
If I change my database to sqlite, I get some don't exist tables, which it I'm working and drop that tables too.
How I can repair it?
UPD
ActiveRecord::Base.logger = Logger.new(STDOUT)
class AddFirst < ActiveRecord::Migration
def up
create_table :persons do |t|
t.integer :idd
t.string :name
t.string :href
t.string :sex
t.string :country
t.string :city
t.boolean :can_message
t.boolean :can_wall
t.string :photo
t.boolean :is_friend
t.boolean :is_client
end
create_table :people do |x|
x.integer :id_general
x.string :description
end
end
def down
drop_table :persons
drop_table :people
end
def keys
add_column :persons, :peoples_id, :integer
add_index :persons, :peoples_id
end
end
> AddFirst.new.up
-- create_table(:persons)
CREATE TABLE `persons` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, `idd` int(11), `name` varchar(255), `href` varchar(255), `sex` varchar(255), `country` varchar(255), `city` varchar(255), `can_message` tinyint(1), `can_wall` tinyint(1), `photo` varchar(255), `is_friend` tinyint(1), `is_client` tinyint(1)) ENGINE=InnoDB
-- create_table(:people)
CREATE TABLE `people` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, `id_general` int(11), `description` varchar(255)) ENGINE=InnoDB
=> {}
AddFirst.new.keys
-- add_column(:persons, :peoples_id, :integer)
ALTER TABLE `persons` ADD `peoples_id` int(11)
-- add_index(:persons, :peoples_id)
CREATE INDEX `index_persons_on_peoples_id` ON `persons` (`peoples_id`)
=> nil
> Person.new
=> #<Person id: nil, id_general: nil, description: nil>
> Persons.new
=> #<Persons id: nil, idd: nil, name: nil, href: nil, sex: nil, country: nil, city: nil, can_message: nil, can_wall: nil, photo: nil, is_friend: nil, is_client: nil>
> People.new
=> #<People id: nil, id_general: nil, description: nil>
Understanding how class names are mapped to table names
You need to understand how ActiveRecord decides the table name for a model.
If you have a class Person, the default table name will be people. AR does this by calling the tableize method on the class name, like this:
'Person'.tableize # => "people"
or
Person.name.tableize # => "people"
That means, for the Person class, you should create the people table (unless you want to override the default). Make sure you get the spelling correct; peoples will not work.
That also means you should not have another class People because the default table name will also be people:
'People'.tableize #=> "people"
There will be a clash, or at best, you'll confuse yourself.
It's not a good idea to use persons as the table name, unless you're overriding the default. That's because AR will not generate persons as the table name for class Person.
I strongly encourage you to read and understand the ActiveRecord basics guide before you try anything else.
Modeling the real world
I'm also not sure why you want Person to belong to :peoples.
Can you describe the real-world scenario you're trying to represent?
Examples:
a person has many books
a person belongs to an organization.

Datamapper - weird may not be NULL error

I'm using DataMapper in a simple application to track sales. I have a Day class, like this:
class Day
include DataMapper::Resource
property :id, Serial, :key => true
property :date, DateTime
property :bestseller, String
property :total_money, Decimal
property :total_sold, Integer
property :total_orders, Integer
has n, :sales
end
and a Sales class:
class Sale
include DataMapper::Resource
belongs_to :day
property :id, Serial, :key => true
property :name, String
property :amount, Integer
property :value, Integer
end
When trying to add a new Sale to the database, like so:
s = Sale.new(:day => Day.get(1), :name => "Foo", :amount => "42", :value => "42"
I get this error when calling save.
DataObjects::IntegrityError at /sell
sales.date may not be NULL
I have no date property in Sale, so I'm not sure where this is coming from. First I thought that the Day object I'm getting doesn't have a day set, so I did d = Day.get(1).date = Time.now and saved it, but this doesn't resolve the error.
What did I break?
EDIT The sqlite3 schema
CREATE TABLE "sales" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" VARCHAR(50),
"amount" INTEGER,
"value" INTEGER,
"day_id" INTEGER NOT NULL,
"drink_id" INTEGER NOT NULL
);
CREATE INDEX "index_sales_day" ON "sales" ("day_id");
CREATE INDEX "index_sales_drink" ON "sales" ("drink_id");
I think I fixed it. Apparently, I had an old date property at one point in Sale. I entered the ruby interpreter, required my model and used DataMapper.auto_migrate! to reset the entire database. This fixed the problem.

datamapper multi-field unique index

In Datamapper, how would one specify the the combination of two fields must be unique. For example categories must have unique names within a domain:
class Category
include DataMapper.resource
property :name, String, :index=>true #must be unique for a given domain
belongs_to :domain
end
You have to create a unique index for the two properties:
class Category
include DataMapper::Resource
property :name, String, :unique_index => :u
property :domain_id, Integer, :unique_index => :u
belongs_to :domain
end
Actually, John, Joschi's answer is correct: the use of named :unique_index values does create a multiple-column index; it's important to read the right-hand side of those hash-rockets (i.e., if it had just been true, you would be right).
Did you try to define both properties as keys? Not sure I have tried it but that way they should become a composite key.
property :name, String, :key => true
property :category, Integer, :key => true

Resources