Rails - correct associations for pre-existing models
I am trying to create Rails models for some pre-existing database tables.
There is 1 MainTable, which has 1 ChildTable.
The problem could be the 'foreign keys' are named differently on each table
class MainTable < ActiveRecord::Base
has_many :child_tables, :class_name => 'ChildTable', :foreign_key => "child_column_name"
accepts_nested_attributes_for :child_tables
self.primary_key = "main_table_column_name"
self.table_name = 'main_table'
end
class ChildTable < ActiveRecord::Base
belongs_to :main_table, :class_name => 'MainTable', foreign_key: "child_column_name", :primary_key => "main_table_column_name"
self.table_name = 'child_table'
self.primary_key = "child_table_column_name"
end
Given that the main table will only every have 1 record per child table, but there could be many child records per main table - do these associations look correct?
I want to be able to do something like:
m = MainTable.new
m.some_value = "123"
m.mntbl_key ="999"
c = ChildTable.new
c.something = "Foo"
m.child_tables << c
m.save!
EDIT: I am no longer getting an error as I have updated the code above. It works now BUT I have to assign the ids on both tables.
If I assign the id on the Master object, it will not automatically give the child object that id in the fk column
i.e.
m = MasterTable.new
m.master_table_column_name = 99
c = ChildTable.new
m.child_tables << c
m.save!
-- This will Insert ok into Master but will INSERT a blank row into the child
However:
m = MasterTable.new
m.master_table_column_name = 99
c = ChildTable.new
c.child_column_name = 99
m.child_tables << c
m.save!
--This works and inserts correctly into both tables
--------------------------------- OLD:
But I am getting the error:
ActiveModel::MissingAttributeError: can't write unknown attribute `ctbl_key'
Am I missing something?
ruby-on-rails
add a comment |
I am trying to create Rails models for some pre-existing database tables.
There is 1 MainTable, which has 1 ChildTable.
The problem could be the 'foreign keys' are named differently on each table
class MainTable < ActiveRecord::Base
has_many :child_tables, :class_name => 'ChildTable', :foreign_key => "child_column_name"
accepts_nested_attributes_for :child_tables
self.primary_key = "main_table_column_name"
self.table_name = 'main_table'
end
class ChildTable < ActiveRecord::Base
belongs_to :main_table, :class_name => 'MainTable', foreign_key: "child_column_name", :primary_key => "main_table_column_name"
self.table_name = 'child_table'
self.primary_key = "child_table_column_name"
end
Given that the main table will only every have 1 record per child table, but there could be many child records per main table - do these associations look correct?
I want to be able to do something like:
m = MainTable.new
m.some_value = "123"
m.mntbl_key ="999"
c = ChildTable.new
c.something = "Foo"
m.child_tables << c
m.save!
EDIT: I am no longer getting an error as I have updated the code above. It works now BUT I have to assign the ids on both tables.
If I assign the id on the Master object, it will not automatically give the child object that id in the fk column
i.e.
m = MasterTable.new
m.master_table_column_name = 99
c = ChildTable.new
m.child_tables << c
m.save!
-- This will Insert ok into Master but will INSERT a blank row into the child
However:
m = MasterTable.new
m.master_table_column_name = 99
c = ChildTable.new
c.child_column_name = 99
m.child_tables << c
m.save!
--This works and inserts correctly into both tables
--------------------------------- OLD:
But I am getting the error:
ActiveModel::MissingAttributeError: can't write unknown attribute `ctbl_key'
Am I missing something?
ruby-on-rails
Can you add migrations files for those tables?
– Raoslaw Szamszur
Nov 20 '18 at 16:21
No the tables preexist and I cannot control anything about them :(
– user3437721
Nov 20 '18 at 16:22
1
You don't need aforeign_key
in themany
side of ahas_many
relationship.
– gd.silva
Nov 20 '18 at 16:27
@user3437721 Yep of course. But what I've meant was not migrations butschema.rb
file. Sorry brain fart.
– Raoslaw Szamszur
Nov 20 '18 at 16:35
add a comment |
I am trying to create Rails models for some pre-existing database tables.
There is 1 MainTable, which has 1 ChildTable.
The problem could be the 'foreign keys' are named differently on each table
class MainTable < ActiveRecord::Base
has_many :child_tables, :class_name => 'ChildTable', :foreign_key => "child_column_name"
accepts_nested_attributes_for :child_tables
self.primary_key = "main_table_column_name"
self.table_name = 'main_table'
end
class ChildTable < ActiveRecord::Base
belongs_to :main_table, :class_name => 'MainTable', foreign_key: "child_column_name", :primary_key => "main_table_column_name"
self.table_name = 'child_table'
self.primary_key = "child_table_column_name"
end
Given that the main table will only every have 1 record per child table, but there could be many child records per main table - do these associations look correct?
I want to be able to do something like:
m = MainTable.new
m.some_value = "123"
m.mntbl_key ="999"
c = ChildTable.new
c.something = "Foo"
m.child_tables << c
m.save!
EDIT: I am no longer getting an error as I have updated the code above. It works now BUT I have to assign the ids on both tables.
If I assign the id on the Master object, it will not automatically give the child object that id in the fk column
i.e.
m = MasterTable.new
m.master_table_column_name = 99
c = ChildTable.new
m.child_tables << c
m.save!
-- This will Insert ok into Master but will INSERT a blank row into the child
However:
m = MasterTable.new
m.master_table_column_name = 99
c = ChildTable.new
c.child_column_name = 99
m.child_tables << c
m.save!
--This works and inserts correctly into both tables
--------------------------------- OLD:
But I am getting the error:
ActiveModel::MissingAttributeError: can't write unknown attribute `ctbl_key'
Am I missing something?
ruby-on-rails
I am trying to create Rails models for some pre-existing database tables.
There is 1 MainTable, which has 1 ChildTable.
The problem could be the 'foreign keys' are named differently on each table
class MainTable < ActiveRecord::Base
has_many :child_tables, :class_name => 'ChildTable', :foreign_key => "child_column_name"
accepts_nested_attributes_for :child_tables
self.primary_key = "main_table_column_name"
self.table_name = 'main_table'
end
class ChildTable < ActiveRecord::Base
belongs_to :main_table, :class_name => 'MainTable', foreign_key: "child_column_name", :primary_key => "main_table_column_name"
self.table_name = 'child_table'
self.primary_key = "child_table_column_name"
end
Given that the main table will only every have 1 record per child table, but there could be many child records per main table - do these associations look correct?
I want to be able to do something like:
m = MainTable.new
m.some_value = "123"
m.mntbl_key ="999"
c = ChildTable.new
c.something = "Foo"
m.child_tables << c
m.save!
EDIT: I am no longer getting an error as I have updated the code above. It works now BUT I have to assign the ids on both tables.
If I assign the id on the Master object, it will not automatically give the child object that id in the fk column
i.e.
m = MasterTable.new
m.master_table_column_name = 99
c = ChildTable.new
m.child_tables << c
m.save!
-- This will Insert ok into Master but will INSERT a blank row into the child
However:
m = MasterTable.new
m.master_table_column_name = 99
c = ChildTable.new
c.child_column_name = 99
m.child_tables << c
m.save!
--This works and inserts correctly into both tables
--------------------------------- OLD:
But I am getting the error:
ActiveModel::MissingAttributeError: can't write unknown attribute `ctbl_key'
Am I missing something?
ruby-on-rails
ruby-on-rails
edited Nov 21 '18 at 9:45
user3437721
asked Nov 20 '18 at 16:16
user3437721user3437721
88911532
88911532
Can you add migrations files for those tables?
– Raoslaw Szamszur
Nov 20 '18 at 16:21
No the tables preexist and I cannot control anything about them :(
– user3437721
Nov 20 '18 at 16:22
1
You don't need aforeign_key
in themany
side of ahas_many
relationship.
– gd.silva
Nov 20 '18 at 16:27
@user3437721 Yep of course. But what I've meant was not migrations butschema.rb
file. Sorry brain fart.
– Raoslaw Szamszur
Nov 20 '18 at 16:35
add a comment |
Can you add migrations files for those tables?
– Raoslaw Szamszur
Nov 20 '18 at 16:21
No the tables preexist and I cannot control anything about them :(
– user3437721
Nov 20 '18 at 16:22
1
You don't need aforeign_key
in themany
side of ahas_many
relationship.
– gd.silva
Nov 20 '18 at 16:27
@user3437721 Yep of course. But what I've meant was not migrations butschema.rb
file. Sorry brain fart.
– Raoslaw Szamszur
Nov 20 '18 at 16:35
Can you add migrations files for those tables?
– Raoslaw Szamszur
Nov 20 '18 at 16:21
Can you add migrations files for those tables?
– Raoslaw Szamszur
Nov 20 '18 at 16:21
No the tables preexist and I cannot control anything about them :(
– user3437721
Nov 20 '18 at 16:22
No the tables preexist and I cannot control anything about them :(
– user3437721
Nov 20 '18 at 16:22
1
1
You don't need a
foreign_key
in the many
side of a has_many
relationship.– gd.silva
Nov 20 '18 at 16:27
You don't need a
foreign_key
in the many
side of a has_many
relationship.– gd.silva
Nov 20 '18 at 16:27
@user3437721 Yep of course. But what I've meant was not migrations but
schema.rb
file. Sorry brain fart.– Raoslaw Szamszur
Nov 20 '18 at 16:35
@user3437721 Yep of course. But what I've meant was not migrations but
schema.rb
file. Sorry brain fart.– Raoslaw Szamszur
Nov 20 '18 at 16:35
add a comment |
2 Answers
2
active
oldest
votes
A one-to-many association only uses one foreign key column - not two.
Typically in rails its setup as so:
class Parent
has_many :children
end
class Child
belongs_to :parent
end
The actual association is stored in the children.parent_id
column.
So lets say we have an non-conventional foreign key:
class Parent
has_many :children, foreign_key: 'padre_id'
end
class Child
belongs_to :parent, foreign_key: 'padre_id'
end
Easy enough. We just have to tell the associations on both side what the foreign key is. Note that the class_name
option is not needed as long as the class name can be deduced from the name of the association.
Custom primary keys or table names are not a problem either as ActiveRecord looks at the model class definitions when resolving associations.
class Parent
self.primary_key = :custom_pk
has_many :children, foreign_key: 'padre_id'
end
class Child
self.table_name = 'bar'
# this will correctly reference parents.foo
belongs_to :parent, foreign_key: 'padre_id'
end
There is also the universal issue that you must save a record before you can add children to it:
irb(main):024:0> m = MainTable.new
=> #<MainTable custom_pk: nil, created_at: nil, updated_at: nil>
irb(main):025:0> m.child_tables.new
=> #<ChildTable id: nil, padre_id: nil, created_at: nil, updated_at: nil>
irb(main):026:0> m.save!
(0.3ms) BEGIN
(0.4ms) ROLLBACK
ActiveRecord::RecordInvalid: Validation failed: Child tables is invalid
from (irb):26
Starting in Rails 5 belongs_to
associations are non-optional by default. So the child_tables
instance is not valid since the padre_id
is nil.
You either need to save the parent record first:
irb(main):033:0> m = MainTable.create!
(0.5ms) BEGIN
MainTable Create (0.7ms) INSERT INTO "main_table" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "custom_pk" [["created_at", "2018-11-20 17:31:09.545476"], ["updated_at", "2018-11-20 17:31:09.545476"]]
(0.8ms) COMMIT
=> #<MainTable custom_pk: 5, created_at: "2018-11-20 17:31:09", updated_at: "2018-11-20 17:31:09">
irb(main):034:0> m.child_tables.new
=> #<ChildTable id: nil, padre_id: 5, created_at: nil, updated_at: nil>
irb(main):035:0> m.save
(0.3ms) BEGIN
MainTable Load (0.6ms) SELECT "main_table".* FROM "main_table" WHERE "main_table"."custom_pk" = $1 LIMIT $2 [["custom_pk", 5], ["LIMIT", 1]]
ChildTable Create (0.9ms) INSERT INTO "child_table" ("padre_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["padre_id", 5], ["created_at", "2018-11-20 17:31:21.737989"], ["updated_at", "2018-11-20 17:31:21.737989"]]
(0.6ms) COMMIT
=> true
irb(main):036:0>
Or build from the belongs_to association on the other side:
irb(main):027:0> c = ChildTable.new
=> #<ChildTable id: nil, padre_id: nil, created_at: nil, updated_at: nil>
irb(main):028:0> c.build_main_table
=> #<MainTable custom_pk: nil, created_at: nil, updated_at: nil>
irb(main):029:0> c.save!
(0.3ms) BEGIN
MainTable Create (0.8ms) INSERT INTO "main_table" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "custom_pk" [["created_at", "2018-11-20 17:24:29.344332"], ["updated_at", "2018-11-20 17:24:29.344332"]]
ChildTable Create (1.1ms) INSERT INTO "child_table" ("padre_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["padre_id", 3], ["created_at", "2018-11-20 17:24:29.346764"], ["updated_at", "2018-11-20 17:24:29.346764"]]
(0.7ms) COMMIT
=> true
but my foreign keys are named different on each table, I'm not sure what your saying here?
– user3437721
Nov 20 '18 at 17:40
Are you saying it can't be done with a foreign key called something different in each table?
– user3437721
Nov 20 '18 at 18:02
No, I'm saying that a one to many association is built with only one foreign key (which is on thebelongs_to
side). The foreign key references the primary key on the other table. Since its one column on one table it cannot have different names on each side. This is really basic relational database design stuff and applies everywhere. Not just rails.
– max
Nov 20 '18 at 18:57
Thanks I'll try it out and update tomorrow, appreciate the response.
– user3437721
Nov 20 '18 at 19:07
Could you maybe update the answer so it is clear which column I should reference as the foreign key, in your example you use 'padre_id' which I assume is the parent column? Remember it is not the primary key of the parent table which is stored in the child table.
– user3437721
Nov 20 '18 at 19:23
|
show 10 more comments
Not sure if it's the issue you're facing, but at the moment you're initializing new MainTables and ChildTables, however you're not saving them.
This means they won't be assigned an ID, which in turn means when you try and assign a Child Table to the Main Table, it doesn't have an ID for the main table (or itself) to create a join record with.
Try saving the records before shovelling in child records
the idea is to have a cascading save so they all get saved at the same time..
– user3437721
Nov 20 '18 at 16:42
I'm not sure it's possible to assign a child record before the parent record is saved - it will try to create a join record which requires both the parent ID and child ID. I'm pretty sure the minimum is that the parent record is saved first
– Mark
Nov 20 '18 at 16:45
If you really want to go down that route I think you'll need to look at api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/…, specifically 'accepts_nested_attributes_for', which allows you to create a parent and child record in one transaction
– Mark
Nov 20 '18 at 16:46
In your case this would let you do: MainTable.create(title: 'bla', child_table_attributes: { title: 'bla2' } ) Which will create a Main Table, with a child table related to it
– Mark
Nov 20 '18 at 16:47
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53397174%2frails-correct-associations-for-pre-existing-models%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
A one-to-many association only uses one foreign key column - not two.
Typically in rails its setup as so:
class Parent
has_many :children
end
class Child
belongs_to :parent
end
The actual association is stored in the children.parent_id
column.
So lets say we have an non-conventional foreign key:
class Parent
has_many :children, foreign_key: 'padre_id'
end
class Child
belongs_to :parent, foreign_key: 'padre_id'
end
Easy enough. We just have to tell the associations on both side what the foreign key is. Note that the class_name
option is not needed as long as the class name can be deduced from the name of the association.
Custom primary keys or table names are not a problem either as ActiveRecord looks at the model class definitions when resolving associations.
class Parent
self.primary_key = :custom_pk
has_many :children, foreign_key: 'padre_id'
end
class Child
self.table_name = 'bar'
# this will correctly reference parents.foo
belongs_to :parent, foreign_key: 'padre_id'
end
There is also the universal issue that you must save a record before you can add children to it:
irb(main):024:0> m = MainTable.new
=> #<MainTable custom_pk: nil, created_at: nil, updated_at: nil>
irb(main):025:0> m.child_tables.new
=> #<ChildTable id: nil, padre_id: nil, created_at: nil, updated_at: nil>
irb(main):026:0> m.save!
(0.3ms) BEGIN
(0.4ms) ROLLBACK
ActiveRecord::RecordInvalid: Validation failed: Child tables is invalid
from (irb):26
Starting in Rails 5 belongs_to
associations are non-optional by default. So the child_tables
instance is not valid since the padre_id
is nil.
You either need to save the parent record first:
irb(main):033:0> m = MainTable.create!
(0.5ms) BEGIN
MainTable Create (0.7ms) INSERT INTO "main_table" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "custom_pk" [["created_at", "2018-11-20 17:31:09.545476"], ["updated_at", "2018-11-20 17:31:09.545476"]]
(0.8ms) COMMIT
=> #<MainTable custom_pk: 5, created_at: "2018-11-20 17:31:09", updated_at: "2018-11-20 17:31:09">
irb(main):034:0> m.child_tables.new
=> #<ChildTable id: nil, padre_id: 5, created_at: nil, updated_at: nil>
irb(main):035:0> m.save
(0.3ms) BEGIN
MainTable Load (0.6ms) SELECT "main_table".* FROM "main_table" WHERE "main_table"."custom_pk" = $1 LIMIT $2 [["custom_pk", 5], ["LIMIT", 1]]
ChildTable Create (0.9ms) INSERT INTO "child_table" ("padre_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["padre_id", 5], ["created_at", "2018-11-20 17:31:21.737989"], ["updated_at", "2018-11-20 17:31:21.737989"]]
(0.6ms) COMMIT
=> true
irb(main):036:0>
Or build from the belongs_to association on the other side:
irb(main):027:0> c = ChildTable.new
=> #<ChildTable id: nil, padre_id: nil, created_at: nil, updated_at: nil>
irb(main):028:0> c.build_main_table
=> #<MainTable custom_pk: nil, created_at: nil, updated_at: nil>
irb(main):029:0> c.save!
(0.3ms) BEGIN
MainTable Create (0.8ms) INSERT INTO "main_table" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "custom_pk" [["created_at", "2018-11-20 17:24:29.344332"], ["updated_at", "2018-11-20 17:24:29.344332"]]
ChildTable Create (1.1ms) INSERT INTO "child_table" ("padre_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["padre_id", 3], ["created_at", "2018-11-20 17:24:29.346764"], ["updated_at", "2018-11-20 17:24:29.346764"]]
(0.7ms) COMMIT
=> true
but my foreign keys are named different on each table, I'm not sure what your saying here?
– user3437721
Nov 20 '18 at 17:40
Are you saying it can't be done with a foreign key called something different in each table?
– user3437721
Nov 20 '18 at 18:02
No, I'm saying that a one to many association is built with only one foreign key (which is on thebelongs_to
side). The foreign key references the primary key on the other table. Since its one column on one table it cannot have different names on each side. This is really basic relational database design stuff and applies everywhere. Not just rails.
– max
Nov 20 '18 at 18:57
Thanks I'll try it out and update tomorrow, appreciate the response.
– user3437721
Nov 20 '18 at 19:07
Could you maybe update the answer so it is clear which column I should reference as the foreign key, in your example you use 'padre_id' which I assume is the parent column? Remember it is not the primary key of the parent table which is stored in the child table.
– user3437721
Nov 20 '18 at 19:23
|
show 10 more comments
A one-to-many association only uses one foreign key column - not two.
Typically in rails its setup as so:
class Parent
has_many :children
end
class Child
belongs_to :parent
end
The actual association is stored in the children.parent_id
column.
So lets say we have an non-conventional foreign key:
class Parent
has_many :children, foreign_key: 'padre_id'
end
class Child
belongs_to :parent, foreign_key: 'padre_id'
end
Easy enough. We just have to tell the associations on both side what the foreign key is. Note that the class_name
option is not needed as long as the class name can be deduced from the name of the association.
Custom primary keys or table names are not a problem either as ActiveRecord looks at the model class definitions when resolving associations.
class Parent
self.primary_key = :custom_pk
has_many :children, foreign_key: 'padre_id'
end
class Child
self.table_name = 'bar'
# this will correctly reference parents.foo
belongs_to :parent, foreign_key: 'padre_id'
end
There is also the universal issue that you must save a record before you can add children to it:
irb(main):024:0> m = MainTable.new
=> #<MainTable custom_pk: nil, created_at: nil, updated_at: nil>
irb(main):025:0> m.child_tables.new
=> #<ChildTable id: nil, padre_id: nil, created_at: nil, updated_at: nil>
irb(main):026:0> m.save!
(0.3ms) BEGIN
(0.4ms) ROLLBACK
ActiveRecord::RecordInvalid: Validation failed: Child tables is invalid
from (irb):26
Starting in Rails 5 belongs_to
associations are non-optional by default. So the child_tables
instance is not valid since the padre_id
is nil.
You either need to save the parent record first:
irb(main):033:0> m = MainTable.create!
(0.5ms) BEGIN
MainTable Create (0.7ms) INSERT INTO "main_table" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "custom_pk" [["created_at", "2018-11-20 17:31:09.545476"], ["updated_at", "2018-11-20 17:31:09.545476"]]
(0.8ms) COMMIT
=> #<MainTable custom_pk: 5, created_at: "2018-11-20 17:31:09", updated_at: "2018-11-20 17:31:09">
irb(main):034:0> m.child_tables.new
=> #<ChildTable id: nil, padre_id: 5, created_at: nil, updated_at: nil>
irb(main):035:0> m.save
(0.3ms) BEGIN
MainTable Load (0.6ms) SELECT "main_table".* FROM "main_table" WHERE "main_table"."custom_pk" = $1 LIMIT $2 [["custom_pk", 5], ["LIMIT", 1]]
ChildTable Create (0.9ms) INSERT INTO "child_table" ("padre_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["padre_id", 5], ["created_at", "2018-11-20 17:31:21.737989"], ["updated_at", "2018-11-20 17:31:21.737989"]]
(0.6ms) COMMIT
=> true
irb(main):036:0>
Or build from the belongs_to association on the other side:
irb(main):027:0> c = ChildTable.new
=> #<ChildTable id: nil, padre_id: nil, created_at: nil, updated_at: nil>
irb(main):028:0> c.build_main_table
=> #<MainTable custom_pk: nil, created_at: nil, updated_at: nil>
irb(main):029:0> c.save!
(0.3ms) BEGIN
MainTable Create (0.8ms) INSERT INTO "main_table" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "custom_pk" [["created_at", "2018-11-20 17:24:29.344332"], ["updated_at", "2018-11-20 17:24:29.344332"]]
ChildTable Create (1.1ms) INSERT INTO "child_table" ("padre_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["padre_id", 3], ["created_at", "2018-11-20 17:24:29.346764"], ["updated_at", "2018-11-20 17:24:29.346764"]]
(0.7ms) COMMIT
=> true
but my foreign keys are named different on each table, I'm not sure what your saying here?
– user3437721
Nov 20 '18 at 17:40
Are you saying it can't be done with a foreign key called something different in each table?
– user3437721
Nov 20 '18 at 18:02
No, I'm saying that a one to many association is built with only one foreign key (which is on thebelongs_to
side). The foreign key references the primary key on the other table. Since its one column on one table it cannot have different names on each side. This is really basic relational database design stuff and applies everywhere. Not just rails.
– max
Nov 20 '18 at 18:57
Thanks I'll try it out and update tomorrow, appreciate the response.
– user3437721
Nov 20 '18 at 19:07
Could you maybe update the answer so it is clear which column I should reference as the foreign key, in your example you use 'padre_id' which I assume is the parent column? Remember it is not the primary key of the parent table which is stored in the child table.
– user3437721
Nov 20 '18 at 19:23
|
show 10 more comments
A one-to-many association only uses one foreign key column - not two.
Typically in rails its setup as so:
class Parent
has_many :children
end
class Child
belongs_to :parent
end
The actual association is stored in the children.parent_id
column.
So lets say we have an non-conventional foreign key:
class Parent
has_many :children, foreign_key: 'padre_id'
end
class Child
belongs_to :parent, foreign_key: 'padre_id'
end
Easy enough. We just have to tell the associations on both side what the foreign key is. Note that the class_name
option is not needed as long as the class name can be deduced from the name of the association.
Custom primary keys or table names are not a problem either as ActiveRecord looks at the model class definitions when resolving associations.
class Parent
self.primary_key = :custom_pk
has_many :children, foreign_key: 'padre_id'
end
class Child
self.table_name = 'bar'
# this will correctly reference parents.foo
belongs_to :parent, foreign_key: 'padre_id'
end
There is also the universal issue that you must save a record before you can add children to it:
irb(main):024:0> m = MainTable.new
=> #<MainTable custom_pk: nil, created_at: nil, updated_at: nil>
irb(main):025:0> m.child_tables.new
=> #<ChildTable id: nil, padre_id: nil, created_at: nil, updated_at: nil>
irb(main):026:0> m.save!
(0.3ms) BEGIN
(0.4ms) ROLLBACK
ActiveRecord::RecordInvalid: Validation failed: Child tables is invalid
from (irb):26
Starting in Rails 5 belongs_to
associations are non-optional by default. So the child_tables
instance is not valid since the padre_id
is nil.
You either need to save the parent record first:
irb(main):033:0> m = MainTable.create!
(0.5ms) BEGIN
MainTable Create (0.7ms) INSERT INTO "main_table" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "custom_pk" [["created_at", "2018-11-20 17:31:09.545476"], ["updated_at", "2018-11-20 17:31:09.545476"]]
(0.8ms) COMMIT
=> #<MainTable custom_pk: 5, created_at: "2018-11-20 17:31:09", updated_at: "2018-11-20 17:31:09">
irb(main):034:0> m.child_tables.new
=> #<ChildTable id: nil, padre_id: 5, created_at: nil, updated_at: nil>
irb(main):035:0> m.save
(0.3ms) BEGIN
MainTable Load (0.6ms) SELECT "main_table".* FROM "main_table" WHERE "main_table"."custom_pk" = $1 LIMIT $2 [["custom_pk", 5], ["LIMIT", 1]]
ChildTable Create (0.9ms) INSERT INTO "child_table" ("padre_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["padre_id", 5], ["created_at", "2018-11-20 17:31:21.737989"], ["updated_at", "2018-11-20 17:31:21.737989"]]
(0.6ms) COMMIT
=> true
irb(main):036:0>
Or build from the belongs_to association on the other side:
irb(main):027:0> c = ChildTable.new
=> #<ChildTable id: nil, padre_id: nil, created_at: nil, updated_at: nil>
irb(main):028:0> c.build_main_table
=> #<MainTable custom_pk: nil, created_at: nil, updated_at: nil>
irb(main):029:0> c.save!
(0.3ms) BEGIN
MainTable Create (0.8ms) INSERT INTO "main_table" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "custom_pk" [["created_at", "2018-11-20 17:24:29.344332"], ["updated_at", "2018-11-20 17:24:29.344332"]]
ChildTable Create (1.1ms) INSERT INTO "child_table" ("padre_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["padre_id", 3], ["created_at", "2018-11-20 17:24:29.346764"], ["updated_at", "2018-11-20 17:24:29.346764"]]
(0.7ms) COMMIT
=> true
A one-to-many association only uses one foreign key column - not two.
Typically in rails its setup as so:
class Parent
has_many :children
end
class Child
belongs_to :parent
end
The actual association is stored in the children.parent_id
column.
So lets say we have an non-conventional foreign key:
class Parent
has_many :children, foreign_key: 'padre_id'
end
class Child
belongs_to :parent, foreign_key: 'padre_id'
end
Easy enough. We just have to tell the associations on both side what the foreign key is. Note that the class_name
option is not needed as long as the class name can be deduced from the name of the association.
Custom primary keys or table names are not a problem either as ActiveRecord looks at the model class definitions when resolving associations.
class Parent
self.primary_key = :custom_pk
has_many :children, foreign_key: 'padre_id'
end
class Child
self.table_name = 'bar'
# this will correctly reference parents.foo
belongs_to :parent, foreign_key: 'padre_id'
end
There is also the universal issue that you must save a record before you can add children to it:
irb(main):024:0> m = MainTable.new
=> #<MainTable custom_pk: nil, created_at: nil, updated_at: nil>
irb(main):025:0> m.child_tables.new
=> #<ChildTable id: nil, padre_id: nil, created_at: nil, updated_at: nil>
irb(main):026:0> m.save!
(0.3ms) BEGIN
(0.4ms) ROLLBACK
ActiveRecord::RecordInvalid: Validation failed: Child tables is invalid
from (irb):26
Starting in Rails 5 belongs_to
associations are non-optional by default. So the child_tables
instance is not valid since the padre_id
is nil.
You either need to save the parent record first:
irb(main):033:0> m = MainTable.create!
(0.5ms) BEGIN
MainTable Create (0.7ms) INSERT INTO "main_table" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "custom_pk" [["created_at", "2018-11-20 17:31:09.545476"], ["updated_at", "2018-11-20 17:31:09.545476"]]
(0.8ms) COMMIT
=> #<MainTable custom_pk: 5, created_at: "2018-11-20 17:31:09", updated_at: "2018-11-20 17:31:09">
irb(main):034:0> m.child_tables.new
=> #<ChildTable id: nil, padre_id: 5, created_at: nil, updated_at: nil>
irb(main):035:0> m.save
(0.3ms) BEGIN
MainTable Load (0.6ms) SELECT "main_table".* FROM "main_table" WHERE "main_table"."custom_pk" = $1 LIMIT $2 [["custom_pk", 5], ["LIMIT", 1]]
ChildTable Create (0.9ms) INSERT INTO "child_table" ("padre_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["padre_id", 5], ["created_at", "2018-11-20 17:31:21.737989"], ["updated_at", "2018-11-20 17:31:21.737989"]]
(0.6ms) COMMIT
=> true
irb(main):036:0>
Or build from the belongs_to association on the other side:
irb(main):027:0> c = ChildTable.new
=> #<ChildTable id: nil, padre_id: nil, created_at: nil, updated_at: nil>
irb(main):028:0> c.build_main_table
=> #<MainTable custom_pk: nil, created_at: nil, updated_at: nil>
irb(main):029:0> c.save!
(0.3ms) BEGIN
MainTable Create (0.8ms) INSERT INTO "main_table" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "custom_pk" [["created_at", "2018-11-20 17:24:29.344332"], ["updated_at", "2018-11-20 17:24:29.344332"]]
ChildTable Create (1.1ms) INSERT INTO "child_table" ("padre_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["padre_id", 3], ["created_at", "2018-11-20 17:24:29.346764"], ["updated_at", "2018-11-20 17:24:29.346764"]]
(0.7ms) COMMIT
=> true
edited Nov 20 '18 at 17:32
answered Nov 20 '18 at 17:09
maxmax
46.2k1060104
46.2k1060104
but my foreign keys are named different on each table, I'm not sure what your saying here?
– user3437721
Nov 20 '18 at 17:40
Are you saying it can't be done with a foreign key called something different in each table?
– user3437721
Nov 20 '18 at 18:02
No, I'm saying that a one to many association is built with only one foreign key (which is on thebelongs_to
side). The foreign key references the primary key on the other table. Since its one column on one table it cannot have different names on each side. This is really basic relational database design stuff and applies everywhere. Not just rails.
– max
Nov 20 '18 at 18:57
Thanks I'll try it out and update tomorrow, appreciate the response.
– user3437721
Nov 20 '18 at 19:07
Could you maybe update the answer so it is clear which column I should reference as the foreign key, in your example you use 'padre_id' which I assume is the parent column? Remember it is not the primary key of the parent table which is stored in the child table.
– user3437721
Nov 20 '18 at 19:23
|
show 10 more comments
but my foreign keys are named different on each table, I'm not sure what your saying here?
– user3437721
Nov 20 '18 at 17:40
Are you saying it can't be done with a foreign key called something different in each table?
– user3437721
Nov 20 '18 at 18:02
No, I'm saying that a one to many association is built with only one foreign key (which is on thebelongs_to
side). The foreign key references the primary key on the other table. Since its one column on one table it cannot have different names on each side. This is really basic relational database design stuff and applies everywhere. Not just rails.
– max
Nov 20 '18 at 18:57
Thanks I'll try it out and update tomorrow, appreciate the response.
– user3437721
Nov 20 '18 at 19:07
Could you maybe update the answer so it is clear which column I should reference as the foreign key, in your example you use 'padre_id' which I assume is the parent column? Remember it is not the primary key of the parent table which is stored in the child table.
– user3437721
Nov 20 '18 at 19:23
but my foreign keys are named different on each table, I'm not sure what your saying here?
– user3437721
Nov 20 '18 at 17:40
but my foreign keys are named different on each table, I'm not sure what your saying here?
– user3437721
Nov 20 '18 at 17:40
Are you saying it can't be done with a foreign key called something different in each table?
– user3437721
Nov 20 '18 at 18:02
Are you saying it can't be done with a foreign key called something different in each table?
– user3437721
Nov 20 '18 at 18:02
No, I'm saying that a one to many association is built with only one foreign key (which is on the
belongs_to
side). The foreign key references the primary key on the other table. Since its one column on one table it cannot have different names on each side. This is really basic relational database design stuff and applies everywhere. Not just rails.– max
Nov 20 '18 at 18:57
No, I'm saying that a one to many association is built with only one foreign key (which is on the
belongs_to
side). The foreign key references the primary key on the other table. Since its one column on one table it cannot have different names on each side. This is really basic relational database design stuff and applies everywhere. Not just rails.– max
Nov 20 '18 at 18:57
Thanks I'll try it out and update tomorrow, appreciate the response.
– user3437721
Nov 20 '18 at 19:07
Thanks I'll try it out and update tomorrow, appreciate the response.
– user3437721
Nov 20 '18 at 19:07
Could you maybe update the answer so it is clear which column I should reference as the foreign key, in your example you use 'padre_id' which I assume is the parent column? Remember it is not the primary key of the parent table which is stored in the child table.
– user3437721
Nov 20 '18 at 19:23
Could you maybe update the answer so it is clear which column I should reference as the foreign key, in your example you use 'padre_id' which I assume is the parent column? Remember it is not the primary key of the parent table which is stored in the child table.
– user3437721
Nov 20 '18 at 19:23
|
show 10 more comments
Not sure if it's the issue you're facing, but at the moment you're initializing new MainTables and ChildTables, however you're not saving them.
This means they won't be assigned an ID, which in turn means when you try and assign a Child Table to the Main Table, it doesn't have an ID for the main table (or itself) to create a join record with.
Try saving the records before shovelling in child records
the idea is to have a cascading save so they all get saved at the same time..
– user3437721
Nov 20 '18 at 16:42
I'm not sure it's possible to assign a child record before the parent record is saved - it will try to create a join record which requires both the parent ID and child ID. I'm pretty sure the minimum is that the parent record is saved first
– Mark
Nov 20 '18 at 16:45
If you really want to go down that route I think you'll need to look at api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/…, specifically 'accepts_nested_attributes_for', which allows you to create a parent and child record in one transaction
– Mark
Nov 20 '18 at 16:46
In your case this would let you do: MainTable.create(title: 'bla', child_table_attributes: { title: 'bla2' } ) Which will create a Main Table, with a child table related to it
– Mark
Nov 20 '18 at 16:47
add a comment |
Not sure if it's the issue you're facing, but at the moment you're initializing new MainTables and ChildTables, however you're not saving them.
This means they won't be assigned an ID, which in turn means when you try and assign a Child Table to the Main Table, it doesn't have an ID for the main table (or itself) to create a join record with.
Try saving the records before shovelling in child records
the idea is to have a cascading save so they all get saved at the same time..
– user3437721
Nov 20 '18 at 16:42
I'm not sure it's possible to assign a child record before the parent record is saved - it will try to create a join record which requires both the parent ID and child ID. I'm pretty sure the minimum is that the parent record is saved first
– Mark
Nov 20 '18 at 16:45
If you really want to go down that route I think you'll need to look at api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/…, specifically 'accepts_nested_attributes_for', which allows you to create a parent and child record in one transaction
– Mark
Nov 20 '18 at 16:46
In your case this would let you do: MainTable.create(title: 'bla', child_table_attributes: { title: 'bla2' } ) Which will create a Main Table, with a child table related to it
– Mark
Nov 20 '18 at 16:47
add a comment |
Not sure if it's the issue you're facing, but at the moment you're initializing new MainTables and ChildTables, however you're not saving them.
This means they won't be assigned an ID, which in turn means when you try and assign a Child Table to the Main Table, it doesn't have an ID for the main table (or itself) to create a join record with.
Try saving the records before shovelling in child records
Not sure if it's the issue you're facing, but at the moment you're initializing new MainTables and ChildTables, however you're not saving them.
This means they won't be assigned an ID, which in turn means when you try and assign a Child Table to the Main Table, it doesn't have an ID for the main table (or itself) to create a join record with.
Try saving the records before shovelling in child records
answered Nov 20 '18 at 16:38
MarkMark
1,9961724
1,9961724
the idea is to have a cascading save so they all get saved at the same time..
– user3437721
Nov 20 '18 at 16:42
I'm not sure it's possible to assign a child record before the parent record is saved - it will try to create a join record which requires both the parent ID and child ID. I'm pretty sure the minimum is that the parent record is saved first
– Mark
Nov 20 '18 at 16:45
If you really want to go down that route I think you'll need to look at api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/…, specifically 'accepts_nested_attributes_for', which allows you to create a parent and child record in one transaction
– Mark
Nov 20 '18 at 16:46
In your case this would let you do: MainTable.create(title: 'bla', child_table_attributes: { title: 'bla2' } ) Which will create a Main Table, with a child table related to it
– Mark
Nov 20 '18 at 16:47
add a comment |
the idea is to have a cascading save so they all get saved at the same time..
– user3437721
Nov 20 '18 at 16:42
I'm not sure it's possible to assign a child record before the parent record is saved - it will try to create a join record which requires both the parent ID and child ID. I'm pretty sure the minimum is that the parent record is saved first
– Mark
Nov 20 '18 at 16:45
If you really want to go down that route I think you'll need to look at api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/…, specifically 'accepts_nested_attributes_for', which allows you to create a parent and child record in one transaction
– Mark
Nov 20 '18 at 16:46
In your case this would let you do: MainTable.create(title: 'bla', child_table_attributes: { title: 'bla2' } ) Which will create a Main Table, with a child table related to it
– Mark
Nov 20 '18 at 16:47
the idea is to have a cascading save so they all get saved at the same time..
– user3437721
Nov 20 '18 at 16:42
the idea is to have a cascading save so they all get saved at the same time..
– user3437721
Nov 20 '18 at 16:42
I'm not sure it's possible to assign a child record before the parent record is saved - it will try to create a join record which requires both the parent ID and child ID. I'm pretty sure the minimum is that the parent record is saved first
– Mark
Nov 20 '18 at 16:45
I'm not sure it's possible to assign a child record before the parent record is saved - it will try to create a join record which requires both the parent ID and child ID. I'm pretty sure the minimum is that the parent record is saved first
– Mark
Nov 20 '18 at 16:45
If you really want to go down that route I think you'll need to look at api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/…, specifically 'accepts_nested_attributes_for', which allows you to create a parent and child record in one transaction
– Mark
Nov 20 '18 at 16:46
If you really want to go down that route I think you'll need to look at api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/…, specifically 'accepts_nested_attributes_for', which allows you to create a parent and child record in one transaction
– Mark
Nov 20 '18 at 16:46
In your case this would let you do: MainTable.create(title: 'bla', child_table_attributes: { title: 'bla2' } ) Which will create a Main Table, with a child table related to it
– Mark
Nov 20 '18 at 16:47
In your case this would let you do: MainTable.create(title: 'bla', child_table_attributes: { title: 'bla2' } ) Which will create a Main Table, with a child table related to it
– Mark
Nov 20 '18 at 16:47
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53397174%2frails-correct-associations-for-pre-existing-models%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Can you add migrations files for those tables?
– Raoslaw Szamszur
Nov 20 '18 at 16:21
No the tables preexist and I cannot control anything about them :(
– user3437721
Nov 20 '18 at 16:22
1
You don't need a
foreign_key
in themany
side of ahas_many
relationship.– gd.silva
Nov 20 '18 at 16:27
@user3437721 Yep of course. But what I've meant was not migrations but
schema.rb
file. Sorry brain fart.– Raoslaw Szamszur
Nov 20 '18 at 16:35