Whats wrong with my has_and_belongs_to_many relationships? - mysql

FIXED BY:
changing model names to match rails naming conventions
I'm getting the following error when I try to join a skill to a user:
irb(main):006:0> user.skills << skill
NameError: uninitialized constant Users::Skill
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/activerecord-3.1.3/lib/active_r
ecord/base.rb:1341:in `compute_type'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/activerecord-3.1.3/lib/active_r
ecord/reflection.rb:173:in `klass'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/activerecord-3.1.3/lib/active_r
ecord/associations/collection_association.rb:146:in `transaction'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/activerecord-3.1.3/lib/active_r
ecord/associations/collection_association.rb:124:in `concat'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/activerecord-3.1.3/lib/active_r
ecord/associations/collection_proxy.rb:118:in `<<'
from (irb):6
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/railties-3.1.3/lib/rails/comman
ds/console.rb:45:in `start'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/railties-3.1.3/lib/rails/comman
ds/console.rb:8:in `start'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/railties-3.1.3/lib/rails/comman
ds.rb:40:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
irb(main):007:0>
Here is my users.rb file:
class Users < ActiveRecord::Base
has_and_belongs_to_many :skills
end
Here is my skills.rb file:
class Skills < ActiveRecord::Base
has_and_belongs_to_many :users
end
And here is my schema.rb file:
ActiveRecord::Schema.define(:version => 20111214152402) do
create_table "skills", :force => true do |t|
t.integer "user_id"
t.string "description", :null => false
t.string "skill_name", :null => false
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "skills", ["user_id"], :name => "index_skills_on_user_id"
create_table "users", :force => true do |t|
t.integer "skill_id"
t.string "first_name", :limit => 25
t.string "last_name", :limit => 50
t.string "email", :default => ""
t.string "password", :limit => 40
t.string "location", :limit => 100
t.string "status"
t.text "bio"
t.string "question1"
t.string "question2"
t.string "availability"
t.string "image"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "users", ["skill_id"], :name => "index_users_on_skill_id"
create_table "users_skills", :id => false, :force => true do |t|
t.integer "user_id"
t.integer "page_id"
end
add_index "users_skills", ["user_id", "page_id"], :name => "index_users_skills_on_user_id_and_page_id"
end
If you can help that'd be awesome! If you need to see other files let me know.

user.skills is an array so in order to add a skill, you need to do
user.skills << skill
Additionally, on your schema, I notice that your user_skils table contains wrong foreign key id. I believe they should be user_id and skill_id.
Update
Your users table also looks wrong. You do not need a skill_id in your users table. Similarly, you do not need user_id in your skills table.
This is a small diagram of how the tables and attributes should look like
users skills_users skills
id ----> user_id
skill_id ---> id
Update 2
Your model name looks wrong. It is a convention in RoR to use singular model name. Users should be User. Similarly, for skills.

I think, the problem is that the skill is an instance of Skill, while user.skills expects a collection.
users.skills = [skill] should work, but there are different ways of creating associations.
upd. Dave Newton is right. To add records to an association you should use <<

Related

Active Record DB Modeling - Ruby on Rails

Ok guys, this might be a little bit confusing so stick with me. Let me start by introducing my tables. Basically I created four tables upon migration.
Students
Teachers
Subjects
Admin User
Here are my migration files:
Students:
def up
create_table :students, :id => false do |t|
t.integer "student_id",:primary_key => true
t.string "first_name", :limit => 25
t.string "last_name", :limit => 50
t.string "email", :default => ' ', :null => false
t.string "birthday"
t.string "subjects"
t.string "teachers"
t.string "username", :limit => 25
t.string "password_digest"
t.timestamps
end
Teachers:
def up
create_table :teachers, :id => false do |t|
t.integer "teacher_id", :primary_key => true
t.string "first_name"
t.string "last_name"
t.string "email", :default => ' ', :null => false
t.string "birthday"
t.string "subjects"
t.string "username", :limit => 25
t.string "password_digest"
t.timestamps
end
Subjects:
def up
create_table :subjects, :id => false do |t|
t.integer "subject_id", :primary_key => true
t.string "subject_name"
t.timestamps
end
end
Admin Users:
def up
create_table :admin_users, :id => false do |t|
t.integer "admin_user_id", :primary_key => true
t.string "username", :limit => 25
t.string "password_digest"
t.timestamps
end
end
So now let me get through the explanation. Basically:
one student can have many teachers
one teacher can have many students
one student can have many subjects
one teacher can teach many subjects
the admin can access all of this (create, edit, delete)
I created this fields and set them up on my models like this:
class Student < ApplicationRecord
has_and_belongs_to_many :subjects
has_and_belongs_to_many :teachers
has_many :admin_users
has_secure_password
self.primary_key = :student_id
end
class Teacher < ApplicationRecord
has_and_belongs_to_many :subjects
has_and_belongs_to_many :students
has_many :admin_users
has_secure_password
end
class Subject < ApplicationRecord
has_and_belongs_to_many :students
has_and_belongs_to_many :teachers
has_many :admin_users
# scope :search, lambda { |query| where(["name LIKE ?", "%#{query}%"])}
end
class AdminUser < ApplicationRecord
has_secure_password
scope :newest_first, lambda { order("created_at ASC") }
scope :oldest_first, lambda { order("created_at DESC") }
# scope :search, lambda { |query| where(["name LIKE ?", "%#{query}%"])}
end
Now I manually inserted data on mysql data record and then tried to pull up the records for student's subjects and teachers form fields.
How can I manage my database effectively esp teachers, students, and subjects??? Is there anything I need to do first to correlate the tables I have? Please help. Sorry for the long post. I am a newbie here. Appreciate your explanation (layman's term) and answers.
Check my models. Do I need to create a separate table to correlate teachers, students, and subjects?
Side Note: When I pull up my students and teachers field it gives me an error ActiveRecord_Associations_CollectionProxy:0x007f9c504361d0 ERROR.
I think this relation should work in your case:
Students:
def up
create_table :students, :id => false do |t|
t.integer "student_id",:primary_key => true
t.string "first_name", :limit => 25
t.string "last_name", :limit => 50
t.string "email", :default => ' ', :null => false
t.string "birthday"
t.string "subjects"
t.string "username", :limit => 25
t.string "password_digest"
t.timestamps
end
end
Ref: Generating auto increment with sequence 1001
Teachers:
def up
create_table :teachers, :id => false do |t|
t.integer "teacher_id", :primary_key => true
t.string "first_name"
t.string "last_name"
t.string "email", :default => ' ', :null => false
t.string "birthday"
t.string "subjects"
t.string "username", :limit => 25
t.string "password_digest"
t.timestamps
end
end
Subjects:
def up
create_table :subjects, :id => false do |t|
t.integer "subject_id", :primary_key => true
t.string "subject_name"
t.timestamps
end
end
Enrolled Subjects:
def up
create_table :enrolled_subjects, :id => false do |t|
t.integer "subject_id"
t.integer "teacher_id"
t.integer "student_id"
end
end
Model:
class Student < ApplicationRecord
has_many :enrolled_subjects
has_many :subjects, through: :enrolled_subjects
has_many :teachers, through: :enrolled_subjects
def teacher_names
self.teachers.map(&:first_name).join(", ")
end
end
class Teacher < ApplicationRecord
has_many :enrolled_subjects
has_many :subjects, through: :enrolled_subjects
has_many :students, through: :enrolled_subjects
end
class Subject < ApplicationRecord
has_many :enrolled_subjects
has_many :students, through: :enrolled_subjects
has_many :teachers, through: :enrolled_subjects
end
class EnrolledSubject < ActiveRecord::Base
belongs_to :student, foreign_key: :student_id
belongs_to :subject, foreign_key: :subject_id
belongs_to :teacher, foreign_key: :teacher_id
end
Example Repository

How to automatically increment a custom id with starting value in Ruby on Rails 5?

I am working on a migration and I remove the default 'id' for each table. I created a special field instead 'student_id' and I want to make it auto increment starting with 1001.
Here is my code:
class CreateStudents < ActiveRecord::Migration[5.0]
def up
create_table :students, :id => false do |t|
t.integer "student_id"
t.string "first_name", :limit => 25
t.string "last_name", :limit => 50
t.string "email", :default => ' ', :null => false
t.string "birthday"
t.string "subjects"
t.string "teachers"
t.string "username", :limit => 25
t.string "password_digest", :limit => 40
t.timestamps
end
execute "CREATE SEQUENCE students_student_id_seq OWNED BY students.student_id INCREMENT BY 1 START WITH 1001"
end
def down
drop_table :students
execute "DELETE SEQUENCE students_student_id_seq"
end
end
I got the ff error:
Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SEQUENCE students_student_id_seq OWNED BY students.student_id INCREMENT BY 1 STA' at line 1
How to auto a custom id increment with starting value in Ruby on Rails 5?
execute "CREATE SEQUENCE students_student_id_seq OWNED BY students.student_id INCREMENT BY 1 START WITH 1001"
The above is Postgresql syntax and your database seems to be MySQL.
Anyway, you can achieve what you want by setting student_id as a primary key and then updating the increment starting value.
def change
create_table :students, :id => false do |t|
t.integer "student_id", primary_key: true
t.string "first_name", :limit => 25
t.string "last_name", :limit => 50
t.string "email", :default => ' ', :null => false
t.string "birthday"
t.string "subjects"
t.string "teachers"
t.string "username", :limit => 25
t.string "password_digest", :limit => 40
t.timestamps
end
reversible do |dir|
dir.up { execute "ALTER TABLE students AUTO_INCREMENT = 1000" }
end
end

ActiveRecord::StatementInvalid: Mysql2::Error: Cannot delete or update a parent row - Rails 4.2.6

Error that need to be solved:
ActiveRecord::StatementInvalid: Mysql2::Error: Cannot delete or update
a parent row: a foreign key constraint fails
(slap_chat_development.chatrooms, CONSTRAINT fk_rails_496733c195
FOREIGN KEY (group_id) REFERENCES groups (id)): DELETE FROM
groups WHERE groups.id = 1
QUESTION IS:
can someone guide me where from should this error be fixed.
As I see problem persists in relation between groups and chatrooms tables.
Further details:
schema.rb
ActiveRecord::Schema.define(version: 20160606100750) do
create_table "chatrooms", force: :cascade do |t|
t.integer "group_id", limit: 4
t.string "name", limit: 255
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "chatrooms", ["group_id"], name: "index_chatrooms_on_group_id", using: :btree
create_table "chatrooms_users", force: :cascade do |t|
t.integer "chatroom_id", limit: 4
t.integer "user_id", limit: 4
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "chatrooms_users", ["chatroom_id"], name: "index_chatrooms_users_on_chatroom_id", using: :btree
add_index "chatrooms_users", ["user_id"], name: "index_chatrooms_users_on_user_id", using: :btree
create_table "groups", force: :cascade do |t|
t.string "name", limit: 255
t.integer "user_id", limit: 4
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "groups", ["user_id"], name: "index_groups_on_user_id", using: :btree
create_table "groups_users", force: :cascade do |t|
t.integer "group_id", limit: 4
t.integer "user_id", limit: 4
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "groups_users", ["group_id"], name: "index_groups_users_on_group_id", using: :btree
add_index "groups_users", ["user_id"], name: "index_groups_users_on_user_id", using: :btree
create_table "posts", force: :cascade do |t|
t.integer "user_id", limit: 4
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "content", limit: 255
end
add_index "posts", ["user_id"], name: "index_posts_on_user_id", using: :btree
create_table "users", force: :cascade do |t|
t.string "email", limit: 255, default: "", null: false
t.string "encrypted_password", limit: 255, default: "", null: false
t.string "reset_password_token", limit: 255
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", limit: 4, default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip", limit: 255
t.string "last_sign_in_ip", limit: 255
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "first_name", limit: 255
t.string "nick_name", limit: 255
t.string "last_name", limit: 255
end
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_foreign_key "chatrooms", "groups"
add_foreign_key "chatrooms_users", "chatrooms"
add_foreign_key "chatrooms_users", "users"
add_foreign_key "groups", "users"
add_foreign_key "groups_users", "groups"
add_foreign_key "groups_users", "users"
add_foreign_key "posts", "users"
end
Group model:
class Group < ActiveRecord::Base
has_many :chatrooms
belongs_to :group_admin, class_name: "User", foreign_key: :user_id
has_and_belongs_to_many :members, class_name: "User", association_foreign_key: :user_id
validates :user_id, presence: true
validates :name, presence: true, length: { minimum: 3 }
before_create { self.name = self.name.capitalize }
after_create :assign_creator
around_destroy :destroy_all_associates
def general_room
self.chatrooms.where("name = ?", "general").take
end
def except_general_room
self.chatrooms.select { |room| room != self.general_room }
end
def assign_creator
member = self.group_admin
self.members << member
self.general_room.members << member
end
def destroy_all_associates
rooms = self.chatrooms
yield
rooms.each do |room|
room.destroy
end
end
end
Chatroom model:
class Chatroom < ActiveRecord::Base
belongs_to :group
has_and_belongs_to_many :members, class_name: "User", association_foreign_key: :user_id
validates :name, presence: true, length: { minimum: 3 }
before_save { self.name = self.name.downcase }
around_create :ensure_group_presence
around_destroy :destroy_all_associates
def feed
ids = Array.new
self.members.each do |member|
ids += member.post_ids
end
Post.where("id IN (?)", ids)
end
def ensure_group_presence
yield
self.group_id.present?
end
def destroy_all_associates
feed = self.feed
yield
feed.destroy_all
end
end
You can try using this in your group model.
class Group < ActiveRecord::Base
has_many :chatrooms , dependent: :destroy
end
Now when you execute, Group.last.destroy, it should delete the dependent associated chatrooms before and no hanging data would be left
It seems like you are trying to delete a group that has one or many chatrooms.
But because you added a foreign key constraint (add_foreign_key "chatrooms", "groups"), it is not allowed to delete a group when there is still a chatroom assigned.
To solve this issue you have to destroy all associated chatrooms before destroying the group itself.
I think it depends on whether you need to delete the associated table.
If you need to delete the associated table, you should
has_many :chatrooms , dependent: :destroy
However, if you do not want to delete the associated table, you should
has_many :chatrooms , dependent: :nullify
There are detailed descriptions in the rails API
:dependent
Controls what happens to the associated objects when their owner is
destroyed. Note that these are implemented as callbacks, and Rails
executes callbacks in order. Therefore, other similar callbacks may
affect the :dependent behavior, and the :dependent behavior may
affect other callbacks.
:destroy causes all the associated objects to also be destroyed.
:delete_all causes all the associated objects to be deleted directly
from the database (so callbacks will not be executed).
:nullify causes the foreign keys to be set to NULL. Callbacks are not executed.
:restrict_with_exception causes an exception to be raised if there are any associated records.
:restrict_with_error causes an error to be added to the owner if there are any associated objects.
If using with the :through option, the association on the join model must be a belongs_to, and the records which get deleted are the join records, rather than the associated records.
If using dependent: :destroy on a scoped association, only the scoped objects are destroyed. For example, if a Post model defines has_many
:comments, -> { where published: true }, dependent: :destroy and destroy is called on a post, only published comments are destroyed.
This means that any unpublished comments in the database would still contain a foreign key pointing to the now deleted post.
RailsApi
Error: Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails
Solution:
I think you have used .delete or delete_all,
Instead of .delete or delete_all use .destroy
.destroy will delete all associated values with that model
Now It will work !

Rails: Query across tables without JOIN

For some reason cannot get this to work. I've been going through Code School's lessons and watched this video:
http://railsforzombies.org/levels/2
at the 9:13 mark he queries across models.. I am just trying to do the same..
This is what I've done.. I have two models.. User and Product
my relationships:
class Product < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :products
end
and then my schema
create_table "products", force: true do |t|
t.integer "upc"
t.string "description"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
then finally, the actual query..
s = Product.find(1)
Product Load (1.3ms) SELECT "products".* FROM "products" WHERE "products"."id" = ? LIMIT 1 [["id", 1]]
=> #<Product id: 1, upc: 1, description: "Desc. 1", user_id: 1, created_at: "2015-04-16 16:14:48", updated_at: "2015-04-16 16:14:48">
and then I just want to return the name of the user who owns the first product.. like so
s.user.name
this gives me this error:
NoMethodError: undefined method `user' for #<Product:0x00000005593850>
but it is not working, What can be the issue here?

Mysql join in rails

I have two tables orientations and registrations that I would like to perform a join query on. Here is the schema for each of the tables...
create_table "orientations", :force => true do |t|
t.date "class_date"
t.text "class_time"
t.integer "seats"
t.boolean "active", :default => true
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "registrations", :force => true do |t|
t.integer "orientation_id"
t.string "first_name"
t.string "last_name"
t.string "email"
t.string "student_id"
t.string "phone"
t.string "registration_cancellation_token"
t.datetime "registration_cancelled_at"
t.boolean "checked_in", :default => false
t.boolean "cancelled", :default => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.text "program"
end
What I am looking for is all registrations for all orientations between two dates. I came up with this...
Registration.where(Orientation.where created_at: => #start_date..#end_date)
Of course that syntax is bogus, but hopfully it will help get across what I am looking for.
Use this to get all registrations for all orientations between two dates:
Registration.joins(:orientation).where(orientations: {:class_date => #start_date..#end_date})
joins performs an INNER JOIN which filters the rows that don't have association.
But, if you wish to keep the rows that don't have associations then go for includes which performs a LEFT OUTER JOIN
Registration.includes(:orientation).where(orientations: {:class_date => #start_date..#end_date})