Add a reference column using migration having non standard primary key column - mysql

I have a model which has a non-rails conventional primary key.
class Guoid < ActiveRecord::Base
self.primary_key = :guoid
end
and related migration
class CreateGuoids < ActiveRecord::Migration
def change
create_table :guoids, id: false do |t|
t.integer :guoid, limit: 8, auto_increment: true, primary_key: true
t.integer :parent_guoid, limit: 8
t.string :resource_type, limit: 60
end
end
end
Now I want to reference this model in another model and trying to create migration using references which doesn't to work.
class ContentUnit < ActiveRecord::Base
self.primary_key = :guoid
end
class Content < ActiveRecord::Base
self.primary_key = :guoid
belongs_to :user
belongs_to :content_unit
end
and related migration
class CreateContents < ActiveRecord::Migration
def change
create_table :contents, id: false do |t|
t.references :content_unit, index: true, foreign_key: true
t.references :user, index: true, foreign_key: true
end
end
end
When I run the migration, I am getting following error.
Mysql2::Error: Can't create table `myapp_development_cr1`.`#sql-54a_308` (errno: 150 "Foreign key constraint is incorrectly formed"): ALTER TABLE `contents` ADD CONSTRAINT `fk_rails_823443bd0d`
FOREIGN KEY (`content_unit_id`)
REFERENCES `content_units` (`id`)
I am expecting to create content_unit_guoid foreign key in contents table referencing guoid in guoids table.
I used activerecord-mysql-awesome gem to work well with non-rails convention primary keys.
Here is a trigger which first creates a record in guids table and use it's pk as pk of the target table.
DELIMITER $$
CREATE TRIGGER `content_before_insert` BEFORE INSERT ON `content`
FOR EACH ROW BEGIN
IF NEW.guoid = 0 THEN
INSERT INTO `content`.guoids (resource_type)
VALUES('Content');
SET NEW.guoid = LAST_INSERT_ID();
END IF;
END
$$
DELIMITER ;

That is not a viable database design for ActiveRecord or even in general.
ActiveRecord (and any decent ORM) requires each table to have a primary key. This is what enables relations and lets Rails differentiate records.
class Content < ActiveRecord::Base
self.primary_key = :guoid
belongs_to :user
end
This will never work since self.primary_key = :guoid references contents.guoid not guoids.guoid. You cannot use relations in ActiveRecord as primary keys. Even if you could this would really be a performance killer since every query would need to join the guoids table - even recursively!
Rails is strongly convention driven and really smiles on you if you take a little time to learn the Rails way vs fighting the framework to make it work like framework X. The rails guides are a good place to start.
Stick with id for primary keys and _id for foreign key columns. You will have less fuss and not be treated like idiots if you have to collaborate with other developers.
There are valid cases where you would want to use unique identifiers (UUIDs) instead of auto-incrementing values. For example if you have several databases auto-incrementing values can cause race conditions. But in that case you still need to use a primary key on each table - the difference is simply the contents of the primary key.
This is either done by generating a hashes with a algorithm that has a low chance of collision on the application level or more recently by using binary UUID types in the database. The later being preferable today.
Not by using relations. AR just does not work that way.
http://geekhmer.github.io/blog/2014/12/06/using-uuid-as-primary-key-in-ruby-on-rails-with-mysql-guide/
http://blog.arkency.com/2014/10/how-to-start-using-uuid-in-activerecord-with-postgresql/
Using non-standard foreign keys.
The belongs_to and reference macros are an example of things that just work if you follow the conventions.
For foreign key constrainst that does not match the conventions you need to manually create it in the migration:
class CreateStores < ActiveRecord::Migration
def change
create_table :contents do |t|
t.references :manger, index: true, foreign_key: false
end
add_foreign_key :stores, :users, column: 'manager_id', primary_key: 'uuid'
end
end
Note that this will not fix your issue since your general approach is not viable!

