Working my way through Lynda.com's course on Rails 5 and I ran into a glitch. Don't know if the issue is my setup, a difference in revs (I believe the instructor is using v5.0.0, while I'm using v5.1.4), I'm doing it wrong, or there's a bug in rails console.
The lesson is Chapter 6 Associations, section 2 One-to-One Associations. The lesson uses two tables: subjects and pages. Their models are:
class Subject < ApplicationRecord
has_one :page
# and a bunch of scopes from an earlier lesson
end
And:
class Page < ApplicationRecord
belongs_to :subject
end
Using MySQL v14.14 Distrib 5.7.19 for osx10.12.
mysql> SHOW FIELDS FROM subjects;
+------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| name | varchar(50) | YES | | NULL | |
| position | int(11) | YES | | NULL | |
| visible | tinyint(1) | YES | | 0 | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+------------+-------------+------+-----+---------+----------------+
And:
mysql> SHOW FIELDS FROM pages;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| subject_id | int(11) | YES | MUL | NULL | |
| name | varchar(50) | YES | | NULL | |
| permalink | varchar(100) | YES | MUL | NULL | |
| position | int(11) | YES | | NULL | |
| visible | tinyint(1) | YES | | 0 | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+
The field to watch here is pages.subject_id. Note that it can be nulled.
From rails console I'd created a series of records in subjects, in a previous lesson. In this lesson I create a record in pages and link it with subject record. Great! Works perfectly. Then I try to unlink it just as the instructor does, BUT IT FAILS FOR ME.
At the Rails command line I retrieve a couple of objects from the database:
iirb(main):022:0* subject2 = Subject.find_by_name("Next Subject")
Subject Load (0.6ms) SELECT `subjects`.* FROM `subjects` WHERE `subjects`.`name` = 'Next Subject' LIMIT 1
=> #<Subject id: 2, name: "Next Subject", position: 2, visible: true, created_at: "2017-09-21 21:17:25", updated_at: "2017-09-21 21:51:39">
irb(main):023:0> page2 = Page.find_by_name("Next Page")
Page Load (3.3ms) SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Next Page' LIMIT 1
=> #<Page id: 6, subject_id: nil, name: "Next Page", permalink: "next", position: 2, visible: false, created_at: "2017-09-24 19:00:52", updated_at: "2017-09-24 19:53:38">
Note that pages.subject_id is nil. The records are unlinked.
From the Rails command line both page2.subject and subject2.page return nil, as expected. I can link them with:
irb(main):029:0> subject2.page = page2
(2.7ms) BEGIN
SQL (9.2ms) UPDATE `pages` SET `subject_id` = 2, `updated_at` = '2017-09-25 14:40:09' WHERE `pages`.`id` = 6
(3.0ms) COMMIT
=> #<Page id: 6, subject_id: 2, name: "Next Page", permalink: "next", position: 2, visible: false, created_at: "2017-09-24 19:00:52", updated_at: "2017-09-25 14:40:09">
Now page2.subject and subject2.page works, as expected.
AND THEN THE PROBLEM. The instructor says I can unlink the records with subject2.page = nil, but:
irb(main):037:0* subject2.page = nil
(0.3ms) BEGIN
(0.2ms) ROLLBACK
ActiveRecord::RecordNotSaved: Failed to remove the existing associated page. The record failed to save after its foreign key was set to nil.
from (irb):37
It claims the record failed to save. Could it be the value of nil (or null) in pages.subject_id?
The page2 object shows that subject_id has been reset to its original value, not nil. If I try to set this to nil and then save page2 to the database, it fails.
But if I reload the page2 object in memory from the database, manually set subject_id to nil, and then save page2, it works! At least the pages database shows correct contents.
However if I check page2.subject and subject2.page the Rails command line shows they're still linked. I need to reload these objects in memory from the database before what's in memory shows the correct result.
It seems subject2.page = nil (which works for the instructor) unlinks the records in both the database and memory--at least that's how it's supposed to work. Manually unlinking works in the database, but memory needs to be updated from the database for full functionality.
So what's going on?
Thanks a million for your help.
PS: Mac OS-X v10.12.6 (Sierra), Ruby v2.4.1p111, Rails v5.4.1 (both installed via Homebrew), using VS Code v1.16.1 as my editor, and project setup through the command line (showing exactly the same files and directories as instructor has)
Added info: relevant part of schema.rb
ActiveRecord::Schema.define(version: 20170920222914) do
create_table "pages", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.integer "subject_id"
t.string "name", limit: 50
t.string "permalink", limit: 100
t.integer "position"
t.boolean "visible", default: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["permalink"], name: "index_pages_on_permalink"
t.index ["subject_id"], name: "index_pages_on_subject_id"
end
create_table "subjects", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.string "name", limit: 50
t.integer "position"
t.boolean "visible", default: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
Moving comment down with a bit more detail.
In Rails 5.0 and up they've changed how the default belongs_to associations work. From the upgrade guides it states:
belongs_to will now trigger a validation error by default if the association is not present.
This can be turned off per-association with optional: true.
In your question you show your pages table (thanks for the details by the way) and it indicates that your foreign key can be null. But when you go to save, rails does it's own default validation and rollsback your save since you're using rails 5.1.4.
I believe this is now a default validation as it's helping enforce foreign key constraints. Typically you'd delete the subject and cascade delete the page, or delete the page but keep the subject. But there are times you want to do what you're requesting depending on your structure.
I'm assuming the tutorial your following is using rails 4.2.
It sounds like there's a foreign key constraint in place in your database maybe? Can you update your original post and provide your entire db/schema.rb file please?
If you do not want the Page record to be deleted when you nullify the foreign key, you should specify dependent: :nullify on your has_one association. That may fix your issue too.
class Subject < ApplicationRecord
has_one :page, dependent: :nullify
# and a bunch of scopes from an earlier lesson
end
Related
Problem: When trying to save a record (Sale.create even), I get this error every time
PG::NotNullViolation: ERROR: null value in column "id" of relation "sales" violates not-null constraint
Work-around: If I drop and re-create my database it fixes the problem. After the work-around it will work for a while until it starts happening again.
My hypothesis: Somehow my test database seems to be getting into a state when it "loses" (🤷) it's auto-increment of the ID column for at least 1 table.
Confounding problem: This didn't happen with SQLite.
Background: We know that Rails creates the ID column with auto_increment and internally the database tracks that somewhere.
Question: How can I debug this further?
Versions:
Rails 6.0.3.4
Ruby 2.7.2
Postgres 13.1
Sale Class
class Sale < ApplicationRecord
belongs_to :horse_variant, optional: true
belongs_to :origin_location, optional: true
has_many :sale_adjustments, dependent: :delete_all
has_many :fulfillments, dependent: :delete_all
has_many :fulfillment_orders, dependent: :delete_all
has_many :refunds, dependent: :delete_all
enum fulfillment_status: {
null: 0,
partial: 1,
fulfilled: 2,
}
scope :unknown_variant, -> { where(horse_variant_id: HorseVariant.unknown_horse_variant_id) }
Postgres schema dump
Table "public.sales"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------------------+--------------------------------+-----------+----------+-----------------------------------+----------+--------------+-------------
id | bigint | | not null | nextval('sales_id_seq'::regclass) | plain | |
horse_variant_id | bigint | | not null | | plain | |
origin_location_id | bigint | | not null | | plain | |
quantity | bigint | | not null | | plain | |
date | date | | not null | | plain | |
shopify_order_name | character varying | | not null | | extended | |
shopify_order_id | bigint | | not null | | plain | |
order_line_item_id | bigint | | not null | | plain | |
created_at | timestamp(6) without time zone | | not null | | plain | |
updated_at | timestamp(6) without time zone | | not null | | plain | |
discount | double precision | | | | plain | |
fulfillment_status | integer | | | 0 | plain | |
status | integer | | | 0 | plain | |
Indexes:
"sales_pkey" PRIMARY KEY, btree (id)
"index_sales_on_date" btree (date)
"index_sales_on_date_and_horse_variant_id" btree (date, horse_variant_id)
"index_sales_on_horse_variant_id" btree (horse_variant_id)
"index_sales_on_order_line_item_id" UNIQUE, btree (order_line_item_id)
"index_sales_on_origin_location_id" btree (origin_location_id)
"index_sales_on_shopify_order_id" btree (shopify_order_id)
Foreign-key constraints:
"fk_rails_6df22f6f3c" FOREIGN KEY (origin_location_id) REFERENCES origin_locations(id)
"fk_rails_cab8258b6a" FOREIGN KEY (horse_variant_id) REFERENCES horse_variants(id)
Referenced by:
TABLE "sale_adjustments" CONSTRAINT "fk_rails_19fd8872e1" FOREIGN KEY (sale_id) REFERENCES sales(id)
TABLE "refunds" CONSTRAINT "fk_rails_4e4e302c3a" FOREIGN KEY (sale_id) REFERENCES sales(id)
TABLE "fulfillment_orders" CONSTRAINT "fk_rails_8897bbd770" FOREIGN KEY (sale_id) REFERENCES sales(id)
TABLE "fulfillments" CONSTRAINT "fk_rails_e8f7460348" FOREIGN KEY (sale_id) REFERENCES sales(id)
Access method: heap
IRB (pry) session
[1] pry(main)> Sale.create
W, [2021-04-04T18:57:11.712216 #58350] WARN -- : Creating scope :test. Overwriting existing method Sale.test.
D, [2021-04-04T18:57:11.782069 #58350] DEBUG -- : (0.2ms) BEGIN
D, [2021-04-04T18:57:11.787486 #58350] DEBUG -- : Sale Create (5.1ms) INSERT INTO "sales" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", "2021-04-04 22:57:11 UTC"], ["updated_at", "2021-04-04 22:57:11 UTC"]]
D, [2021-04-04T18:57:11.787842 #58350] DEBUG -- : Query Trace:
bin/rails:9:in `<main>'
D, [2021-04-04T18:57:11.788096 #58350] DEBUG -- : (0.1ms) ROLLBACK
D, [2021-04-04T18:57:11.788316 #58350] DEBUG -- : Query Trace:
bin/rails:9:in `<main>'
ActiveRecord::NotNullViolation: PG::NotNullViolation: ERROR: null value in column "id" of relation "sales" violates not-null constraint
DETAIL: Failing row contains (null, null, null, null, null, null, null, null, 2021-04-04 22:57:11 UTC, 2021-04-04 22:57:11 UTC, null, 0, 0).
from /Users/marcbeaupre/.rvm/gems/ruby-2.7.2/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/postgresql_adapter.rb:675:in `exec_params'
Caused by PG::NotNullViolation: ERROR: null value in column "id" of relation "sales" violates not-null constraint
DETAIL: Failing row contains (null, null, null, null, null, null, null, null, 2021-04-04 22:57:11 UTC, 2021-04-04 22:57:11 UTC, null, 0, 0).
from /Users/marcbeaupre/.rvm/gems/ruby-2.7.2/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/postgresql_adapter.rb:675:in `exec_params'
Using Ruby 2.7.0 and Ruby on Rails 6.0.3.4
Okay, suppose I have two models En and Em and I want to set an N:M relationship between them. The Rails docs are a bit scarce, but they advise the use of the create_join_table migration method for establishing HABTM relationships. Soon enough, running rails g migration En Em gives me a migration file with
The setup:
def change
create_join_table :ens, :ems do |t|
t.index [:en_id, :em_id]
t.index [:em_id, :en_id]
end
end
Great. I also have to signal this N:M relationship in my En and Em models, and I do so:
class En < ApplicationRecord
has_and_belongs_to_many :ems
end
class Em < ApplicationRecord
has_and_belongs_to_many :ens
end
Looking good so far, my simple test works well too:
m = Em.create
n = En.new
n.ems << m
n.save
En.find(n.id).ems # equals [em], unsurprisingly
The Problem
The moment I want to do anything more fancy with ActiveRecord, say finding some En through Em through En, I run into issues:
Em.where(ens: En.where(id: En.first)) #fails
En.where(ems: Em.where(id: Em.first)) #fails
Error:
ActiveRecord::UnknownPrimaryKey: Unknown primary key for table ems_ens in model En::HABTM_Ems.
Because create_join_table does not give me a primary key, so I have to add one myself. No problem, one more migration should do the trick, right?
def change
add_column :ems_ens, :id, :primary_key
end
Error:
DRb::DRbRemoteError: PG::UndefinedColumn: ERROR: column "en_id" does not exist
LINE 1: ...CT "ems".* FROM "ems" WHERE "ems"."id" IN (SELECT en_id FROM...
If it helps, I did quickly write a repo to replicate this behavior exactly in this post. Surely I'm missing something obvious here, right? I'm having similar issues with my work code, but oddly enough I'm having issues one level deeper (something like En.where(ems: Em.where(ens: ...)), hierarchies in relational databases are no fun).
Additional information
Since one commenter requested, the auto-generated schema.rb and described tables.
ActiveRecord::Schema.define(version: 2020_11_09_210057) do
enable_extension "plpgsql"
create_table "ems", force: :cascade do |t|
t.string "mane"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "ems_ens", force: :cascade do |t|
t.bigint "en_id", null: false
t.bigint "em_id", null: false
t.index ["em_id", "en_id"], name: "index_ems_ens_on_em_id_and_en_id"
t.index ["en_id", "em_id"], name: "index_ems_ens_on_en_id_and_em_id"
end
create_table "ens", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
end
Table "public.ems_ens"
Column | Type | Collation | Nullable | Default
--------+--------+-----------+----------+-------------------------------------
en_id | bigint | | not null |
em_id | bigint | | not null |
id | bigint | | not null | nextval('ems_ens_id_seq'::regclass)
Indexes:
"ems_ens_pkey" PRIMARY KEY, btree (id)
"index_ems_ens_on_em_id_and_en_id" btree (em_id, en_id)
"index_ems_ens_on_en_id_and_em_id" btree (en_id, em_id)
Table "public.ems"
Column | Type | Collation | Nullable | Default
------------+--------------------------------+-----------+----------+---------------------------------
id | bigint | | not null | nextval('ems_id_seq'::regclass)
mane | character varying | | |
created_at | timestamp(6) without time zone | | not null |
updated_at | timestamp(6) without time zone | | not null |
Indexes:
"ems_pkey" PRIMARY KEY, btree (id)
Table "public.ens"
Column | Type | Collation | Nullable | Default
------------+--------------------------------+-----------+----------+---------------------------------
id | bigint | | not null | nextval('ens_id_seq'::regclass)
name | character varying | | |
created_at | timestamp(6) without time zone | | not null |
updated_at | timestamp(6) without time zone | | not null |
Indexes:
"ens_pkey" PRIMARY KEY, btree (id)
The problem here is that you're not joining onto the ens relation, and while the where method is smart enough to know that it needs to look for the en_id column, it's not smart enough to add that join onto your query. As a result, the join table isn't in the FROM clause, so the WHERE clause ends up invalid. To fix it, you can just add joins(:ens).
Incorrect (note how the FROM clause is not joining onto your join table):
irb(main):026:0> Em.where(ens: En.where(id: En.first))
En Load (0.9ms) SELECT "ens".* FROM "ens" ORDER BY "ens"."id" ASC LIMIT $1 [["LIMIT", 1]]
Em Load (0.8ms) SELECT "ems".* FROM "ems" WHERE "ems"."id" IN (SELECT en_id FROM "ens" WHERE "ens"."id" = $1) LIMIT $2 [["id", 1], ["LIMIT", 11]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column "en_id" does not exist)
LINE 1: ...CT "ems".* FROM "ems" WHERE "ems"."id" IN (SELECT en_id FROM...
Corrected (note how the WHERE stays the same -- only the INNER JOIN part is new):
irb(main):027:0> Em.joins(:ens).where(ens: En.where(id: En.first))
En Load (0.3ms) SELECT "ens".* FROM "ens" ORDER BY "ens"."id" ASC LIMIT $1 [["LIMIT", 1]]
Em Load (1.0ms) SELECT "ems".* FROM "ems" INNER JOIN "ems_ens" ON "ems_ens"."em_id" = "ems"."id" INNER JOIN "ens" ON "ens"."id" = "ems_ens"."en_id" WHERE "ems"."id" IN (SELECT en_id FROM "ens" WHERE "ens"."id" = $1) LIMIT $2 [["id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Em id: 1, mane: nil, created_at: "2020-11-10 03:40:07", updated_at: "2020-11-10 03:40:07">]>
I have the following models to describe a many-to-many relationship between steps and product:
class Step < ApplicationRecord
has_and_belongs_to_many :products
end
class Product < ApplicationRecord
has_and_belongs_to_many :steps
end
class ProductsStep < ApplicationRecord
belongs_to :product
belongs_to :step
end
And these are my tables:
steps:
Column | Type | Modifiers | Storage | Stats target | Description
-------------+-----------------------------+----------------------------------------------------+----------+--------------+-------------
id | bigint | not null default nextval('steps_id_seq'::regclass) | plain | |
name | character varying | not null | extended | |
created_at | timestamp without time zone | | plain | |
updated_at | timestamp without time zone | | plain | |
Indexes:
"steps_pkey" PRIMARY KEY, btree (id)
"index_steps_on_weeks_id" btree (weeks_id)
products:
Column | Type | Modifiers | Storage | Stats target | Description
--------------------+-----------------------------+-------------------------------------------------------+----------+--------------+-------------
id | bigint | not null default nextval('products_id_seq'::regclass) | plain | |
name | character varying | not null | extended | |
photo_file_name | character varying | | extended | |
photo_content_type | character varying | | extended | |
photo_file_size | integer | | plain | |
photo_updated_at | timestamp without time zone | | plain | |
created_at | timestamp without time zone | not null | plain | |
updated_at | timestamp without time zone | not null | plain | |
Indexes:
"products_pkey" PRIMARY KEY, btree (id)
products_steps:
Column | Type | Modifiers | Storage | Stats target | Description
------------+-----------------------------+-----------+---------+--------------+-------------
step_id | integer | not null | plain | |
product_id | integer | not null | plain | |
created_at | timestamp without time zone | not null | plain | |
updated_at | timestamp without time zone | not null | plain | |
Indexes:
"products_steps_pkey" PRIMARY KEY, btree (product_id, step_id)
I would like to save the products with the associated steps, but I'm receiving the following message:
Completed 422 Unprocessable Entity in 81ms (ActiveRecord: 10.6ms)
ActiveRecord::RecordInvalid (The validation failed: Steps isn't valid)
I did the following:
def create
steps = Array.new()
params[:product][:steps].split(/,/).each do |s|
steps << s.to_i
end
#product = Product.new(product_params)
#product.steps << Step.where(:id => steps)
puts "#{#product.steps.first.name}" #the name is printed
product = Product.find_by(name: #product.name, brand: #product.brand)
if product.nil?
if #product.save!
render :json => {:status => :success}
end
end
end
The steps params is a string like "1,2,3" that represents the step id.
I have no idea what I did wrong, I did exactly the same thing in other situations and it worked.
EDIT:
Here is all my request and the errors message, including the sugested validation:
Bad request content body
Started POST "/products/?product[name]=test+1&product[brand]=test&product[released_for]=Low+Poo&product[weight]=50.0&product[measurement]=g&product[steps]=1%2C2%2C3&product[user_id]=test%40gmail.com&" for 192.168.2.5 at 2018-06-19 17:56:23 -0300
Processing by ProductsController#create as HTML
Parameters: {"product"=>{"name"=>"test 1", "brand"=>"test", "released_for"=>"Low Poo", "weight"=>"50.0", "measurement"=>"g", "steps"=>"1,2,3", "user_id"=>"test#gmail.com"}}
Unpermitted parameter: :steps
Step Load (0.4ms) SELECT "steps".* FROM "steps" WHERE "steps"."id" IN ($1, $2, $3) [["id", 1], ["id", 2], ["id", 3]]
↳ app/controllers/products_controller.rb:73
------LOGS-------
#<ActiveModel::Errors:0x007f2840acb628 #base=#<Product id: nil, name: "Test 1", brand: "Test", measurement: "g", weight: #<BigDecimal:7f28409f2ee0,'0.5E2',9(18)>, photo_file_name: nil, photo_content_type: nil, photo_file_size: nil, photo_updated_at: nil, released_for: "Low Poo", created_at: nil, updated_at: nil, user_id: "test#gmail.com">, #messages={:steps=>["não é válido"]}, #details={:steps=>[{:error=>:invalid}, {:error=>:invalid}, {:error=>:invalid}]}>
-------------
Hidratação
Product Load (0.3ms) SELECT "products".* FROM "products" WHERE "products"."name" = $1 AND "products"."brand" = $2 LIMIT $3 [["name", "Test 1"], ["brand", "Test"], ["LIMIT", 1]]
↳ app/controllers/products_controller.rb:78
#<ActiveModel::Errors:0x007f2840acb628 #base=#<Product id: nil, name: "Test 1", brand: "Test", measurement: "g", weight: #<BigDecimal:7f28409f2ee0,'0.5E2',9(18)>, photo_file_name: nil, photo_content_type: nil, photo_file_size: nil, photo_updated_at: nil, released_for: "Low Poo", created_at: nil, updated_at: nil, user_id: "test#gmail.com">, #messages={:steps=>["não é válido"]}, #details={:steps=>[{:error=>:invalid}, {:error=>:invalid}, {:error=>:invalid}]}>
(0.2ms) BEGIN
↳ app/controllers/products_controller.rb:82
(0.2ms) ROLLBACK
↳ app/controllers/products_controller.rb:82
Completed 422 Unprocessable Entity in 75ms (ActiveRecord: 9.4ms)
ActiveRecord::RecordInvalid (A validação falhou: Steps não é válido):
app/controllers/products_controller.rb:82:in `create'
As per the description shared it seems like there could one of the below mentioned issues which could be raising the exception.
Firstly, the below mentioned log trail shows that only steps is not permitted apart from that all other parameter is being permitted to the database.
Parameters: {"product"=>{"name"=>"test 1", "brand"=>"test", "released_for"=>"Low Poo", "weight"=>"50.0", "measurement"=>"g", "steps"=>"1,2,3", "user_id"=>"test#gmail.com"}}
Unpermitted parameter: :steps
So, does the user_id column has a datatype string, also could not find the user_id column in the table structure shared.
Secondly, for the ActiveRecord::RecordInvalid, this is raised when a validation is failed for the corresponding model.
Please check if there are any validation failure happening for the above case.
The validation failed: Steps isn't valid. This means that step id which you are passing is not valid. you can print #product and #product.steps to check what is printing. When i check code #product.steps << Step.where(:id => steps) this code will make array of array so it must be #product.steps = Step.where(:id => steps) or #product.steps += Step.where(:id => steps). Print #product and #product.steps this will give clear detail what data is trying to save in table.
I have this db migration for my Rails 3.2 plugin:
class CreateIssueChangeApprovements < ActiveRecord::Migration
def change
create_table :issue_change_approvements do |t|
t.timestamps
t.references :issue, :null => false
t.references :user, :null => false
t.string :field_type, :limit => 30
t.string :old_value
t.string :value
t.integer :approved_by_id
t.boolean :is_closed, :default => 0
t.string :status, :default => '', :limit => 30
end
add_index :issue_change_approvements, :issue_id
add_index :issue_change_approvements, :user_id
end
end
Once I run it against a MySQL database, I see no NOT NULL constraints on issue_id and user_id fields as well as no foreign keys.
Can anyone tell me what is the right way to do this?
UPD:
The weird thing here is that when I go to MySQL Workbench and generate a script for my newly created table, i get this:
CREATE TABLE `issue_change_approvements` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`issue_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`field_type` varchar(30) DEFAULT NULL,
`old_value` varchar(255) DEFAULT NULL,
`value` varchar(255) DEFAULT NULL,
`approved_by_id` int(11) DEFAULT NULL,
`is_closed` tinyint(1) DEFAULT '0',
`status` varchar(30) DEFAULT '',
PRIMARY KEY (`id`),
KEY `index_issue_change_approvements_on_issue_id` (`issue_id`),
KEY `index_issue_change_approvements_on_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
However when I run a statement like this:
insert into issue_change_approvements(value) values('hello')
it executes successfully and adds a row with lots of NULL values.
UPD 2:
This is what describe issue_change_approvements shows:
+----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
| issue_id | int(11) | NO | MUL | NULL | |
| user_id | int(11) | NO | MUL | NULL | |
| field_type | varchar(30) | YES | | NULL | |
| old_value | varchar(255) | YES | | NULL | |
| value | varchar(255) | YES | | NULL | |
| approved_by_id | int(11) | YES | | NULL | |
| is_closed | tinyint(1) | YES | | 0 | |
| status | varchar(30) | YES | | | |
+----------------+--------------+------+-----+---------+----------------+
Rails does not create any constraints by default. The null: false option will just not allow null values for this sql field, but it will not add a constraint.
Instead of foreign_key constraints in rails you usually just set the desired option on model relationship, e.g has_many :issues, dependent: :destroy
I guess this strategy was chosen for faster schema migrations with less downtime.
However there is an option to use constraints on db level by explicitly specifying them. You can use the foreigner gem prior to rails 4.2 and with rails 4.2 the dsl for them is included by default.
See
Have Some (Referential) Integrity with Foreign Keys
2.5 Foreign Key Support
class CreateTestings < ActiveRecord::Migration
def self.up
create_table :testings do |t|
t.string "name"
t.boolean "visible"
t.string "description"
t.integer "roll"
t.references "students"
t.timestamps
end
end
def self.down
drop_table :testings
end
end
Hello, i just ran this test migration to see how Rails handles Migrations. Even though i have
t.references "students"
Rails created the students_id in my testings table successfully but however didn't specified any foreign key on it:
mysql> DESC testings;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | | NULL | |
| visible | tinyint(1) | YES | | NULL | |
| description | varchar(255) | YES | | NULL | |
| roll | int(11) | YES | | NULL | |
| students_id | int(11) | YES | | NULL | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
Is this how Rails works or otherwise i should've had
t.references :student instead of t.references "students"
Thanks!
This is how rails works. It doesn't specify foreign key dependencies in its migrations. You'll have to do this manually with an 'execute' command and SQL code if you do want to specify foreign-key dependencies.