I want to make it so the following will return all received messages:
current_user.messages.received
And the following will make it so I will see all sent messages:
current_user.messages.sent
The problem I am having is that I am not sure how to specify that 2 columns in a single table both represent the foreign key for a user. How do I associate both sender_id and recipient_id to a user?
db/schema.rb
create_table "messages", force: true do |t|
t.string "notification", null: false
t.integer "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "recipient_id", null: false
end
app/models/message.rb
class Messages < ActiveRecord::Base
has_and_belongs_to_many :user
end
app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :messages, dependent: :destroy
end
In this case, each message has exactly one sender and possible many receivers.
So your models could be:
class Messages < ActiveRecord::Base
has_and_belongs_to_many :receivers, class_name: 'User'
belongs_to :sender, class_name: 'User'
end
and
class User < ActiveRecord::Base
has_many :messages, dependent: :destroy
has_and_belongs_to_many :received_messages, class_name: 'Message'
end
You need an join table for the "sent messages":
class CreateJoin < ActiveRecord::Migration
def change
create_join_table :users, :messages
end
end
Now you can access your sent and received messages:
current_user.messages # sent messages
current_userreceived_messages
Related
Here's a has_many :through relationship:
# app/models/user.rb
class User < ApplicationRecord
enum role: {
intern: 0,
associate: 1,
partner: 2,
admin: 3
}
has_many :assignments, dependent: :destroy
has_many :projects, through: :assignments
end
# app/models/project.rb
class Assignment < ApplicationRecord
belongs_to :project
belongs_to :user
end
# app/models/project.rb
class Project < ApplicationRecord
has_many :assignments, dependent: :destroy
has_many :assignees, through: :assignments, source: :user
end
In other words, users can have many projects; projects can have many users ('assignees' in this case).
These assignments can be qualified by their nature: each assignee is either a project owner, or not (that would be the is_owner boolean).
create_table "assignments", force: :cascade do |t|
t.boolean "is_owner"
t.integer "project_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["project_id"], name: "index_assignments_on_project_id"
t.index ["user_id"], name: "index_assignments_on_user_id"
end
How can I enforce that there's always 1) at lease one owner and 2) never more than one owner?
I have tried numerous variants, but my money would be on a validation rule on the project model. Any suggestions as to how to achieve this?
I have a base class Place and multiple sub-classes using STI conventions. I have a separate model Post, which belongs_to one of the sub-classes of Place:
class Place < ApplicationRecord
end
class SubPlace < Place
has_many :posts, class_name: "SubPlace", foreign_key: "sub_place_id"
end
class Post < ApplicationRecord
belongs_to :sub_place, class_name: "SubPlace", foreign_key: "sub_place_id"
end
It is possible to save a new Post record using Rails console, but I get the following error when trying to find Posts for a specific SubPlace:
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column places.sub_place_id does not exist)
Is there a way to make this work, or must my associations relate to the base class only?
Added Schema:
create_table "posts", force: :cascade do |t|
t.string "title"
t.bigint "sub_place_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["sub_place_id"], name: "index_posts_on_sub_place_id"
end
create_table "places", force: :cascade do |t|
t.string "name"
t.string "type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
A better way to handle assocations and STI is to just setup the assocations to the base class:
class Place < ApplicationRecord
end
class SubPlace < Place
has_many :posts, foreign_key: 'place_id', inverse_of: 'place'
end
class AnotherKindOfPlace < Place
has_many :posts, foreign_key: 'place_id', inverse_of: 'place'
end
class Post < ApplicationRecord
belongs_to :place
end
This keeps things nice and simple since Post does not know or care that there are different kinds of places. When you access #post.place ActiveRecord reads the places.type column and will instanciate the correct subtype.
If the base Post class also has the association you just write it as:
class Place < ApplicationRecord
has_many :posts, foreign_key: 'place_id', inverse_of: 'place'
end
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column
places.sub_place_id does not exist)
Your association in SubPlace is not valid. You should re-write that to just
class SubPlace < Place
has_many :posts
end
I have 4 models.
UserModel
SkillModel
UserSkillModel
PreferenceSkillModel
Association I used:
UserModel
has_many :skills, through: :user_skills
has_many :user_skills
has_many :skills, :through: :preference_skills
has_many :preference_skills
SkillModel
has_many :users, through: :user_skills
has_many :users_skills
has_many :users, :through: :preference_skills
has_many :preference_skills
UserSkillModel
belongs_to :user
belongs_to :skill
PreferenceSkillModel
belongs_to :user
belongs_to :skill
Schema:
create_table "preference_skills", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.integer "user_id", default: 0
t.integer "skill_id", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "user_skills", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.integer "user_id"
t.integer "skill_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "skills", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "name", default: ""
t.integer "skill_count", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
How can I implement this association in a correct way?
How can I implement this association in a correct way?
What is correct may depend on what you are trying to do. The following is correct, by naming separate associations for users-skills and users-preference_skills.
class User
has_many :user_skills
has_many :my_skills, through: :user_skills, class_name: 'Skill'
has_many :preference_skills
has_many :my_preference_skills, :through: :preference_skills, class_name: 'Skill'
...
end
class SkillModel
has_many :users_skills
has_many :direct_users, through: :user_skills, class_name: 'User'
has_many :preference_skills
has_many :preference_users, :through: :preference_skills, class_name: 'User'
...
end
If you want one collection with all the skills you need to use STI or polymorphism. Read the Rails Guides for more information.
I don't think there's a need for separate user_skills & preference_skills models.
You can use STI here like -
skill.rb
Skill < ApplicationRecord
end
user_skill.rb
UserSkill < Skill
end
preference_skill.rb
PreferenceSkill < Skill
end
Note: The Skill model that I've used is different from what you are assuming.
You might want to rename your current Skills model to more explainatory name like SkillDetail.
After this you can have your associations like -
class User < AR
has_many :skills
has_many :skill_details, through: :skills
end
class Skill < AR
belongs_to :user
belongs_to :skill_detail
end
class SkillDetail < AR
has_many :skills
has_many :users, through: :skills
end
P.S. Do some research on Single Table Inheritance (STI)
Hope this helps.
Also note that you got a downvote because this is a very vague question & you are directly asking to implement a business logic.
Setup:
Reservations can be assigned multiple Resources. A reservation-resource combo can have multiple SetUps.
I tried to set up the model like this:
class SetUp < ApplicationRecord
has_many :reservation_resource_set_ups, dependent: :destroy
has_many :reservations, through: :reservation_resource_set_ups
has_many :resources, through: :reservation_resource_set_ups
end
class Resource < ApplicationRecord
has_many :reservation_resources, dependent: :destroy
has_many :reservation_resource_set_ups, dependent: :destroy
has_many :reservations, through: :reservation_resources
has_many :set_ups, through: :reservation_resource_set_ups
end
class Reservation < ApplicationRecord
has_many :reservation_resources, dependent: :destroy
has_many :reservation_resource_set_ups, dependent: :destroy
has_many :resources, through: :reservation_resources
has_many :set_ups, through: :reservation_resource_set_ups
end
class ReservationResource < ApplicationRecord
belongs_to :reservation
belongs_to :resource
has_many :reservation_resource_set_ups
has_many :set_ups, through: :reservation_resource_set_ups
end
class ReservationResourceSetUp < ApplicationRecord
belongs_to :reservation
belongs_to :resource
belongs_to :set_up
end
Steps:
Create a reservation, assigning a resource, works:
res1 = Reservation.create(name:"res name")
res1.resources << Resource.find(1) # resource with id = 1 exists
The reservations and reservation_resources tables are updated correctly.
Assign a setup to the reservation_resource, fails:
res1.resources.first.set_ups << SetUp.find(1) # set_ups with id = 1 exists
This fails with error ActiveRecord::RecordInvalid (Validation failed: Reservation must exist)
Can you help point me in the right direction? Thanks!
(Here's the schema, if helpful...)
create_table "reservation_resource_set_ups", force: :cascade do |t|
t.integer "reservation_id"
t.integer "resource_id"
t.integer "set_up_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["reservation_id"], name: "index_reservation_resource_set_ups_on_reservation_id"
t.index ["resource_id"], name: "index_reservation_resource_set_ups_on_resource_id"
t.index ["set_up_id"], name: "index_reservation_resource_set_ups_on_set_up_id"
end
create_table "reservation_resources", force: :cascade do |t|
t.integer "reservation_id"
t.integer "resource_id"
t.text "comments"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["reservation_id"], name: "index_reservation_resources_on_reservation_id"
t.index ["resource_id"], name: "index_reservation_resources_on_resource_id"
end
create_table "reservations", force: :cascade do |t|
t.string "name"
...
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["end_date"], name: "index_reservations_on_end_date"
t.index ["repeat_end_date"], name: "index_reservations_on_repeat_end_date"
t.index ["start_date"], name: "index_reservations_on_start_date"
end
create_table "resources", force: :cascade do |t|
t.string "name"
t.text "description"
t.string "resource_type"
t.text "location"
t.integer "quantity", default: 1
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "set_ups", force: :cascade do |t|
t.string "name"
t.text "instructions"
t.string "image"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
<< method useless when you need to setup more than one foreign key.
The collection<< method adds one or more objects to the collection by setting their foreign keys to the primary key of the calling model.
So you cat treat the code
res1.resources.first.set_ups << SetUp.find(1) # set_ups with id = 1 exists
as
ReservationResourceSetUp.create(
set_up: SetUp.find(1), # from << SetUp.find(1)
resource: res1.resources.first, # from left side
reservation: nil # raises the error
)
To create the entry, just specify all the keys:
ReservationResourceSetUp.create(
set_up: SetUp.find(1),
resource: res1.resources.first,
reservation: res1
)
You have some problems with your models. For example:
ReservationResource has_many :reservation_resource_set_ups. ActiveRecord assumes a reservation_resource_id in ReservationResourceSetUp
but
ReservationResourceSetUp does not belongs_to :reservation_resource
I suggest some changes in your models:
class Resource < ApplicationRecord
has_many :reservation_resources, dependent: :destroy
has_many :reservations, through: :reservation_resources
has_many :reservation_resource_set_ups, through: :reservation_resources
has_many :set_ups, through: :reservation_resource_set_ups
end
class Reservation < ApplicationRecord
has_many :reservation_resources, dependent: :destroy
has_many :resources, through: :reservation_resources
has_many :reservation_resource_set_ups, through: :reservation_resources
has_many :set_ups, through: :reservation_resource_set_ups
end
class ReservationResource < ApplicationRecord
belongs_to :reservation
belongs_to :resource
has_many :reservation_resource_set_ups
has_many :set_ups, through: :reservation_resource_set_ups
end
class SetUp < ApplicationRecord
has_many :reservation_resource_set_ups, dependent: :destroy
has_many :reservations_resources, through: :reservation_resource_set_ups
has_many :resources, through: :reservation_resources
has_many :reservations, through: :reservation_resources
end
class ReservationResourceSetUp < ApplicationRecord
belongs_to :reservation_resource
belongs_to :set_up
end
The main change is that ReservationResourceSetUp now belongs to ReservationResource and SetUp (instead of Reservation, Resource and SetUp). In practice it's the same, but I think it handles your situation in a better way. You first create a Reservation for a Resource. This ReservationResource is then assigned one or more SetUps. I think your code would work if you make these changes. You must of course change your migrations. Now your ReservationResourceSetUp will have a reservation_resource_id and a set_up_id.
So I am developing a rails app that will have two kinds of Users, student/tutor. but I only have one User model (using cancan for auth), so when I try to set up the meeting model (which has one tutor and one student) how do I do this? This is the model:
class Meeting < ActiveRecord::Base
belongs_to :student
belongs_to :tutor
attr_accessible :price, :subject, :time
end
and here's the relevant part of the schema:
create_table "meetings", :force => true do |t|
t.string "subject"
t.integer "student_id"
t.integer "tutor_id"
t.datetime "time"
t.integer "price"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "meetings", ["student_id"], :name => "index_meetings_on_student_id"
add_index "meetings", ["tutor_id"], :name => "index_meetings_on_tutor_id"
Without having to have two extra models containing student and tutor can I use those labels?
one way to do it..
class Meeting < ActiveRecord::Base
belongs to :student, class_name: 'User'
belongs to :tutor, class_name: 'User'
class User < ActiveRecord::Base
has_many :meet_with_students, class_name: 'Meeting', foreign_key: :tutor_id
has_many :students, through: :meet_with_students, source: :student
has_many :meet_with_tutors, class_name: 'Meeting', foreign_key: :student_id
has_many :tutors, through: :meet_with_tutors:, source: :tutor
I think you're looking for class_name:
class Meeting < ActiveRecord::Base
belongs_to :student, class_name: "User"
belongs_to :tutor, class_name: "User"
end