Rails has_many through across workspaces ignores class_name - ruby-on-rails

I'm working with a simple has_many through relationship between 2 models.
The Achievement model describes an achievement that can be conquered by a Professional::Company, through the ConqueredAchievement table, that holds when it was conquered.
As I am working acrosss namespaces, I know I have to supply the full class name when creating the relationship, but it doesn't seem to have any effect, as it throws the exact same error with or without the value passed to class_name.
Here's the code for the models:
class Achievement < ApplicationRecord
has_many :conquered_achievements
end
class Professional::Company < ApplicationRecord
has_many :conquered_achievements
has_many :achievements, through: :conquered_achievements
end
class ConqueredAchievement < ApplicationRecord
belongs_to :achievement
belongs_to :professional_company, class_name: 'Professional::Company'
end
When I try to create an association, by using:
c.conquered_achievements.create!(achievement: a)
I get the error (same error with and without the class_name on the ConqueredAchievement model):
ActiveModel::UnknownAttributeError (unknown attribute 'company_id' for ConqueredAchievement.)
On the generated table, the row is actually named professional_company_id.
I've seen a lot of complaints about has_many through not working properly across namespaces. Am I doing something wrong, or is this actually a bug?

Try:
class Professional::Company < ApplicationRecord
has_many :conquered_achievements, foreign_key: :professional_company_id
has_many :achievements, through: :conquered_achievements
end
The clue is in the error:
ActiveModel::UnknownAttributeError (unknown attribute 'company_id' for ConqueredAchievement.)
The association is trying to use an attribute named company_id on ConqueredAchievement to find the Professional::Company. But, as you say, that attribute doesn't exist.
Instead of letting rails try to infer the foreign key, you can stipulate the foreign key using the foreign_key: option as described in the docs, section 4.3.2.6.

Related

Cannot destroy a model with a has_many through assocation

