I have a blog like set up were there are things called events (which are essentially posts) but they have different types ("kinds") which will help me categorize where to put them in my layout. Anyway when I go to the events/edit page I get this error:
Mysql2::Error: Unknown column 'event_kinds.event_id' in 'where clause': SELECT event_kinds.* FROM event_kinds WHERE event_kinds.event_id = 1 LIMIT 1
I thought it might be an association issue, so is this right?
EventKind has_many :events
Event has_one :event_kind
?
EDIT:
I've renamed event_kinds to kinds. Right now I don't have a table called event_kinds that links together events and kinds models since each event can only be of one kind I just put the kind id inside the events table. Is there a way so that I can skip the events_kind...
You need to add event_id column to the event_kinds table.
Just create a migration
rails g migration add_event_id_to_envent_kind
Edit he file and add the below content.
def change
add_column :envent_kinds, :event_id, :integer
end
Run migration
rake db:migrate
This should solve the error.
Related
with what migration can I add a column to every model at once? This would be handy almost every time you forgot to add a field anyway because most of the time a field you want to add is not limited to one model only. Like "email" In this case I forgot to add an "order" field.
Thanks
You can do as follows -
def change
tables = [:users, :products, :addresses]
tables.each do |table_name|
add_column table_name, :order, :integer
end
end
It's called a group migration
Answering your first question "With what migration can I add a column to every model at once?". Answer: None. Rails migrations are a way to alter database schemas over time in a consistent way.
Rails migrations are Ruby classes using Rails methods as instructions to modify your database as needed. So your question could be better formulated as "How can I create a migration to add a column to every model at once?"
IMHO I don't think there's going to be a specific method to do this, as the requeriment is pretty custom, but, depending in your Rails version you can get all the ApplicationRecord.descendants:
Zeitwerk::Loader.eager_load_all
ApplicationRecord.descendants.map { |table| table.name.downcase.pluralize }.each do |table|
add_column table, :logdate, :datetime
end
Or all those tables from the database that can be safe_constantized:
ActiveRecord::Base.connection.tables.map do |table|
table.classify.safe_constantize
end.reject(&:nil?).each do |table|
add_column table, :logdate, :datetime
end
That way you get the name of each table/model and use it as the first argument for add_column.
The difference is that in Rails 6 the default code loader is Zeitwerk, so you can eager load all the project dependencies. In other versions you could do the same but using Rails.application.eager_load!.
The second version would work without having to load the models as dependencies as it makes a query asking for their tables and then maps them as constants.
Full Code on GitHub, https://github.com/Marium36/blog
I am taking online Ruby on Rails course that guides us to create simple blog application.
Running this on Ubuntu 18.04
Following commands work
> rails new blog
> cd blog
> rails generate scaffold Post title:string body:text
> rails generate scaffold Comment post:references body:text
> rake db:migrate
> rake routes
> rails server
As soon as I run this (per the course)
> rake db:test:load
> rake test
I get error
Run options: --seed 62159
# Running:
.E
Error:
PostsControllerTest#test_should_destroy_post:
ActiveRecord::InvalidForeignKey: SQLite3::ConstraintException: FOREIGN KEY constraint failed
app/controllers/posts_controller.rb:57:in `destroy'
test/controllers/posts_controller_test.rb:43:in `block (2 levels) in <class:PostsControllerTest>'
test/controllers/posts_controller_test.rb:42:in `block in <class:PostsControllerTest>'
rails test test/controllers/posts_controller_test.rb:41
............
Finished in 7.151961s, 1.9575 runs/s, 2.2371 assertions/s.
14 runs, 16 assertions, 0 failures, 1 errors, 0 skips
The error you're experiencing (Foreign Key constraint failed) is telling you that you have a foreign key constraint in your database that is not met when some particular piece of code is run.
What is a foreign key?*
In relational databases—like MySQL, PostgreSQL, or SQLite—you typically have tables of data that relate to one another in some way. It's very common for rows in those tables to reference one another using an ID, which is referred to as the “key”. You might have, as you do here, a comments table with a post_id column that references the id column of the posts table. In this case, the post_id is referencing the foreign key posts.id.
What is a constraint?
In relational databases, constraints provide a way to ensure the integrity of your data. A constraint can do things like ensure a price column is never negative (i.e. verify based on the data itself), ensure one value is lower than another in the same row, or verify that the data matches another record in another table entirely.
Putting it together: what is a foreign key constraint?
A foreign key constraint, if you haven't already put it together, is just a special type of constraint that ensures that the “foreign record”—the row in the table you're referencing—actually exists. In this case, comments.post_id has a foreign key constraint on posts.id, which ensures that a row can't be added to the comments table with a post_id that doesn't exist in the posts.id column.
So why did you get this error?
This is your migration creating the comments table:
class CreateComments < ActiveRecord::Migration[6.0]
def change
create_table :comments do |t|
t.references :post, null: false, foreign_key: true
t.text :body
t.timestamps
end
end
end
Note the foreign_key: true part! That's telling Rails to add a foreign key constraint between comments and posts on the foreign key.
This is the test that is failing:
test "should destroy post" do
assert_difference('Post.count', -1) do
delete post_url(#post)
end
assert_redirected_to posts_url
end
It's deleting a Post record, which tells us that there is a row in the comments table with a post_id column referencing the Post we're trying to remove in this test, hence the failure.
What can I do to fix it?
First, you probably want to add an inverse relationship to your Post model, like this:
class Post < ApplicationRecord
has_many :comments
Then you'll want to tell Rails how to handle associated records when you're removing a Post. You can do that with the dependent option, which takes a handful of options. I'll use :delete_all here, which tells Rails to use a single SQL query to delete all “dependent” comments (that is, comments that reference this Post) without loading them first:
class Post < ApplicationRecord
has_many :comments, dependent: :delete_all
end
With that change your test should succeed because it will have removed the comments as well as the post in the same transaction, preventing the foreign key constraint from failing.
I have following models and association.
class Stock < ApplicationRecord
has_many :stock_components, -> {order(:position)}
...
before_save :set_default_values
def set_default_values
self.material_cost = 0.0 if self.material_cost.nil?
end
...
end
When I run migrations from scratch, a migration that inserts a record in this table, breaks at line self.material_cost = 0.0 if self.material_cost.nil? with following -
Caused by:
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'stock_components.position' in 'order clause': SELECT `stocks`.* FROM `stocks` INNER JOIN `stock_components` ON `stocks`
.`id` = `stock_components`.`component_id` WHERE `stock_components`.`stock_id` = 1 ORDER BY `stock_components`.`position` ASC
This is because migration for adding position hasn't run yet.
What could be solution for this?
That's really unfortunate, but it's a common issue which is almost never addressed.
When a data migration on a model is written, either to change or set values, or to add data, typically one relies on the application model (with all the corresponding callbacks etc).
But then at a later time, more callbacks are added, and previous migrations are broken. Even worse, a later migration might rename or remove a column, and the inserted data is not valid anymore.
The solution is that you should NEVER use application models in a migration, but rather some local model that uses whatever is in the database at the time:
class PopulateStock < ActiveRecord::Migration
class Stock < ActiveRecord::Model
self.table_name = 'stocks'
end
def up
Stock.create(...)
end
end
Now the Stock model that is referenced in the migration is completely defined by the status of the table at the time of migration (no extra columns, no callbacks etc) instead of depending on all the application model.
Unfortunately this means that you have to go through all your app's migrations and create local models for them until migrations can be run.
I have a number of many-to-many relationships in my app.
I do not need to store information about the relationships themselves, so am using the has_and_belongs_to_many relation in my models.
I've read the Active Record documentation and it seems to confirm my strategy, BUT I'm not clear if I still need to create join tables in the database or if ActiveRecord in Rails 3.2 is smart enough to handle it using the model relations alone.
Any references or explanations would be appreciated.
----- Break -----
If I did need to store data about the relationship itself and I were using has_many => through in my model, would I need to remove the Primary Key from the "through" table (e.g. so that it only has the two foreign keys?)
Thank you!
Yes, you need to create the join table for a has_and_belongs_to_many association. Remember in Rails you need to 'migrate to create'. Using this article as an example, say we have an Account model and a Role model, we can create a join table through this migration:
rails generate migration create_accounts_roles_join_table
Now we will edit the migration file that was just created
create_table :accounts_roles, :id => false do |t|
t.integer :account_id
t.integer :role_id
end
It is important to include :id => false as this will leave off the primary key that is normally generated when you create a table. Also, we specified the two foreign keys account_id and role_id.
run rake db:migrate and add the HABTM associations in both models and everything is set up.
Also, as a side note, adding join_table to the end of the migration generator is not required but is more descriptive and integer can be replaced with references when adding the foreign keys. They are equivalent but maybe a bit more descriptive.
On the second part of your question, you don't need to remove the primary key from the table.
I have a Rails 3.1 app with a User model and a Venue model. These two models have a HABTM relationship - A user may manage many venues and a venue may be managed by many users.
I'd like users to be able to select a default venue so I'm trying to add a default_venue_id attribute to User with the following migration:
class AddDefaultVenueIdToUser < ActiveRecord::Migration
def self.up
add_column :users, :default_venue_id, :integer
end
def self.down
remove_column :users, :default_venue_id
end
end
The problem is that when I run that migration against my PostgreSQL database, it's assuming that default_venue_id is the foreign key for a relationship with the non-existent default_venues table and throws the following error:
PGError: ERROR: relation "default_venues" does not exist
: ALTER TABLE "users" ADD FOREIGN KEY ("default_venue_id") REFERENCES "default_venues" ("id")
Should I be doing something to tell the database that I'm not trying to create a relationship or am I going about this the wrong way?
Edit: I've just realised that another developer who worked on the project added the schema_plus gem which automatically defines constraints for columns ending in _id
That explains why I've never run into this behaviour before!
It would be helpful to see the Models as well to diagnose this problem.
However with that being said, I think that if you are using HABTM associations here it might be a good idea to take a look at a has many through relationship. example: VenueManagement which would have your user_id and venue_id. That way you could handle extra attributes on the association where it makes sense, like a default flag.
Hope that helps.
Since Rails seems to pick up on the _id part of the tag and tries linking it to a table, the easy solution to this is to try name it differently as convention says a field with _id links to a table.
An example of which could be
:default_id_for_venue, or just :defualt_venue