rails - HasManyThroughAssociationNotFoundError on plural has_many - ruby-on-rails

I have the following models:
User
Team
UserTeam
There are users and there are teams. Each team has 0 or more users, the join table is UserTeams
I want to get all teams and for each team get its users. Initially I tried for one team and get its users, like this
Team.find('759ccbb7-2965-4558-b254-3e437ca721aa').users
but rails complains about this with:
Could not find the association :user_team in model Team
This is how my Team model looks like:
class Team < ActiveRecord::Base
has_many :users, through: :user_team
has_many :user_teams, dependent: :destroy
accepts_nested_attributes_for :user_teams, :reject_if => lambda { |a| a[:user_id] == '0' }
validates :name, presence: true
end
Team model:
class UserTeam < ActiveRecord::Base
belongs_to :team
belongs_to :user
validates :user_id, presence: true
end
Weird enough, if I change Team model like this:
has_many :user_team
using the singular word, it works, but I read that it has to be pluralized

Just change this:
has_many :users, through: :user_team
has_many :user_teams, dependent: :destroy
to
has_many :user_teams, dependent: :destroy
has_many :users, through: :user_teams
The through part should use existing association.
In your code, you specified through :user_team, but you don't have has_many :user_team, that's why rails complains.

The solution was to put these inside User model:
has_many :user_teams
has_many :teams, through: :user_teams
I forgotten about these. Now it works

Related

How to find all projects where client & user involved in Ruby on Rails

At first, I want to show my associated models as like
#=> client.rb
class Client < ApplicationRecord
has_many :client_assignments, dependent: :destroy
has_many :projects, through: :client_assignments
end
#=> client_assignment.rb
class ClientAssignment < ApplicationRecord
belongs_to :project
belongs_to :client
end
#=> project.rb
class Project < ApplicationRecord
has_many :client_assignments, dependent: :destroy
has_many :clients, through: :client_assignments
validates :client_assignments, presence: true
has_many :user_assignments, dependent: :destroy
has_many :users, through: :user_assignments
validates :user_assignments, presence: true
end
#=> user.rb
class User < ApplicationRecord
has_many :user_assignments, dependent: :destroy
has_many :projects, through: :user_assignments
end
#=> user_assignment.rb
class UserAssignment < ApplicationRecord
belongs_to :project
belongs_to :user
end
The concept is "A project involved with a client & current_user".
I'm struggling for that how to find current_user involved projects where client is specific, For example: I have a client_id & which is 2 so how I find all projects for this client where matching current_user.id.
I don't know how I describe this.
Please let me know if you have any confusion.
I haven't tested this, but I feel like something like this should work:
current_user.projects.joins(:clients).where('clients.id = ?', client_id)
you want all the projects associated w/ the current user and then find all the clients associated w/ those projects and filter by the specific client_id.
This should do the same thing and may be faster because it avoids a join:
current_user.projects.joins(:client_assignments).where(clients_id:client_id)

When renaming column in Users table getting: SQLite3::ConstraintException: FOREIGN KEY constraint failed: DROP TABLE "users"

I am working on a Rails app with Sqlite and have a users table associated with several other tables. When trying to rename a column in Users, I'm getting the subject error when running rails db:migrate.
I see a lot of posts here with similar issues, but none has worked. Specifically, the common remedy seems to be to use "dependent: :destroy" on all has_many and has_one associations. I am doing this but am still getting the error.
What am I doing wrong?
Below is my code:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :profile, dependent: :destroy
has_many :bikes, dependent: :destroy
has_many :bookings, dependent: :destroy
has_many :rented_bikes, through: :bookings, source: :bike
has_many :conversations, dependent: :destroy
has_many :likes, dependent: :destroy
has_many :liked_bikes, through: :likes, :source => :bike
has_many :viewed_bikes, through: :views, :source => :bike
has_many :views, dependent: :destroy
has_many :reviews, dependent: :destroy
end
class Profile < ApplicationRecord
belongs_to :user
end
class Bike < ApplicationRecord
belongs_to :user
has_many :images, dependent: :destroy
has_many :bookings, dependent: :destroy
has_many :booked_users, through: :bookings, source: :user
has_many :conversations, dependent: :destroy
has_many :likes, dependent: :destroy
has_many :liking_users, :through => :likes, :source => :user
has_one :amenity, dependent: :destroy
has_many :places, dependent: :destroy
has_many :views, dependent: :destroy
end
class Booking < ApplicationRecord
belongs_to :bike
belongs_to :user
has_one :review, dependent: :destroy
validates :date_start, presence: true
validates :date_end, presence: true
validates :user_id, presence: true
end
class Conversation < ApplicationRecord
belongs_to :user
belongs_to :bike
has_many :messages, dependent: :destroy
end
class Like < ApplicationRecord
belongs_to :user
belongs_to :flat
end
class View < ApplicationRecord
belongs_to :user
belongs_to :flat
end
class Review < ApplicationRecord
belongs_to :user
belongs_to :booking
end
Migration:
class ChangeCustomerIdToUserId < ActiveRecord::Migration[5.1]
def change
rename_column :users, :customer_id, :client_id
end
end
You have a couple problems happening at once:
SQLite doesn't support renaming columns so the ActiveRecord driver implements column renaming the hard way: create a new table with the new column names, copy all the data, drop the original table, rename the new one. Note that this has recently changed so the latest SQLite does support renaming columns in-place.
You have foreign keys in other tables that reference your users table.
(2) is what is triggering your error during your migration: you can't drop a table (see (1)) when there are foreign keys referencing it since dropping the table would violate those foreign keys.
The solution is to drop all the offending FKs in your migration, then do the rename_column, and then add all the FKs back again. Another option would be to try to turn off FKs and turn them back on in your migration, something like:
connection.execute("PRAGMA defer_foreign_keys = ON")
connection.execute("PRAGMA foreign_keys = OFF")
rename_column :users, :customer_id, :client_id
connection.execute("PRAGMA foreign_keys = ON")
connection.execute("PRAGMA defer_foreign_keys = OFF")
might work.
There was a commit made to Rails three months ago that should fix this problem but I don't think it has made it into any release version yet.