So, your trying to make a foreign key from the contents table to the guoids table using this correct?
t.references :content_unit, index: true, foreign_key: true
references takes a table name as the argument and tries to find a column called id on it to make the foreign key between the tables. So you can see in your error message its trying to find the column id on content_units table. This is in no way referencing your guoids.
Its perfectly reasonable to want globally unique identifiers (typically GUID's or UUID's, however i'm not sure why you are storing them on a separate table and then (i'm assuming) going to make everything foreign key to it creating some massive many to many table that connects every table in your database? Seems really unscalable. Postgress handles uuid's for you nicely, but as I have in the past, it looks like your using mysql. Here is how I have done it.
Models
Class Teacher < ActiveRecord::Base
has_many :students
before_validation :generate_id
self.primary_key = :id
private
def generate_id
write_attribute(:id, SecureRandom.uuid) unless read_attribute(:id)
end
end
Class Student < ActiveRecord::Base
belongs_to :teacher
end
Migrations
create_table :teachers, id: false do |t|
t.string :id, null: false, unique: true, limit: 36
end
create_table :students do |t|
t.string :teacher_id, limit: 36
end
add_foreign_key :model_a, :model_b

Related

rails migration making a table with foreign keys while not re-indexing

I am making a simple junction / relational table in my mysql database on my rails app.
I run:
bin/rails generate model personplace person:references place:references
I get a very large error that begins with:
Index name 'index_place_on_place_id' on table 'places' already exists/home/myname/.rvm/gems/ruby-2.1.7/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract/schema_statements.rb:939:in `add_index_options'
It is saying that I am indexing twice and that's not allowed.
I am not trying to index twice, I am just trying to make a couple foreign keys.
My goal is to make a good model/migration of a junction table with 2 foreign keys pointing to 2 separate tables.
EDIT 1:
The table places was made like this:
bin/rails generate model places person:references name:string creator:string size:integer
It already references the person table. That person the person that owns the place
The new table is for keeping track of all the people at the place.
EDIT 2:
class CreatePlaces < ActiveRecord::Migration
def change
create_table :places do |t|
t.references :user, index: true, foreign_key: true
t.string :name
t.string :creator
t.integer :size
t.timestamps null: false
end
end
end

Rails associations and deletion

I was going through Rails association link. But I am not able to understand how the association works. I am using mysql db.
These are the files I generated:
user.rb
class User < ActiveRecord::Base
has_many :orders
end
order.rb
class Order < ActiveRecord::Base
belongs_to :user
end
20150911181301_create_orders.rb
class CreateOrders < ActiveRecord::Migration
def change
create_table :orders do |t|
t.string :content
t.integer :user_id
t.timestamps null: false
end
end
end
20150911181351_create_users.rb
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.string :email
t.timestamps null: false
end
end
end
First I was expecting db:migrate will generate some foreign key relationship itself but it did not happen. Then I thought may be rails manages it internally but when I deleted a user through rails c , it did not delete corresponding orders from the order table.
Where is my understanding incorrect? Also give me some links which explains how does this works?
It's debatable but the traditional 'Rails way' is to manage model-related things like defaults, foreign keys, and triggers is at the ActiveRecord level instead of in the database.
That said, you're free to add a foreign key for referential integrity in a migration using the following:
add_foreign_key :orders, :users
More information is available in the Rails Guides.
The 'Rails way' to automatically destroy child objects when the parent is destroyed is to specify the :dependent strategy on the child collection. There is a great Stackoverflow discussion here going into detail about the two :dependentoptions: :destroy vs :delete_all.
Everything is in the docs
-You should note that "In any case, Rails will not create foreign key columns for you. You need to explicitly define them as part of your migrations."
-You should also note that when it comes to associations, Rails needs you to tell it that asides two models being related with belongs_to and has_many, you want the associated model to be deleted when it's parent model is deleted. That is where the dependent: :destroy comes in.
Now all you need to do in your code for orders related to a user to get deleted when the user is:
class User < ActiveRecord::Base
has_many :orders, dependent: :destroy
end
Source: Rails Guides

Rails belongs_to and has_many is not creating a primary foreign key relation

I am creating a primary -> foreign key relationship among my columns while doing the db design. my db design should be:
Users table: userid (primary key), name
Shoppinglist table: shoppingid (primary key), userid(foreign key)
Here is my migration file:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.integer :userid, :primary_key
has_many :shoppinglists, dependent: :destroy
t.timestamps null: false
end
end
end
class CreateShoppinglists < ActiveRecord::Migration
def change
create_table :shoppinglists do |t|
t.integer :shoppingid, :primary_key
t.belongs_to :userid
t.timestamps null: false
end
end
end
Isnt there a way to create the above while creating the table instead of creating a new migration file?
I use this command to check my relationships and I dont see any relations created:
User.reflect_on_all_associations
Since your migration fails to apply, you may have concluded that it contains errors. You seem to not understand the purpose of migrations and their difference with models. Plus, you're violating plenty of conventions, so it does a bit of what you're not expecting.
has_many and belongs_to are not migrations' scope. These methods are from models and all they add is some query methods. They can even be used on an existing database without the migration mechanism at all, as horrible as that may be.
Rails already adds a primary key column id to every table, unless told otherwise with an option id: false in create_table. So add another one and violate conventions at your own peril.
Rails' convention of column naming is that of method naming: snake_case, not likethis. It uses this to properly pluralize and singularize the given terms to infer configuration through convention. Consequently, foreign key columns are specified in form <ASSOCIATION>_id, i. e. user_id.
Foreign key support has come quite recently, in Rails 4.2 only, and since it doesn't (as guides claim) work with sqlite, Rails don't add these by default. When following conventions, you only need to specify affected tables, Rails will infer the rest. Of course, make sure you're running Rails 4.2 (or newer) before trying. Before that, the notion of primary-foreign keys belonged not to the database, but to the models that were housing their own queries generated via has_many and the company.
You can create a migration something like:
rails g migration addShoppingListAssociationToUsers
And have something similar to following as the content of the migration:
class AddShoppingListAssociationToUser < ActiveRecord::Migration
def change
add_reference :shoppinglists, :user, index: true, foreign_key: true
end
end
I am assuming that you do not need to create primary keys explicitly and can use the ones that are automatically generated with each table by Rails.
# User.rb
has_many :shopping_lists
# ShoppingList.rb
belongs_to :user
# Migration Files
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.timestamps null: false
end
end
end
class CreateShoppinglists < ActiveRecord::Migration
def change
create_table :shoppinglists do |t|
t.references :user, index: true
t.timestamps null: false
end
end
end

Set Association in Ruby on Rails

I used a migration in ruby to create these tables together with a model.
I have a Table Information and a Table Detail, each entry in the information table can have one association in the detail table.
So i created them with:
create_table :details do |t|
t.integer :id
t.string :Bezeichnung
t.binary :Koordinaten
end
create_table :informations do |t|
t.integer :id
t.integer :DetailID
t.integer :Longitude
t.integer :Latitude
end
In my Information Table i got a DetailID which should Reference to the id of the Detail table.
Now i did:
ALTER TABLE informations
ADD CONSTRAINT fk_informations_details
FOREIGN KEY (DetailID)
REFERENCES details(id)
Is this correct? Have i set the FOREIGN KEY correct? Or do i have to put the foreign key in the other table!?
Because i want to use in my Information Model following:
has_one :detail, :foreign_key => 'DetailID'
And in the Detail Model following:
belongs_to :main
Some notes:
Perform all kinds of alterations on the database using migrations, never do them by hand.
Use Ruby on Rails naming convention whenever possible. This means using detail_id instead of DetailID
Your associations are wrong.
If Information has_one :detail, Ruby on Rails will look for an entity with a matching information_id in the details table.
In your model, it is the other way around - the Information contains a detail_id. In other words Ruby on Rails would specify this as Information belongs_to :detail and Detail has_one :information.

When to add what indexes in a table in Rails

I have a question about Rails database.
Should I add "index" to all the foreign keys like "xxx_id"?
Should I add "index" to the automatically created "id" column?
Should I add "index(unique)" to the automatically created "id" column?
If I add index to two foreign keys at once (add_index (:users, [:category, :state_id]), what happens? How is this different from adding the index for each key?
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :name
t.integer :category_id
t.integer :state_id
t.string :email
t.boolean :activated
t.timestamps
end
# Do I need this? Is it meaningless to add the index to the primary key?
# If so, do I need :unique => true ?
add_index :users, :id
# I don't think I need ":unique => true here", right?
add_index :users, :category_id # Should I need this?
add_index :users, :state_id # Should I need this?
# Are the above the same as the following?
add_index (:users, [:category, :state_id])
end
end
Great answer so far. Additional question.
I should add "index with unique" for xxx_id, right?
Should I add "index" to all the foreign keys like "xxx_id"?
It would be better, because it accelerates the search in sorting in this column. And Foreign keys are something searched for a lot.
Since Version 5 of rails the index will be created automatically, for more information see here.
Should I add "index" to the automatically created "id" column?
No, this is already done by rails
Should I add "index(unique)" to the automatically created "id" column?
No, same as above
If I add index to two foreign keys at once (add_index (:users, [:category_id, :state_id]), what happens? How is this different from adding the index for each key?
Then the index is a combined index of the two columns. That doesn't make any sense, unless you want all entries for one category_id AND one state_id (It should be category_id not category) at the same time.
An Index like this would speed the following request up:
# rails 2
User.find(:all, :conditions => { :state_id => some_id, :category_id => some_other_id })
# rails 3
User.where(:state_id => some_id, :category_id => some_other_id)
Where
add_index :users, :category_id
add_index :users, :state_id
will speed up these requests:
# rails 2+3
User.find_by_category_id(some_id)
User.find_by_state_id(some_other_id)
# or
# rails 2
User.find(:all, :conditions => {:category_id => some_id})
User.find(:all, :conditions => {:state_id => some_other_id})
# rails 3
User.where(:category_id => some_id)
User.where(:state_id => some_other_id)
I should add "index with unique" for xxx_id, right?
No, because if you do this, only one user can be in one category, but the meaning of category is that you can put more many user into one category. In your User model you have something like this belongs_to :category and in your Category model something like has_many :users. If you have a has_many relationship the foreign_key field must not be unique!
For more detailed information on this you should take a look at tadman's great answer.
Indexing can be a tricky, subtle thing, but there are general rules that apply that can make determining which to use a lot easier.
The first thing to remember is that indexes can work in more than one way. An index on A, B, C also works for A, B and simply A, so you can design your indexes to be more versatile if you order them correctly. The phone book is indexed on Last Name, First Name, so you can look up people easily by their last name, or a combination of last name and first name. You cannot, however, look them up directly by their first name. You'd need a separate index for that. The same goes for phone number, which you would have to index as well.
With that in mind, there are many things that will dictate how you create indexes:
If you have a belongs_to-has_many relationship pairing, you need to have an index on the foreign key used.
If you order your records, and there is a large number of them that will be paginated, you should add that order column to the end of the index.
If you have a has_many :through relationship, your join table should have a unique index on both properties involved in the join as a compound key.
If you fetch a record directly using a unique identifier such as username or email, that should be a unique index.
If you fetch sets of records from a has_many relationship using a scope, make sure there's an index that includes the has_many foreign key and the scope column in that order.
The goal with indexes is to eliminate the dreaded "table scan" or "file sort" operations that occur when your data is not indexed properly.
In simple terms, look at the queries being generated by your application and ensure that columns referenced in WHERE or HAVING conditions and ORDER BY clauses are represented in that order.
Always index foreign keys
Always index columns you will order by
All unique fields (to ensure uniqueness at a database level. Example migration: add_index :users, :email, unique: true)
If you order by two things, or search by two things, for example:
order by [a, b] or find where( a and b ), then you need a double index:
Concrete example:
If you have:
default_scope :order => 'photos.created_at DESC, photos.version DESC'
You should add:
add_index :photos, [:created_at, :version]
Note:
An index takes up extra space on the disk and makes it slower to create and update each record, because it has to rebuild each index.
Credit:
https://tomafro.net/2009/08/using-indexes-in-rails-choosing-additional-indexes, rails - created_at when user for ordering, Should you add an Index to the table?, and the answers above.