RoR: Accessing :through attributes - ruby-on-rails

I'm new to rails and working in this app where an User can create many Events. Many Users can be invited to these Events, therefore I have the following Models:
User:
class User < ActiveRecord::Base
...
has_many :events, dependent: :destroy
end
Event:
class Event < ActiveRecord::Base
belongs_to :user
has_many :event_guest
has_many :guests, :through => :event_guest, :source => :user
end
Event_Guest:
class EventGuest < ActiveRecord::Base
belongs_to :user
belongs_to :event
end
What I am looking for is being able to access (and add) the guest users to an event, for which I've tried all the variations I could thing of "Event.find(1).guests", only to get the following error:
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: event_guests.event_id: SELECT "users".* FROM "users" INNER JOIN "event_guests" ON "users"."id" = "event_guests"."user_id" WHERE "event_guests"."event_id" = ? AND "users"."event_id" = 1
My event_guest migration was the following:
create_table :event_guest do |t|
t.belongs_to :user, index: true
t.belongs_to :event, index: true
end

Like #Dharam mentioned, your naming convention is incorrect. You need to rename the event_guest table.
$ rails g migration rename_event_guest_table
And then the migration looks like this:
def change
rename_table :event_guest, :event_guests
end
You then need to update your event.rb model to be
class Event < ActiveRecord::Base
belongs_to :user
has_many :event_guests
has_many :guests, :through => :event_guests, class_name: 'User'
end
And your user.rb model:
class User < ActiveRecord::Base
...
has_many :event_guests, dependent: :destroy
has_many :events, through: :event_guests
end
Move the dependent destroy off the events association and to the event_guests relationship. You don't want to destroy the event just because one guest isn't going...

Try adding self.table_name = "event_guest" to EventGuest as that's what you have in migration which is contrary to rails expectation of "event_guests" from the model name.

Related

Dependent destroy to tables with more than one references

I have a table Atribuition with 2 references from User table.
class Attribuition < ApplicationRecord
belongs_to :user, class_name: 'User', foreign_key: 'user_id'
belongs_to :not_rated, class_name: 'User', foreign_key: 'not_rated_id'
end
The User model:
class User < ApplicationRecord
has_many :attribuitions, dependent: :destroy
end
When i destroy an user marked in not_rated i want it to be destroyed, but it just happens when i destroied an user marked as user_id, then the attribute row is deleted. I wanna make dependent:: destroy to work for many references of same model. That is possible?
My migration is:
class CreateAttribuitions < ActiveRecord::Migration[5.2]
def change
create_table :attribuitions do |t|
t.references :user
t.references :not_rated, index: { unique: true }
t.timestamps
end
end
end
Edit:
First you do following change as rails use convention over configuration
class Attribuition < ApplicationRecord
- belongs_to :user, class_name: 'User', foreign_key: 'user_id'
+ belongs_to :user
end
Changes needed
When you mention has_many :attribuitions, dependent: :destroy by side of User model class_name will be Attribuition and foreign_key will be user_id stored in attributions table.
So if you need to destroy attribuitions related by foreign_key not_rated_id & user_id then you need following changes.
class User < ApplicationRecord
has_many :attribuitions, dependent: :destroy # default foreign_key is user_id
has_many :not_rated_attribuitions, foreign_key: 'not_rated_id', dependent: :destroy
end

Rails HABTM has_many destroy error

I have a has_and_belongs_to_many using has_many between users and workspaces.
user.rb
class User < ActiveRecord::Base
has_many :user_workspaces, dependent: :destroy
has_many :workspaces, through: :user_workspaces
before_destroy :delete_workspaces
def delete_workspaces
self.workspaces.each(&:destroy)
end
workspace.rb
class Workspace < ActiveRecord::Base
has_many :user_workspaces
has_many :users, through :user_workspaces
end
user_workspaces class and migration:
class UserWorkspace < ActiveRecord::Base
self.table_name = "user_workspaces"
belongs_to :user
belongs_to :workspace
end
class CreateUsersAndWorkspaces < ActiveRecord::Migration
def change
create_table :users_workspaces, id: false do |t|
t.belongs_to :user, index: true
t.belongs_to :workspace, index: true
t.boolean :admin, default: true
end
end
end
class RenameUsersWorkspaces < ActiveRecord::Migration
def change
rename_table('users_workspaces', 'user_workspaces')
end
end
I want this both test to pass:
should "destroy all associatios and objects" do
user = User.create!(attributes_for(:user))
w = user.workspaces.create!(attributes_for(:workspace))
user.destroy
assert UserWorkspace.all.empty? #asser there are no associations
end
which gives me the error
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column:
user_workspaces.: DELETE FROM "user_workspaces" WHERE
"user_workspaces"."" = ?
should "destroy association when destroying workspace" do
user = User.create!(attributes_for(:user))
w = user.workspaces.create!(attributes_for(:workspace))
w.destroy
assert UserWorkspace.all.empty?
end
How can I cover both scenarios? destroying an user destroys the association and the workspaces, destroying the workspace also destroys the association
According to http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html just need to ignore the join table.
Workspace.rb now has dependent destroy, to destroy only the join (intended behavior of destroy)
has_many :users, through: :user_workspaces,
inverse_of: :workspaces,
dependent: :destroy
then user.rb keeps the delete_workspaces functions, but adds dependent: :destroy, to also destroy the association.
user.rb
before_destroy: :delete_workspaces
has_many :workspaces, through: :user_workspaces,
dependent: :destroy
def delete_workspaces
self.workspaces.each(&:destroy)
end
now both test passes.

Add extra field in Rails habtm joins

