I have a question about rails 6 and model association with or without migrations.
Let me be more specific.
Let's say we want to place a foreign key inside users table which links to the id column of the courses table.
Most of the tutorials and courses i run into follow this route:
create a migration with add_column :users, :course_id, :integer
place has_one or has_many and belongs_to to the appropriate models.
And that's it.
My thought is, how about this:
create a migration with add_reference :users, :course, foreign_key: true
place the appropriate has_one/has_many and belongs_to to the apropriate models.
or is it the same?
Doesn't the second option create indexes as well whereas the first does not?
Which is a best practice?
add_reference is meant to be a shortcut as part of the rails convention, under the hood, it will call add_column and other options that you specify [you can check the code for rails-6 here]
while foreign keys are not required, they are considered a best practice because they guarantee referential integrity. you can read more about it here https://edgeguides.rubyonrails.org/active_record_migrations.html#foreign-keys
or is it the same? Doesn't the second option create indexes as well whereas the first does not?
having said that, your 2 migrations are not entirely the same even though they work the same (they achieve the same goal) - the second option will add an index by default unless you specify the option not to - I definitely agree with you about using add_reference as this is an easier and more foolproof shortcut
of course, you can also achieve that manually by using your first migration by adding an index and a foreign key as well
add_column :users, :course_id, :integer
add_index :users, :course_id # uniq or not
add_foreign_key :courses, :users
Related
I would like to define a polymorphic table. My problem being that one table's primary_key is of type uuid(string) and the other id(integer).
I thought maybe having a model_able_id and a model_able_uuid varying depending on the model_type but i cannot figure that out and it would probably break tons of activerecord features for polymorphic.
Some other things i have thought of would be to use STI, but i'm still confused, and, of course, i could migrate ids to uuids and that'd sort me out (but i'd rather not).
Specify the type for the (polymorphic) foreign key in the options. Change :string to :uuid if required.
create_table :table_name do |t|
t.references :target, type: :string, polymorphic: true, null: false
end
Then both string and integer target_ids are acceptable.
Check the API docs.
Was facing same issue, there is no official solution yet, these two issues are open in rails repo about this:
https://github.com/rails/rails/issues/33407
https://github.com/rails/rails/issues/33525
There is a suggestion in one of these issues, it didn't work for me(it was generating both joins, the default one generated by rails which causes failure and one which i defined).
I ended up not using association at all, i defined a method to get the actual records properly with a custom query. I didn't need dependent: destroy otherwise i could have defined a before_destroy method too.
Hope it helps someone.
I had exactly the same problem. My solution modifies the type of the _id field to string.
def change
add_reference :ratings, :rater, polymorphic: true, index: true
change_column :ratings, :rater_id, :string
end
I hope it helps.
I need to need to make a column as foreign key.
I did lot of research on it.
I understood that I need to add associations. I am clear on belongs_to , has_one and has_many options.
After creating associations rails know that there is a foreign key association.If I remove the main record then dependent record will be deleted by rails app.
I was reading on migrations and I came across http://edgeguides.rubyonrails.org/active_record_migrations.html
where it is mentioned:
$ bin/rails generate migration AddUserRefToProducts user:references
will generate:
class AddUserRefToProducts < ActiveRecord::Migration
def change
add_reference :products, :user, index: true, foreign_key: true
end
end
Now on website: http://guides.rubyonrails.org/active_record_migrations.html
where it is mentioned:
$ bin/rails generate migration AddUserRefToProducts user:references
will generate:
class AddUserRefToProducts < ActiveRecord::Migration
def change
add_reference :products, :user, index: true
end
end
I understand the creation of index.
Do I need to have foreign_key: true explicitly or not? what difference will it make?
If you specify the foreign key in your migration, Rails will add a foreign key constraint at the database level. If you leave it out, no constraint will be added and it will behave like all previous versions of Rails e.g. you can delete records with the risk of orphaned records being left behind.
You can do something like
class AddUserRefToProducts < ActiveRecord::Migration
def change
add_reference :products, :user, index: true
add_foreign_key :products, :user
end
end
Similar solution here
First of all according to my knowledge http://edgeguides.rubyonrails.org/active_record_migrations.html is yet to be released. so the change foreign_key: true in your migration is not applicable yet so it wont make difference.
See this http://edgeguides.rubyonrails.org/index.html.
For stable versions, use http://guides.rubyonrails.org/
Now in http://guides.rubyonrails.org/active_record_migrations.html there is no option like foreign_key: true.
Even if you pass it, it wont make difference because according to the method add_reference
http://apidock.com/rails/v4.0.2/ActiveRecord/ConnectionAdapters/SchemaStatements/add_reference
It is not expecting foreign_key to be as option.
And lastly add_reference is basically calling http://apidock.com/rails/v4.0.2/ActiveRecord/ConnectionAdapters/SchemaStatements/add_column add_column method in it, which is also not expecting foreign_key to be passed as parameter, so it is not at all necessary.
Hope it makes sense
Actually, you don't need the option if you defined the association on the model. You migration could look like this:
class AddUserRefToProducts < ActiveRecord::Migration
def change
add_column :products, :user_id, :integer
end
end
If the Product model has the association, it should work
class Product < ActiveRecord::Base
belongs_to :user
end
I have a has_many relation in my app. e.g. department has many users.
I want to covert it to a has_and_belongs_to_many relation.
As part of the migration I need to preserve the current relation between users and departments, meaning I have to move all the data to the new connecting table.
This is the migration I created:
class CreateUserDepartment < ActiveRecord::Migration
def change
create_table :users_departments do |t|
t.belongs_to :user
t.belongs_to :department
end
###############################################
# need to move the data to the new table here #
###############################################
remove_column :users, :sub_department_id
end
end
what is the best way to write the missing line?
If you must, you can use execute "your SQL". See this question:
How do I add some inserts in rails migration?
The main value of the "Don't" answer to that question is in explaining why you would not want to use your models to do this. Also, I'd be surprised if you can or would want to do this using change, you would probably need to use self.up.
I'm getting 'rake aborted! ... posts_count is marked readonly' errors.
I have two models: user and post.
users has_many posts.
posts belongs_to :user, :counter_cache => true
I have a migration which adds the posts_count column to the users table and then calculates and records the current number of posts per user.
self.up
add_column :users, :posts_count, :integer, :default => 0
User.reset_column_information
User.all.each do |u|
u.update_attribute( :posts_count, u.posts.count)
end
end
when I run the migration I get the error. This is pretty clear-cut, of course and if I remove the :counter_cache declaration from the posts model, e.g.
belongs_to :user
the migration runs fine. This obviously, does not make sense because you couldn't really implement it this way. What am I missing?
You should be using User.reset_counters to do this. Additionally, I would recommend using find_each instead of each because it will iterate the collection in batches instead of all at once.
self.up
add_column :users, :posts_count, :integer, :default => 0
User.reset_column_information
User.find_each do |u|
User.reset_counters u.id, :posts
end
end
OK, the documentation states:
Counter cache columns are added to the
containing model’s list of read-only
attributes through attr_readonly.
I think this is what happens: you declare the counter in the model's definition, thus rendering the "posts_count" attribute read-only. Then, in the migration, you attempt to update it directly, resulting in the error you mention.
The quick-and-dirty solution is to remove the counter_cache declaration from the model, run the migration (in order to add the required column to the database AND populate it with the current post counts), and then re-add the counter_cache declaration to the model. Should work but is nasty and requires manual intervention during the migration - not a good idea.
I found this blog post which suggests altering the model's list of read-only attributes during the migration, it's a bit oudated but you might want to give it a try.
Do I need to re-migrate whenever I change the models in Rails? Can someone tell me what migration really does? I am reading it and I am somewhat confused with the difference between the stuff inside db/migrate and the stuff inside app/models.
For example, if I add a has_one realtionship inside my model, do I need to re-migrate this? Why?
If your database changes, use a migration. If you're just adding methods to your model, no need to have a migration.
Example:
We start out and we just have first_name, last_name. We want to store those in the database, so we have a migration that does:
/app/models/human.rb
# empty
/db/migrate/xxxxx.rb
add_column :humans, :first_name, :string
add_column :humans, :last_name, :string
Then we get married, so we want to track that
/app/models/human.rb
belongs_to :spouse
We need to have a spouse_id field in the database, so we need a migration
/db/migrate/xxxxx.rb
add_column :humans, :spouse_id, :integer
We then have a kid.... In fact, we were all kids at one point, but to keep it simple, we'll have Humans and Offspring
/app/models/offspring.rb
belongs_to :human
/db/migrate/xxxxx.rb
create_table ...
However, no need to add anything to the Human migration, since no tables change here. We do need to add:
/app/models/human.rb
has_many :offspring
If you want to be able to get at, easily, your first born, you'd just add a method to your model. No need for a migration here:
/app/models/human.rb
def first_born
offspring.first
end