Is it good practice to validate IDs in a join model?

I have a HMT association setup between my Artist and Group models:
class Artist < ApplicationRecord
has_many :artist_groups, dependent: :destroy
has_many :artist_groups, through: :artist_groups
end
class ArtistGroup < ApplicationRecord
has_many :memberships, class_name: "ArtistGroupMembership", dependent: :destroy
belongs_to :artist
belongs_to :group
has_and_belongs_to_many :roles
accepts_nested_attributes_for :memberships, reject_if: :all_blank, allow_destroy: true
validates_presence_of :artist_id, :group_id
end
class Group < ApplicationRecord
has_many :artist_groups, dependent: :destroy
has_many :members, through: :artist_groups, source: :artist
end
As you'll notice in my ArtistGroup join model it validates to make sure the an artist and group are present.
When the association is saved, whether I do something like this:
artist.groups.push(Group.first)
or create a form in my view (sans ID inputs) ActiveRecord is smart enough to map the association. With this in my should I even be validating these IDs in my join models? I notice this becomes even more of a pain when dealing with polymorphic associations.
Rails 5 automatically requires that the belongs_to :artist refers to an existing artist so having extra validation is completely unnecessary. You can make that requirement optional by doing
belongs_to :artist, optional: true

Get attribute value from the join in a many-to-many relationship

I have a many-to-many relation between User and "Link".
The join model is called LinkAddress and besides for saving the IDs of the other two models, it has an attribute called address - information it collects at creation.
How can I access the address attribute for a certain link in a request scenario like the following: User.first.links.first.address ?
Models:
class User < ActiveRecord::Base
has_many :link_addresses, dependent: :destroy
has_many :links, through: :link_addresses
accepts_nested_attributes_for :link_addresses, allow_destroy: true
end
class LinkAddress < ActiveRecord::Base
belongs_to :user
belongs_to :link
end
class Link < ActiveRecord::Base
has_many :link_addresses, dependent: :destroy
has_many :users, through: :link_addresses
end
You could access it through User since it's a has_many ... :through relation:
User.first.link_addresses.first.address
Or, if you'd like to go through links then:
User.first.links.first.link_addresses.first.address
SQL Aliases
I had this exact question: Rails Scoping For has_many :through To Access Extra Data
Here's the answer I got:
#Images
has_many :image_messages, :class_name => 'ImageMessage'
has_many :images, -> { select("#{Image.table_name}.*, #{ImageMessage.table_name}.caption AS caption") }, :class_name => 'Image', :through => :image_messages, dependent: :destroy
This uses SQL Aliases which I found at this RailsCast (at around 6:40 in). It allows us to call #user.image.caption (even though .caption is in the join model)
Your Code
For your query, I'd use this:
class User < ActiveRecord::Base
has_many :link_addresses, dependent: :destroy
has_many :links, -> { select("#{Link.table_name}.*, #{LinkAddress.table_name}.address AS address") }, through: :link_addresses
accepts_nested_attributes_for :link_addresses, allow_destroy: true
end
This will allow you to write #user.links.first.address, and gracefully handles an absence of the address record

Rails: ignoring duplicates in an nested association

I have models User, Team, Document. There's a many-to-many relationship between Users and Teams, and a many-to-many relationship between Teams and Documents, using join tables called TeamMembership and TeamDocument respectively.
The relationships in my models look like this:
class Document < ActiveRecord::Base
has_many :team_documents, dependent: :destroy
has_many :teams, through: :team_documents
end
class User < ActiveRecord::Base
has_many :team_memberships, dependent: :destroy, foreign_key: :member_id
has_many :teams, through: :team_memberships
has_many :documents, through: :teams
end
class TeamDocument < ActiveRecord::Base
belongs_to :team
belongs_to :document
end
class TeamMembership < ActiveRecord::Base
belongs_to :team
belongs_to :member, class_name: "User"
end
class Team < ActiveRecord::Base
has_many :team_documents, dependent: :destroy
has_many :documents, through: :team_documents
has_many :team_memberships, dependent: :destroy
has_many :members, through: :team_memberships
end
The idea is that users can belong to many teams, a document can be associated with many teams, and users will only have access to documents that "belong" to at least one team that the user is a member of.
Here's the question: I can use User#documents to retrieve a list of all the documents that this user is allowed to view. But this will return duplicates if a document is viewable by more than one team which the user is a member of. How can I avoid this?
I know I can remove the duplicates after the fact with #user.documents.uniq, but as I will never want to include the duplicates in any case, is there a way I can just make #documents not include duplicates every time?
I don't have nested has_many :through like yours to test it, but I suspect using uniq option on your user association would help :
class User < ActiveRecord::Base
has_many :documents, through: :teams, uniq: true
end
You can add a default_scope on Document model:
class Document < ActiveRecord::Base
default_scope group: { documents: :id }

Resources