I have this relationship between users, teams
class CreateTeamsUsers < ActiveRecord::Migration
def change
create_table :teams_users, :id => false do |t|
t.references :user
t.references :team
t.timestamps
end
end
end
class User < ActiveRecord::Base
has_and_belongs_to_many :teams
end
class Team < ActiveRecord::Base
has_and_belongs_to_many :users
end
The issue is that I want to add extra attribute in HABTM,attribute name is "user_name"
How to do this?
Instead of HABTM you'd use has_many and has_many :through.
class User < ActiveRecord::Base
has_many :memberships
has_many :team, through: :membership
end
class Membership < ActiveRecord::Base # This would be your old 'join table', now a full model
belongs_to :user
belongs_to :team
end
class Team < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
end
Short version, you can't do what your're trying to do without a little refactoring. Here is how I would do it (apologies if there's syntax issues, I'm doing this from memory I haven't tested the code but the principle is sound)
Create a new model to represent "membership" of a team (maybe call it "Membership") and the associated migration to create the table:
class Membership
belongs_to :team
belongs_to :user
end
Then change your team and user models to use this new model:
class User
has_many :memberships
has_many :teams, through: :memberships
end
class Team
has_many :memberships
has_many :users, through: :memberships
end
Once you've refactored this far, adding additional columns / attributes to "memberships" is easy because you can just treat it like any other model.

rails 3 association without table

in rails 3 i have 2 models: User,Event. User has_many Event through events_staffs and Event has_many Event through events_staffs.
class Staff < ActiveRecord::Base
has_many :events_staffs
has_many :events, through: :events_staffs
end
class Event < ActiveRecord::Base
has_many :events_staffs, dependent: :destroy
has_many :staffs, through: :events_staffs
end
i wish that an Event have an author and some members where author and members are record of staffs table.
I wish I could do in the console something like this:
e=Event.first #ok it works
e.author=Staff.first
e.members=Staff.all - [Staff.first]
is possible to do it?
SOLUTION
#Event model
has_many :events_staffs, dependent: :destroy
has_many :members, through: :events_staffs, source: :staff #http://stackoverflow.com/a/4632456/1066183
#http://stackoverflow.com/a/13611537/1066183
belongs_to :author,class_name: 'Staff'
#migration:
class AddForeignKeyToEventsTable < ActiveRecord::Migration
def change
change_table :events do |t|
t.integer :author_id
end
end
end
Yes, quite easy. Add an author_id integer column to your Event model, then update your code as follows:
class Event < ActiveRecord::Base
has_many :events_staffs, dependent: :destroy
has_many :staffs, through: :events_staffs
belongs_to :author, class_name: 'Staff'
end
As for members, I'd create another join table similar to your staffs_events table, but for members. Within that model you'd need to do the same belongs_to association as with Event where you specify the class_name for the member.

Rails model associations, has_many :through and has_one with the same model

I have two models: User and State. The state model has records for each of the 50 states in the United States.
I would like each User to have two attributes: one "home" state, and many states to "visit".
I know I have to set up some sort of model associations to achieve this, but not sure what the best approach is.
Here's what I have so far, but I know there must be something wrong with have has_many and has_one association to the same model.
#user.rb
class User < ActiveRecord::Base
has_many :visits
has_many :states, :through => :visits
has_one :state
end
#visit.rb
class Visit < ActiveRecord::Base
belongs_to :user
belongs_to :state
end
#state.rb
class State < ActiveRecord::Base
has many :visits
has many :users, :through => :visits
belongs_to :user
end
Any suggestions?
In my opinion what you already have is almost right, except you would store the home state foreign key on the user like thus:
# user.rb
class User < ActiveRecord::Base
belongs_to :state
has_many :visits
has_many :states, through: visits
end
# visit.rb
class Visit < ActiveRecord::Base
belongs_to :user
belongs_to :state
end
# state.rb
class State < ActiveRecord::Base
has_many :visits
has_many :users, through: :visits
end
You would then access the home state like thus:
u = User.first
u.state
And the visited states, like thus:
u = User.first
u.states
For programming clarity, you can rename your relations:
# user.rb
class User < ActiveRecord::Base
belongs_to :home_state, class_name: "State"
has_many :visits
has_many :visited_states, class_name: "State", through: visits
end
# state.rb
class State < ActiveRecord::Base
has_many :residents, class_name: "User"
has_many :visits
has_many :visitors, class_name: "User", through: :visits
end
Your domain model would make more sense:
u = User.first
u.home_state
u.visited_states
s = State.first
s.residents
s.visitors
I expect you'll probably want to store additional information about the visit, so keeping the HMT join table for the Visit model will allow you to do this, rather than going with a HABTM relation. You could then add attributes to the visit:
# xxxxxxxxxxxxxxxx_create_visits.rb
class CreateVisits < ActiveRecord::Migration
def change
create_table :visits do |t|
t.text :agenda
t.datetime commenced_at
t.datetime concluded_at
t.references :state
t.references :user
end
end
end
You can't have a has_many and has_one relationship on a single model, in this case state. One solution is to:
create a static model of states, they do not need to be a database model, they could be a static variable on the state model: US_STATES = {'1' => 'AK', '2' => 'AL', etc} or you could use fixtures to load a table of states into the database (more complicated because you need to use a rake task or the db:seed task to load the fixtures into the db, but nice because you can use active record to manage the model).
then you can provide a home_state_id on the user model that defines the home_state and the visits are simply a join between user_id and the state_id.
I would like each User to have two attributes: one "home" state, and many states to "visit".
In your models, a state may only be home to one user (belongs_to).
The correct semantics would be
class User < AR::Base
belongs_to :home_state, :class_name => "State", :foreign_key => "home_state_id", :inverse_of => :users_living
has_and_belongs_to_many :visited_states, :through => :visits
# ...
end
class State < AR::Base
has_many :users_living, :class_name => "User", :inverse_of => :home_state
# ...
end

Resources