I'm very new to Rails, and currently for our class, we have to create an application with databases.
I have a bunch of tables that have foreign keys to other tables, and I've defined these associations in the migration files (using execute("ALTER TABLE...")), but not in my models (I only have self.primary_key defined in my models).
My question is, since I've set these associations in my migrations, do I also have to set the associations in my models (i.e. has_may, belongs_to, validates_uniqueness_of, etc)?
I'll be creating an app that would allow user input, and these input would be inserted to the database.
Hoping that my confusion can be cleared up. Thank you!
Example
Migration file:
class CreateSchools < ActiveRecord::Migration[5.0]
def up
create_table :schools, {:id => false} do |t|
t.integer :school_id
t.string :school_name
t.string :contact_number
t.integer :a_id
t.timestamps
end
execute "ALTER TABLE schools ADD PRIMARY KEY(school_id);"
execute "ALTER TABLE schools ADD CONSTRAINT a_id UNIQUE(a_id);"
execute "ALTER TABLE schools ADD CONSTRAINT school_references_location FOREIGN KEY (a_id) REFERENCES locations(a_id) ON DELETE CASCADE ON UPDATE CASCADE;"
change_column_null :schools, :school_name, false
change_column_null :schools, :a_id, false
end
def down
execute "ALTER TABLE schools DROP PRIMARY KEY;"
execute "ALTER TABLE schools DROP FOREIGN KEY school_references_location;"
execute "ALTER TABLE schools DROP INDEX a_id;"
drop_table :schools
end
end
Model:
class School < ApplicationRecord
self.primary_key = :school_id
end
Yes you'll need to define the associations in Model as well.
For all the rails applications,
Migrating and changing db according to associations is the first step.
Then add association statements in the models so that the associations from db can be accessed using simple methods. Like user.posts etc.
Adding association statements in Models provide you methods that generates queries according to the method called to retrieve data from db easily. You can also query db using join and select statements if you don't want to include association statements in Models.
So, in short, designing schema to manage associations is necessary where as mentioning them in Model is not. But we do it to make our life and code simpler.
Related
Following a guide, I ran the following command:
rails g migration CreateSnippetsUsers snippet:belongs_to user:belongs_to
This created the following migration:
class CreateSnippetsUsers < ActiveRecord::Migration[5.0]
def change
create_table :snippets_users do |t|
t.belongs_to :snippet, foreign_key: true
t.belongs_to :user, foreign_key: true
end
end
end
In the past I've seen the same thing, but with index: true instead of foreign_key: true. What's the difference between the two?
Indexes, foreign keys and foreign keys constraints are strictly related concepts in databases that are often confused or misunderstood.
REFERENCES
When you declare a reference, you're simply saying to include a column whose values should match those of another table (and in Rails you also get some useful methods to navigate through the associated models). In the example:
create_table :appointments do |t|
t.references :student
end
the appointments table will have a column named student_id whose values should be in the pool of students' id values.
INDEXES
Since when you add a reference you will probably use that column often, you may (and probably should!) also tell you database to boost the look up speed using the reference column. You can do this with the option index: true (which by the way is a default option in the reference method since Rails 5). Indexes have few drawbacks, the main being a larger memory consumption.
FOREIGN KEY CONSTRAINTS
From what said so far, reference column and foreign column are synonyms. But do you remember when I said that a reference column's values should match those of another table? If you simply declare a reference, it's your responsibility to ensure that a matching row on the referenced table exists, or someone will end up doing nonsense actions like creating appointments for non-existing students. This is an example of database integrity, and fortunately there are some mechanisms that will grant a stronger level of integrity. These mechanisms are called ' database constraints'. What the option foreign_key: true does is exactly to add this kind of constraint on the reference column, to reject any entry whose foreign key values are not in the referenced table.
Database integrity is a complex task, growing in difficulty with the database's complexity. You probably should add also other kind of constraints, like using they keywords dependent: :destroy in your class to ensure that when you delete a student, all of its existing appointments are also destroyed.
As usual, here's a RTFM link: https://guides.rubyonrails.org/association_basics.html
Index improve speed of data retrieval operations on database tables. When we write index: true to any column, it adds a database index to this column. For example I was creating a table:
create_table :appointments do |t|
t.references :student, index: true
end
It will create student_id column in appointments table.
A foreign key have different use case, it is a relationship between tables. It allow us to declare an index in one table that is related to an index in another table and also some constraints are placed.The database enforces the rules of this relationship to maintain referential integrity. For example we have two table profiles and educations, and a profile may have many educations.
create_table :educations do |t|
t.belongs_to :profile, index: true, foreign_key: true
end
Now we have profile_id column in educations table which is foreign key of profiles table. It prevents a record from being entered into the educations table unless it contains a profile_id value that exists in the profiles table. So referential integrity will be maintained.
I am new to RoR. I have a basic doubt with establishing relationship between models. Lets take a simple example of Employee and Manager. Manager manages many employees and an employee belongs to one manager. At the schema level, i will have a managerid foreign key in the employee table.
class Employee < ActiveRecord::Base
attr_accessible :employee_id, :employee_name, :manager_id
belongs_to :manager
end
class Manager < ActiveRecord::Base
attr_accessible :manager_id, :manager_name
has_many :employeees
end
Once i specify such relationships in the model how can i ensure such data integrity is maintained in the database? I ran rake db:migrate command but it doesn't seem to affect anything in the database. What should i do to establish foreign key-primary key relationship between manager and employee table in the database? I am using sql-server 2008.
Thanks in advance.
This isn't provided directly in rails because the implementation must be database specific. The foreigner gem adds a 'add_foreign_key' command for migrations that works for mysql, postgres and sql_lite; the oracle_enhanced adapter supplies it for Oracle.
If you can't find a gem that works for sql server and your adapter doesn't provide it, you can always add a raw sql statement to your migration:
sql = "CREATE FOREIGN KEY ..."
execute(sql)
If you're doing this a lot, you might want to wrap it up in your own add_foreign_key helper, preferably using the same API as the above gems use.
Have you edited the migration files created when you generated the models? In the employee migration you'll need to specify the foreign key like so:
class CreateEmployees < ActiveRecord::Migration
def change
create_table :employees do |t|
t.string :manager_name
t.integer :manager_id
end
end
end
You can find the migration files in the db/migrations folder. You can roll back your migration to the point before creating the employees table and modify the create_table block or you can create a new migration to add the foreign key.
I've been using the tutorial by Michael Hartl and I am trying to create a new model called "Recipe" to allow users to post recipes. The model is essentially the same as the micropost model, in that a user should be able to post many recipes and all recipes should be linked to one user. I therefore used the same command and migration as for the micropost:
class CreateMicroposts < ActiveRecord::Migration
def change
create_table :microposts do |t|
t.string :content
t.integer :user_id
t.timestamps
end
add_index :microposts, [:user_id, :created_at]
end
end
Created using the command: rails generate model Micropost content:string user_id:integer
The issue I am having though is that the primary key in both tables is the user_id. Will this work, or when I go to try to enter a recipe will it try to pull from the microposts table instead? Thanks in advance
when I go to try to enter a recipe will it try to pull from the microposts table instead?
No. The Recipe model in your application by default will interact with a recipes table in your database. Similarly, a Micropost model will interact with a microposts table in your database by default.
As long as you work with the Recipe model, an instance of Recipe will not pull from the microposts table.
It sounds like you may have a fundamental misunderstanding about what a table's primary key is for, and perhaps how database tables work; but for now that's outside of the scope of this question.
As an aside, you probably should have done
rails generate model MicroPost content:string user_id:integer
(notice the uppercase P in MicroPost). This is a better name for the clase and will create a micro_posts table in your database.
The primary key is not the user_id in any of the two tables. Each model generates a table with an implicit numeric autoincremented id column that is used as the PK.
What the add_index does is add an index on the user_id column. An index is not a PK.
Anyway, you should change your add_index statement in your recipes migration to:
add_index :recipes, [:user_id, :created_at]
Is there any difference between using t.references and executing SQL command to create foreign key relationship between products and category table as shown below? In other words, are the two different ways of doing the same thing or am I missing anything here?
class ExampleMigration < ActiveRecord::Migration
def up
create_table :products do |t|
t.references :category
end
#add a foreign key
execute <<-SQL
ALTER TABLE products
ADD CONSTRAINT fk_products_categories
FOREIGN KEY (category_id)
REFERENCES categories(id)
SQL
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
def down
rename_column :users, :email_address, :email
remove_column :users, :home_page_url
execute <<-SQL
ALTER TABLE products
DROP FOREIGN KEY fk_products_categories
SQL
drop_table :products
end
end
They're not the same thing. Rails by default doesn't enforce foreign keys in the database. Instead, references when creating from the command line also creates a regular index, like this:
add_index :products, :category_id
Update:
Rails 5 actually does exactly the same thing now. So, to answer the original question: Nowadays, both are the same.
I found some thing intresting in this page.
http://railsforum.com/viewtopic.php?id=17318
From a comment :
Rails doesn't use foreign keys to perform his backend tasks. This
because some db like sqlite doesn't allow foreign keys on its tables.
So Rails doesn't provide an helper to build a foreign key
Also there is a gem foreigner for adding foreign keys to database table.
What creates the FOREIGN KEY constraint in Ruby on Rails 3?
So I have two rails models - user and role - each specified with a habtm and a join table of users_roles.
When I save from my user form, which has checkboxes for which roles to choose, rails is trying to manually insert a value into the id column of the join table. This doesn't make sense to me, as the id column should be set to auto_increment and so wouldn't need a value passed to it. Any time I try to save, I get a MySQL error.
Am I missing something?
Thanks.
if you are using the has_and_belongs_to_many association then you don't need any model for the join table and the join table should not be created with an id column, it has to contain only the user_id and role_id columns:
class CreateUsersRolesJoinTable < ActiveRecord::Migration
def self.up
create_table :users_roles, :id => false do |t|
t.integer :user_id
t.integer :role_id
end
end
def self.down
drop_table :users_roles
end
end
See also:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001836
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association