Whenever i try to add any of the user's role (say admin) and grade in user_grade table it is giving errors other roles must exist (teacher, student, guardian). I don't know how to fix this as this is bit complex relationship structure.
or can anyone suggest any better way of doing this?
class Admin < User
# can post many posts
has_many :posts, dependent: :destroy , foreign_key: 'user_id'
# admin post can have many tags
has_many :post_tags, dependent: :destroy, foreign_key: 'user_id'
has_many :tags , through: :post_tags
# can have many grades to see other grades posts
has_many :user_grades, foreign_key: 'user_id'
has_many :grades, through: :user_grades
end
class Teacher < User
# can post many posts
has_many :posts, dependent: :destroy , foreign_key: 'user_id'
# teacher post can have many tags
has_many :post_tags, dependent: :destroy, foreign_key: 'user_id'
has_many :tags , through: :post_tags
# can teach many grades
has_many :user_grades, dependent: :destroy, foreign_key: 'user_id'
has_many :grades , through: :user_grades
end
class Student < User
# a student can only in particular grade
has_one :user_grade , dependent: :destroy, foreign_key: 'user_id'
has_one :grade , through: :user_grade
end
class Guardian < User
# parents can have many children in different classes
has_many :user_grades, dependent: :destroy, foreign_key: 'user_id'
has_many :grades, through: :user_grades
end
class Grade < ApplicationRecord
has_many :user_grades, dependent: :destroy
has_many :admins, through: :user_grades
has_many :teachers, through: :user_grades
has_many :students , through: :user_grades
has_one :guardian, through: :user_grade
end
class UserGrade < ApplicationRecord
belongs_to :grade
belongs_to :admin
belongs_to :teacher
belongs_to :student
belongs_to :guardian
end
Database:
ActiveRecord::Schema.define(version: 20180410102940) do
create_table "grades", force: :cascade do |t|
t.integer "cls"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "post_tags", force: :cascade do |t|
t.integer "user_id"
t.integer "post_id"
t.integer "tag_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "posts", force: :cascade do |t|
t.integer "user_id", null: false
t.string "title"
t.text "content"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "roles", force: :cascade do |t|
t.string "name"
t.string "resource_type"
t.integer "resource_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
t.index ["name"], name: "index_roles_on_name"
t.index ["resource_type", "resource_id"], name: "index_roles_on_resource_type_and_resource_id"
end
create_table "tags", force: :cascade do |t|
t.string "tag_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "user_grades", force: :cascade do |t|
t.integer "user_id"
t.integer "grade_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token"
t.datetime "locked_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "type"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
end
create_table "users_roles", id: false, force: :cascade do |t|
t.integer "user_id"
t.integer "role_id"
t.index ["role_id"], name: "index_users_roles_on_role_id"
t.index ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id"
t.index ["user_id"], name: "index_users_roles_on_user_id"
end
end
I used rolify and cancancan gem here for user's role.
Try adding optional parameter to belongs_to relation
class UserGrade < ApplicationRecord
belongs_to :grade
belongs_to :admin, optional: true
belongs_to :teacher, optional: true
belongs_to :student, optional: true
belongs_to :guardian, optional: true
end
Rails 5 makes belongs_to association required by default. We can avoid validation on belongs_to relation by adding optional: true.
OR
You can turn off this behaviour for all models by keeping the value of belongs_to_required_by_default to false.
# file => config/application.rb
config.active_record.belongs_to_required_by_default = false
Related
I'm trying to call current_user.games and keep getting the error:
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column assignments.user_id does not exist)
LINE 1: ..." ON "games"."id" = "assignments"."game_id" WHERE "assignmen...
I believe my models are set up right to handle such a query but something tells me I have to run a migration that adds users as a reference?
Models:
class User < ApplicationRecord
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :invitable, invite_for: 2.weeks
has_many :assignments
has_many :games, through: :assignments
end
class Game < ApplicationRecord
has_one :assignment, dependent: :destroy
has_many :users, through: :assignment
after_save :create_assignment
def create_assignment
Assignment.create(game_id: id)
end
end
class Assignment < ApplicationRecord
belongs_to :game
belongs_to :user
# belongs_to :assignor, class_name: "User", optional: true
belongs_to :center_referee, class_name: 'User', optional: true
belongs_to :assistant_referee_1, class_name: 'User', optional: true
belongs_to :assistant_referee_2, class_name: 'User', optional: true
accepts_nested_attributes_for :game
end
Schema:
create_table "assignments", force: :cascade do |t|
t.integer "game_id"
t.integer "center_referee_id"
t.integer "assistant_referee_1_id"
t.integer "assistant_referee_2_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "games", force: :cascade do |t|
t.string "home_team"
t.string "away_team"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "first_name"
t.string "last_name"
t.string "role"
end
Short answer: you won't achieve what you are trying to using has_many. At least not the way your table is designed. You should try using scope instead, unfortunately.
On the other hand, there is a different way to design your system. See the new Assignment. Schema:
create_table "assignments", force: :cascade do |t|
t.integer "game_id"
t.integer "user_id"
end
Models:
class Assignment < ApplicationRecord
belongs_to :game
belongs_to :user
end
class User < ApplicationRecord
has_many :assignments
has_many :games, through: :assignments
end
class Game < ApplicationRecord
has_many :assignments
has_many :users, through: :assignments
end
You may wanna differ a center referee from an assistant referee.
I don't know exactly what your role column at the User model means, so I will assume your role column has nothing to do with center/assistant referee.
If this is the case, I'd add an unsigned tinyint role to Assignment and use it as enum. So my model would be:
class Assignment < ApplicationRecord
belongs_to :game
belongs_to :user
enum role: { center_referee: 0, assistant_referee_1: 1, assistant_referee_2: 2 }
# in case there must be only a kind of referee for a game:
validates(:role, uniqueness: { scope: [:game_id] })
end
And my Schema:
create_table "assignments", force: :cascade do |t|
t.integer "role", limit: 1, unsigned: true
t.integer "game_id"
t.integer "user_id"
end
TLDR: Creating groups that users can join with invite features returns a clear error:
PG::UndefinedColumn: ERROR: column user_groups.user_id does not exist
But the answer is not clear to me. UserGroups have an inviter and invitee. Therefore how can I set this up to ensure that the error is not returned?
Problem:
I wish to set up a User, who can join a Group. This relationship will be managed by UserGroup as a user can be a member of multiple groups. A Group will also have a User who is an owner, this is the creator and manager of the group.
A User can also be an Inviter, and may also invite a User who is an Invitee. In order to invite a friend to the group, the User in the case the Inviter will send the Invitee an invite. The Invitee then needs to accept in order to be a member of the Group.
Schema.rb
ActiveRecord::Schema.define(version: 2020_09_29_204316) do
enable_extension "plpgsql"
create_table "addresses", force: :cascade do |t|
t.string "address_type"
t.string "address_line_1"
t.string "address_line_2"
t.string "address_line_3"
t.string "city"
t.string "county"
t.string "postcode"
t.string "country"
t.bigint "user_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["user_id"], name: "index_addresses_on_user_id"
end
create_table "groups", force: :cascade do |t|
t.string "group_name"
t.bigint "owner_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["owner_id"], name: "index_groups_on_owner_id"
end
create_table "offers", force: :cascade do |t|
t.string "partner"
t.string "offer_copy"
t.string "offer_url"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "requests", force: :cascade do |t|
t.bigint "requester_id"
t.bigint "requestee_id"
t.integer "accepted", default: 0
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["requestee_id"], name: "index_requests_on_requestee_id"
t.index ["requester_id"], name: "index_requests_on_requester_id"
end
create_table "user_groups", force: :cascade do |t|
t.bigint "group_id", null: false
t.bigint "invitee_id"
t.bigint "inviter_id"
t.integer "accepted", default: 0
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["group_id"], name: "index_user_groups_on_group_id"
t.index ["invitee_id"], name: "index_user_groups_on_invitee_id"
t.index ["inviter_id"], name: "index_user_groups_on_inviter_id"
end
create_table "users", force: :cascade do |t|
t.string "first_name", default: "", null: false
t.string "last_name", default: "", null: false
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.date "birthday", default: "2020-10-22", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "invitation_token"
t.datetime "invitation_created_at"
t.datetime "invitation_sent_at"
t.datetime "invitation_accepted_at"
t.integer "invitation_limit"
t.string "invited_by_type"
t.bigint "invited_by_id"
t.integer "invitations_count", default: 0
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["invitation_token"], name: "index_users_on_invitation_token", unique: true
t.index ["invitations_count"], name: "index_users_on_invitations_count"
t.index ["invited_by_id"], name: "index_users_on_invited_by_id"
t.index ["invited_by_type", "invited_by_id"], name: "index_users_on_invited_by_type_and_invited_by_id"
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "addresses", "users"
add_foreign_key "groups", "users", column: "owner_id"
add_foreign_key "requests", "users", column: "requestee_id"
add_foreign_key "requests", "users", column: "requester_id"
add_foreign_key "user_groups", "groups"
add_foreign_key "user_groups", "users", column: "invitee_id"
add_foreign_key "user_groups", "users", column: "inviter_id"
end
Groups Migration
class CreateGroups < ActiveRecord::Migration[6.0]
def change
create_table :groups do |t|
t.string :group_name
t.references :owner, foreign_key: { to_table: :users }
t.timestamps
end
end
end
UserGroups Migration
class CreateUserGroups < ActiveRecord::Migration[6.0]
def change
create_table :user_groups do |t|
t.references :group, null: false, foreign_key: true, null: false
t.references :invitee, foreign_key: { to_table: :users }
t.references :inviter, foreign_key: { to_table: :users }
t.integer :accepted, default: 0
t.timestamps
end
end
end
User.rb (Model)
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :trackable
validates :first_name, :last_name, :birthday, presence: true
has_many :accepted_sent_requests, -> { where accepted: 1 }, foreign_key: :requester_id, class_name: 'Request'
has_many :friends, through: :accepted_sent_requests, source: :requestee
has_many :sent_requests, foreign_key: :requester_id, class_name: 'Request', dependent: :destroy
has_many :received_requests, foreign_key: :requestee_id, class_name: 'Request', dependent: :destroy
has_many :requestees, through: :sent_requests, dependent: :destroy
has_many :requesters, through: :received_requests, dependent: :destroy
has_many :user_groups
has_many :accepted_sent_invites, -> { where accepted: 1 }, foreign_key: :inviter_id, class_name: 'UserGroup'
has_many :friend_groups, through: :accepted_sent_invites, source: :invitee
has_many :sent_invites, foreign_key: :inviter_id, class_name: 'UserGroup', dependent: :destroy
has_many :received_invites, foreign_key: :invitee_id, class_name: 'UserGroup', dependent: :destroy
has_many :invitees, through: :sent_invites, dependent: :destroy
has_many :inviters, through: :received_invites, dependent: :destroy
has_many :groups, through: :user_groups
has_many :groups_owned, foreign_key: :owner_id, class_name: 'Group'
has_many :addresses, dependent: :destroy
searchkick match: :word, searchable: [:email]
after_create :send_welcome_email
private
def send_welcome_email
unless invitation_token?
UserMailer.with(user: self).welcome.deliver_now
end
end
def search_data
{
email: email
}
end
end
UserGroup.rb (Model)
class UserGroup < ApplicationRecord
belongs_to :inviter, class_name: 'User', optional: true
belongs_to :invitee, class_name: 'User', optional: true
belongs_to :group, optional: true
def accept
self.update_attributes(accepted: 1)
Request.create!(inviter_id: self.invitee_id,
invitee_id: self.inviter_id,
accepted: 1)
end
end
Group.rb (Model)
class Group < ApplicationRecord
has_many :user_groups
has_many :users, through: :user_groups
belongs_to :owner, class_name: 'User'
end
The above error was solved by editing the User model to include the association for the user_groups:
has_many :user_groups, foreign_key: :inviter_id, class_name: 'UserGroup'
has_many :user_groups, foreign_key: :invitee_id, class_name: 'UserGroup'
has_many :groups, through: :user_groups, foreign_key: :inviter_id, class_name: 'UserGroup'
has_many :groups, through: :user_groups, foreign_key: :invitee_id, class_name: 'UserGroup'
To quote #james00794:
The error is quite clear. It is saying that there is no user_id column
on the user_groups table, which is true according to your schema. Your
has_many :groups, through: :user_groups is likely what is causing the
error when you call user.groups, since the has_many :user_groups
relation does not specify a foreign key. Rails then implicitly assumes
that the column is called user_id. If you specify a foreign_key on the
has_many :user_groups relation, this should resolve your error. Though
you may need separate through relations for the inviters and invitees.
I want to set up a relationship where users can start events and then also join them as members. I figured this uses a many to many relationship, but it also has a simple belongs_to relationship from Event to User.
class User < ActiveRecord::Base
has_secure_password
has_many :events # => Owning
has_many :events, through: :members # => Joining
end
class Event < ActiveRecord::Base
belongs_to :user # => event owner
has_many :users, through: :members # => joining users
end
class Member < ActiveRecord::Base
belongs_to :user
belongs_to :event
end
But I'm having trouble getting queries like Event.first.members to work... I can't figure out what's preventing me from using these methods. Am I thinking of these relationships the wrong way? My Schema.rb:
ActiveRecord::Schema.define(version: 20161205220807) do
create_table "events", force: :cascade do |t|
t.string "title"
t.string "location"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "events", ["user_id"], name: "index_events_on_user_id"
create_table "members", force: :cascade do |t|
t.integer "user_id"
t.integer "event_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "members", ["event_id"], name: "index_members_on_event_id"
add_index "members", ["user_id"], name: "index_members_on_user_id"
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.integer "age"
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
Updated to fix my mistake
class User < ActiveRecord::Base
has_secure_password
has_many :members
has_many :events, through: :members
end
class Event < ActiveRecord::Base
#belongs_to :user # => event owner
has_many :members
has_many :users, through: :members
class Member < ActiveRecord::Base
belongs_to :user
belongs_to :event
end
If this is giving you a stack level too deep error im guessing the issue is elsewhere. Your logs might have more info.
I would like to specify a custom foreign key in the join table for a has_many through association in my rails 4 app. Please see below, what I have so far...
When I execute user.team_memberships.create in the rails console, I receive this error:
ActiveRecord::UnknownAttributeError: unknown attribute 'user_id' for TeamMembership.
users.rb
class User < ActiveRecord::Base
has_many :team_memberships
has_many :teams, through: :team_memberships
teams.rb
class Team < ActiveRecord::Base
has_many :team_memberships
has_many :members, through: :team_memberships
team_membership.rb
class TeamMembership < ActiveRecord::Base
belongs_to :member, class_name: 'User', foreign_key: 'member_id'
belongs_to :team
schema.rb
create_table "team_memberships", force: :cascade do |t|
t.integer "member_id"
t.integer "team_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "team_memberships", ["member_id"], name: "index_team_memberships_on_member_id", using: :btree
add_index "team_memberships", ["team_id"], name: "index_team_memberships_on_team_id", using: :btree
create_table "teams", force: :cascade do |t|
t.string "name"
t.integer "team_snap_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
t.integer "teamsnap_uid"
t.string "teamsnap_access_token"
end
You'll need to add foreign_key: 'member_id' to the has_many declaration on User. You can also remove the foreign_key option on the belongs_to in TeamMembership - Rails will infer this automatically.
user.rb
class User < ActiveRecord::Base
has_many :team_memberships, foreign_key: 'member_id'
has_many :teams, through: :team_memberships
team_membership.rb
class TeamMembership < ActiveRecord::Base
belongs_to :member, class_name: 'User'
belongs_to :team
You need to specify foreign key in both models
class User < ActiveRecord::Base
has_many :team_memberships, foreign_key: 'member_id'
has_many :teams, through: :team_memberships
end
and
class TeamMembership < ActiveRecord::Base
belongs_to :member, class_name: 'User', foreign_key: 'member_id'
belongs_to :team
end
if you do not add foreign key in user model then you will not be able to create team membership for a user similarly if you will not specify foreign key in team membership then you will not able to get user of a team membership.
I cannot make good associations when the foreign key has not the default name.
I would like to access to all subjects which belongs_to one participant (foreign key = questioner_id).
It raise me an error
p = Participant.first
p.subjects
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: subject_participants.participant_id: SELECT "participants".* FROM "participants" INNER JOIN "subject_participants" ON "participants"."id" = "subject_participants"."subject_id" WHERE "subject_participants"."participant_id" = ?
Why does it looks for subject_participants.participant_id ? It's just a has_many association, I don't think that subject_participants table should be called in this case...
interested_id and questioner_id are from the same model but not the same role. One has to go through subject_participants table and the other has to go directly in subjects table
My models :
participant.rb
class Participant < ActiveRecord::Base
has_many :subjects, foreign_key: "questioner_id", class_name: "Participant" #questioner
has_many :subjects, through: :subject_participants, foreign_key: "interested", class_name: "Participant" #interested
has_many :subject_participants
has_many :conference_participants
has_many :conferences, through: :conference_participants
end
subject.rb
class Subject < ActiveRecord::Base
validates_presence_of :title, :questioner, :conference, :description
has_many :subject_participants
has_many :interested, through: :subject_participants, :class_name => "Participant" #interested
belongs_to :questioner, :class_name => "Participant"
belongs_to :conference
end
subject_participant.rb
class SubjectParticipant < ActiveRecord::Base
validates_presence_of :interested_id, :subject_id
belongs_to :interested, :class_name => "Participant"
belongs_to :subject
end
schema.rb
create_table "participants", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
end
add_index "participants", ["email"], name: "index_participants_on_email", unique: true
add_index "participants", ["reset_password_token"], name: "index_participants_on_reset_password_token", unique: true
create_table "subject_participants", force: :cascade do |t|
t.integer "interested_id"
t.integer "subject_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "subjects", force: :cascade do |t|
t.string "title", null: false
t.text "description"
t.integer "questioner_id", null: false
t.integer "conference_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Change your participant.rb to
class Participant < ActiveRecord::Base
.....
has_many :subject_participants,class_name: "SubjectParticipant", foreign_key: "interested_id"
end
You make me find the solution, thanks for help :
participant.rb
class Participant < ActiveRecord::Base
has_many :subject_participants, class_name: "SubjectParticipant", foreign_key: "interested_id"
has_many :subjects_interested_in, through: :subject_participants, :source => "subject"
has_many :subjects, foreign_key: "questioner_id"
has_many :conference_participants
has_many :conferences, through: :conference_participants
end
subject.rb
class Subject < ActiveRecord::Base
validates_presence_of :title, :questioner, :conference, :description
has_many :subject_participants
has_many :interested, through: :subject_participants #interested
belongs_to :questioner, class_name: "Participant"
belongs_to :conference
end