I have the following models linked through a has_many :through associaton. Below is the following models
class Campaign
has_many :email_notification_code_percentages, dependent: :destroy
has_many :email_notification_code_percentage_trackers, through: :email_notification_code_percentages
end
class EmailNotificationCodePercentage < ApplicationRecord
belongs_to :campaign
has_many :email_notification_code_percentage_trackers, dependent: :destroy
end
class EmailNotificationCodePercentageTracker < ApplicationRecord
belongs_to :email_notification_code_percentage
end
When I try execute the following #campaign.email_notification_code_percentage_trackers.destroy_all I get the following error:
Cannot modify association 'Campaign#email_notification_code_percentage_trackers' because the source reflection class 'EmailNotificationCodePercentageTracker' is associated to 'EmailNotificationCodePercentage' via :has_many. (ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection)
What is the issue here? This should be a simple has many through association as defined through the rails examples. What am I missing here? This looks correct to me.
This is the expected behavior, as you can read in the API docs (https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html):
An important caveat with going through has_one or has_many
associations on the join model is that these associations are
read-only.
Associations with the :through option or defined via the has_and_belongs_to_many macro are a bit special.
So, a couple of alternative ways:
Iterate over each elem:
# be careful if the association is very large, you can run into some memory or timeout issues
#campaign.email_notification_code_percentage_trackers.each(&:destroy)
Make the query a bit different to be able to use the destroy_all:
EmailNotificationCodePercentage.merge(#campaign.email_notification_code_percentage_trackers).destroy_all

has_many :through association cannot find a valid model

I am creating association pretty much identical with the Rails Guides Patient-Appointment-Physician data model. A user has many prospects through prospect_subscription. However, when trying to access user.prospects in rails console, it throws the following error:
Rails couldn't find a valid model for Prospects association. Please provide the :class_name option on the association declaration. If :class_name is already provided, make sure it's an ActiveRecord::Base subclass. (NameError)
uninitialized constant User::Prospects (NameError)
Which is strange because all three models are right there. Migration has been run and sample data has been populated and can be checked in pgAdmin. Why can't Rails find the model?
Association defined at the models are as follows:
models/prospect.rb
class Prospect < ApplicationRecord
has_many :prospect_subscriptions
has_many :users, through: :prospect_subscriptions
end
models/user.rb
class User < ApplicationRecord
has_many :prospect_subscriptions
has_many :prospects, through: :prospect_subscriptions
end
models/prospect_subscription.rb
class ProspectSubscription < ApplicationRecord
belongs_to :user
belongs_to :prospect
end
I figured that wiping the database records clean and re-seeding helps. The difference is this time I assigned as user.prospects << [prospect_name], to make sure that the joins are created in the backend.

What is the correct naming method for Rails models that represent a join table?

I have two classes (organisation.rb and user.rb) which are linked through a join table via ActiveRecord with a many to many relationship. A user can belong to many organisations, and an organisation can have many users.
Initially, I represented this relationship in this manner and this worked fine:
class Organisation < ActiveRecord::Base
has_many :members, through: :memberships, source: :user
has_many :memberships
end
class User < ActiveRecord::Base
has_many :organisation_members, through: :memberships, source: :organisation
has_many :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :organisation
end
I asked a friend to review my pull request and he suggested that the Rails convention is to name the join table with a lexical ordered list of the tables to be joined. I searched and confirmed this:
Rails naming convention for join table
As such, I reverted my migrations and started anew, with the new relationships:
class Organisation < ActiveRecord::Base
has_many :members, through: :organisations_users, source: :user
has_many :organisations_users
end
class User < ActiveRecord::Base
has_many :organisation_memberships, through: :organisations_users, source: :organisation
has_many :organisations_user
end
class OrganisationsUser < ActiveRecord::Base
belongs_to :user
belongs_to :organisation
end
Having made these changes, a number of my tests failed due to not recognising the model 'organisation_user.rb'. I then tried 'organisations_users.rb', and also no luck. This was due to my ignorance of rails' pluralisation, and therefore naming the join table model correctly resulted in all tests passing. As described above, the correct naming is:
class OrganisationsUser
This results in a rather strange model name of a pluralised organisation followed by a singular user.
I understand that this is technically correct. The model name should singular, however it isn't particularly easy to understand when compared with the 'memberships' join table.
What is the correct way to handle this - should I revert back to memberships, find a way to override Rails' default pluralisation, or continue with the awkwardly named status quo?

Understanding use of foreign_key and class_name in association

I am new to rails and this is a very basic question. I am trying to understand the need of foreign key and class_name.
has_many :task, foreign_key: "created_by"
has_many :memberships, class_name: "TaskMembership"
Can anyone explain the need of foreign_key & class_name.
Here is the answer of my question
Suppose you have a User model and Post model.And you have to set an association like User has many post
User Model
has_many :posts
Post Model
belongs_to :user
Now suppose your user is some author so we have to set some meaningful name so instead of user we will use author but have to specify which class it is referring
Post Model
belongs_to :author, class_name: 'User'
Now problem will occur because rails will look for author_id column in posts table .So here foreign key will come into picture.We will have to find user_id
Post Model
belongs_to :author, class_name: 'User', foreign_key: 'user_id'
See more better explanation association
has_many association is used for for one-to-many type relationships in rails. For instance, if you have a model User which can has many profiles, your User to Profile association will be has many.
class User < ActiveRecord::Base
has_many :profiles
end
class Profile < ActiveRecord::Base
belongs_to :user
end
If you have a foreign key different than user_id in profiles table, you explicitly specify foreign_key. Same is the case with class name. If your association name is different than actual model name, you explicitly specify class name after association (as you did for memberships).
Hope it helps.
in your model
class First < ActiveRecord::Base
has_many :seconds
end
class Second < ActiveRecord::Base
belongs_to :first
end
and in your second class table,create first_id column

Rails has_many dual relationship with other model

I'm trying to get two has_many relationships from one model to another.
Specifically, I want:
class Driver < Active:Record::Base
has_many :reservations
has_many :requested_reservations
and
class Reservations < Active:Record::Base
belongs_to :driver
belongs_to :requester
The first one is a normal has_many/belongs_to relationship using driver_id on the reservations model.
But for the second one, I want to be able to call #driver.requested_reservations and #reservation.requester, and have it use the requester_id column in the Reservations class.
What do I need to put at the end of those has_many and belongs_to lines to get it to work properly?
I believe you can set the class and foreign key to get the desired results.
class Driver < Active:Record::Base
has_many :reservations
has_many :requested_reservations, class_name: 'Reservation', foreign_key: 'your_id'
...
end
class Reservations < Active:Record::Base
belongs_to :driver
belongs_to :requester, class_name: 'Driver', foreign_key: 'requester_id'
...
end
There are similar questions that have been asked before. See the following links for more information:
Rails multiple associations between two models
how to specify multiple relationships between models in rails using ActiveRecord associations

Resources