new to Rails and using Ruby On Rails 4.0.3 with Ruby 2.2.0 with PostgreSQL, I'm attempting to have this database sctructure using GUID's as Id's.
My Goal: I want to have many phone numbers belong to many people as an example.
So i built a little test app which can be found here and basically followed the Association guide to create a Many-to-Many table.
So here is my model with comments added for clarity:
class Address < ActiveRecord::Base
belongs_to :contact
end
class Contact < ActiveRecord::Base
has_many :addresses
has_many :phoneNumbers
end
class PhoneNumber < ActiveRecord::Base
has_many :contacts
end
Here is the migration setup:
class CreateAddresses < ActiveRecord::Migration
def change
create_table :addresses, id: :uuid do |t|
#t.belongs_to :contacts
t.text :street1
t.text :city
t.timestamps
end
add_column :addresses, :contact_id, :uuid
end
end
class CreateContacts < ActiveRecord::Migration
def change
create_table :contacts, id: :uuid do |t|
t.text :firstName
t.text :lastName
t.timestamps
end
end
end
class CreatePhoneNumbers < ActiveRecord::Migration
def change
create_table :phone_numbers, id: :uuid do |t|
t.text :number
t.text :name
t.timestamps
end
end
end
# the MTM
class CreateContactsPhoneNumbers < ActiveRecord::Migration
def change
create_table :contacts_phone_numbers, id: :uuid
add_column :contacts_phone_numbers, :contact_id, :uuid
add_column :contacts_phone_numbers, :phone_number_id, :uuid
end
end
The SQL is output as such:
CREATE TABLE addresses (
id uuid DEFAULT uuid_generate_v4() NOT NULL,
street1 text,
city text,
created_at timestamp without time zone,
updated_at timestamp without time zone,
contact_id uuid
);
CREATE TABLE contacts (
id uuid DEFAULT uuid_generate_v4() NOT NULL,
"firstName" text,
"lastName" text,
created_at timestamp without time zone,
updated_at timestamp without time zone
);
CREATE TABLE phone_numbers (
id uuid DEFAULT uuid_generate_v4() NOT NULL,
number text,
name text,
created_at timestamp without time zone,
updated_at timestamp without time zone
);
CREATE TABLE contacts_phone_numbers (
id uuid DEFAULT uuid_generate_v4() NOT NULL,
contact_id uuid,
phone_number_id uuid
);
and here is a part of my example seed script
varC = Contact.create(firstName: "John", lastName: "Doe")
varA = Address.create(street1: "10 Somewhere Pl", city: "Ogurek", contact: varC)
Note here that you can succesfully get the contact when querying like so after jsut this migration
puts Address.first.contact.firstname
# John
however, once I chagne the seed script to this:
varC = Contact.create(firstName: "John", lastName: "Doe")
varA = Address.create(street1: "10 Somewhere Pl", city: "Ogurek", contact: varC)
varP = PhoneNumber.create(number: "2342342345", name: "home")
varC.phoneNumbers = varP
varC.save
varP.contacts = varC
varP.save
and I run rake db:migrate, I'll get the following output (note the error):
== EnablePostgresUuid: migrating =============================================
-- execute("create extension \"uuid-ossp\"")
-> 0.0018s
== EnablePostgresUuid: migrated (0.0024s) ====================================
== CreateAddresses: migrating ================================================
-- create_table(:addresses, {:id=>:uuid})
-> 0.0036s
-- add_column(:addresses, :contact_id, :uuid)
-> 0.0005s
== CreateAddresses: migrated (0.0053s) =======================================
== CreateContacts: migrating =================================================
-- create_table(:contacts, {:id=>:uuid})
-> 0.0024s
== CreateContacts: migrated (0.0031s) ========================================
== CreatePhoneNumbers: migrating =============================================
-- create_table(:phone_numbers, {:id=>:uuid})
-> 0.0023s
== CreatePhoneNumbers: migrated (0.0029s) ====================================
== CreateContactsPhoneNumbers: migrating =====================================
-- create_table(:contacts_phone_numbers, {:id=>:uuid})
-> 0.0014s
-- add_column(:contacts_phone_numbers, :contact_id, :uuid)
-> 0.0004s
-- add_column(:contacts_phone_numbers, :phone_number_id, :uuid)
-> 0.0003s
== CreateContactsPhoneNumbers: migrated (0.0038s) ============================
** Invoke db:seed (first_time)
** Execute db:seed
** Invoke db:abort_if_pending_migrations (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute db:abort_if_pending_migrations
rake aborted!
undefined method `each' for #<PhoneNumber:0xa532460>
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/activemodel-4.0.3/lib/active_model/attribute_methods.rb:439:in `method_missing'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/activerecord-4.0.3/lib/active_record/attribute_methods.rb:155:in `method_missing'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/activerecord-4.0.3/lib/active_record/associations/collection_association.rb:333:in `replace'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/activerecord-4.0.3/lib/active_record/associations/collection_association.rb:42:in `writer'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/activerecord-4.0.3/lib/active_record/associations/builder/association.rb:78:in `phoneNumbers='
/_apps/test_site/rails_test1/rtest1/db/seeds.rb:5:in `<top (required)>'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/activesupport-4.0.3/lib/active_support/dependencies.rb:223:in `load'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/activesupport-4.0.3/lib/active_support/dependencies.rb:223:in `block in load'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/activesupport-4.0.3/lib/active_support/dependencies.rb:214:in `load_dependency'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/activesupport-4.0.3/lib/active_support/dependencies.rb:223:in `load'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/railties-4.0.3/lib/rails/engine.rb:540:in `load_seed'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/activerecord-4.0.3/lib/active_record/tasks/database_tasks.rb:154:in `load_seed'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/activerecord-4.0.3/lib/active_record/railties/databases.rake:181:in `block (2 levels) in <top (required)>'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/task.rb:236:in `call'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/task.rb:236:in `block in execute'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/task.rb:231:in `each'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/task.rb:231:in `execute'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/task.rb:175:in `block in invoke_with_call_chain'
/home/vagrant/.rvm/rubies/ruby-head/lib/ruby/2.2.0/monitor.rb:211:in `mon_synchronize'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/task.rb:168:in `invoke_with_call_chain'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/task.rb:161:in `invoke'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/application.rb:149:in `invoke_task'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/application.rb:106:in `block (2 levels) in top_level'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/application.rb:106:in `each'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/application.rb:106:in `block in top_level'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/application.rb:115:in `run_with_threads'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/application.rb:100:in `top_level'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/application.rb:78:in `block in run'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/application.rb:165:in `standard_exception_handling'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/lib/rake/application.rb:75:in `run'
/home/vagrant/.rvm/gems/ruby-head@rails4/gems/rake-10.1.1/bin/rake:33:in `<top (required)>'
/home/vagrant/.rvm/gems/ruby-head@rails4/bin/rake:23:in `load'
/home/vagrant/.rvm/gems/ruby-head@rails4/bin/rake:23:in `<main>'
Tasks: TOP => db:seed
Thus my Question: How can I add a queryable model(s) that can have a MTM relationship using ActiveRecord and how do I save and query it?
Ok, so I finally figured it out, here it is in all of its glory:
First, I had to change to using a has_and_belongs_to_many association instead of a has_many :through like I was trying to (although I'm sure I could get the has_many :through figured out somehow as well), yet as I understand the guide, since I don't care about the join table, I can just use the has_and_belongs_to_many association. Yet, our problem is that we're using GUID's and not Integers.
So I missed something in the Associations guid, there is one line and its in parentheses:
(though you'll need to remember to create the joining table in the database)
So I changed my model to this:
class Contact < ActiveRecord::Base
has_many :addresses
has_and_belongs_to_many :phoneNumbers
end
class PhoneNumber < ActiveRecord::Base
has_and_belongs_to_many :contacts
end
and migration:
class CreateContacts < ActiveRecord::Migration
def change
create_table :contacts, id: :uuid do |t|
t.text :firstName
t.text :lastName
t.timestamps
end
end
end
class CreatePhoneNumbers < ActiveRecord::Migration
def change
create_table :phone_numbers, id: :uuid do |t|
t.text :number
t.text :name
t.timestamps
end
end
end
I then added this migration:
class CreateContactsPhoneNumbers < ActiveRecord::Migration
def change
create_table :contacts_phone_numbers, id: :uuid do |t|
t.uuid :contact_id
t.uuid :phone_number_id
end
end
end
all of the above churned out the following SQL:
CREATE TABLE contacts (
id uuid DEFAULT uuid_generate_v4() NOT NULL,
"firstName" text,
"lastName" text,
created_at timestamp without time zone,
updated_at timestamp without time zone
);
CREATE TABLE phone_numbers (
id uuid DEFAULT uuid_generate_v4() NOT NULL,
number text,
name text,
created_at timestamp without time zone,
updated_at timestamp without time zone
);
CREATE TABLE contacts_phone_numbers (
id uuid DEFAULT uuid_generate_v4() NOT NULL,
contact_id uuid,
phone_number_id uuid
);
and I can add models like such taken from my seeds.rb:
c = Contact.create(firstName: "John", lastName: "Smith")
p = PhoneNumber.create(number: "2342342345", name: "home", contacts: [c])
I put the final solution up on the BitBucket test project, please see Commit 762a546.
So, if anyone has any pointers to how I can fix this to be more in line with how the real Rubyists do it, I would love to here it in the comments. Thanks.